;;;;;; Part 1: Definining ~all~ most of the nodes ; This section contains all of the "simple" definitions. All of the places where a single ; tree-sitter node corresponds to an AST node. ; Create the module node first, so it always appears first in the output. (module) @mod { let @mod.node = (ast-node @mod "Module") } (_) @anynode { scan (node-type @anynode) { "^(ERROR|MISSING)$" { let @anynode.node = (ast-node @anynode "SyntaxErrorNode") attr (@anynode.node) source = (source-text @anynode) } } } (parenthesized_expression) @nd { let @nd.node = (ast-node @nd "Expr") } (assignment !type) @assign { let @assign.node = (ast-node @assign "Assign") } [ (expression_list) (tuple) (tuple_pattern) (pattern_list) (index_expression_list) ] @tuple { let @tuple.node = (ast-node @tuple "Tuple") } (list_pattern) @list { let @list.node = (ast-node @list "List") } (call) @call { let @call.node = (ast-node @call "Call") } (for_statement) @for { let @for.node = (ast-node @for "For") } [ (if_statement) (elif_clause) ] @if { let @if.node = (ast-node @if "If") } (continue_statement) @continue { let @continue.node = (ast-node @continue "Continue") } (break_statement) @break { let @break.node = (ast-node @break "Break") } (pass_statement) @pass { let @pass.node = (ast-node @pass "Pass") } (assert_statement) @assert { let @assert.node = (ast-node @assert "Assert") } (assignment type: (_)) @assign { let @assign.node = (ast-node @assign "AnnAssign") } (augmented_assignment) @assign { let @assign.node = (ast-node @assign "AugAssign") } (delete_statement) @del { let @del.node = (ast-node @del "Delete") } (global_statement) @global { let @global.node = (ast-node @global "Global") } (nonlocal_statement) @nonlocal { let @nonlocal.node = (ast-node @nonlocal "Nonlocal") } [(import_statement) (import_from_statement name: (_))] @import { let @import.node = (ast-node @import "Import") } (import_from_statement (wildcard_import)) @importstar { let @importstar.node = (ast-node @importstar "ImportFrom") } (raise_statement) @raise { let @raise.node = (ast-node @raise "Raise") } (binary_operator) @binop { let @binop.node = (ast-node @binop "BinOp") } (keyword_argument) @kwarg { let @kwarg.node = (ast-node @kwarg "keyword") } [(function_definition) (class_definition) (decorated_definition)] @def { let @def.node = (ast-node @def "Assign") } (decorator) @decorator { let @decorator.node = (ast-node @decorator "Call") } (expression_statement) @stmt { let @stmt.node = (ast-node @stmt "Expr") } [ (integer) (float) ] @num { let @num.node = (ast-node @num "Num") } (identifier) @name { let @name.node = (ast-node @name "Name") } (list) @list { let @list.node = (ast-node @list "List") } [(list_splat) (list_splat_pattern)] @starred { let @starred.node = (ast-node @starred "Starred") } (comment) @comment { let @comment.node = (ast-node @comment "Comment") } [ (future_import_statement name: (_) @alias) (import_from_statement name: (_) @alias) (import_statement name: (_) @alias) ] { let @alias.node = (ast-node @alias "alias") } ; A string _without_ interpolations is just a `Str`, _except_ if it's inside a string ; concatenation, in which case it's a `StringPart`. (string !interpolation) @str { var str_class = "Str" if (instance-of (get-parent @str) "concatenated_string") { set str_class = "StringPart" } let @str.node = (ast-node @str str_class) } (string interpolation: (_)) @fstring { let @fstring.node = (ast-node @fstring "JoinedStr") } (string string_content: (_) @part) { let @part.node = (ast-node @part "StringPart") } ; A string concatenation that contains no interpolated expressions is just a `Str` (and its children ; will be `StringPart`s). A string concatenation that contains interpolated expressions is a ; `JoinedStr`, however. (concatenated_string (string interpolation: (_))* @interpolations ) @string { var string_class = "Str" ; Check if there are any interpolations in the string. ; We cannot use an optional match in the above query, since it could match several times, ; and subsequent definitions of `@string.node` would then fail. for _ in @interpolations { set string_class = "JoinedStr" } let @string.node = (ast-node @string string_class) } (string interpolation: (_)) @fstring { if (not (instance-of (get-parent @fstring) "concatenated_string")) { attr (@fstring.node) _fixup = #true } } (pair) @kvpair { let @kvpair.node = (ast-node @kvpair "KeyValuePair") } (dictionary) @dict { let @dict.node = (ast-node @dict "Dict") } (dictionary_splat) @dictunpacking { let @dictunpacking.node = (ast-node @dictunpacking "DictUnpacking") } (set) @set { let @set.node = (ast-node @set "Set") } (boolean_operator) @boolop { let @boolop.node = (ast-node @boolop "BoolOp") } (comparison_operator) @compop { let @compop.node = (ast-node @compop "Compare") } [ (unary_operator) (not_operator) ] @unaryop { let @unaryop.node = (ast-node @unaryop "UnaryOp") } (exec_statement) @exec { let @exec.node = (ast-node @exec "Exec") } (print_statement) @print { let @print.node = (ast-node @print "Print") } (return_statement) @return { let @return.node = (ast-node @return "Return") } (yield . "from"? @from) @yield { var yield_node = "Yield" if some @from { set yield_node = "YieldFrom" } let @yield.node = (ast-node @yield yield_node) } (ellipsis) @ellipsis { let @ellipsis.node = (ast-node @ellipsis "Ellipsis") } (await) @await { let @await.node = (ast-node @await "Await") } (try_statement) @try { let @try.node = (ast-node @try "Try") } (except_clause) @except { let @except.node = (ast-node @except "ExceptStmt") } (except_group_clause) @except { let @except.node = (ast-node @except "ExceptGroupStmt") } (named_expression) @assignexpr { let @assignexpr.node = (ast-node @assignexpr "AssignExpr") } (conditional_expression) @ifexp { let @ifexp.node = (ast-node @ifexp "IfExp") } (subscript) @subscript { let @subscript.node = (ast-node @subscript "Subscript") } (slice) @slice { let @slice.node = (ast-node @slice "Slice") } (attribute) @attribute { let @attribute.node = (ast-node @attribute "Attribute") } (while_statement) @while { let @while.node = (ast-node @while "While") } (generator_expression) @generatorexp { let @generatorexp.node = (ast-node @generatorexp "GeneratorExp") } (for_in_clause) @for { let @for.node = (ast-node @for "For") } (if_clause) @if { let @if.node = (ast-node @if "If") } (list_comprehension) @listcomp { let @listcomp.node = (ast-node @listcomp "ListComp") } (set_comprehension) @setcomp { let @setcomp.node = (ast-node @setcomp "SetComp") } (dictionary_comprehension) @dictcomp { let @dictcomp.node = (ast-node @dictcomp "DictComp") } [ (with_statement) (with_item)] @with { let @with.node = (ast-node @with "With") } (match_statement) @match { let @match.node = (ast-node @match "Match") } ; Do not create an AST node for 'cases', we just wire up the children instead. (case_block) @case { let @case.node = (ast-node @case "Case") } (match_as_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchAsPattern") } (match_or_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchOrPattern") } (match_literal_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchLiteralPattern") } (match_capture_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchCapturePattern") } (match_wildcard_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchWildcardPattern") } (match_value_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchValuePattern") } (match_group_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchGroupPattern") } (match_sequence_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchSequencePattern") } (match_star_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchStarPattern") } (match_mapping_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchMappingPattern") } (match_double_star_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchDoubleStarPattern") } (match_key_value_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchKeyValuePattern") } (match_class_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchClassPattern") } ; Do not create AST nodes for 'only_positionals', 'only_keywords', ; 'partly_positionals', and 'partly_keywords'. We just wire up the children instead. (match_keyword_pattern) @pattern { let @pattern.node = (ast-node @pattern "MatchKeywordPattern") } (guard) @guard { let @guard.node = (ast-node @guard "Guard") } [(parameters) (lambda_parameters)] @params { let @params.node = (ast-node @params "arguments") } [(false) (true) (none)] @const { let @const.node = (ast-node @const "Name") } (lambda) @lambda { let @lambda.node = (ast-node @lambda "Lambda") } (future_import_statement) @import { let @import.node = (ast-node @import "Import") } (typevar_parameter) @typevar { let @typevar.node = (ast-node @typevar "TypeVar") } (typevartuple_parameter) @typevartuple { let @typevartuple.node = (ast-node @typevartuple "TypeVarTuple") } (paramspec_parameter) @paramspec { let @paramspec.node = (ast-node @paramspec "ParamSpec") } (type_alias_statement) @typealias { let @typealias.node = (ast-node @typealias "TypeAlias") } ;;;;;; End of part 1. ;;;;;; Part 2: The awkward bunch. ;;;;;; Workarounds for node locations ; These are (hopefully temporary) workarounds for the nodes for which the default start and end does ; not agree with what our internal AST provides. ; Once the new parser is in place, we can consider getting rid of these workarounds. ;;; If ; End position is set to the end of the `:` after the condition. [ (if_statement condition: (_) . ":" @colon) @if (elif_clause condition: (_) . ":" @colon) @if ] { attr (@if.node) _location_end = (location-end @colon) } ;;; For ; Same as with `if`, we must include the `:` in the position. (for_statement right: (_) . ":" @colon ) @for { attr (@for.node) _location_end = (location-end @colon) } ;;; While ; Same as with `if`, we must include the `:` in the position. (while_statement condition: (_) . ":" @colon ) @while { attr (@while.node) _location_end = (location-end @colon) } ;;; Tuples ; In the Python AST tuple start and end positions are set to the start and end of the first and last ; elements. In `tree-sitter-python`, the parentheses are included. [ (tuple . (comment)* . element: (_) @first) (tuple_pattern . (comment)* . element: (_) @first) ] @tuple { attr (@tuple.node) _location_start = (location-start @first) } [ (tuple !trailing_comma element: (_) @last . (comment)* . ")" .) (tuple trailing_comma: _ @last) (tuple_pattern element: (_) @last .) ] @tuple { attr (@tuple.node) _location_end = (location-end @last) } ;;; Try (try_statement ":" @colon) @try { attr (@try.node) _location_end = (location-end @colon) } (except_clause ":" @colon) @except { attr (@except.node) _location_end = (location-end @colon) } ;;; GeneratorExp (generator_expression . "(" . (comment)* . (expression) @start [(for_in_clause) (if_clause)] @end . (comment)* . ")" .) @generatorexp { attr (@generatorexp.node) _location_start = (location-start @start) attr (@generatorexp.node) _location_end = (location-end @end) } (if_clause (expression) @expr) @if { attr (@if.node) _location_start = (location-start @expr) attr (@if.node) _location_end = (location-end @expr) } (generator_expression . "(" . (comment)* . (expression) @start (for_in_clause) @child [(for_in_clause) (if_clause)] @end . (comment)* . ")" .) @genexpr { attr (@child.node) _location_start = (location-start @start) attr (@child.node) _location_end = (location-end @end) } (generator_expression . "(" . (comment)* . (expression) @start (for_in_clause) @end . (comment)* . ")" .) @genexpr { attr (@end.node) _location_start = (location-start @start) attr (@end.node) _location_end = (location-end @end) } (list_comprehension (for_in_clause) @child) @genexpr { attr (@child.node) _location_start = (location-start @genexpr) attr (@child.node) _location_end = (location-end @genexpr) } (set_comprehension (for_in_clause) @child) @genexpr { attr (@child.node) _location_start = (location-start @genexpr) attr (@child.node) _location_end = (location-end @genexpr) } (dictionary_comprehension (for_in_clause) @child) @genexpr { attr (@child.node) _location_start = (location-start @genexpr) attr (@child.node) _location_end = (location-end @genexpr) } ;;; With (with_statement "with" @start (with_clause . (with_item) @first) ":" @end ) { attr (@first.node) _location_start = (location-start @start) attr (@first.node) _location_end = (location-end @end) } ;;;;;; End of workarounds ;;;;;; End of part 2 ;;;;;; Part 3: All of the simple nodes. ;;;;;; Module ; Nodes with a `body` field containing statements. (module (_) @stmt) @parent { edge @parent.node -> @stmt.node attr (@parent.node -> @stmt.node) body = (named-child-index @stmt) } ;;;;;; Comments (comment) @comment { attr (@comment.node) text = (source-text @comment) } ;;;;;; Expressions (parenthesized_expression inner: (_) @inner ) @outer { attr (@outer.node) _skip_to = @inner.node attr (@inner.node) parenthesised = #true } (keyword_argument name: (_) @name value: (_) @value ) @kwarg { attr (@kwarg.node) arg = (source-text @name) attr (@kwarg.node) value = @value.node } ;;;;;; Num [ (integer) (float) ] @num { ; As we must support a large variety of number literals, we simply forward the source string ; representation to the Python AST reconstruction. let source = (source-text @num) attr (@num.node) n = source attr (@num.node) text = source } ;;;;;; End of Num ;;;;;; Delete (delete_statement target: (expression_list element: (_) @target ) ) @del { edge @del.node -> @target.node attr (@del.node -> @target.node) targets = (named-child-index @target) } (delete_statement target: (_) @target) @del { attr (@target.node) ctx = "del" } (delete_statement [(identifier) (subscript) (attribute)] @id) @del { edge @del.node -> @id.node attr (@del.node -> @id.node) targets = 0 } ;;;;;; Name [(identifier) (false) (true) (none)] @id { attr (@id.node) variable = (source-text @id) } ;;;;;; End of Name ;;;;;; Arguments [ (keyword_argument value: (_) @id) (argument_list element: (_) @id) ] { attr (@id.node) ctx = "load" } [ (keyword_argument name: (_) @id) ] { attr (@id.node) ctx = "store" } ;;;;;; End of Arguments ;;;;;; BinOp (binary_operator left: (_) @left operator: _ @op right: (_) @right ) @bin { attr (@bin.node) left = @left.node attr (@bin.node) right = @right.node attr (@bin.node) op = (source-text @op) attr (@left.node) ctx = "load" attr (@right.node) ctx = "load" } ;;;;;; End of BinOp ;;;;;; If ; If.test [ (if_statement condition: (_) @test) @if (elif_clause condition: (_) @test) @if ] { attr (@if.node) test = @test.node attr (@test.node) ctx = "load" } ; If.orelse - first `elif` clause (if_statement consequence: (_) . (comment)* . (elif_clause) @elif ) @if { edge @if.node -> @elif.node attr (@if.node -> @elif.node) orelse = 0 } ; If.orelse - link up adjacent `elif` clauses ( (elif_clause) @elif1 . (comment)* . (elif_clause) @elif2 ) { edge @elif1.node -> @elif2.node attr (@elif1.node -> @elif2.node) orelse = 0 } ; If.orelse - match outer `else` up with last `elif` clause (i.e. innermost `if`) (if_statement (elif_clause) @elif . (comment)* . alternative: (else_clause body: (block (_) @orelse)) ) { edge @elif.node -> @orelse.node attr (@elif.node -> @orelse.node) orelse = (named-child-index @orelse) } ; If.orelse - when there are no `elif` clauses. (if_statement consequence: (_) . (comment)* . alternative: (else_clause body: (block (_) @orelse)) ) @if { edge @if.node -> @orelse.node attr (@if.node -> @orelse.node) orelse = (named-child-index @orelse) } ; If.body [ (if_statement consequence: (block (_) @stmt)) @parent (elif_clause consequence: (block (_) @stmt)) @parent ] { edge @parent.node -> @stmt.node attr (@parent.node -> @stmt.node) body = (named-child-index @stmt) } ;;;;;; end of If ;;;;;; For statements (for_statement left: (_) @left right: (_) @right ) @for { attr (@for.node) target = @left.node attr (@left.node) ctx = "store" attr (@for.node) iter = @right.node attr (@right.node) ctx = "load" } (for_statement body: (block (_) @body) ) @for { edge @for.node -> @body.node attr (@for.node -> @body.node) body = (named-child-index @body) } (for_statement alternative: (else_clause body: (block (_) @orelse)) ) @for { edge @for.node -> @orelse.node attr (@for.node -> @orelse.node) orelse = (named-child-index @orelse) } (for_statement "async" "for" @for_keyword) @for { attr (@for.node) is_async = #true attr (@for.node) _location_start = (location-start @for_keyword) } ;;;;;; end of For ;;;;;; Call expressions (`a(b, c, *d, **e)`) (call function: (_) @func) @call { attr (@call.node) func = @func.node attr (@func.node) ctx = "load" } ; Handle non-keyword arguments (call arguments: (argument_list element: (_) @arg)) @call { if (not (or (instance-of @arg "keyword_argument") (instance-of @arg "dictionary_splat"))) { edge @call.node -> @arg.node attr (@call.node -> @arg.node) positional_args = (named-child-index @arg) } } (call arguments: (argument_list element: (keyword_argument) @arg)) @call { edge @call.node -> @arg.node attr (@call.node -> @arg.node) named_args = (named-child-index @arg) } (call arguments: (argument_list element: (dictionary_splat) @arg)) @call { edge @call.node -> @arg.node attr (@call.node -> @arg.node) named_args = (named-child-index @arg) } (call arguments: (generator_expression) @gen) @call { edge @call.node -> @gen.node attr (@call.node -> @gen.node) positional_args = 0 } ;;;;;; end of Call (`a(b, c, *d, **e)`) ;;;;;; End of part 3 ;;;;;; Part 4: All of the complicated bits (e.g. nodes that need additional synthesis) ;;;;;; ListComp (`[a for b in c if d]`) ; See GeneratorExp for details. (list_comprehension) @genexpr { ; Synthesize the `genexpr` function let @genexpr.fun = (ast-node @genexpr "Function") attr (@genexpr.node) function = @genexpr.fun attr (@genexpr.fun) name = "listcomp" ; Synthesize the `.0` parameter let @genexpr.arg = (ast-node @genexpr "Name") attr (@genexpr.arg) variable = ".0" attr (@genexpr.arg) ctx = "param" edge @genexpr.fun -> @genexpr.arg attr (@genexpr.fun -> @genexpr.arg) args = 0 attr (@genexpr.fun) kwonlyargs = #null attr (@genexpr.fun) kwarg = #null ; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter ; ("param" vs. "load") hence we must create another node. let @genexpr.arg_use = (ast-node @genexpr "Name") attr (@genexpr.arg_use) variable = ".0" attr (@genexpr.arg_use) ctx = "load" } ;;;;;; End of ListComp (`[a for b in c if d]`) ;;;;;; SetComp (`{a for b in c if d}`) ; See GeneratorExp for details. (set_comprehension) @genexpr { ; Synthesize the `genexpr` function let @genexpr.fun = (ast-node @genexpr "Function") attr (@genexpr.node) function = @genexpr.fun attr (@genexpr.fun) name = "setcomp" ; Synthesize the `.0` parameter let @genexpr.arg = (ast-node @genexpr "Name") attr (@genexpr.arg) variable = ".0" attr (@genexpr.arg) ctx = "param" edge @genexpr.fun -> @genexpr.arg attr (@genexpr.fun -> @genexpr.arg) args = 0 attr (@genexpr.fun) kwonlyargs = #null attr (@genexpr.fun) kwarg = #null ; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter ; ("param" vs. "load") hence we must create another node. let @genexpr.arg_use = (ast-node @genexpr "Name") attr (@genexpr.arg_use) variable = ".0" attr (@genexpr.arg_use) ctx = "load" } ;;;;;; End of SetComp (`{a for b in c if d}`) ;;;;;; DictComp (`{a: b for c in d if e}`) ; See GeneratorExp for details. (dictionary_comprehension body: (pair key: (_) @key value: (_) @value ) ) @genexpr { ; Synthesize the `genexpr` function let @genexpr.fun = (ast-node @genexpr "Function") attr (@genexpr.node) function = @genexpr.fun attr (@genexpr.fun) name = "dictcomp" ; Synthesize the `.0` parameter let @genexpr.arg = (ast-node @genexpr "Name") attr (@genexpr.arg) variable = ".0" attr (@genexpr.arg) ctx = "param" edge @genexpr.fun -> @genexpr.arg attr (@genexpr.fun -> @genexpr.arg) args = 0 attr (@genexpr.fun) kwonlyargs = #null attr (@genexpr.fun) kwarg = #null ; Synthesize the use of `.0` in the innermost `yield`. This has a different context than the parameter ; ("param" vs. "load") hence we must create another node. let @genexpr.arg_use = (ast-node @genexpr "Name") attr (@genexpr.arg_use) variable = ".0" attr (@genexpr.arg_use) ctx = "load" } ;;;;;; End of DictComp (`{a: b for c in d if e}`) ;;;;;; GeneratorExp (`(a for b in c if d)`) ; The big one. This one will require quite a bit of setup. ; ; First of all, we need to explain what the old parser does to generator expressions. ; ; The following generator expression ; ; (a ; for b in c ; if d ; if e ; for f in g ; if h ; if i ; ) ; ; becomes ; ; def genexpr(.0): ; for b in .0: ; if e: ; if d: ; for f in g: ; if i: ; if h: ; yield a ; ; where `.0` is a (very oddly named) variable. ; ; Note in particular the reversing of the `if`s, the way `c` is replaced with `.0`, and the way ; `a` is used in the innermost `yield`. ; First of all, we need to set up the generated function and its parameter. These both copy the location ; information for the entire generator expression (yes, it is a wide parameter!) and so we must recreate the logic for ; setting this location information correctly. (generator_expression . "(" . (comment)* . (expression) @start [(for_in_clause) (if_clause)] @end . (comment)* . ")" .) @genexpr { ; Synthesize the `genexpr` function let @genexpr.fun = (ast-node @genexpr "Function") attr (@genexpr.fun) _location_start = (location-start @start) attr (@genexpr.fun) _location_end = (location-end @end) attr (@genexpr.node) function = @genexpr.fun attr (@genexpr.fun) name = "genexpr" ; Synthesize the `.0` parameter let @genexpr.arg = (ast-node @genexpr "Name") attr (@genexpr.arg) _location_start = (location-start @start) attr (@genexpr.arg) _location_end = (location-end @end) attr (@genexpr.arg) variable = ".0" attr (@genexpr.arg) ctx = "param" edge @genexpr.fun -> @genexpr.arg attr (@genexpr.fun -> @genexpr.arg) args = 0 attr (@genexpr.fun) kwonlyargs = #null attr (@genexpr.fun) kwarg = #null ; Default to true, but we'll set it to false if we're inside a call var genexpr_parenthesised = #true if (instance-of (get-parent @genexpr) "call") { set genexpr_parenthesised = #null } attr (@genexpr.node) parenthesised = genexpr_parenthesised ; Synthesize the use of `.0` in the outermost `for`. This has a different context than the parameter ; ("param" vs. "load") hence we must create another node. let @genexpr.arg_use = (ast-node @genexpr "Name") attr (@genexpr.arg_use) _location_start = (location-start @start) attr (@genexpr.arg_use) _location_end = (location-end @end) attr (@genexpr.arg_use) variable = ".0" attr (@genexpr.arg_use) ctx = "load" } ; Link up the outermost `for` [ (generator_expression body: (_) . (comment)* . (for_in_clause left: (_) @target right: (_) @iterable ) @forin ) @genexpr (list_comprehension body: (_) . (comment)* . (for_in_clause left: (_) @target right: (_) @iterable ) @forin ) @genexpr (set_comprehension body: (_) . (comment)* . (for_in_clause left: (_) @target right: (_) @iterable ) @forin ) @genexpr (dictionary_comprehension body: (_) . (comment)* . (for_in_clause left: (_) @target right: (_) @iterable ) @forin ) @genexpr ] { attr (@genexpr.node) iterable = @iterable.node attr (@iterable.node) ctx = "load" edge @genexpr.fun -> @forin.node attr (@genexpr.fun -> @forin.node) body = 0 attr (@forin.node) target = @target.node attr (@target.node) ctx = "store" attr (@forin.node) iter = @genexpr.arg_use } ; Set up all subsequent `for ... in ...` [ (generator_expression body: (_) [(for_in_clause) (if_clause)] (for_in_clause left: (_) @target right: (_) @iter) @forin ) (list_comprehension body: (_) [(for_in_clause) (if_clause)] (for_in_clause left: (_) @target right: (_) @iter) @forin ) (set_comprehension body: (_) [(for_in_clause) (if_clause)] (for_in_clause left: (_) @target right: (_) @iter) @forin ) (dictionary_comprehension body: (_) [(for_in_clause) (if_clause)] (for_in_clause left: (_) @target right: (_) @iter) @forin ) ] { attr (@forin.node) target = @target.node attr (@target.node) ctx = "store" attr (@forin.node) iter = @iter.node attr (@iter.node) ctx = "load" } ; Set up each `if ...` (if_clause (expression) @test) @if { attr (@if.node) test = @test.node attr (@test.node) ctx = "load" } ; Link adjacent `for` clauses together (_ (for_in_clause) @forin1 . (comment)* . (for_in_clause) @forin2 ) { edge @forin1.node -> @forin2.node attr (@forin1.node -> @forin2.node) body = 0 } ; For the first `if` clause after a `for` clause, record both the `for` and `if` clauses in variables that we ; will propagate along. That way, when we get to the last `if` clause, we can link it up with the `for` ; clause, and we can link up the _first_ `if` clause with whatever follows the last `if` clause. (_ (for_in_clause) @forin . (comment)* . (if_clause) @if ) { let @if.for = @forin.node let @if.first_if = @if.node } ; Link up adjacent `if` clauses (note the reversed order!) and propagate the `for` and `first_if` values. (_ (if_clause) @if1 . (comment)* . (if_clause) @if2 ) { edge @if2.node -> @if1.node attr (@if2.node -> @if1.node) body = 0 let @if2.for = @if1.for let @if2.first_if = @if1.first_if } ; After the last `if` in a chain, we hook it up as the body of its associated `for`, and hook up the _first_ ; `if` as the one that has the following `for` as its body. ; The case where there is no `for` following the last `if` is handled later. (_ (if_clause) @if . (comment)* . (for_in_clause) @forin ) { edge @if.for -> @if.node attr (@if.for -> @if.node) body = 0 edge @if.first_if -> @forin.node attr (@if.first_if -> @forin.node) body = 0 } ; For everything except dictionary comprehensions, the innermost expression is just the `body` of the ; comprehension. [ (generator_expression body: (_) @body) @genexpr (list_comprehension body: (_) @body) @genexpr (set_comprehension body: (_) @body) @genexpr ] { let @genexpr.result = @body.node } ; For dict comprehensions, we build an explicit tuple using the key and value pair. (dictionary_comprehension body: (pair key: (_) @key value: (_) @value ) @body ) @genexpr { let tuple = (ast-node @body "Tuple") edge tuple -> @key.node attr (tuple -> @key.node) elts = 1 edge tuple -> @value.node attr (tuple -> @value.node) elts = 0 ; TODO verify that it is correct to use a `(value, key)` tuple, and not a `(key, value)` tuple above. ; That is what the current parser does... attr (tuple) ctx = "load" let @genexpr.result = tuple } ; For the final clause, we need to hook it up with the rest of the expression. ; If it's an `if` clause, we need to hook it up with the `yield` expression and with its associated ; `for` clause. ; If it's a `for` clause, we only need to create and hook it up with the `yield` expression. ; ; It would be tempting to use anchors here, but they just don't work. In particular, an anchor of ; the form `. (comment)* . )` (which would be needed in order to handle the case where there are ; comments after the last clause) cause the `tree-sitter` query engine to match _all_ clauses, not ; just the last one. ; Instead, we gather up all clauses in a list (these will be in the order they appear in the source ; code), and extract the last element using a custom Rust function. [ (generator_expression body: (_) @body [(if_clause) (for_in_clause)]+ @last_candidates ) @genexpr (list_comprehension body: (_) @body [(if_clause) (for_in_clause)]+ @last_candidates ) @genexpr (set_comprehension body: (_) @body [(if_clause) (for_in_clause)]+ @last_candidates ) @genexpr (dictionary_comprehension body: (_) @body [(if_clause) (for_in_clause)]+ @last_candidates ) @genexpr ] { let last = (get-last-element @last_candidates) let expr = (ast-node @body "Expr") let yield = (ast-node @body "Yield") let @genexpr.expr = expr let @genexpr.yield = yield attr (expr) value = yield attr (yield) value = @genexpr.result attr (@body.node) ctx = "load" if (instance-of last "if_clause") { edge last.first_if -> expr attr (last.first_if -> expr) body = 0 ; Hook up this `if` clause with its `for` clause edge last.for -> last.node attr (last.for -> last.node) body = 0 } else { ; If the last clause is a `for`, we only have to create and hook up the `yield` expression. edge last.node -> expr attr (last.node -> expr) body = 0 } } ; For whatever reason, we do not consider parentheses around the yielded expression if they are present, so ; we must adapt the location accordingly. [ (generator_expression body: (_ . "(" . _ @first) ) (list_comprehension body: (_ . "(" . _ @first) ) (set_comprehension body: (_ . "(" . _ @first) ) (dictionary_comprehension body: (_ . "(" . _ @first) ) ] @genexpr { attr (@genexpr.expr) _location_start = (location-start @first) attr (@genexpr.yield) _location_start = (location-start @first) } ; Annoyingly, setting the end location of the synthesized `Expr` and `Yield` is a big mess, ; so we have to use mutable variables. [ (generator_expression body: (_) @body) (list_comprehension body: (_) @body) (set_comprehension body: (_) @body) (dictionary_comprehension body: (_) @body) ] @genexpr { var @genexpr.body_end = (location-end @body) } ; The reason we need to do this mutably is because the query `(_ _ @last . ")" .)`, despite the liberal use ; of anchors, is broken (due to a bug in `tree-sitter`). Specifically, it will match both `b` and the ; following `,` in the tuple expression `(a, b,)`. This means we cannot set the attribute in this stanza ; (since overwriting attributes is not allowed) and so we instead write it to a mutable variable and set it ; later. Because the order in which the captures are returned results in `b` being matched before `,` this ; gives the correct behaviour. [ (generator_expression body: (_ _ @last . ")" .) ) (list_comprehension body: (_ _ @last . ")" .) ) (set_comprehension body: (_ _ @last . ")" .) ) (dictionary_comprehension body: (_ _ @last . ")" .) ) ] @genexpr { set @genexpr.body_end = (location-end @last) } [ (generator_expression) (list_comprehension) (set_comprehension) (dictionary_comprehension) ] @genexpr { attr (@genexpr.expr) _location_end = @genexpr.body_end attr (@genexpr.yield) _location_end = @genexpr.body_end } ;;;;;; End of GeneratorExp (`(a for b in c if d)`) ;;;;;; Class statements ; A class definition ; ; class Foo(*bases, **keywords): body ; ; is turned into an actual assignment statement, with the class name as the left-hand side. ; ; Foo = $classexpr(name='Foo', bases, keywords, inner_scope=$class(name='Foo', body)) ; ; (with a suitably magical definition of the `$` prefix). ; ; So we have to synthesize both the outer assignment, and also the two representatives of the class. (class_definition name: (identifier) @name ":" @colon ) @class { ; To make it clearer that the outer node is an assignment, we create an alias for it. let @class.assign = @class.node ; We reuse the identifier as the left hand side of the assignment. let @class.assign_lhs = @name.node ; Synthesized nodes: the class_expr node, and the class node. let @class.class_expr = (ast-node @class "ClassExpr") let @class.inner_scope = (ast-node @class "Class") ; Setting up the outer assignment edge @class.assign -> @class.assign_lhs attr (@class.assign -> @class.assign_lhs) targets = 0 attr (@class.assign) value = @class.class_expr attr (@class.assign) _location_end = (location-end @colon) attr (@class.assign_lhs) ctx = "store" let class_name = (source-text @name) ; The right hand side of the assignment, a `ClassExpr`. attr (@class.class_expr) name = class_name attr (@class.class_expr) inner_scope = @class.inner_scope ; `bases` will be set elsewhere ; `keywords` will be set elsewhere attr (@class.class_expr) _location_end = (location-end @colon) ; The inner scope of the class_expr, a `Class`. attr (@class.inner_scope) name = class_name ; body will be set in a separate stanza. attr (@class.inner_scope) _location_end = (location-end @colon) } ; Class.body (class_definition body: (block (_) @stmt) ) @class { edge @class.inner_scope -> @stmt.node attr (@class.inner_scope -> @stmt.node) body = (named-child-index @stmt) } ; Class.bases - using `(_ !value !name)` as a proxy for all non-keyword arguments. ; In particular, `keyword_argument` nodes have a `name` field, and `dictionary_splat` ; nodes have a `value` field. (class_definition superclasses: (argument_list element: (_ !value !name) @arg) ) @class { edge @class.class_expr -> @arg.node attr (@class.class_expr -> @arg.node) bases = (named-child-index @arg) } ; Class.keywords of the form `foo=bar` (class_definition superclasses: (argument_list element: (keyword_argument) @arg) ) @class { edge @class.class_expr -> @arg.node attr (@class.class_expr -> @arg.node) keywords = (named-child-index @arg) } ; Class.keywords of the form `**kwargs` (class_definition superclasses: (argument_list element: (dictionary_splat) @arg) ) @class { edge @class.class_expr -> @arg.node attr (@class.class_expr -> @arg.node) keywords = (named-child-index @arg) } ;;;;;; End of Class ;;;;;; Assign statements ; Assignment statements require a bit of interesting handling, since we represent a chained ; assignment such as `a = b = 5` as a single `Assign` node with multiple targets and a single ; right-hand side. This makes it somewhat complicated (but still doable) to determine the index of ; any single target in the resulting list. ; ; The way we handle this is by explicitly propagating two variables inwards. The first variable ; keeps track of the outermost node in a chain of assignments, and the second variable keeps track of ; the index of the left-hand side of the current assignment. ; Base case, for the outermost assignment we set the outermost node to this node, and the index to zero. (expression_statement (assignment !type) @assign) @expr { let @assign.outermost_assignment = @assign.node let @assign.target_index = 0 } ; Propagating the two variables inwards, increasing the index by one. Note that this depends on ; having the query match from the outside in -- if this evaluation order ever changes, this will break. (assignment !type right: (assignment) @inner) @outer { let @inner.outermost_assignment = @outer.outermost_assignment let @inner.target_index = (plus @outer.target_index 1) } ; Finally, with the above variables set, we can -- for each assignment -- create an edge from the ; outermost assignment to it, and set its index to the index that we've calculated for this node. (assignment !type left: (_) @target) @assign { edge @assign.outermost_assignment -> @target.node attr (@assign.outermost_assignment -> @target.node) targets = @assign.target_index attr (@target.node) ctx = "store" } ; In addition to the above, we must ensure that the `value` attribute of the outermost assignment ; points to the _innermost_ right-hand side. We do this by first setting the `value` attribute for ; _all_ assignments... (assignment !type right: (_) @value) @assign { attr (@assign.node) value = @value.node attr (@value.node) ctx = "load" } ; ... and then for assignments that are _inside_ other assigments, we use the `_skip_to` attribute ; to jump across the outer assignment. ; ; Thus, the outermost assignment's `value` will point to its right-hand side, but this one will (if ; it's an assignment itself) skip to _its_ right-hand side, and so on until we reach a right-hand side ; that is not an assignment. (assignment !type right: (assignment right: (_) @inner) @outer) { attr (@outer.node) _skip_to = @inner.node } ;;;;;; End of Assign ;;;;;; AnnAssign (assignment left: (_) @target type: (type (expression) @type) ) @assign { attr (@assign.node) target = @target.node attr (@target.node) ctx = "store" attr (@assign.node) annotation = @type.node attr (@type.node) ctx = "load" } (assignment left: (_) @target type: (_) right: (_) @value ) @assign { attr (@assign.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of AnnAssign ;;;;;; AugAssign (augmented_assignment left: (_) @left operator: _ @op right: (_) @right ) @augassign { let binop = (ast-node @augassign "BinOp") attr (@augassign.node) operation = binop attr (binop) left = @left.node attr (@left.node) ctx = "load" ; yes, it really is "load". attr (binop) op = (source-text @op) attr (binop) right = @right.node attr (@right.node) ctx = "load" } ;;;;;; End of AugAssign ;;;;;; Global (global_statement (identifier) @name) @global { edge @global.node -> @name.node attr (@global.node -> @name.node) names = (named-child-index @name) attr (@name.node) _is_literal = (source-text @name) } ;;;;;; End of Global ;;;;;; Nonlocal (nonlocal_statement (identifier) @name) @nonlocal { edge @nonlocal.node -> @name.node attr (@nonlocal.node -> @name.node) names = (named-child-index @name) attr (@name.node) _is_literal = (source-text @name) } ;;;;;; End of Nonlocal ;;;;;; Import (`import ...`) ; `import j1.j2 as j3, j4, ...` becomes ; ; Import: ; names: [ ; alias: ; value: ; ImportExpr: ; level: 0 # always 0 for absolute imports ; name: 'j1.j2' ; top: False ; asname: ; Name: ; variable: Variable('j3', None) ; ctx: Store ; alias: ; value: ; ImportExpr: ; level: 0 # always 0 for absolute imports ; name: 'j4' ; top: True ; asname: ; Name: ; variable: Variable('j4', None) ; ctx: Store ; ... ; ] ; ; from ; ; module ; import_statement ; name: aliased_import ; name: dotted_name ; identifier # j1 ; identifier # j2 ; alias: identifier j3 ; name: dotted_name ; identifier # j4 ; ; This means we have to hang our `alias` nodes off of the `dotted_name` and ; `aliased_import` nodes. ; Import.names (import_statement name: (_) @name) @import { edge @import.node -> @name.node attr (@import.node -> @name.node) names = (named-child-index @name) } ; Imports without an explicit alias -- extract the root module name (import_statement name: (dotted_name . (identifier) @first) @alias) { let import_expr = (ast-node @alias "ImportExpr") attr (import_expr) level = 0 attr (import_expr) name = (source-text @alias) attr (import_expr) top = #true attr (@alias.node) value = import_expr attr (@alias.node) asname = @first.node attr (@first.node) ctx = "store" } ; Not strictly needed (but the AST reconstruction will complain otherwise) we ; assign a context to each identifier in a dotted name (except the first part, ; which already gets one elsewhere). (dotted_name (identifier) (identifier) @name) { attr (@name.node) ctx = "load" } ; For dotted imports `a.b.c` the location for the `Name` corresponding to the ; `a` part covers the entire expression, so we explicitly match the final ; element and set the location appropriately. If there is only one element, ; this stanza doesn't fire, but in that case the location is actually correct ; already. (import_statement name: (dotted_name . (identifier) @first (identifier) @last . ) ) { attr (@first.node) _location_end = (location-end @last) } ; Imports with an explicit alias (import_statement (aliased_import name: (dotted_name . (identifier) @first) @name alias: (identifier) @asname ) @alias ) { let import_expr = (ast-node @name "ImportExpr") attr (import_expr) level = 0 attr (import_expr) name = (source-text @name) attr (import_expr) top = #false attr (@alias.node) value = import_expr attr (@alias.node) asname = @asname.node attr (@asname.node) ctx = "store" attr (@first.node) ctx = "load" } ;;;;;; End of Import (`import ...`) ;;;;;; Import (`from ... import ...`) ; Oh what a twisty mess these are. First, the prototypical layout of a ; `from some_module import x1 as y1, x2, ...` statement is as follows: ; ; Import: ; names: [ ; alias: ; value: ; ImportMember: ; module: ; ImportExpr; ; level: ; name: ; top: #false ; name: ; asname: ; Name: ; variable: Variable(, None) ; ctx: "store" ; alias: ; value: ; ImportMember: ; module: ; ImportExpr: ; level: ; name: ; top: #false ; name: ; asname: ; Name: ; variable: Variable(, None) # Note the reuse! ; ctx: "store" ; ... ; ] ; ; In particular, `alias` nodes are used even if no aliasing takes place. ; Now, on the flip side we have the `tree-sitter-python` output. Here ; the corresponding structure for `from ..some_module import x1 as y1, x2` ; is as follows: ; ; module ; import_from_statement ; module_name: relative_import ; import_prefix # `..` ; dotted_name ; identifier # some_module ; name: aliased_import ; name: dotted_name ; identifier # x1 ; alias: identifier # y1 ; name: dotted_name ; identifier # x2 ; ; Now, we need to pin our `alias` nodes on something, and the only thing we can ; really rely on is whatever is in the `name` field of the ; `import_from_statement` ; Import.names [ (import_from_statement name: (_) @alias ) (future_import_statement name: (_) @alias ) ] @import { edge @import.node -> @alias.node attr (@import.node -> @alias.node) names = (named-child-index @alias) } ; Setting up the synthesized nodes for `ImportMember` and `ImportExpr` ; when the module name is _not_ a relative import. [ (import_from_statement module_name: (dotted_name) @name name: (_) @alias ) (future_import_statement "__future__" @name name: (_) @alias ) ] { let @alias.import_member = (ast-node @alias "ImportMember") let @alias.import_expr = (ast-node @name "ImportExpr") attr (@alias.node) value = @alias.import_member attr (@alias.import_member) module = @alias.import_expr attr (@alias.import_expr) level = 0 attr (@alias.import_expr) name = (source-text @name) attr (@alias.import_expr) top = #false } ; Setting up the synthesized nodes for `ImportMember` and `ImportExpr` ; when the module name _is_ a relative import. (import_from_statement module_name: (relative_import name: (dotted_name) @name) @rel name: (_) @alias ) { let @alias.import_member = (ast-node @alias "ImportMember") let @alias.import_expr = (ast-node @rel "ImportExpr") attr (@alias.node) value = @alias.import_member attr (@alias.import_member) module = @alias.import_expr ; ImportExpr.level is computed elsewhere attr (@alias.import_expr) name = (source-text @name) attr (@alias.import_expr) top = #false } ; Setting up the synthesized nodes for `ImportMember` and `ImportExpr` ; when the module is a relative import with no module name (e.g. `from . import ...`). (import_from_statement module_name: (relative_import !name) @rel name: (_) @alias ) { let @alias.import_member = (ast-node @alias "ImportMember") let @alias.import_expr = (ast-node @rel "ImportExpr") attr (@alias.node) value = @alias.import_member attr (@alias.import_member) module = @alias.import_expr ; ImportExpr.level is computed elsewhere attr (@alias.import_expr) name = #null attr (@alias.import_expr) top = #false } ; Set the level for relative imports (import_from_statement module_name: (relative_import (import_prefix) @prefix) name: (_) @alias ) { var level = 0 ; Figure out the number of `.`s in the prefix. scan (source-text @prefix) { "\." { set level = (plus level 1) } } attr (@alias.import_expr) level = level } ; Set aliases for non-aliased imports [ (import_from_statement name: (dotted_name (identifier) @name) @alias ) (future_import_statement name: (dotted_name (identifier) @name) @alias ) ] { attr (@alias.node) asname = @name.node attr (@alias.import_member) name = (source-text @name) attr (@name.node) ctx = "store" } ; Set aliases for aliased imports (import_from_statement name: (aliased_import name: (dotted_name) @first alias: (identifier) @asname ) @alias ) { attr (@alias.node) asname = @asname.node attr (@alias.import_member) name = (source-text @first) attr (@asname.node) ctx = "store" } ; Fix up remaining identifiers without contexts. (import_from_statement module_name: (dotted_name . (identifier) @first) ) { attr (@first.node) ctx = "load" } (import_from_statement module_name: (relative_import (dotted_name . (identifier) @first)) ) { attr (@first.node) ctx = "load" } (import_from_statement name: (aliased_import (dotted_name (identifier) @first)) ) { attr (@first.node) ctx = "load" } (import_from_statement module_name: (_) @name (wildcard_import) ) @importfrom { let importexpr = (ast-node @name "ImportExpr") let @importfrom.importexpr = importexpr attr (@importfrom.node) module = importexpr attr (importexpr) top = #false } ; Absolute star import: `from a import *` (import_from_statement module_name: (dotted_name) @name (wildcard_import) ) @importfrom { attr (@importfrom.importexpr) name = (source-text @name) attr (@importfrom.importexpr) level = 0 } ; Relative star import, with module name: `from ..a import *` (import_from_statement module_name: (relative_import (dotted_name) @name ) (wildcard_import) ) @importfrom { attr (@importfrom.importexpr) name = (source-text @name) } ; Relative star import, without module name: `from ... import *` (import_from_statement module_name: (relative_import (import_prefix) @prefix ) (wildcard_import) ) @importfrom { var level = 0 ; Figure out the number of `.`s in the prefix. scan (source-text @prefix) { "\." { set level = (plus level 1) } } attr (@importfrom.importexpr) level = level } ;;;;;; End of Import (`from ... import ...`) ;;;;;; Raise (`raise ...`) ; This one is interesting, since the `tree-sitter-python` grammar doesn't let ; us distinguish between `raise foo` and `raise foo, bar`. At the level of the ; `tree-sitter-python` output, both are `raise_statement` nodes with a single ; child. In the latter case, the child is an `expression_list` but there's ; currently no way to match _against_ a particular node type in a query. ; To get around this, we instead do the matching inside the stanza itself. (raise_statement . (_) @exc) @raise { if (not (instance-of @exc "expression_list") ) { attr (@raise.node) exc = @exc.node } attr (@exc.node) ctx = "load" } ; `raise ... from cause` (raise_statement cause: (_) @cause ) @raise { attr (@raise.node) cause = @cause.node attr (@cause.node) ctx = "load" } ; `raise type, inst` (raise_statement (expression_list . (_) @type . (_) @inst )) @raise { attr (@raise.node) type = @type.node attr (@raise.node) inst = @inst.node } ; `raise type, inst, tback` (raise_statement (expression_list . (_) . (_) . (_) @tback . )) @raise { attr (@raise.node) tback = @tback.node } ;;;;;; End of Raise (`raise ...`) ;;;;;; Assert (`assert ...`) (assert_statement . (_) @test ) @assert { attr (@assert.node) test = @test.node attr (@test.node) ctx = "load" } (assert_statement . (_) . (_) @msg ) @assert { attr (@assert.node) msg = @msg.node attr (@msg.node) ctx = "load" } ;;;;;; End of Assert (`assert ...`) ;;;;;; String (`"foo"`) ; For regular strings, see the handling of `(string !interpolation)` below. ; For concatenated strings, the necessary manipulations are quite complicated to express, ; so we instead move this problem into the Python side of things. Thus, a concatenated ; string only has to keep track of what its children are. (concatenated_string) @string { attr (@string.node) _prefix = (string-prefix @string) attr (@string.node) _fixup = #true } (concatenated_string (string) @part) @string { edge @string.node -> @part.node attr (@string.node -> @part.node) _children = (named-child-index @part) } ;;;;;; End of String (`"foo"`) ;;;;;; JoinedStr (`f"foo"`) ; f-strings are quite complicated for a variety of reasons. First of all, ; we need to synthesize empty strings to appear in-between interpolations ; that are immediately adjacent. Thus, the string `f"{1}{2}"`, which has ; a `tree-sitter-python` representation of the form ; ; (string (interpolation (integer)) (interpolation (integer))) ; ; needs to have three empty additional strings synthesized: ; - `f"{`, before the `1`, ; - `}{`, between the `1` and `2`, and ; - `}"`, after the `2`. ; ; Because of this, children of an f-string are indexed using triples of integers. ; The first component is either 0, 1, or 2, indicating whether this string appears at the ; beginning, in between, or at the end of the f-string. (At the beginning and end, the other ; two components are irrelevant.) The second component is the index of child, as seen by ; `tree-sitter`. The third component allows us to insert empty strings between adjacent children ; of the f-string. Thus, the string `f"{1}{2}"` has the following children at the given indices: ; `f"{"` at `[0,0,0]` ; `1` at `[1,1,0]` ; `}{` at `[1,1,1]` ; `2` at `[1,2,0]` ; `}"` at `[2,0,0]` ; First, we add any strings parts that appear either before or after an interpolation: [ (string interpolation: (_) string_content: (_) @part ) (string string_content: (_) @part interpolation: (_) ) ] @fstring { edge @fstring.node -> @part.node attr (@fstring.node -> @part.node) values = [1, (named-child-index @part), 0] let safe_string = (concatenate-strings (string-safe-prefix @fstring) (source-text @part) (string-quotes @fstring)) attr (@part.node) s = safe_string attr (@part.node) text = safe_string } ; In a similar fashion, any expressions that are interpolated: (string interpolation: (interpolation expression: (_) @part) @interp) @fstring { edge @fstring.node -> @part.node attr (@fstring.node -> @part.node) values = [1, (named-child-index @interp), 0] attr (@part.node) ctx = "load" } ; Any expressions inside the format specifier are appended at the end (string interpolation: (interpolation (format_specifier (format_expression expression: (_) @part ) @format_expression ) ) @interp ) @fstring { edge @fstring.node -> @part.node attr (@fstring.node -> @part.node) values = [1, (named-child-index @interp), (plus 1 (named-child-index @format_expression))] attr (@part.node) ctx = "load" } ; Next, the empty string before the first interpolation: (string . (interpolation "{" @end) ) @fstring { let empty_string = (ast-node @fstring "StringPart") edge @fstring.node -> empty_string attr (@fstring.node -> empty_string) values = [0, 0, 0] attr (empty_string) prefix = (string-prefix @fstring) attr (empty_string) s = "\"\"" let quotes = (string-quotes @fstring) attr (empty_string) text = (concatenate-strings quotes quotes) attr (empty_string) _location_end = (location-end @end) } ; Then, the empty string between two immediately adjacent interpolations: (string (interpolation "}" @start) @before . (interpolation "{" @end) ) @fstring { let empty_string = (ast-node @fstring "StringPart") edge @fstring.node -> empty_string attr (@fstring.node -> empty_string) values = [1, (named-child-index @before), 1] attr (empty_string) prefix = (string-prefix @fstring) attr (empty_string) s = "\"\"" let quotes = (string-quotes @fstring) attr (empty_string) text = (concatenate-strings quotes quotes) attr (empty_string) _location_start = (location-start @start) attr (empty_string) _location_end = (location-end @end) } ; And finally, the empty string after the last interpolation: (string (interpolation "}" @start) . ) @fstring { let empty_string = (ast-node @fstring "StringPart") edge @fstring.node -> empty_string attr (@fstring.node -> empty_string) values = [2, 0, 0] attr (empty_string) prefix = (string-prefix @fstring) attr (empty_string) s = "\"\"" let quotes = (string-quotes @fstring) attr (empty_string) text = (concatenate-strings quotes quotes) attr (empty_string) _location_start = (location-start @start) } ; If the f-string begins with a non-empty string, we must adjust the start and ; end location of this part: (string . string_content: (_) @part . interpolation: (interpolation "{" @int_start) ) @fstring { attr (@part.node) prefix = (string-prefix @fstring) attr (@part.node) _location_start = (location-start @fstring) attr (@part.node) _location_end = (location-end @int_start) } ; And similarly for any string that follows an interpolation: (string interpolation: (interpolation "}" @int_end) . string_content: (_) @part) @fstring { attr (@part.node) prefix = (string-prefix @fstring) attr (@part.node) _location_start = (location-start @int_end) } ; Finally, we must adjust the end of the last part: (string interpolation: (_) string_content: (_) @part . ) @fstring { attr (@part.node) _location_end = (location-end @fstring) } ; For f-strings without interpolations, we simply treat them as regular strings (or `StringPart`s if ; they are part of a concatenation): (string !interpolation string_content: (_) @part) @fstring { let safe_text = (concatenate-strings (string-safe-prefix @fstring) (source-text @part) (string-quotes @fstring)) if (instance-of (get-parent @fstring) "concatenated_string"){ ; StringPart attr (@fstring.node) text = safe_text } else { ; regular string attr (@fstring.node) implicitly_concatenated_parts = #null } attr (@fstring.node) s = safe_text attr (@fstring.node) prefix = (string-prefix @fstring) } ; For f-strings without interpolations _or_ string-content, we simply treat them as regular empty strings: (string !interpolation !string_content) @fstring { let empty_text = "\"\"" if (instance-of (get-parent @fstring) "concatenated_string"){ ; StringPart attr (@fstring.node) text = empty_text } else { ; regular string attr (@fstring.node) implicitly_concatenated_parts = #null } attr (@fstring.node) s = empty_text attr (@fstring.node) prefix = (string-prefix @fstring) } ;;;;;; End of JoinedStr (`f"foo"`) ;;;;;; List (`[...]`) (list element: (_) @elt) @list { edge @list.node -> @elt.node attr (@list.node -> @elt.node) elts = (named-child-index @elt) } ;;;;;; End of List (`[...]`) ;;;;;; Starred (`*some_sequence`) [ (list_splat (expression) @value) (list_splat_pattern vararg: (_) @value) ] @starred { attr (@starred.node) value = @value.node attr (@value.node) _inherited_ctx = @starred.node } ;;;;;; End of Starred (`*some_sequence`) ;;;;;; Dict (`{... : ..., ...}`) (dictionary element: (_) @item) @dict { edge @dict.node -> @item.node attr (@dict.node -> @item.node) items = (named-child-index @item) attr (@item.node) ctx = "load" } (pair key: (_) @key value: (_) @value) @item { attr (@item.node) key = @key.node attr (@item.node) value = @value.node attr (@key.node) ctx = "load" attr (@value.node) ctx = "load" } ;;;;;; End of Dict (`{... : ..., ...}`) ;;;;;; DictUnpacking (`**some_dict`) (dictionary_splat (expression) @value) @dictunpacking { attr (@dictunpacking.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of DictUnpacking (`**some_dict`) ;;;;;; Set (`{..., ...}`) (set element: (_) @elt) @set { edge @set.node -> @elt.node attr (@set.node -> @elt.node) elts = (named-child-index @elt) } ;;;;;; End of Set (`{..., ...}`) ;;;;;; BoolOp (`... and ...`, `... or ...`) ; This is probably the single most complex thing in this file. Read it slowly. ; First of all, the problem is that `tree-sitter-python` represents boolean operators as if they are binary, ; whereas in Python they are really n-ary. This means we have to collapse nested `and`s and `or`s in order to ; correctly create the intended AST structure. ; ; We have a structure like this: ; ; or ; / \ ; v_0 or ; / \ ; v_1 ... ; \ ; or ; / \ ; v_n-1 v_n ; ; where each `v_i` may be a value or a subtree, but not an `or`. ; From this we will produce a graph of the form: ; ; or -0-> v_0 ; -1-> v_1 ; ... ; -(n-1)-> v_n-1 ; -n-> [or -skip_to->]* v_n ; ; where we see that the last node may be found by a series of `skip_to` edges along the nested `or` nodes, ; if such are present. ; ; As an intermediate step, we will decorate the `or` nodes of the tree with a field `index`, and for the outermost ; `or` node we will also set `last_index`, initially to 1 but we increment it each time we see a nested `or`, so it ends ; up being `n`: ; ; or index:0, last_index: n ; / \ ; v_0 or index: 1 ; / \ ; v_1 ... ; \ ; or index: n-1 ; / \ ; v_n-1 v_n ; ; This collapsing goes to the outermost operator (`and` or `or`) ; and so the first step is to correctly identify these. ; For the outermost nodes, we can now assign ; some special variables that we will propagate inwards. Firstly, we record what the outermost node is (in ; this case just the node itself), next the index of the value in its left argument (initially `0`), and ; finally the index at which the _innermost_ right-hand-side value should appear in the resulting list of ; values. This final variable is mutable, and will be updated as we go through the nested sequence of similar ; operators. (boolean_operator operator: _ @op right: (_)) @boolop { ; this binary operator is outermost if it does not have a parent performing the same operation (`and` or `or`) if (not (is-boolean-operator (get-parent @boolop) (source-text @op))) { let @boolop.outermost = @boolop let @boolop.index = 0 var @boolop.innermost_index = 1 } } ; Now, we propagate/modify the variables mentioned in the previous stanza. The `outermost` field is simply ; propagated, and the `index` and `innermost_index` fields are propagated and updated respectively. ; ; We also set the `_skip_to` field on the inner operator, making it point to its right child. That way, the ; `right` child of the _outermost_ operator will (once resolved) point to the _innermost_ `right` child (i.e. ; the last child in this nested sequence of operators). [ (boolean_operator operator: "or" right: (boolean_operator operator: "or" right: (_) @inner_right ) @inner ) (boolean_operator operator: "and" right: (boolean_operator operator: "and" right: (_) @inner_right ) @inner ) ] @outer { let @inner.outermost = @outer.outermost let @inner.index = (plus @outer.index 1) attr (@inner.node) _skip_to = @inner_right.node let outermost = @outer.outermost set outermost.innermost_index = (plus outermost.innermost_index 1) } ; For each boolean operator, we hook its left child up as a child of the outermost operator, at the index we ; calculated previously. (boolean_operator left: (_) @value) @boolop { edge @boolop.outermost.node -> @value.node attr (@boolop.outermost.node -> @value.node) values = @boolop.index attr (@value.node) ctx = "load" } ; For the outermost boolean operator, we hook up its right child (which ultimately points to the innermost ; right child) as a child at the index we calculated previously. (boolean_operator operator: _ @op right: (_) @value ) @boolop { ; this binary operator is outermost if it does not have a parent performing the same operation (`and` or `or`) if (not (is-boolean-operator (get-parent @boolop) (source-text @op))) { edge @boolop.node -> @value.node attr (@boolop.node -> @value.node) values = @boolop.innermost_index } } (boolean_operator right: (_) @value) { attr (@value.node) ctx = "load" } (boolean_operator ["and" "or"] @op) @boolop { attr (@boolop.node) op = (source-text @op) } ;;;;;; End of BoolOp (`... and ...`, `... or ...`) ;;;;;; Compare (`... < ...`, `... <= ...`, etc.) (comparison_operator . (primary_expression) @left) @compare { attr (@compare.node) left = @left.node attr (@left.node) ctx = "load" } ; Hook up all of the compared values. These are simply the named children (except the first one, ; which was handled above), as the operators are all unnamed. (comparison_operator (primary_expression) (primary_expression) @right) @compare { edge @compare.node -> @right.node attr (@compare.node -> @right.node) comparators = (named-child-index @right) attr (@right.node) ctx = "load" } ; Record the operators in the `ops` fields. ; ; A complication here is that we want to construct a field pointing to a list of ; literals (and not AST nodes as we do almost everywhere else). To get around this, ; we create a placeholder node for the operation, and then set the `_is_literal` field ; to override it with a literal value. (comparison_operator ["<" "<=" ">" ">=" "==" "!=" "<>" "in" "is"] @op) @compare { let @op.node = (ast-node @op "cmpop") attr (@op.node) _is_literal = (node-type @op) edge @compare.node -> @op.node attr (@compare.node -> @op.node) ops = (unnamed-child-index @op) } ; The `not in` and `is not` operators are complicated by the fact that the query ; `(comparison_operator "not in" @op)` ; matches _twice_ for each `not in` operator (in effect for both the `not` and `in` parts, even ; though these should have been aliased to a single token). To avoid producing duplicate operators, ; we only create an operator for _one_ of these matches, by checking whether the index is even. (comparison_operator "not in"+ @op) @compare { for op in @op { let index = (unnamed-child-index op) if (eq (mod index 2) 0) { let op.node = (ast-node op "cmpop") attr (op.node) _is_literal = "not in" edge @compare.node -> op.node attr (@compare.node -> op.node) ops = index } } } (comparison_operator "is not"+ @op) @compare { for op in @op { let index = (unnamed-child-index op) if (eq (mod index 2) 0) { let op.node = (ast-node op "cmpop") attr (op.node) _is_literal = "is not" edge @compare.node -> op.node attr (@compare.node -> op.node) ops = index } } } ;;;;;; End of Compare (`... < ...`, `... <= ...`, etc.) ;;;;;; UnaryOp (`-x`, `~x`, etc.., `not x`) [ (unary_operator argument: (_) @operand) (not_operator argument: (_) @operand) ] @unaryop { attr (@unaryop.node) operand = @operand.node attr (@operand.node) ctx = "load" } (unary_operator "~" @op) @unaryop { attr (@unaryop.node) op = "~" } (unary_operator "+") @unaryop { attr (@unaryop.node) op = "uadd" } (unary_operator "-") @unaryop { attr (@unaryop.node) op = "usub" } (not_operator) @unaryop { attr (@unaryop.node) op = "not" } ;;;;;; End of UnaryOp (`-x`, `not x`) ;;;;;; Exec (`exec ...`) (exec_statement (_) @body) @exec { attr (@exec.node) body = @body.node } ;;;;;; End of Exec (`exec ...`) ;;;;;; Print (`print ...`) (print_statement argument: (_) @value) @print { edge @print.node -> @value.node attr (@print.node -> @value.node) values = (named-child-index @value) attr (@value.node) ctx = "load" } (print_statement (chevron (_) @dest)) @print { attr (@print.node) dest = @dest.node attr (@dest.node) ctx = "load" } (print_statement ","? @comma .) @print { var nl = #true if some @comma { set nl = #false } attr (@print.node) nl = nl } ;;;;;; End of Print (`print ...`) ;;;;;; Return (`return ...`) (return_statement (_) @value) @return { attr (@return.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of Return (`return ...`) ;;;;;; Yield and YieldFrom (`yield ...` and `yield from ...`) (yield (_) @value) @yield { attr (@yield.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of Yield and YieldFrom (`yield ...` and `yield from ...`) ;;;;;; Await (`await ...`) (await (_) @value) @await { attr (@await.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of Await (`await ...`) ;;;;;; Try (`try: ... except: ... else: ... finally: ...`) (try_statement body: (block (_) @stmt)) @try { edge @try.node -> @stmt.node attr (@try.node -> @stmt.node) body = (named-child-index @stmt) } (try_statement (except_clause) @except) @try { edge @try.node -> @except.node attr (@try.node -> @except.node) handlers = (named-child-index @except) } (try_statement (except_group_clause) @except) @try { edge @try.node -> @except.node attr (@try.node -> @except.node) handlers = (named-child-index @except) } (try_statement (else_clause body: (block (_) @stmt))) @try { edge @try.node -> @stmt.node attr (@try.node -> @stmt.node) orelse = (named-child-index @stmt) } (try_statement (finally_clause body: (block (_) @stmt))) @try { edge @try.node -> @stmt.node attr (@try.node -> @stmt.node) finalbody = (named-child-index @stmt) } (except_clause body: (block (_) @stmt)) @except { edge @except.node -> @stmt.node attr (@except.node -> @stmt.node) body = (named-child-index @stmt) } (except_clause type: (_) @type) @except { attr (@except.node) type = @type.node attr (@type.node) ctx = "load" } (except_clause alias: (_) @name) @except { attr (@except.node) name = @name.node attr (@name.node) ctx = "store" } (except_group_clause body: (block (_) @stmt)) @except { edge @except.node -> @stmt.node attr (@except.node -> @stmt.node) body = (named-child-index @stmt) } (except_group_clause type: (_) @type) @except { attr (@except.node) type = @type.node attr (@type.node) ctx = "load" } (except_group_clause alias: (_) @name) @except { attr (@except.node) name = @name.node attr (@name.node) ctx = "store" } ;;;;;; End of Try (`try: ... except: ... else: ... finally: ...`) ;;;;;; AssignExpr (`a := b`) (named_expression name: (_) @name value: (_) @value ) @assignexpr { attr (@assignexpr.node) target = @name.node attr (@name.node) ctx = "store" attr (@assignexpr.node) value = @value.node attr (@value.node) ctx = "load" } ;;;;;; End of AssignExpr (`a := b`) ;;;;;; IfExpr (`a if b else c`) (conditional_expression (expression) @body (expression) @test (expression) @orelse ) @ifexp { attr (@ifexp.node) body = @body.node attr (@body.node) ctx = "load" attr (@ifexp.node) test = @test.node attr (@test.node) ctx = "load" attr (@ifexp.node) orelse = @orelse.node attr (@orelse.node) ctx = "load" } ;;;;;; End of IfExpr (`a if b else c`) ;;;;;; Attribute (`a.b`) (attribute object: (_) @value attribute: (_) @attr ) @attribute { attr (@attribute.node) value = @value.node attr (@value.node) ctx = "load" attr (@attribute.node) attr = (source-text @attr) ; Not actually used, but we need to set it to something. attr (@attr.node) ctx = "load" } ;;;;;; End of Attribute (`a.b`) ;;;;;; Subscript (`a[b]`) (subscript value: (_) @value subscript: (_) @index ) @subscript { attr (@subscript.node) value = @value.node attr (@value.node) ctx = "load" attr (@subscript.node) index = @index.node attr (@index.node) ctx = "load" } ;;;;;; End of Subscript (`a[b]`) ;;;;;; Slice (`a:b:c`) (slice start: (_) @start) @slice { attr (@slice.node) start = @start.node attr (@start.node) ctx = "load" } (slice stop: (_) @stop) @slice { attr (@slice.node) stop = @stop.node attr (@stop.node) ctx = "load" } (slice step: (_) @step) @slice { attr (@slice.node) step = @step.node attr (@step.node) ctx = "load" } ;;;;;; End of Slice (`a:b:c`) ;;;;;; While (`while a: ... else: ...`) (while_statement condition: (_) @test) @while { attr (@while.node) test = @test.node attr (@test.node) ctx = "load" } (while_statement body: (block (_) @stmt)) @while { edge @while.node -> @stmt.node attr (@while.node -> @stmt.node) body = (named-child-index @stmt) } (while_statement alternative: (else_clause (block (_) @stmt))) @while { edge @while.node -> @stmt.node attr (@while.node -> @stmt.node) orelse = (named-child-index @stmt) } ;;;;;; End of While (`while a: ... else: ...`) ;;;;;; With (`with a as b, c as d: ...`) (with_statement (with_clause . (with_item) @first)) @with { attr (@with.node) _skip_to = @first.node let @with.first = @first.node } ; Async status ; NOTE: We only set the `is_async` field on the _first_ clause of the `with` statement, ; as this is the behaviour of the old parser. (with_statement "async" "with" @with_keyword (with_clause . (with_item) @with)) { attr (@with.node) is_async = #true } (with_item value: (_) @value ) @with { attr (@with.node) context_expr = @value.node attr (@value.node) ctx = "load" } (with_item alias: (_) @alias ) @with { attr (@with.node) optional_vars = @alias.node attr (@alias.node) ctx = "store" } (with_clause (with_item) @with1 . (comment)* . (with_item) @with2 ) { edge @with1.node -> @with2.node attr (@with1.node -> @with2.node) body = 0 } (with_statement (with_clause (with_item) @last . ) body: (block (_) @stmt) ) { edge @last.node -> @stmt.node attr (@last.node -> @stmt.node) body = (named-child-index @stmt) } ;;;;;; End of With (`with a as b, c as d: ...`) ;;;;;; Match (`match a: ...`) (match_statement subject: (_) @subject ) @match { attr (@match.node) subject = @subject.node attr (@subject.node) ctx = "load" } (match_statement cases: (cases (case_block) @case) ) @match { edge @match.node -> @case.node attr (@match.node -> @case.node) cases = (named-child-index @case) } (case_block pattern: (_) @pattern ) @case { attr (@case.node) pattern = @pattern.node } (case_block guard: (_) @guard ) @case { attr (@case.node) guard = @guard.node } (guard test: (_) @test ) @guard { attr (@guard.node) test = @test.node attr (@test.node) ctx = "load" } (case_block body: (block (_) @stmt) ) @case { edge @case.node -> @stmt.node attr (@case.node -> @stmt.node) body = (named-child-index @stmt) } ;;; The various pattern shapes need to have their children set up correctly (match_as_pattern pattern: (_) @pattern ) @match { attr (@match.node) pattern = @pattern.node } (match_as_pattern alias: (_) @alias ) @match { attr (@match.node) alias = @alias.node attr (@alias.node) ctx = "store" } (match_or_pattern (_) @pattern ) @match_or_pattern { edge @match_or_pattern.node -> @pattern.node attr (@match_or_pattern.node -> @pattern.node) patterns = (named-child-index @pattern) } (match_literal_pattern !real (_) @literal) @match_literal_pattern { attr (@match_literal_pattern.node) literal = @literal.node attr (@literal.node) ctx = "load" } (match_literal_pattern prefix_operator: _? @prefix_op real: (_) @left operator: _? @op imaginary: (_)? @right ) @match_literal_pattern { ; Set `left_node` to point to the left hand side (or only part) of the literal, ; synthesizing it if needed. var left_node = #null if some @prefix_op { set left_node = (ast-node @left "UnaryOp") attr (left_node) _start_location = (location-start @prefix_op) attr (left_node) operand = @left.node attr (left_node) op = "usub" } else { set left_node = @left.node } attr (left_node) ctx = "load" ; Synthesize the binary operator node, if needed. var literal_node = #null if some @right { ; Synthesize the node for the binary operation set literal_node = (ast-node @match_literal_pattern "BinOp") attr (literal_node) left = left_node attr (literal_node) right = @right.node attr (literal_node) op = (source-text @op) attr (@right.node) ctx = "load" attr (literal_node) ctx = "load" } else { set literal_node = left_node } attr (@match_literal_pattern.node) literal = literal_node } (match_capture_pattern (identifier) @pattern) @match_capture_pattern { attr (@match_capture_pattern.node) variable = @pattern.node attr (@pattern.node) ctx = "store" } ; We have a structure where the match_value_pattern has a child for each ; step in the attribute access. ; We will turn each child into an actual attribute access of its predecessor. ; ; We start with (@match_value_pattern) -> id_1 .. id_n ; result is ; id_1 is a Name ; for i > 1: ; @id_i -skip-> Attribute -value-> @id_{i-1} ; -attr-> #text ; @match_value_pattern -value-> @id_n (match_value_pattern (identifier) @obj . (identifier) @attr ) @match_value_pattern { let attribute = (ast-node @attr "Attribute") attr (@attr.node) _skip_to = attribute attr (attribute) value = @obj.node attr (attribute) attr = (source-text @attr) attr (attribute) ctx = "load" } ; First id ; this needs a ctx (match_value_pattern . (identifier) @id ) @match_value_pattern { attr (@id.node) ctx = "load" } ; Last id ; this should be linked from the pattern. (match_value_pattern (identifier) @attr . ) @match_value_pattern { attr (@match_value_pattern.node) value = @attr.node } ; Group patterns only exist in the parser. ; They are elided from the AST, where the information is ; instead recorded in the field `parenthesised`. (match_group_pattern content: (_) @pattern ) @match_group_pattern { attr (@match_group_pattern.node) _skip_to = @pattern.node attr (@match_group_pattern.node) parenthesised = #true } (match_sequence_pattern (_) @pattern ) @match_sequence_pattern { edge @match_sequence_pattern.node -> @pattern.node attr (@match_sequence_pattern.node -> @pattern.node) patterns = (named-child-index @pattern) } (match_star_pattern target: (_) @target ) @match_star_pattern { attr (@match_star_pattern.node) target = @target.node } (match_mapping_pattern [ (match_key_value_pattern) @mapping (match_double_star_pattern) @mapping ] ) @pattern { edge @pattern.node -> @mapping.node attr (@pattern.node -> @mapping.node) mappings = (named-child-index @mapping) } (match_double_star_pattern target: (_) @target ) @match_double_star_pattern { attr (@match_double_star_pattern.node) target = @target.node } (match_key_value_pattern key: (_) @key value: (_) @value ) @key_value { attr (@key_value.node) key = @key.node attr (@key_value.node) value = @value.node } ; Similar situation to the match_value_pattern. ; We have a structure where the match_class_pattern has a child for each ; step in the attribute access. ; We will turn each child into an actual attribute access of its predecessor. (pattern_class_name (identifier) @obj . (identifier) @attr ) { let attribute = (ast-node @attr "Attribute") attr (@attr.node) _skip_to = attribute attr (attribute) value = @obj.node attr (attribute) attr = (source-text @attr) attr (attribute) ctx = "load" } ; First id (pattern_class_name . (identifier) @id ) { attr (@id.node) ctx = "load" } ; Last id ; this should be linked from the pattern. (match_class_pattern class: (pattern_class_name (identifier) @attr . ) ) @match_class_pattern { attr (@match_class_pattern.node) class_name = @attr.node } (match_class_pattern (match_positional_pattern (_) @positional) @positional_pattern ) @match_class_pattern { edge @match_class_pattern.node -> @positional.node attr (@match_class_pattern.node -> @positional.node) positional = (named-child-index @positional_pattern) } (match_class_pattern (match_keyword_pattern) @keyword ) @match_class_pattern { edge @match_class_pattern.node -> @keyword.node attr (@match_class_pattern.node -> @keyword.node) keyword = (named-child-index @keyword) } (match_keyword_pattern attribute: (_) @attribute ) @match_keyword_pattern { attr (@match_keyword_pattern.node) attribute = @attribute.node attr (@attribute.node) ctx = "load" } (match_keyword_pattern value: (_) @pattern ) @match_keyword_pattern { attr (@match_keyword_pattern.node) value = @pattern.node} ;;;;;; End of Match (`match a: ...`) ;;;;;; Lambda (`lambda a: ...`) ; Lambdas are tricky, much like function definitions. ; ; One complication is that we need to distinguish the cases where the parameter has a default value and ; where it does not. This leads to an unfortunate explosion in mostly similar cases... (lambda body: (_) @body) @lambda { ; Lambdas contain a `Function` much like regular functions. let @lambda.function = (ast-node @lambda "Function") attr (@lambda.function) name = "lambda" attr (@lambda.node) inner_scope = @lambda.function ; The single child of this function is a synthesised return statement. let return = (ast-node @body "Return") edge @lambda.function -> return attr (@lambda.function -> return) body = 0 attr (return) value = @body.node attr (@body.node) ctx = "load" } ; Lambdas without parameters just get a dummy `arguments` child. (lambda !parameters) @lambda { attr (@lambda.node) args = (ast-node @lambda "arguments") } (lambda parameters: (_) @params) @lambda { attr (@lambda.node) args = @params.node } (lambda parameters: (lambda_parameters (list_splat_pattern vararg: (_) @vararg) @starred ) ) @lambda { attr (@lambda.function) vararg = @vararg.node attr (@starred.node) ctx = "param" ; Not actually used attr (@vararg.node) ctx = "param" } (lambda parameters: (lambda_parameters (dictionary_splat_pattern kwarg: (_) @kwarg) ) ) @lambda { attr (@lambda.function) kwarg = @kwarg.node attr (@kwarg.node) ctx = "param" } (lambda parameters: (lambda_parameters [(list_splat_pattern) (keyword_separator)]? @is_kwarg [ (identifier) @name (default_parameter name: (_) @name value: (_) @value ) ] @param ) @params ) @lambda { let none = (ast-node @params "None") attr (none) _is_literal = #null attr (none) ctx = "load" edge @params.node -> none ; Even though lambda parameters cannot have annotations, we must still record this fact. if some @is_kwarg { attr (@params.node -> none) kw_annotations = (named-child-index @param) } else { attr (@params.node -> none) annotations = (named-child-index @param) } edge @lambda.function -> @name.node attr (@name.node) ctx = "param" if some @is_kwarg { attr (@lambda.function -> @name.node) kwonlyargs = (named-child-index @param) } else { attr (@lambda.function -> @name.node) args = (named-child-index @param) } var default_node = none if some @value { set default_node = @value.node edge @params.node -> default_node attr (default_node) ctx = "load" } if some @is_kwarg { attr (@params.node -> default_node) kw_defaults = (named-child-index @param) } else { attr (@params.node -> default_node) defaults = (named-child-index @param) } } ;;;;;; End of Lambda (`lambda a: ...`) ;;;;;; Function (`def a(b, c): ...`) ; Much like lambdas, the main difficulty here is that we need to account for the absence of the positional ; argument separator. We do this using the exact same machinery. ; ; Also, all arguments can now also have a type/annotation, so get ready for _twice_ the number of cases. (function_definition name: (_) @name ":" @end ) @funcdef { let end = (location-end @end) attr (@funcdef.node) _location_end = end edge @funcdef.node -> @name.node attr (@funcdef.node -> @name.node) targets = 0 attr (@name.node) ctx = "store" let @funcdef.funcexpr = (ast-node @funcdef "FunctionExpr") attr (@funcdef.funcexpr) _location_end = end attr (@funcdef.node) value = @funcdef.funcexpr attr (@funcdef.funcexpr) name = (source-text @name) let @funcdef.function = (ast-node @funcdef "Function") attr (@funcdef.function) _location_end = end attr (@funcdef.function) name = (source-text @name) attr (@funcdef.funcexpr) inner_scope = @funcdef.function } (function_definition body: (block (_) @stmt) ) @funcdef { edge @funcdef.function -> @stmt.node attr (@funcdef.function -> @stmt.node) body = (named-child-index @stmt) } (function_definition parameters: (_) @params ) @funcdef { attr (@funcdef.funcexpr) args = @params.node } (function_definition parameters: (parameters [(list_splat_pattern) (keyword_separator)]? @is_kwarg [ (identifier) @name (default_parameter name: (_) @name value: (_) @value ) (typed_parameter (identifier) @name . type: (type (_) @type) ) (typed_default_parameter name: (_) @name type: (type (_) @type) value: (_) @value ) ] @param ) @params ) @funcdef { let none = (ast-node @params "None") attr (none) _is_literal = #null attr (none) ctx = "load" edge @params.node -> none var type_node = none if some @type { set type_node = @type.node edge @params.node -> type_node attr (type_node) ctx = "load" } if some @is_kwarg { attr (@params.node -> type_node) kw_annotations = (named-child-index @param) } else { attr (@params.node -> type_node) annotations = (named-child-index @param) } edge @funcdef.function -> @name.node attr (@name.node) ctx = "param" if some @is_kwarg { attr (@funcdef.function -> @name.node) kwonlyargs = (named-child-index @param) } else { attr (@funcdef.function -> @name.node) args = (named-child-index @param) } var default_node = none if some @value { set default_node = @value.node edge @params.node -> default_node attr (default_node) ctx = "load" } if some @is_kwarg { attr (@params.node -> default_node) kw_defaults = (named-child-index @param) } else { attr (@params.node -> default_node) defaults = (named-child-index @param) } } ; `*args` argument (function_definition parameters: (parameters [ (list_splat_pattern vararg: (_) @name) @starred (typed_parameter (list_splat_pattern vararg: (_) @name) @starred type: (type (_) @type) ) ] ) @params ) @funcdef { attr (@funcdef.function) vararg = @name.node attr (@starred.node) ctx = "param" ; Not actually used attr (@name.node) ctx = "param" if some @type { attr (@params.node) varargannotation = @type.node attr (@type.node) ctx = "load" } } ; Return type (function_definition return_type: (type (_) @type) ) @funcdef { attr (@funcdef.funcexpr) returns = @type.node attr (@type.node) ctx = "load" } ; `**kwargs` argument (function_definition (parameters [ (dictionary_splat_pattern kwarg: (identifier) @name) (typed_parameter (dictionary_splat_pattern kwarg: (identifier) @name) type: (type (_) @type) ) ] ) @params ) @funcdef { attr (@funcdef.function) kwarg = @name.node attr (@name.node) ctx = "param" if some @type { attr (@params.node) kwargannotation = @type.node attr (@type.node) ctx = "load" } } ; Async status (function_definition "async" "def" @def_keyword) @funcdef { let start = (location-start @def_keyword) attr (@funcdef.function) is_async = #true attr (@funcdef.node) _location_start = start attr (@funcdef.function) _location_start = start attr (@funcdef.funcexpr) _location_start = start } ;;; Decorators (decorated_definition . (decorator) @first definition: (function_definition name: (_) @name ":" @end) @funcdef ) @decorator { attr (@decorator.node) value = @first.node attr (@decorator.node) _location_start = (location-start @funcdef) attr (@decorator.node) _location_end = (location-end @end) edge @decorator.node -> @name.node attr (@decorator.node -> @name.node) targets = 0 } (decorated_definition . (decorator) @first definition: (class_definition name: (_) @name ":" @end) @funcdef ) @decorator { attr (@decorator.node) value = @first.node attr (@decorator.node) _location_start = (location-start @funcdef) attr (@decorator.node) _location_end = (location-end @end) edge @decorator.node -> @name.node attr (@decorator.node -> @name.node) targets = 0 } (decorator (expression) @exp) @decorator { attr (@decorator.node) _location_start = (location-start @exp) attr (@exp.node) ctx = "load" } (decorated_definition (decorator (expression) @exp1) @dec1 . (comment)* . (decorator (expression) @exp2) @dec2 ) @decorator { attr (@dec1.node) func = @exp1.node edge @dec1.node -> @dec2.node attr (@dec1.node -> @dec2.node) positional_args = 0 } (decorated_definition (decorator (expression) @exp) @last . (comment)* . definition: (function_definition) @funcdef ) @decorator { attr (@last.node) func = @exp.node edge @last.node -> @funcdef.funcexpr attr (@last.node -> @funcdef.funcexpr) positional_args = 0 attr (@last.node) _location_end = (location-end @exp) } (decorated_definition (decorator (expression) @exp) @last . (comment)* . definition: (class_definition) @class ) @decorator { attr (@last.node) func = @exp.node edge @last.node -> @class.class_expr attr (@last.node -> @class.class_expr) positional_args = 0 attr (@last.node) _location_end = (location-end @exp) } ;;; Type parameters (function_definition type_parameters: (type_parameters type_parameter: (_) @param) ) @funcdef { edge @funcdef.function -> @param.node attr (@funcdef.function -> @param.node) type_parameters = (named-child-index @param) } (class_definition type_parameters: (type_parameters type_parameter: (_) @param) ) @class { edge @class.class_expr -> @param.node attr (@class.class_expr -> @param.node) type_parameters = (named-child-index @param) } ;;;;;; End of Function (`def a(b, c): ...`) ;;;;;; TypeAlias (`type a[...] = ...`) (type_alias_statement name: (_) @name value: (_) @value ) @type_alias { attr (@name.node) ctx = "store" attr (@value.node) ctx = "load" attr (@type_alias.node) name = @name.node attr (@type_alias.node) value = @value.node } (type_alias_statement type_parameters: (type_parameters type_parameter: (_) @param) ) @type_alias { edge @type_alias.node -> @param.node attr (@type_alias.node -> @param.node) type_parameters = (named-child-index @param) } ;;;;;; End of TypeAlias (`type a[...] = ...`) ;;;;;; Type parameters (`T: ..., *T, **T`) (typevar_parameter name: (_) @name bound: (_)? @bound default: (_)? @default ) @typevar { attr (@name.node) ctx = "store" attr (@typevar.node) name = @name.node if some @bound { attr (@bound.node) ctx = "load" attr (@typevar.node) bound = @bound.node } if some @default { attr (@default.node) ctx = "load" attr (@typevar.node) default = @default.node } } (typevartuple_parameter name: (_) @name default: (_)? @default ) @typevartuple { attr (@name.node) ctx = "store" attr (@typevartuple.node) name = @name.node if some @default { attr (@default.node) ctx = "load" attr (@typevartuple.node) default = @default.node } } (paramspec_parameter name: (_) @name default: (_)? @default ) @paramspec { attr (@name.node) ctx = "store" attr (@paramspec.node) name = @name.node if some @default { attr (@default.node) ctx = "load" attr (@paramspec.node) default = @default.node } } ;;;;;; End of Type parameters (`T: ..., *T, **T`) ; Nodes with an `elts` field [ ; Left hand side of an assignment such as `foo, bar = ...` (pattern_list element: (_) @elt) @parent ; Left hand side of an assignment such as `[foo, bar] = ...` (list_pattern element: (_) @elt) @parent ; An unadorned tuple such as in `x = y, z` (expression_list element: (_) @elt) @parent ; An index containing multiple indices such as in `x[y, z]` (index_expression_list element: (_) @elt) @parent ; A regular tuple such as `(x, y, z)` (tuple element: (_) @elt) @parent (tuple_pattern element: (_) @elt) @parent ] { edge @parent.node -> @elt.node attr (@parent.node -> @elt.node) elts = (named-child-index @elt) } ; Expressions that do not produce an `Expr` node in the AST. (expression_statement [(assignment) (augmented_assignment)] @inner) @outer { attr (@outer.node) _skip_to = @inner.node } ; Expressions that may result in an `Expr` node in the AST ; ("may" because of the `_skip_to` field). (expression_statement . (_) @expr . ) @stmt { attr (@stmt.node) value = @expr.node attr (@expr.node) ctx = "load" } ; Sequence expressions where the elements inherit the load/store context [ (list element: (_) @elt) (tuple element: (_) @elt) (tuple_pattern element: (_) @elt) (pattern_list element: (_) @elt) (list_pattern element: (_) @elt) (expression_list element: (_) @elt) (index_expression_list element: (_) @elt) (parenthesized_expression inner: (_) @elt) (set element: (_) @elt) (match_sequence_pattern (_) @elt) ] @seq { attr (@elt.node) _inherited_ctx = @seq.node } [(tuple element: (_)) (tuple_pattern)] @tup { ; In order to avoid writing to the `parenthesised` attribute twice, we only set it here ; if the surrounding expression is not a `parenthesized_expression`. if (not (instance-of (get-parent @tup) "parenthesized_expression")) { attr (@tup.node) parenthesised = #true } }