From 55194dd757bda4a849eb72ba207332db96e1d5b7 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 11 May 2026 15:15:38 +0200 Subject: [PATCH] Unified: Support for calls and member access --- unified/extractor/ast_types.yml | 13 ++++ .../extractor/src/languages/swift/swift.rs | 64 +++++++++++++++- .../extractor/tests/corpus/swift/closures.txt | 26 ++++++- .../tests/corpus/swift/collections.txt | 29 +++++++- .../tests/corpus/swift/control-flow.txt | 74 +++++++++++++++++-- .../tests/corpus/swift/functions.txt | 26 ++++++- .../corpus/swift/optionals-and-errors.txt | 10 ++- 7 files changed, 224 insertions(+), 18 deletions(-) diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index 7ed6f9cf854..22a5e8b19fb 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -5,6 +5,8 @@ supertypes: - string_literal - binary_expr - unary_expr + - call_expr + - member_access_expr - lambda_expr - unsupported_node stmt: @@ -52,6 +54,17 @@ named: operand: expr operator: operator + # A function or method call, such as `f(x)` or `obj.m(x)`. Method calls + # are represented as a call whose `function` is a `member_access_expr`. + call_expr: + function: expr + argument*: expr + + # Member access, such as `obj.member`. + member_access_expr: + target: expr + member: identifier + lambda_expr: parameter*: parameter body: [expr, stmt] diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 47bfe437ef2..23b516815cb 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1,5 +1,30 @@ use codeql_extractor::extractor::simple; -use yeast::{rule, DesugaringConfig, PhaseKind}; +use yeast::{build::BuildCtx, rule, DesugaringConfig, PhaseKind}; + +/// Names of output AST kinds that belong to the `expr` supertype. Kept in +/// sync with `ast_types.yml`. `unsupported_node` is intentionally omitted +/// because it is also a member of the `stmt` supertype. +const EXPR_KINDS: &[&str] = &[ + "name_expr", + "int_literal", + "string_literal", + "binary_expr", + "unary_expr", + "call_expr", + "member_access_expr", + "lambda_expr", +]; + +/// If `id` is an `expr`, wrap it in `expr_stmt` so it can sit in a `stmt` +/// position; otherwise return it unchanged. +fn wrap_expr_in_stmt(ctx: &mut BuildCtx, id: usize) -> usize { + let kind = ctx.ast.get_node(id).map(|n| n.kind()).unwrap_or(""); + if EXPR_KINDS.contains(&kind) { + yeast::tree!(ctx, (expr_stmt expr: {id})) + } else { + id + } +} fn translation_rules() -> Vec { vec![ @@ -149,11 +174,44 @@ fn translation_rules() -> Vec { // ---- Block / statement wrapping ---- // A `(statements ...)` node corresponds to a brace-delimited block. // Each child is mapped through translation; bare expression results - // get wrapped in `expr_stmt`. + // get wrapped in `expr_stmt` so they fit the `body*: stmt` field. rule!( (statements (_)* @stmts) => - (block_stmt body: {..stmts}) + (block_stmt body: {..stmts.iter().copied().map(|n| + wrap_expr_in_stmt(&mut __yeast_ctx, n.into()) + ).collect::>()}) + ), + // ---- Calls and member access ---- + // Member access, e.g. `obj.member`. The Swift parser wraps the + // member name as `(navigation_suffix suffix: (simple_identifier))`. + rule!( + (navigation_expression + target: (_) @target + suffix: (navigation_suffix + suffix: (simple_identifier) @member)) + => + (member_access_expr + target: {target} + member: (identifier #{member})) + ), + // Function / method call. The callee is the first child of + // `call_expression`; the second is a `call_suffix` whose + // `value_arguments` (if present) hold the parenthesized args. A + // trailing closure (`call_suffix` with a `lambda_literal` child) + // is appended as a final argument. + rule!( + (call_expression + (_) @callee + (call_suffix + (value_arguments + (value_argument value: (_) @args)*)? + (lambda_literal)? @trailing)) + => + (call_expr + function: {callee} + argument: {..args.iter().copied().map(Into::into) + .chain(trailing.map(Into::into)).collect::>()}) ), // ---- Guard statement ---- // `guard let x = e else { ... }` — currently only handles the diff --git a/unified/extractor/tests/corpus/swift/closures.txt b/unified/extractor/tests/corpus/swift/closures.txt index 0abb205bbfe..c10aa78c1fa 100644 --- a/unified/extractor/tests/corpus/swift/closures.txt +++ b/unified/extractor/tests/corpus/swift/closures.txt @@ -123,7 +123,23 @@ source_file --- top_level - body: unsupported_node "xs.map { $0 * 2 }" + body: + call_expr + argument: + lambda_expr + body: + binary_expr + operator: operator "*" + left: + name_expr + identifier: identifier "$0" + right: int_literal "2" + function: + member_access_expr + target: + name_expr + identifier: identifier "xs" + member: identifier "map" === Closure with capture list @@ -168,7 +184,13 @@ top_level variable_declarator value: lambda_expr - body: unsupported_node "self?.doThing()" + body: + call_expr + argument: + function: + member_access_expr + target: unsupported_node "self" + member: identifier "doThing" pattern: var_pattern identifier: identifier "f" diff --git a/unified/extractor/tests/corpus/swift/collections.txt b/unified/extractor/tests/corpus/swift/collections.txt index d5639054488..d5950a18109 100644 --- a/unified/extractor/tests/corpus/swift/collections.txt +++ b/unified/extractor/tests/corpus/swift/collections.txt @@ -187,11 +187,17 @@ top_level Subscript access === +// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape +// as `xs(0)`), so the mapping currently produces a call_expr. Update the +// parser / add a separate subscript_expr node and remap when fixed. let first = xs[0] --- source_file + comment "// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape" + comment "// as `xs(0)`), so the mapping currently produces a call_expr. Update the" + comment "// parser / add a separate subscript_expr node and remap when fixed." property_declaration name: pattern @@ -210,10 +216,18 @@ source_file top_level body: + unsupported_node "// TODO: tree-sitter-swift parses `xs[0]` as a call_expression (same shape" + unsupported_node "// as `xs(0)`), so the mapping currently produces a call_expr. Update the" + unsupported_node "// parser / add a separate subscript_expr node and remap when fixed." variable_declaration_stmt variable_declarator: variable_declarator - value: unsupported_node "xs[0]" + value: + call_expr + argument: int_literal "0" + function: + name_expr + identifier: identifier "xs" pattern: var_pattern identifier: identifier "first" @@ -222,11 +236,15 @@ top_level Dictionary subscript === +// TODO: same parser issue as the array subscript case above — +// `d["key"]` is parsed as `call_expression(d, ("key"))`. let v = d["key"] --- source_file + comment "// TODO: same parser issue as the array subscript case above —" + comment "// `d[\"key\"]` is parsed as `call_expression(d, (\"key\"))`." property_declaration name: pattern @@ -247,10 +265,17 @@ source_file top_level body: + unsupported_node "// TODO: same parser issue as the array subscript case above —" + unsupported_node "// `d[\"key\"]` is parsed as `call_expression(d, (\"key\"))`." variable_declaration_stmt variable_declarator: variable_declarator - value: unsupported_node "d[\"key\"]" + value: + call_expr + argument: string_literal "\"key\"" + function: + name_expr + identifier: identifier "d" pattern: var_pattern identifier: identifier "v" diff --git a/unified/extractor/tests/corpus/swift/control-flow.txt b/unified/extractor/tests/corpus/swift/control-flow.txt index 6792c94dd7b..de2b17e52bb 100644 --- a/unified/extractor/tests/corpus/swift/control-flow.txt +++ b/unified/extractor/tests/corpus/swift/control-flow.txt @@ -39,7 +39,16 @@ top_level right: int_literal "0" then: block_stmt - body: unsupported_node "print(x)" + body: + expr_stmt + expr: + call_expr + argument: + name_expr + identifier: identifier "x" + function: + name_expr + identifier: identifier "print" === If-else @@ -95,10 +104,31 @@ top_level right: int_literal "0" else: block_stmt - body: unsupported_node "print(-x)" + body: + expr_stmt + expr: + call_expr + argument: + unary_expr + operator: operator "-" + operand: + name_expr + identifier: identifier "x" + function: + name_expr + identifier: identifier "print" then: block_stmt - body: unsupported_node "print(x)" + body: + expr_stmt + expr: + call_expr + argument: + name_expr + identifier: identifier "x" + function: + name_expr + identifier: identifier "print" === If-else-if chain @@ -178,13 +208,34 @@ top_level right: int_literal "0" else: block_stmt - body: unsupported_node "print(3)" + body: + expr_stmt + expr: + call_expr + argument: int_literal "3" + function: + name_expr + identifier: identifier "print" then: block_stmt - body: unsupported_node "print(2)" + body: + expr_stmt + expr: + call_expr + argument: int_literal "2" + function: + name_expr + identifier: identifier "print" then: block_stmt - body: unsupported_node "print(1)" + body: + expr_stmt + expr: + call_expr + argument: int_literal "1" + function: + name_expr + identifier: identifier "print" === If-let optional binding @@ -227,7 +278,16 @@ top_level identifier: identifier "value" then: block_stmt - body: unsupported_node "print(value)" + body: + expr_stmt + expr: + call_expr + argument: + name_expr + identifier: identifier "value" + function: + name_expr + identifier: identifier "print" === Guard let diff --git a/unified/extractor/tests/corpus/swift/functions.txt b/unified/extractor/tests/corpus/swift/functions.txt index 5943b75d04d..eb12e995bac 100644 --- a/unified/extractor/tests/corpus/swift/functions.txt +++ b/unified/extractor/tests/corpus/swift/functions.txt @@ -207,7 +207,14 @@ source_file --- top_level - body: unsupported_node "foo(1, 2)" + body: + call_expr + argument: + int_literal "1" + int_literal "2" + function: + name_expr + identifier: identifier "foo" === Function call with labelled arguments @@ -233,7 +240,12 @@ source_file --- top_level - body: unsupported_node "greet(person: \"Bob\")" + body: + call_expr + argument: string_literal "\"Bob\"" + function: + name_expr + identifier: identifier "greet" === Method call @@ -258,7 +270,15 @@ source_file --- top_level - body: unsupported_node "list.append(1)" + body: + call_expr + argument: int_literal "1" + function: + member_access_expr + target: + name_expr + identifier: identifier "list" + member: identifier "append" === Generic function diff --git a/unified/extractor/tests/corpus/swift/optionals-and-errors.txt b/unified/extractor/tests/corpus/swift/optionals-and-errors.txt index 43c0e7d7f0d..acaff1f6ac8 100644 --- a/unified/extractor/tests/corpus/swift/optionals-and-errors.txt +++ b/unified/extractor/tests/corpus/swift/optionals-and-errors.txt @@ -69,7 +69,15 @@ top_level variable_declaration_stmt variable_declarator: variable_declarator - value: unsupported_node "obj?.foo?.bar" + value: + member_access_expr + target: + member_access_expr + target: + name_expr + identifier: identifier "obj" + member: identifier "foo" + member: identifier "bar" pattern: var_pattern identifier: identifier "n"