Skip to content

Slang 1.1.0 #1123

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 5 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Nomic Foundation has put a lot of effort in providing a set of compiler APIs tha

Since v2.0.0 this package will ship with the Slang parser and this change must be implemented in existing configurations by replacing `parser: 'solidity-parse'` with `parser: 'slang-solidity'`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the parser names might confuse users here:

  • For the new/default parser, WDYT of just using slang? since it is the project/NPM package/repository name..
  • For the ANTLR parser, WDYT of using deprecated-solidity-parser instead of solidity-parse? this matches the exact package name, and highlights that it is deprecated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought here is to keep the same historical name for the old parser in order for nothing to be break (while at least triggering a warning) and allow developers to slowly adopt the new parser.
I'm open to change this if @fvictorio thinks it's a better approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as for the name slang there is a possibility that version 2.1.0 could release with slang-yul alongside slang-solidity. if we feel this is a good feature.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I now remember the conversation about slang-yul. Let's keep this as-is then. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, since this is a major version, and I don't think we'll have a 3.0 soon, I'd rather have that breaking change now than keep names that are not great. Especially because setting the parser is more likely to happen now. I'll work on this on a separate PR.


The `antlr4` parser (`solidity-parse`) is still supported for the time being and will trigger a deprecation warning, since Slang gives us a much more powerful tool, is faster, allowed us to fully transition into typescript (minimizing the introduction of mismatching type bugs), and allows prettier to format the code in a much more decoupled way and position comments with a greater precision.

## Installation and usage

