-
Notifications
You must be signed in to change notification settings - Fork 85
Using Slang's language inference tool #1158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8d252fe
updating dependencies
Janther e111b95
Using Slang's language inference tool
Janther a1acee2
Fixing test with conflicting pragmas and syntax
Janther 34ded21
returning an object instead of an array for clarity
Janther 748e39a
adding some documentation and fixing a descending for loop condition
Janther f7cb638
removing unnecessary constant
Janther b727357
small refactor for DRY code
Janther 725530f
removing the logic that added issues for maintainance
Janther 080e940
improving the error messages when the language inference fails becaus…
Janther 4c5f8ab
using the earliest version with the exception when there are no pragm…
Janther ff18c5d
passing tests
Janther a48c29a
Fix typos and adding a check for an empty array
Janther File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,145 +1,66 @@ | ||
| import { VersionExpressionSets as SlangVersionExpressionSets } from '@nomicfoundation/slang/ast'; | ||
| import { NonterminalKind, Query } from '@nomicfoundation/slang/cst'; | ||
| import { NonterminalKind } from '@nomicfoundation/slang/cst'; | ||
| import { Parser } from '@nomicfoundation/slang/parser'; | ||
| import { LanguageFacts } from '@nomicfoundation/slang/utils'; | ||
| import { | ||
| maxSatisfying, | ||
| minSatisfying, | ||
| minor, | ||
| major, | ||
| satisfies, | ||
| validRange | ||
| } from 'semver'; | ||
| import { VersionExpressionSets } from '../slang-nodes/VersionExpressionSets.js'; | ||
| import { minSatisfying } from 'semver'; | ||
|
|
||
| import type { ParseOutput } from '@nomicfoundation/slang/parser'; | ||
| import type { ParserOptions } from 'prettier'; | ||
| import type { AstNode } from '../slang-nodes/types.js'; | ||
|
|
||
| const supportedVersions = LanguageFacts.allVersions(); | ||
| const supportedLength = supportedVersions.length; | ||
|
|
||
| const milestoneVersions = Array.from( | ||
| supportedVersions.reduce((minorRanges, version) => { | ||
| minorRanges.add(`^${major(version)}.${minor(version)}.0`); | ||
| return minorRanges; | ||
| }, new Set<string>()) | ||
| ) | ||
| .reverse() | ||
| .reduce((versions: string[], range) => { | ||
| versions.push(maxSatisfying(supportedVersions, range)!); | ||
| versions.push(minSatisfying(supportedVersions, range)!); | ||
| return versions; | ||
| }, []); | ||
|
|
||
| const queries = [ | ||
| Query.create('[VersionPragma @versionRanges [VersionExpressionSets]]') | ||
| ]; | ||
|
|
||
| let parser: Parser; | ||
| function parserAndOutput( | ||
| text: string, | ||
| version: string | ||
| ): { parser: Parser; parseOutput: ParseOutput } { | ||
| const parser = Parser.create(version); | ||
| return { | ||
| parser, | ||
| parseOutput: parser.parseNonterminal(NonterminalKind.SourceUnit, text) | ||
| }; | ||
| } | ||
|
|
||
| export function createParser( | ||
| text: string, | ||
| options: ParserOptions<AstNode> | ||
| ): [Parser, ParseOutput] { | ||
| const compiler = maxSatisfying(supportedVersions, options.compiler); | ||
| ): { parser: Parser; parseOutput: ParseOutput } { | ||
| const compiler = minSatisfying(supportedVersions, options.compiler); | ||
| if (compiler) { | ||
| if (!parser || parser.languageVersion !== compiler) { | ||
| parser = Parser.create(compiler); | ||
| } | ||
| return [parser, parser.parseNonterminal(NonterminalKind.SourceUnit, text)]; | ||
| } | ||
|
|
||
| let isCachedParser = false; | ||
| if (parser) { | ||
| isCachedParser = true; | ||
| } else { | ||
| parser = Parser.create(milestoneVersions[0]); | ||
| } | ||
|
|
||
| let parseOutput; | ||
| let inferredRanges: string[] = []; | ||
|
|
||
| try { | ||
| parseOutput = parser.parseNonterminal(NonterminalKind.SourceUnit, text); | ||
| inferredRanges = tryToCollectPragmas(parseOutput, parser, isCachedParser); | ||
| } catch { | ||
| for ( | ||
| let i = isCachedParser ? 0 : 1; | ||
| i <= milestoneVersions.length; | ||
| i += 1 | ||
| ) { | ||
| try { | ||
| const version = milestoneVersions[i]; | ||
| parser = Parser.create(version); | ||
| parseOutput = parser.parseNonterminal(NonterminalKind.SourceUnit, text); | ||
| inferredRanges = tryToCollectPragmas(parseOutput, parser); | ||
| break; | ||
| } catch { | ||
| continue; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const satisfyingVersions = inferredRanges.reduce( | ||
| (versions, inferredRange) => { | ||
| if (!validRange(inferredRange)) { | ||
| throw new Error( | ||
| `Couldn't infer any version from the ranges in the pragmas${options.filepath ? ` for file ${options.filepath}` : ''}` | ||
| ); | ||
| } | ||
| return versions.filter((supportedVersion) => | ||
| satisfies(supportedVersion, inferredRange) | ||
| const result = parserAndOutput(text, compiler); | ||
|
|
||
| if (!result.parseOutput.isValid()) | ||
| throw new Error( | ||
| `We encountered the following syntax error:\n\n\t${ | ||
| result.parseOutput.errors()[0].message | ||
| }\n\nBased on the compiler option provided, we inferred your code to be using Solidity version ${ | ||
| result.parser.languageVersion | ||
| }. If you would like to change that, specify a different version in your \`.prettierrc\` file.` | ||
| ); | ||
| }, | ||
| supportedVersions | ||
| ); | ||
|
|
||
| const inferredVersion = | ||
| satisfyingVersions.length > 0 | ||
| ? satisfyingVersions[satisfyingVersions.length - 1] | ||
| : supportedVersions[supportedVersions.length - 1]; | ||
|
|
||
| if (inferredVersion !== parser.languageVersion) { | ||
| parser = Parser.create(inferredVersion); | ||
| parseOutput = parser.parseNonterminal(NonterminalKind.SourceUnit, text); | ||
| return result; | ||
| } | ||
| const inferredRanges: string[] = LanguageFacts.inferLanguageVersions(text); | ||
| const inferredLength = inferredRanges.length; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion | ||
| return [parser, parseOutput!]; | ||
| } | ||
|
|
||
| function tryToCollectPragmas( | ||
| parseOutput: ParseOutput, | ||
| parser: Parser, | ||
| isCachedParser = false | ||
| ): string[] { | ||
| const matches = parseOutput.createTreeCursor().query(queries); | ||
| const ranges: string[] = []; | ||
|
|
||
| let match; | ||
| while ((match = matches.next())) { | ||
| const versionRange = new SlangVersionExpressionSets( | ||
| match.captures.versionRanges[0].node.asNonterminalNode()! | ||
| ); | ||
| ranges.push( | ||
| // Replace all comments that could be in the expression with whitespace | ||
| new VersionExpressionSets(versionRange).comments.reduce( | ||
| (range, comment) => range.replace(comment.value, ' '), | ||
| versionRange.cst.unparse() | ||
| ) | ||
| if (inferredLength === 0) { | ||
| throw new Error( | ||
| `We couldn't infer a Solidity version base on the pragma statements in your code. If you would like to change that, update the pragmas in your source file, or specify a version in your \`.prettierrc\` file.` | ||
| ); | ||
| } | ||
| const result = parserAndOutput( | ||
| text, | ||
| inferredRanges[inferredLength === supportedLength ? inferredLength - 1 : 0] | ||
| ); | ||
|
|
||
| if (ranges.length === 0) { | ||
| // If we didn't find pragmas but succeeded parsing the source we keep it. | ||
| if (!isCachedParser && parseOutput.isValid()) { | ||
| return [parser.languageVersion]; | ||
| } | ||
| // Otherwise we throw. | ||
| if (!result.parseOutput.isValid()) | ||
| throw new Error( | ||
| `Using version ${parser.languageVersion} did not find any pragma statement and does not parse without errors.` | ||
| `We encountered the following syntax error:\n\n\t${ | ||
| result.parseOutput.errors()[0].message | ||
| }\n\nBased on the pragma statements, we inferred your code to be using Solidity version ${ | ||
| result.parser.languageVersion | ||
| }. If you would like to change that, update the pragmas in your source file, or specify a version in your \`.prettierrc\` file.` | ||
| ); | ||
| } | ||
|
|
||
| return ranges; | ||
| return result; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 3 additions & 2 deletions
5
tests/format/AllSolidityFeaturesV0.4.26/__snapshots__/format.test.js.snap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| runFormatTest(import.meta, ['slang']); | ||
| runFormatTest(import.meta, ['slang'], { compiler: '0.4.26' }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| runFormatTest(import.meta, ['slang']); | ||
| runFormatTest(import.meta, ['slang'], { compiler: '0.4.26' }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 3 additions & 2 deletions
5
tests/format/FunctionDefinitionsV0.5.0/__snapshots__/format.test.js.snap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| runFormatTest(import.meta, ['slang']); | ||
| runFormatTest(import.meta, ['slang'], { compiler: '0.5.17' }); |
5 changes: 3 additions & 2 deletions
5
tests/format/FunctionDefinitionsv0.5.0/__snapshots__/format.test.js.snap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| runFormatTest(import.meta, ['slang']); | ||
| runFormatTest(import.meta, ['slang'], { compiler: '0.5.17' }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| runFormatTest(import.meta, ['slang']); | ||
| runFormatTest(import.meta, ['slang'], { compiler: '0.4.26' }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can
inferredRangesbe empty? I think this can only happen if you have two pragma statements whose intersection is empty, which is very unlikely, but maybe throwing an assertion error wouldn't be a bad idea.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so far
slangjust throws an error in this case, but I could add an check here in caseslangchanges this behaviour