From 3e7d83512fb859718335621f6ab913134dfadb19 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 2 Oct 2026 12:38:07 -0300 Subject: [PATCH 1/4] we never interact with the parser outside of asking for the languageVersion --- src/slang-utils/create-parser.ts | 14 +++++----- src/slangSolidityParser.ts | 4 +-- tests/config/test-antlr-format.js | 4 +-- tests/unit/slang-utils/create-parser.test.js | 27 +++++++++++--------- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index f7405c449..06f600536 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -11,11 +11,11 @@ const supportedVersions = LanguageFacts.allVersions(); const supportedLength = supportedVersions.length; const latestSupportedVersion = LanguageFacts.latestVersion(); -function parserAndOutput( +function versionAndOutput( text: string, version: string, reason: string -): { parser: Parser; parseOutput: ParseOutput } { +): { version: string; parseOutput: ParseOutput } { const parser = Parser.create(version); const parseOutput = parser.parseNonterminal(NonterminalKind.SourceUnit, text); @@ -24,16 +24,16 @@ function parserAndOutput( `We encountered the following syntax error:\n\n\t${parseOutput.errors()[0].message}\n\n${reason}` ); - return { parser, parseOutput }; + return { version, parseOutput }; } export function createParser( text: string, options: ParserOptions -): { parser: Parser; parseOutput: ParseOutput } { +): { version: string; parseOutput: ParseOutput } { const compiler = maxSatisfying(supportedVersions, options.compiler); if (compiler) { - return parserAndOutput( + return versionAndOutput( text, compiler, `Based on the compiler option provided, we inferred your code to be using Solidity version ${compiler}. If you would like to change that, specify a different version in your \`.prettierrc\` file.` @@ -44,7 +44,7 @@ export function createParser( const inferredLength = inferredRanges.length; if (inferredLength === 0 || inferredLength === supportedLength) { - return parserAndOutput( + return versionAndOutput( text, latestSupportedVersion, `We couldn't infer a Solidity version based on the pragma statements in your code so we defaulted to ${latestSupportedVersion}. You might be attempting to use a syntax not yet supported by Slang or you might want to specify a version in your \`.prettierrc\` file.` @@ -52,7 +52,7 @@ export function createParser( } const inferredVersion = inferredRanges[inferredLength - 1]; - return parserAndOutput( + return versionAndOutput( text, inferredVersion, `Based on the pragma statements, we inferred your code to be using Solidity version ${inferredVersion}. If you would like to change that, update the pragmas in your source file, or specify a version in your \`.prettierrc\` file.` diff --git a/src/slangSolidityParser.ts b/src/slangSolidityParser.ts index 8963d6252..9381fa24f 100644 --- a/src/slangSolidityParser.ts +++ b/src/slangSolidityParser.ts @@ -11,10 +11,10 @@ export default function parse( text: string, options: ParserOptions ): PrintableNode { - const { parser, parseOutput } = createParser(text, options); + const { version, parseOutput } = createParser(text, options); // We update the compiler version with the inferred one. - options.compiler = parser.languageVersion; + options.compiler = version; const comments: Comment[] = []; const parsed = new SourceUnit( new SlangSourceUnit(parseOutput.tree.asNonterminalNode()), diff --git a/tests/config/test-antlr-format.js b/tests/config/test-antlr-format.js index a55df8d43..a40e433b2 100644 --- a/tests/config/test-antlr-format.js +++ b/tests/config/test-antlr-format.js @@ -18,13 +18,13 @@ function testAntlrFormat(testCase, name) { // Compare with ANTLR's format const createParser = await getCreateParser(); - const { parser } = createParser(code, formatOptions); + const { version } = createParser(code, formatOptions); const prettier = await getPrettier(); const { formatted: antlrOutput } = await prettier.formatWithCursor(code, { ...formatOptions, // Since Slang forces us to decide on a compiler version, we need to do the // same for ANTLR unless it was already given as an option. - compiler: formatOptions.compiler || parser.languageVersion, + compiler: formatOptions.compiler || version, parser: "antlr", plugins: await getPlugins(), }); diff --git a/tests/unit/slang-utils/create-parser.test.js b/tests/unit/slang-utils/create-parser.test.js index 57cba8a38..7ac335206 100644 --- a/tests/unit/slang-utils/create-parser.test.js +++ b/tests/unit/slang-utils/create-parser.test.js @@ -82,32 +82,35 @@ describe('inferLanguage', function () { for (const { description, source, version, skip } of fixtures) { (skip ? test.skip : test)(description, function () { - const { parser } = createParser(source, options); - expect(parser.languageVersion).toEqual(version); + const { version: inferredVersion } = createParser(source, options); + expect(inferredVersion).toEqual(version); }); } test('should use the latest successful version if the source has no pragmas', function () { - let { parser } = createParser(`contract Foo {}`, options); - expect(parser.languageVersion).toEqual(latestSupportedVersion); + let { version: inferredVersion } = createParser(`contract Foo {}`, options); + expect(inferredVersion).toEqual(latestSupportedVersion); - // ({ parser } = createParser(`contract Foo {byte bar;}`, options)); - // expect(parser.languageVersion).toEqual('0.7.6'); + // ({ version } = createParser(`contract Foo {byte bar;}`, options)); + // expect(version).toEqual('0.7.6'); }); test('should use compiler option if given', function () { - let { parser } = createParser(`pragma solidity ^0.8.0;`, { + let { version: inferredVersion } = createParser(`pragma solidity ^0.8.0;`, { compiler: '0.8.20' }); - expect(parser.languageVersion).toEqual('0.8.20'); + expect(inferredVersion).toEqual('0.8.20'); - ({ parser } = createParser(`pragma solidity ^0.8.0;`, { + ({ version: inferredVersion } = createParser(`pragma solidity ^0.8.0;`, { compiler: '0.8.2' })); - expect(parser.languageVersion).toEqual('0.8.2'); + expect(inferredVersion).toEqual('0.8.2'); - ({ parser } = createParser(`pragma solidity ^0.7.0;`, {})); - expect(parser.languageVersion).toEqual('0.7.6'); + ({ version: inferredVersion } = createParser( + `pragma solidity ^0.7.0;`, + {} + )); + expect(inferredVersion).toEqual('0.7.6'); }); test('should throw if compiler option does not match the syntax', function () { From d3765fe97fb08424961c1bf285ce9647b3c32b3d Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 3 Oct 2026 10:38:39 -0300 Subject: [PATCH 2/4] new strategy for inferrence by building a pragma only input. --- src/slang-utils/create-parser.ts | 59 +++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index 06f600536..f60e9eb68 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -11,14 +11,15 @@ const supportedVersions = LanguageFacts.allVersions(); const supportedLength = supportedVersions.length; const latestSupportedVersion = LanguageFacts.latestVersion(); -function versionAndOutput( - text: string, +// Most files allow the latest version, so we parse with it first and only +// parse again if their pragmas ask for an older one. +const latestParser = Parser.create(latestSupportedVersion); + +function validated( version: string, + parseOutput: ParseOutput, reason: string ): { version: string; parseOutput: ParseOutput } { - const parser = Parser.create(version); - const parseOutput = parser.parseNonterminal(NonterminalKind.SourceUnit, text); - if (!parseOutput.isValid()) throw new Error( `We encountered the following syntax error:\n\n\t${parseOutput.errors()[0].message}\n\n${reason}` @@ -27,6 +28,30 @@ function versionAndOutput( return { version, parseOutput }; } +function versionAndOutput( + text: string, + version: string, + reason: string +): { version: string; parseOutput: ParseOutput } { + return validated( + version, + Parser.create(version).parseNonterminal(NonterminalKind.SourceUnit, text), + reason + ); +} + +// The text of every pragma directive in the tree. Slang's version inference +// only looks at pragmas, but given the whole file it analyzes all of it, which +// costs about as much as parsing it. +function pragmasOf(parseOutput: ParseOutput): string { + const cursor = parseOutput.createTreeCursor(); + let pragmas = ''; + while (cursor.goToNextNonterminalWithKind(NonterminalKind.PragmaDirective)) { + pragmas += `${cursor.node.unparse()}\n`; + } + return pragmas; +} + export function createParser( text: string, options: ParserOptions @@ -40,21 +65,29 @@ export function createParser( ); } - const inferredRanges = LanguageFacts.inferLanguageVersions(text); + const latestOutput = latestParser.parseNonterminal( + NonterminalKind.SourceUnit, + text + ); + const inferredRanges = LanguageFacts.inferLanguageVersions( + // If the latest version can't parse the file, error recovery may have + // lost some of its pragmas, so we infer from the whole file instead. + latestOutput.isValid() ? pragmasOf(latestOutput) : text + ); const inferredLength = inferredRanges.length; if (inferredLength === 0 || inferredLength === supportedLength) { - return versionAndOutput( - text, + return validated( latestSupportedVersion, + latestOutput, `We couldn't infer a Solidity version based on the pragma statements in your code so we defaulted to ${latestSupportedVersion}. You might be attempting to use a syntax not yet supported by Slang or you might want to specify a version in your \`.prettierrc\` file.` ); } const inferredVersion = inferredRanges[inferredLength - 1]; - return versionAndOutput( - text, - inferredVersion, - `Based on the pragma statements, we inferred your code to be using Solidity version ${inferredVersion}. If you would like to change that, update the pragmas in your source file, or specify a version in your \`.prettierrc\` file.` - ); + const reason = `Based on the pragma statements, we inferred your code to be using Solidity version ${inferredVersion}. If you would like to change that, update the pragmas in your source file, or specify a version in your \`.prettierrc\` file.`; + + return inferredVersion === latestSupportedVersion + ? validated(latestSupportedVersion, latestOutput, reason) + : versionAndOutput(text, inferredVersion, reason); } From 609f0ce5ea224160a8e2e5fba49e515a7d87cff9 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sun, 4 Oct 2026 13:01:40 -0300 Subject: [PATCH 3/4] Slang already uses the latest parser and doesn't care if the output is valid, it just fetches the pragmas. --- src/slang-utils/create-parser.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index f60e9eb68..c8117593c 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -70,9 +70,7 @@ export function createParser( text ); const inferredRanges = LanguageFacts.inferLanguageVersions( - // If the latest version can't parse the file, error recovery may have - // lost some of its pragmas, so we infer from the whole file instead. - latestOutput.isValid() ? pragmasOf(latestOutput) : text + pragmasOf(latestOutput) ); const inferredLength = inferredRanges.length; From 5107b8a7ce4500b14a03ebdbcded1cf5b1fabfa2 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sun, 4 Oct 2026 20:10:37 -0300 Subject: [PATCH 4/4] unnecessary const --- src/slang-utils/create-parser.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index c8117593c..3bdb6814c 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -8,7 +8,6 @@ import type { ParserOptions } from 'prettier'; import type { PrintableNode } from '../slang-nodes/types.d.ts'; const supportedVersions = LanguageFacts.allVersions(); -const supportedLength = supportedVersions.length; const latestSupportedVersion = LanguageFacts.latestVersion(); // Most files allow the latest version, so we parse with it first and only @@ -74,7 +73,7 @@ export function createParser( ); const inferredLength = inferredRanges.length; - if (inferredLength === 0 || inferredLength === supportedLength) { + if (inferredLength === 0 || inferredLength === supportedVersions.length) { return validated( latestSupportedVersion, latestOutput,