Skip to content

Commit

Permalink
Auto-generate AST boilerplate (#15544)
Browse files Browse the repository at this point in the history
This PR replaces most of the hard-coded AST definitions with a
generation script, similar to what happens in `rust_python_formatter`.
I've replaced every "rote" definition that I could find, where the
content is entirely boilerplate and only depends on what syntax nodes
there are and which groups they belong to.

This is a pretty massive diff, but it's entirely a refactoring. It
should make absolutely no changes to the API or implementation. In
particular, this required adding some configuration knobs that let us
override default auto-generated names where they don't line up with
types that we created previously by hand.

## Test plan

There should be no changes outside of the `rust_python_ast` crate, which
verifies that there were no API changes as a result of the
auto-generation. Aggressive `cargo clippy` and `uvx pre-commit` runs
after each commit in the branch.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
  • Loading branch information
3 people authored Jan 17, 2025
1 parent 4351d85 commit 8e3633f
Show file tree
Hide file tree
Showing 15 changed files with 10,257 additions and 8,696 deletions.
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text

crates/ruff_python_parser/resources/inline linguist-generated=true

ruff.schema.json linguist-generated=true text=auto eol=lf
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
crates/ruff_python_ast/src/generated.rs -diff linguist-generated=true text=auto eol=lf
crates/ruff_python_formatter/src/generated.rs -diff linguist-generated=true text=auto eol=lf
*.md.snap linguist-language=Markdown
6 changes: 6 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ jobs:
- name: "Install Rust toolchain"
run: rustup component add rustfmt
- uses: Swatinem/rust-cache@v2
# Run all code generation scripts, and verify that the current output is
# already checked into git.
- run: python crates/ruff_python_ast/generate.py
- run: python crates/ruff_python_formatter/generate.py
- run: test -z "$(git status --porcelain)"
# Verify that adding a plugin or rule produces clean code.
- run: ./scripts/add_rule.py --name DoTheThing --prefix F --code 999 --linter pyflakes
- run: cargo check
- run: cargo fmt --all --check
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import _SpecialForm, Any, LiteralString
from typing import Any, LiteralString, _SpecialForm

# Special operations
def static_assert(condition: object, msg: LiteralString | None = None) -> None: ...
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
source: crates/ruff/tests/show_settings.rs
assertion_line: 30
info:
program: ruff
args:
- check
- "--show-settings"
- unformatted.py
snapshot_kind: text
---
success: true
exit_code: 0
Expand Down Expand Up @@ -72,6 +72,8 @@ file_resolver.project_root = "[BASEPATH]"
linter.exclude = []
linter.project_root = "[BASEPATH]"
linter.rules.enabled = [
unsorted-imports (I001),
missing-required-import (I002),
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
multiple-statements-on-one-line-colon (E701),
Expand Down Expand Up @@ -133,6 +135,8 @@ linter.rules.enabled = [
raise-not-implemented (F901),
]
linter.rules.should_fix = [
unsorted-imports (I001),
missing-required-import (I002),
multiple-imports-on-one-line (E401),
module-import-not-at-top-of-file (E402),
multiple-statements-on-one-line-colon (E701),
Expand Down
174 changes: 174 additions & 0 deletions crates/ruff_python_ast/ast.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# This file is used by generate.py to autogenerate our Python AST data model.
#
# We have defined a Rust struct for each syntax node in `src/nodes.rs`. Many of
# these nodes belong to groups. For instance, there is a `Stmt` group
# consisting of all of the syntax nodes that represent valid Python statements.
#
# There is a special group named `ungrouped` that contains syntax nodes that do
# not belong to any group.
#
# Each group is defined by two sections below. The `[GROUP]` section defines
# options that control the auto-generation for that group. The `[GROUP.nodes]`
# section defines which syntax nodes belong to that group. The name of each
# entry in the nodes section must match the name of the corresponding Rust
# struct. The value of each entry defines options that control the
# auto-generation for that syntax node.
#
# The following group options are available:
#
# add_suffix_to_is_methods: [true/false]
# Controls the name of the is_foo methods of the group's enums. If false (the
# default), these methods will use the variant name in snake_case. If true,
# then the group prefix will be moved to the end before snake_casing. (That
# is, `StmtIf` will become `if_stmt`.)
#
# anynode_is_label: foo_bar
# Controls the name of the AnyNode::foo_bar, AnyNode::is_foo_bar, and
# AnyNodeRef::is_foo_bar methods. The default is the group name in
# snake_case.
#
# ref_enum_ty:
# The name of the reference enum that we create for this group. The default
# is the group name with `Ref` added to the end.
#
# rustdoc:
# A rustdoc comment that is added to the group's enums.
#
# The following syntax node options are available:
#
# variant:
# The name of the enum variant for this syntax node. Defaults to the node
# name with the group prefix removed. (That is, `StmtIf` becomes `If`.)

[Mod]
anynode_is_label = "module"
rustdoc = "/// See also [mod](https://docs.python.org/3/library/ast.html#ast.mod)"

[Mod.nodes]
ModModule = {}
ModExpression = {}

[Stmt]
add_suffix_to_is_methods = true
anynode_is_label = "statement"
rustdoc = "/// See also [stmt](https://docs.python.org/3/library/ast.html#ast.stmt)"
ref_enum_ty = "StatementRef"

[Stmt.nodes]
StmtFunctionDef = {}
StmtClassDef = {}
StmtReturn = {}
StmtDelete = {}
StmtTypeAlias = {}
StmtAssign = {}
StmtAugAssign = {}
StmtAnnAssign = {}
StmtFor = {}
StmtWhile = {}
StmtIf = {}
StmtWith = {}
StmtMatch = {}
StmtRaise = {}
StmtTry = {}
StmtAssert = {}
StmtImport = {}
StmtImportFrom = {}
StmtGlobal = {}
StmtNonlocal = {}
StmtExpr = {}
StmtPass = {}
StmtBreak = {}
StmtContinue = {}
StmtIpyEscapeCommand = {}

[Expr]
add_suffix_to_is_methods = true
anynode_is_label = "expression"
rustdoc = "/// See also [expr](https://docs.python.org/3/library/ast.html#ast.expr)"
ref_enum_ty = "ExpressionRef"

[Expr.nodes]
ExprBoolOp = {}
ExprNamed = {}
ExprBinOp = {}
ExprUnaryOp = {}
ExprLambda = {}
ExprIf = {}
ExprDict = {}
ExprSet = {}
ExprListComp = {}
ExprSetComp = {}
ExprDictComp = {}
ExprGenerator = {}
ExprAwait = {}
ExprYield = {}
ExprYieldFrom = {}
ExprCompare = {}
ExprCall = {}
ExprFString = {}
ExprStringLiteral = {}
ExprBytesLiteral = {}
ExprNumberLiteral = {}
ExprBooleanLiteral = {}
ExprNoneLiteral = {}
ExprEllipsisLiteral = {}
ExprAttribute = {}
ExprSubscript = {}
ExprStarred = {}
ExprName = {}
ExprList = {}
ExprTuple = {}
ExprSlice = {}
ExprIpyEscapeCommand = {}

[ExceptHandler]
rustdoc = "/// See also [excepthandler](https://docs.python.org/3/library/ast.html#ast.excepthandler)"

[ExceptHandler.nodes]
ExceptHandlerExceptHandler = {}

[FStringElement.nodes]
FStringExpressionElement = {variant = "Expression"}
FStringLiteralElement = {variant = "Literal"}

[Pattern]
rustdoc = "/// See also [pattern](https://docs.python.org/3/library/ast.html#ast.pattern)"

[Pattern.nodes]
PatternMatchValue = {}
PatternMatchSingleton = {}
PatternMatchSequence = {}
PatternMatchMapping = {}
PatternMatchClass = {}
PatternMatchStar = {}
PatternMatchAs = {}
PatternMatchOr = {}

[TypeParam]
rustdoc = "/// See also [type_param](https://docs.python.org/3/library/ast.html#ast.type_param)"

[TypeParam.nodes]
TypeParamTypeVar = {}
TypeParamTypeVarTuple = {}
TypeParamParamSpec = {}

[ungrouped.nodes]
FStringFormatSpec = {}
PatternArguments = {}
PatternKeyword = {}
Comprehension = {}
Arguments = {}
Parameters = {}
Parameter = {}
ParameterWithDefault = {}
Keyword = {}
Alias = {}
WithItem = {}
MatchCase = {}
Decorator = {}
ElifElseClause = {}
TypeParams = {}
FString = {}
StringLiteral = {}
BytesLiteral = {}
Identifier = {}
Loading

0 comments on commit 8e3633f

Please sign in to comment.