mirror of
https://github.com/github/codeql.git
synced 2026-07-29 14:56:51 +02:00
unified: Port function, call, and member-access rules to swift-syntax
Retarget function declarations, calls, member access, and control transfer to the swift-syntax AST, output unchanged: - `functionDecl` -> `function_declaration` (parameters and return type nest under `signature`; the body is a `codeBlock`). A bodyless function (a protocol requirement) still emits an empty `block`. - `functionParameter` -> `parameter`: two names give the external label and internal name, one name just the internal name; the default value is handled inline, so the `ctx.default_value` threading (and its `SwiftContext` field) is removed. The declared type is dropped: in the tree-sitter path the untyped-parameter rule was ordered first and shadowed the typed one, so the baseline emits no parameter type. - A function reference spelled with argument labels (`f(x:y:z:)`) is a `declReferenceExpr` with `argumentNames`; it is mapped to `unsupported_node` (matched before the bare-name rule) so downstream QL isn't handed a malformed reference, as in the tree-sitter path. - `functionCallExpr` -> `call_expr` (a trailing closure becomes a final unlabelled argument); `labeledExpr` -> `argument`; `memberAccessExpr` -> `member_access_expr` (base-ful matched before leading-dot). - `returnStmt`/`breakStmt`/`continueStmt`/`throwStmt` -> `return_expr`/`break_expr`/`continue_expr`/`throw_expr`, collapsing the labelled/unlabelled variants via optional captures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -21,10 +21,6 @@ struct SwiftContext {
|
||||
/// `protocol_property_declaration` when present; read by the
|
||||
/// accessor inner rules.
|
||||
property_type: Option<yeast::Id>,
|
||||
/// Default-value expression for the next translated `parameter`. Set
|
||||
/// by the outer `function_parameter` rule; read by the `parameter`
|
||||
/// rules.
|
||||
default_value: Option<yeast::Id>,
|
||||
/// Translated outer modifiers to attach to each child of a flattening
|
||||
/// outer rule. Set by `property_declaration`, `binding_pattern`,
|
||||
/// `enum_entry`, and `protocol_property_declaration`. For `let`/`var`
|
||||
@@ -168,6 +164,17 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
|
||||
rule!((stringLiteralExpr) => (string_literal)),
|
||||
rule!((regexLiteralExpr) => (regex_literal)),
|
||||
// ---- Names ----
|
||||
// A function reference spelled with argument labels (`f(x:y:z:)`) is a
|
||||
// `declReferenceExpr` carrying `argumentNames`. Mark it unsupported for
|
||||
// now (rather than let the bare-name rule below treat it as a plain
|
||||
// reference), so downstream QL isn't handed a malformed reference. In
|
||||
// the future this should become a lambda expression. Matched before the
|
||||
// bare-name rule.
|
||||
rule!(
|
||||
(declReferenceExpr argumentNames: (declNameArguments))
|
||||
=>
|
||||
(unsupported_node)
|
||||
),
|
||||
// A bare name reference (`x`), and an operator used as a value (`+` in
|
||||
// `reduce(0, +)`), are both `declReferenceExpr`; its `baseName` is the
|
||||
// referenced identifier / operator symbol.
|
||||
@@ -524,108 +531,111 @@ fn translation_rules() -> Vec<Rule<SwiftContext>> {
|
||||
// the 'expression' case is the only remaining possibility when this rule tries to match.
|
||||
rule!((pattern kind: @expr) => (expr_equality_pattern expr: {expr})),
|
||||
// ---- Functions ----
|
||||
// Function declaration
|
||||
// Function declaration (return type optional, body statements optional).
|
||||
// A function declaration (parameters/return type/body optional). The
|
||||
// parameters and return type nest under `signature`; the body is a
|
||||
// `codeBlock`. A bodyless function (a protocol requirement) still emits
|
||||
// an empty `block`, matching the tree-sitter path.
|
||||
rule!(
|
||||
(function_declaration
|
||||
(functionDecl
|
||||
name: @name
|
||||
parameter: _* @params
|
||||
return_type: _? @ret
|
||||
body: (block statement: _* @body_stmts))
|
||||
signature: (functionSignature
|
||||
parameterClause: (functionParameterClause parameters: _* @params)
|
||||
returnClause: (returnClause type: @ret)?)
|
||||
body: (codeBlock statements: _* @body))
|
||||
=>
|
||||
(function_declaration
|
||||
name: (identifier #{name})
|
||||
parameter: {params}
|
||||
return_type: {ret}
|
||||
body: (block stmt: {body_stmts}))
|
||||
body: (block stmt: {body}))
|
||||
),
|
||||
// Parameters are wrapped in function_parameter, which also carries
|
||||
// optional default values. Publishes the default value into `ctx`
|
||||
// before translating the inner `parameter` so the `parameter`
|
||||
// rules can include it as a `default:` field directly.
|
||||
rule!(
|
||||
(function_parameter parameter: @@p default_value: _? @def)
|
||||
(functionDecl
|
||||
name: @name
|
||||
signature: (functionSignature
|
||||
parameterClause: (functionParameterClause parameters: _* @params)
|
||||
returnClause: (returnClause type: @ret)?))
|
||||
=>
|
||||
parameter* {
|
||||
ctx.default_value = def;
|
||||
ctx.translate(p)?
|
||||
(function_declaration
|
||||
name: (identifier #{name})
|
||||
parameter: {params}
|
||||
return_type: {ret}
|
||||
body: (block))
|
||||
),
|
||||
// A function parameter. With two names (`firstName`+`secondName`) the
|
||||
// first is the external argument label and the second the internal name;
|
||||
// with one name it is just the internal name. The default value is
|
||||
// optional.
|
||||
//
|
||||
// PARITY: the declared type is intentionally dropped. In the tree-sitter
|
||||
// path the untyped-parameter rule was ordered before the typed one and
|
||||
// shadowed it (first match wins), so the baseline emits no parameter
|
||||
// type; emitting one here would diverge from it.
|
||||
rule!(
|
||||
(functionParameter
|
||||
firstName: @@first
|
||||
secondName: _? @@second
|
||||
defaultValue: (initializerClause value: @val)?)
|
||||
=>
|
||||
parameter {
|
||||
let (external, name) = match second {
|
||||
Some(second) => (Some(tree!((identifier #{first}))), second),
|
||||
None => (None, first),
|
||||
};
|
||||
tree!((parameter
|
||||
external_name: {external}
|
||||
pattern: (name_pattern identifier: (identifier #{name}))
|
||||
default: {val}))
|
||||
}
|
||||
),
|
||||
// Parameter with external name and type
|
||||
// A function/method call (`foo(1, 2)`). `calledExpression` is the callee
|
||||
// and `arguments` is an (elided) list of `labeledExpr`, each translated
|
||||
// to an `argument` below. A trailing closure (`xs.map { … }`) becomes a
|
||||
// final unlabelled argument; that variant is matched first.
|
||||
rule!(
|
||||
(parameter external_name: @ext name: @name)
|
||||
(functionCallExpr calledExpression: @callee arguments: _* @args trailingClosure: @tc)
|
||||
=>
|
||||
(parameter
|
||||
external_name: (identifier #{ext})
|
||||
pattern: (name_pattern identifier: (identifier #{name}))
|
||||
default: {ctx.default_value})
|
||||
(call_expr callee: {callee} argument: {args} argument: (argument value: {tc}))
|
||||
),
|
||||
rule!(
|
||||
(parameter external_name: @ext name: @name type: @ty)
|
||||
(functionCallExpr calledExpression: @callee arguments: _* @args)
|
||||
=>
|
||||
(parameter
|
||||
external_name: (identifier #{ext})
|
||||
pattern: (name_pattern identifier: (identifier #{name}))
|
||||
type: {ty}
|
||||
default: {ctx.default_value})
|
||||
(call_expr callee: {callee} argument: {args})
|
||||
),
|
||||
// Parameter with just name and type (no external name)
|
||||
// A call argument keeps its optional label as the `name` and its value.
|
||||
// (Enum-case pattern arguments reuse `labeledExpr` too; that handling is
|
||||
// added with the switch/pattern rules.)
|
||||
rule!(
|
||||
(parameter name: @name)
|
||||
(labeledExpr label: @lbl expression: @val)
|
||||
=>
|
||||
(parameter
|
||||
pattern: (name_pattern identifier: (identifier #{name}))
|
||||
default: {ctx.default_value})
|
||||
(argument name: (identifier #{lbl}) value: {val})
|
||||
),
|
||||
rule!(
|
||||
(parameter name: @name type: @ty)
|
||||
=>
|
||||
(parameter
|
||||
pattern: (name_pattern identifier: (identifier #{name}))
|
||||
type: {ty}
|
||||
default: {ctx.default_value})
|
||||
),
|
||||
// Reference to a function, f(x:y:z:). This is parsed as a call with a single argument with multiple reference_specifier labels.
|
||||
// We don't want downstream QL to try to handle this as a call_expr with a weird argument, so explicitly mark it as unsupported for now.
|
||||
// In the future we probably want to translate this to a lambda expression.
|
||||
rule!(
|
||||
(call_expression suffix: (call_suffix arguments: (value_arguments argument: (value_argument reference_specifier: _+) @ref_arg)))
|
||||
=>
|
||||
(unsupported_node)
|
||||
),
|
||||
// Call expression: function(args...)
|
||||
rule!(
|
||||
(call_expression function: @func suffix: (call_suffix arguments: (value_arguments argument: (value_argument)* @args)))
|
||||
=>
|
||||
(call_expr callee: {func} argument: {args})
|
||||
),
|
||||
// Value argument with label (value: _ matches both named nodes and anonymous tokens like nil)
|
||||
rule!(
|
||||
(value_argument name: (value_argument_label name: @label) value: @val)
|
||||
=>
|
||||
(argument name: (identifier #{label}) value: {val})
|
||||
),
|
||||
// Value argument without label
|
||||
rule!(
|
||||
(value_argument value: @val)
|
||||
(labeledExpr expression: @val)
|
||||
=>
|
||||
(argument value: {val})
|
||||
),
|
||||
// Navigation expression → member_access_expr
|
||||
// Member access (`list.append`). The `declName` is itself a
|
||||
// `declReferenceExpr`; pull its `baseName` out as the member identifier.
|
||||
// A leading-dot access (`.foo`) has no explicit base — the base is an
|
||||
// `inferred_type_expr`. The base-ful form is matched first.
|
||||
rule!(
|
||||
(navigation_expression target: @target suffix: (navigation_suffix suffix: @member))
|
||||
(memberAccessExpr base: @base declName: (declReferenceExpr baseName: @member))
|
||||
=>
|
||||
(member_access_expr base: {target} member: (identifier #{member}))
|
||||
(member_access_expr base: {base} member: (identifier #{member}))
|
||||
),
|
||||
// Return / break / continue, one rule per keyword.
|
||||
// The anonymous "return"/"break"/"continue" keywords are matched as
|
||||
// string literals.
|
||||
rule!((control_transfer_statement kind: "return" result: _? @val) => (return_expr value: {val})),
|
||||
rule!((control_transfer_statement kind: "break" result: @lbl) => (break_expr label: (identifier #{lbl}))),
|
||||
rule!((control_transfer_statement kind: "break") => (break_expr)),
|
||||
rule!((control_transfer_statement kind: "continue" result: @lbl) => (continue_expr label: (identifier #{lbl}))),
|
||||
rule!((control_transfer_statement kind: "continue") => (continue_expr)),
|
||||
rule!((control_transfer_statement kind: (throw_keyword) result: @val) => (throw_expr value: {val})),
|
||||
rule!(
|
||||
(memberAccessExpr declName: (declReferenceExpr baseName: @member))
|
||||
=>
|
||||
(member_access_expr base: (inferred_type_expr) member: (identifier #{member}))
|
||||
),
|
||||
// Control transfer, one rule per keyword. `return` carries an optional
|
||||
// value; `break` / `continue` an optional target label; `throw` its
|
||||
// thrown expression.
|
||||
rule!((returnStmt expression: _? @val) => (return_expr value: {val})),
|
||||
rule!((breakStmt label: _? @@lbl) => (break_expr label: {lbl.map(|l| tree!((identifier #{l})))})),
|
||||
rule!((continueStmt label: _? @@lbl) => (continue_expr label: {lbl.map(|l| tree!((identifier #{l})))})),
|
||||
rule!((throwStmt expression: @val) => (throw_expr value: {val})),
|
||||
// ---- Closures ----
|
||||
// Lambda literal with optional type header (parameters + optional return type).
|
||||
// The return_type capture is optional, so this rule covers both cases.
|
||||
|
||||
Reference in New Issue
Block a user