### Using in NodeJS
Expand Down
767 changes: 354 additions & 413 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
},
"devDependencies": {
"@babel/code-frame": "^7.26.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.25.1",
"@types/jest": "^29.5.14",
"@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.31.0",
Expand All @@ -93,6 +95,7 @@
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"esm-utils": "^4.3.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"jest-light-runner": "^0.7.8",
"jest-snapshot-serializer-ansi": "^2.2.1",
Expand All @@ -108,7 +111,7 @@
"webpack-cli": "^6.0.1"
},
"dependencies": {
"@nomicfoundation/slang": "1.0.0",
"@nomicfoundation/slang": "1.1.0",
"@solidity-parser/parser": "^0.20.1",
"semver": "^7.7.1"
},
Expand Down
34 changes: 21 additions & 13 deletions src/slang-comments/handlers/handle-contract-definition-comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export default function handleContractDefinitionComments({
locEnd(comment)
);

// Everything before the InheritanceSpecifier is pushed onto the beginning of
// Everything before the ContractSpecifiers is pushed onto the beginning of
// the ContractDefinition.
if (
followingNode?.kind === NonterminalKind.InheritanceSpecifier ||
followingNode?.kind === NonterminalKind.ContractSpecifiers ||
(followingNode?.kind === NonterminalKind.ContractMembers &&
nextCharacter !== '{')
) {
Expand All @@ -42,17 +42,25 @@ export default function handleContractDefinitionComments({

// The last comments before the body.
if (nextCharacter === '{') {
// If there's an InheritanceSpecifier, the comment is appended to the last
// InheritanceType.
if (
precedingNode?.kind === NonterminalKind.InheritanceSpecifier &&
precedingNode.types.items.length > 0
) {
addTrailingComment(
precedingNode.types.items[precedingNode.types.items.length - 1],
comment
);
return true;
if (precedingNode?.kind === NonterminalKind.ContractSpecifiers) {
if (precedingNode.items.length === 0) {
addTrailingComment(precedingNode, comment);
return true;
}
const lastContractSpecifier =
precedingNode.items[precedingNode.items.length - 1].variant;
// If the last ContractSpecifier's an InheritanceSpecifier, the comment
// is appended to the last InheritanceType.
if (lastContractSpecifier.kind === NonterminalKind.InheritanceSpecifier) {
addCollectionNodeLastComment(lastContractSpecifier.types, comment);
return true;
}
if (
lastContractSpecifier.kind === NonterminalKind.StorageLayoutSpecifier
) {
addTrailingComment(lastContractSpecifier.expression, comment);
return true;
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions src/slang-comments/handlers/handle-contract-specifiers-comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { util } from 'prettier';
import addCollectionNodeLastComment from './add-collection-node-last-comment.js';

import type { HandlerParams } from './types.d.ts';

const { addTrailingComment } = util;

export default function handleContractSpecifiersComments({
precedingNode,
enclosingNode,
comment
}: HandlerParams): boolean {
if (enclosingNode?.kind !== NonterminalKind.ContractSpecifiers) {
return false;
}

if (
precedingNode &&
precedingNode.kind === NonterminalKind.ContractSpecifier
) {
if (precedingNode.variant.kind === NonterminalKind.InheritanceSpecifier) {
addCollectionNodeLastComment(precedingNode.variant.types, comment);
return true;
}
if (precedingNode.variant.kind === NonterminalKind.StorageLayoutSpecifier) {
addTrailingComment(precedingNode.variant.expression, comment);
return true;
}
}

return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { util } from 'prettier';

import type { HandlerParams } from './types.d.ts';

const { addLeadingComment } = util;

export default function handleStorageLayoutSpecifierComments({
enclosingNode,
followingNode,
comment
}: HandlerParams): boolean {
if (enclosingNode?.kind !== NonterminalKind.StorageLayoutSpecifier) {
return false;
}

if (followingNode?.kind === NonterminalKind.Expression) {
addLeadingComment(followingNode, comment);
return true;
}

return false;
}
4 changes: 4 additions & 0 deletions src/slang-comments/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import handleBlockComments from './handle-block-comments.js';
import handleContractDefinitionComments from './handle-contract-definition-comments.js';
import handleContractSpecifiersComments from './handle-contract-specifiers-comments.js';
import handleElseBranchComments from './handle-else-branch-comments.js';
import handleIfStatementComments from './handle-if-statement-comments.js';
import handleInterfaceDefinitionComments from './handle-interface-definition-comments.js';
import handleLibraryDefinitionComments from './handle-library-definition-comments.js';
import handleModifierInvocationComments from './handle-modifier-invocation-comments.js';
import handleParametersDeclarationComments from './handle-parameters-declaration-comments.js';
import handlePositionalArgumentsDeclarationComments from './handle-positional-arguments-declaration-comments.js';
import handleStorageLayoutSpecifierComments from './handle-storage-layout-specifier-comments.js';
import handleWhileStatementComments from './handle-while-statement-comments.js';
import handleYulBlockComments from './handle-yul-block-comments.js';

export default [
handleBlockComments,
handleContractDefinitionComments,
handleContractSpecifiersComments,
handleElseBranchComments,
handleIfStatementComments,
handleInterfaceDefinitionComments,
handleLibraryDefinitionComments,
handleModifierInvocationComments,
handleParametersDeclarationComments,
handlePositionalArgumentsDeclarationComments,
handleStorageLayoutSpecifierComments,
handleWhileStatementComments,
handleYulBlockComments
];
13 changes: 6 additions & 7 deletions src/slang-nodes/ContractDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { coerce, satisfies } from 'semver';
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
import { Identifier } from './Identifier.js';
import { InheritanceSpecifier } from './InheritanceSpecifier.js';
import { ContractSpecifiers } from './ContractSpecifiers.js';
import { ContractMembers } from './ContractMembers.js';

import type * as ast from '@nomicfoundation/slang/ast';
Expand All @@ -24,7 +24,7 @@ export class ContractDefinition implements SlangNode {

name: Identifier;

inheritance?: InheritanceSpecifier;
specifiers: ContractSpecifiers;

members: ContractMembers;

Expand All @@ -33,12 +33,10 @@ export class ContractDefinition implements SlangNode {

this.abstractKeyword = ast.abstractKeyword?.unparse();
this.name = new Identifier(ast.name);
if (ast.inheritance) {
this.inheritance = new InheritanceSpecifier(ast.inheritance, options);
}
this.specifiers = new ContractSpecifiers(ast.specifiers, options);
this.members = new ContractMembers(ast.members, options);

metadata = updateMetadata(metadata, [this.inheritance, this.members]);
metadata = updateMetadata(metadata, [this.specifiers, this.members]);

this.comments = metadata.comments;
this.loc = metadata.loc;
Expand Down Expand Up @@ -68,7 +66,8 @@ export class ContractDefinition implements SlangNode {
this.abstractKeyword ? 'abstract ' : '',
'contract ',
path.call(print, 'name'),
this.inheritance ? [' ', path.call(print, 'inheritance')] : line,
path.call(print, 'specifiers'),
this.specifiers.items.length > 0 ? '' : line,
'{'
]),
path.call(print, 'members'),
Expand Down
48 changes: 48 additions & 0 deletions src/slang-nodes/ContractSpecifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
import { InheritanceSpecifier } from './InheritanceSpecifier.js';
import { StorageLayoutSpecifier } from './StorageLayoutSpecifier.js';

import type * as ast from '@nomicfoundation/slang/ast';
import type { AstPath, Doc, ParserOptions } from 'prettier';
import type { AstNode } from './types.js';
import type { PrintFunction, SlangNode } from '../types.js';

export class ContractSpecifier implements SlangNode {
readonly kind = NonterminalKind.ContractSpecifier;

comments;

loc;

variant: InheritanceSpecifier | StorageLayoutSpecifier;

constructor(ast: ast.ContractSpecifier, options: ParserOptions<AstNode>) {
let metadata = getNodeMetadata(ast);

switch (ast.variant.cst.kind) {
case NonterminalKind.InheritanceSpecifier:
this.variant = new InheritanceSpecifier(
ast.variant as ast.InheritanceSpecifier,
options
);
break;
case NonterminalKind.StorageLayoutSpecifier:
this.variant = new StorageLayoutSpecifier(
ast.variant as ast.StorageLayoutSpecifier,
options
);
break;
default:
throw new Error(`Unexpected variant: ${ast.variant.cst.kind}`);
}
metadata = updateMetadata(metadata, [this.variant]);

this.comments = metadata.comments;
this.loc = metadata.loc;
}

print(path: AstPath<ContractSpecifier>, print: PrintFunction): Doc {
return path.call(print, 'variant');
}
}
52 changes: 52 additions & 0 deletions src/slang-nodes/ContractSpecifiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { doc } from 'prettier';
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { sortContractSpecifiers } from '../slang-utils/sort-contract-specifiers.js';
import { printSeparatedList } from '../slang-printers/print-separated-list.js';
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
import { ContractSpecifier } from './ContractSpecifier.js';

import type * as ast from '@nomicfoundation/slang/ast';
import type { AstPath, Doc, ParserOptions } from 'prettier';
import type { AstNode } from './types.js';
import type { PrintFunction, SlangNode } from '../types.js';

const { group, ifBreak, line, softline } = doc.builders;

export class ContractSpecifiers implements SlangNode {
readonly kind = NonterminalKind.ContractSpecifiers;

comments;

loc;

items: ContractSpecifier[];

constructor(ast: ast.ContractSpecifiers, options: ParserOptions<AstNode>) {
let metadata = getNodeMetadata(ast, true);

this.items = ast.items.map((item) => new ContractSpecifier(item, options));

metadata = updateMetadata(metadata, [this.items]);

this.comments = metadata.comments;
this.loc = metadata.loc;

this.items = this.items.sort(sortContractSpecifiers);
}

print(path: AstPath<ContractSpecifiers>, print: PrintFunction): Doc {
if (this.items.length === 0) return '';

const [specifier1, specifier2] = path.map(print, 'items');
if (typeof specifier2 === 'undefined') return [' ', specifier1];

const groupId = Symbol('Slang.ContractSpecifiers.inheritance');
return printSeparatedList(
[group(specifier1, { id: groupId }), specifier2],
{
firstSeparator: line,
separator: ifBreak('', softline, { groupId })
}
);
}
}
45 changes: 45 additions & 0 deletions src/slang-nodes/StorageLayoutSpecifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { doc } from 'prettier';
import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { printSeparatedItem } from '../slang-printers/print-separated-item.js';
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
import { Expression } from './Expression.js';

import type * as ast from '@nomicfoundation/slang/ast';
import type { AstPath, Doc, ParserOptions } from 'prettier';
import type { AstNode } from './types.js';
import type { PrintFunction, SlangNode } from '../types.js';

const { line } = doc.builders;

export class StorageLayoutSpecifier implements SlangNode {
readonly kind = NonterminalKind.StorageLayoutSpecifier;

comments;

loc;

expression: Expression;

constructor(
ast: ast.StorageLayoutSpecifier,
options: ParserOptions<AstNode>
) {
let metadata = getNodeMetadata(ast);

this.expression = new Expression(ast.expression, options);

metadata = updateMetadata(metadata, [this.expression]);

this.comments = metadata.comments;
this.loc = metadata.loc;
}

print(path: AstPath<StorageLayoutSpecifier>, print: PrintFunction): Doc {
return [
'layout at',
printSeparatedItem(path.call(print, 'expression'), {
firstSeparator: line
})
];
}
}
6 changes: 6 additions & 0 deletions src/slang-nodes/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import type { ContinueStatement } from './ContinueStatement.ts';
import type { ContractDefinition } from './ContractDefinition.ts';
import type { ContractMember } from './ContractMember.ts';
import type { ContractMembers } from './ContractMembers.ts';
import type { ContractSpecifier } from './ContractSpecifier.ts';
import type { ContractSpecifiers } from './ContractSpecifiers.ts';
import type { DecimalNumberExpression } from './DecimalNumberExpression.ts';
import type { DoWhileStatement } from './DoWhileStatement.ts';
import type { ElementaryType } from './ElementaryType.ts';
Expand Down Expand Up @@ -139,6 +141,7 @@ import type { StateVariableAttribute } from './StateVariableAttribute.ts';
import type { StateVariableAttributes } from './StateVariableAttributes.ts';
import type { StateVariableDefinition } from './StateVariableDefinition.ts';
import type { StateVariableDefinitionValue } from './StateVariableDefinitionValue.ts';
import type { StorageLayoutSpecifier } from './StorageLayoutSpecifier.ts';
import type { StorageLocation } from './StorageLocation.ts';
import type { StringExpression } from './StringExpression.ts';
import type { StringLiteral } from './StringLiteral.ts';
Expand Down Expand Up @@ -257,6 +260,7 @@ export type StrictAstNode =
| ContractDefinition
| InheritanceSpecifier
| InheritanceType
| StorageLayoutSpecifier
| InterfaceDefinition
| LibraryDefinition
| StructDefinition
Expand Down Expand Up @@ -411,6 +415,8 @@ export type StrictAstNode =
| YulLiteral
| SourceUnitMembers
| VersionExpressionSet
| ContractSpecifier
| ContractSpecifiers
| ContractMembers
| InterfaceMembers
| LibraryMembers
Expand Down
Loading