From c4b4fde0d7fc8dff97879847c2bf3b7a0ad9d197 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 24 Jun 2026 12:39:50 +0200 Subject: [PATCH 1/4] unified: Make switch_case pattern optional; add or_pattern disjunction node --- unified/extractor/ast_types.yml | 12 +- .../extractor/src/languages/swift/swift.rs | 4 +- .../tests/corpus/swift/control-flow.txt | 112 ++++++++++-------- 3 files changed, 73 insertions(+), 55 deletions(-) diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index 73f8ac7f66df..418772aa2680 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -42,6 +42,7 @@ supertypes: - name_pattern - tuple_pattern - constructor_pattern + - or_pattern - ignore_pattern - expr_equality_pattern - bulk_importing_pattern @@ -359,12 +360,12 @@ named: case*: switch_case # A single `case ...:` (or `default:`) entry in a switch. - # An entry with multiple `case p1, p2:` patterns has multiple `pattern`s. - # A `default:` entry has no patterns. + # An entry with multiple `case p1, p2:` patterns uses an `or_pattern`. + # A `default:` entry has no pattern. # An optional `guard` corresponds to a `where`-clause on the case. switch_case: modifier*: modifier - pattern*: pattern + pattern?: pattern guard?: expr body: block @@ -421,6 +422,11 @@ named: constructor: expr_or_type element*: pattern_element + # A disjunction pattern that matches if any of its sub-patterns match. + or_pattern: + modifier*: modifier + pattern*: pattern + # A pattern with an optional associated name. pattern_element: modifier*: modifier diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index c84e3cf38676..adafc253989e 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -654,9 +654,9 @@ fn translation_rules() -> Vec> { ), // Switch entry with patterns and body rule!( - (switch_entry pattern: (switch_pattern pattern: @pats)* statement: _* @body) + (switch_entry pattern: (switch_pattern pattern: @pats)+ statement: _* @body) => - (switch_case pattern: {..pats} body: (block stmt: {..body})) + (switch_case pattern: (or_pattern pattern: {..pats}) body: (block stmt: {..body})) ), // Switch entry: default case (no patterns) rule!( diff --git a/unified/extractor/tests/corpus/swift/control-flow.txt b/unified/extractor/tests/corpus/swift/control-flow.txt index 9a740cb9d450..2f209f8c6424 100644 --- a/unified/extractor/tests/corpus/swift/control-flow.txt +++ b/unified/extractor/tests/corpus/swift/control-flow.txt @@ -559,8 +559,10 @@ top_level name_expr identifier: identifier "print" pattern: - expr_equality_pattern - expr: int_literal "1" + or_pattern + pattern: + expr_equality_pattern + expr: int_literal "1" switch_case body: block @@ -573,10 +575,12 @@ top_level name_expr identifier: identifier "print" pattern: - expr_equality_pattern - expr: int_literal "2" - expr_equality_pattern - expr: int_literal "3" + or_pattern + pattern: + expr_equality_pattern + expr: int_literal "2" + expr_equality_pattern + expr: int_literal "3" switch_case body: block @@ -699,16 +703,18 @@ top_level name_expr identifier: identifier "print" pattern: - constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "r" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "circle" + or_pattern + pattern: + constructor_pattern + element: + pattern_element + pattern: + name_pattern + identifier: identifier "r" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "circle" switch_case body: block @@ -723,16 +729,18 @@ top_level name_expr identifier: identifier "print" pattern: - constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "s" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "square" + or_pattern + pattern: + constructor_pattern + element: + pattern_element + pattern: + name_pattern + identifier: identifier "s" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "square" value: name_expr identifier: identifier "shape" @@ -844,17 +852,19 @@ top_level name_expr identifier: identifier "print" pattern: - constructor_pattern - element: - pattern_element - key: identifier "isAcknowledged" - pattern: - expr_equality_pattern - expr: boolean_literal "false" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "implicit" + or_pattern + pattern: + constructor_pattern + element: + pattern_element + key: identifier "isAcknowledged" + pattern: + expr_equality_pattern + expr: boolean_literal "false" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "implicit" switch_case body: block @@ -869,19 +879,21 @@ top_level name_expr identifier: identifier "print" pattern: - constructor_pattern - element: - pattern_element - key: identifier "threadRowId" - pattern: ignore_pattern "_" - pattern_element - pattern: - name_pattern - identifier: identifier "rowId" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "thread" + or_pattern + pattern: + constructor_pattern + element: + pattern_element + key: identifier "threadRowId" + pattern: ignore_pattern "_" + pattern_element + pattern: + name_pattern + identifier: identifier "rowId" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "thread" value: name_expr identifier: identifier "x" From 7216d12b9a7b1ecc9200e0c9a7eb55b56dc33bbe Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 24 Jun 2026 12:42:10 +0200 Subject: [PATCH 2/4] unified: Avoid singleton or_pattern in Swift switch case mapping --- .../extractor/src/languages/swift/swift.rs | 15 ++- .../tests/corpus/swift/control-flow.txt | 102 ++++++++---------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index adafc253989e..07b4d8774b79 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -652,11 +652,20 @@ fn translation_rules() -> Vec> { => (switch_expr value: {val} case: {..cases}) ), - // Switch entry with patterns and body + // Switch entry with multiple patterns and body rule!( - (switch_entry pattern: (switch_pattern pattern: @pats)+ statement: _* @body) + (switch_entry + pattern: (switch_pattern pattern: @first) + pattern: (switch_pattern pattern: @rest)+ + statement: _* @body) + => + (switch_case pattern: (or_pattern pattern: {first} pattern: {..rest}) body: (block stmt: {..body})) + ), + // Switch entry with exactly one pattern and body + rule!( + (switch_entry pattern: (switch_pattern pattern: @pat) statement: _* @body) => - (switch_case pattern: (or_pattern pattern: {..pats}) body: (block stmt: {..body})) + (switch_case pattern: {pat} body: (block stmt: {..body})) ), // Switch entry: default case (no patterns) rule!( diff --git a/unified/extractor/tests/corpus/swift/control-flow.txt b/unified/extractor/tests/corpus/swift/control-flow.txt index 2f209f8c6424..00ce2c77db91 100644 --- a/unified/extractor/tests/corpus/swift/control-flow.txt +++ b/unified/extractor/tests/corpus/swift/control-flow.txt @@ -559,10 +559,8 @@ top_level name_expr identifier: identifier "print" pattern: - or_pattern - pattern: - expr_equality_pattern - expr: int_literal "1" + expr_equality_pattern + expr: int_literal "1" switch_case body: block @@ -703,18 +701,16 @@ top_level name_expr identifier: identifier "print" pattern: - or_pattern - pattern: - constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "r" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "circle" + constructor_pattern + element: + pattern_element + pattern: + name_pattern + identifier: identifier "r" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "circle" switch_case body: block @@ -729,18 +725,16 @@ top_level name_expr identifier: identifier "print" pattern: - or_pattern - pattern: - constructor_pattern - element: - pattern_element - pattern: - name_pattern - identifier: identifier "s" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "square" + constructor_pattern + element: + pattern_element + pattern: + name_pattern + identifier: identifier "s" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "square" value: name_expr identifier: identifier "shape" @@ -852,19 +846,17 @@ top_level name_expr identifier: identifier "print" pattern: - or_pattern - pattern: - constructor_pattern - element: - pattern_element - key: identifier "isAcknowledged" - pattern: - expr_equality_pattern - expr: boolean_literal "false" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "implicit" + constructor_pattern + element: + pattern_element + key: identifier "isAcknowledged" + pattern: + expr_equality_pattern + expr: boolean_literal "false" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "implicit" switch_case body: block @@ -879,21 +871,19 @@ top_level name_expr identifier: identifier "print" pattern: - or_pattern - pattern: - constructor_pattern - element: - pattern_element - key: identifier "threadRowId" - pattern: ignore_pattern "_" - pattern_element - pattern: - name_pattern - identifier: identifier "rowId" - constructor: - member_access_expr - base: inferred_type_expr "." - member: identifier "thread" + constructor_pattern + element: + pattern_element + key: identifier "threadRowId" + pattern: ignore_pattern "_" + pattern_element + pattern: + name_pattern + identifier: identifier "rowId" + constructor: + member_access_expr + base: inferred_type_expr "." + member: identifier "thread" value: name_expr identifier: identifier "x" From db449dca6a9b1f77261cb109eb3fa38350a8964e Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 24 Jun 2026 15:12:10 +0200 Subject: [PATCH 3/4] unified: Fix handling of 'if case let' --- .../extractor/src/languages/swift/swift.rs | 6 +- .../tests/corpus/swift/control-flow.txt | 77 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 07b4d8774b79..cb0546ce8e18 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -673,13 +673,13 @@ fn translation_rules() -> Vec> { => (switch_case body: (block stmt: {..body})) ), - // if case let x = expr — the pattern is taken as-is (no Optional wrapping) + // if case PATTERN = expr — preserve the pattern directly (no Optional wrapping) rule!( - (if_let_binding "case" (value_binding_pattern) bound_identifier: @name _ @val) + (if_let_binding "case" pattern: @pat value: @val) => (pattern_guard_expr value: {val} - pattern: (name_pattern identifier: (identifier #{name}))) + pattern: {pat}) ), rule!( (if_let_binding diff --git a/unified/extractor/tests/corpus/swift/control-flow.txt b/unified/extractor/tests/corpus/swift/control-flow.txt index 00ce2c77db91..f7d59e8cfe40 100644 --- a/unified/extractor/tests/corpus/swift/control-flow.txt +++ b/unified/extractor/tests/corpus/swift/control-flow.txt @@ -594,6 +594,83 @@ top_level name_expr identifier: identifier "x" +=== +If-case-let with shadowing in condition value +=== + +if case let x = x + 10 { + print(x) +} + +--- + +source_file + statement: + if_statement + body: + block + statement: + call_expression + function: simple_identifier "print" + suffix: + call_suffix + arguments: + value_arguments + argument: + value_argument + value: simple_identifier "x" + condition: + if_condition + kind: + if_let_binding + pattern: + pattern + kind: + binding_pattern + binding: + value_binding_pattern + mutability: let + pattern: + pattern + bound_identifier: simple_identifier "x" + value: + additive_expression + lhs: simple_identifier "x" + op: + + rhs: integer_literal "10" + +--- + +top_level + body: + block + stmt: + if_expr + condition: + pattern_guard_expr + pattern: + name_pattern + identifier: identifier "x" + value: + binary_expr + operator: infix_operator "+" + left: + name_expr + identifier: identifier "x" + right: int_literal "10" + then: + block + stmt: + call_expr + argument: + argument + value: + name_expr + identifier: identifier "x" + callee: + name_expr + identifier: identifier "print" + === Switch with binding pattern === From 1842382e23377ebfdfc70957d2ba14b6545d0c97 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 24 Jun 2026 13:11:49 +0200 Subject: [PATCH 4/4] unified: regenerate QL --- unified/ql/lib/codeql/unified/Ast.qll | 27 ++++++++++++++++++++++++--- unified/ql/lib/unified.dbscheme | 26 +++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/unified/ql/lib/codeql/unified/Ast.qll b/unified/ql/lib/codeql/unified/Ast.qll index 1c2d5f3dd4a2..602d6ab2a424 100644 --- a/unified/ql/lib/codeql/unified/Ast.qll +++ b/unified/ql/lib/codeql/unified/Ast.qll @@ -978,6 +978,23 @@ module Unified { } } + /** A class representing `or_pattern` nodes. */ + class OrPattern extends @unified_or_pattern, AstNode { + /** Gets the name of the primary QL class for this element. */ + final override string getAPrimaryQlClass() { result = "OrPattern" } + + /** Gets the node corresponding to the field `modifier`. */ + final Modifier getModifier(int i) { unified_or_pattern_modifier(this, i, result) } + + /** Gets the node corresponding to the field `pattern`. */ + final Pattern getPattern(int i) { unified_or_pattern_pattern(this, i, result) } + + /** Gets a field or child node of this node. */ + final override AstNode getAFieldOrChild() { + unified_or_pattern_modifier(this, _, result) or unified_or_pattern_pattern(this, _, result) + } + } + /** A class representing `parameter` nodes. */ class Parameter extends @unified_parameter, AstNode { /** Gets the name of the primary QL class for this element. */ @@ -1109,14 +1126,14 @@ module Unified { final Modifier getModifier(int i) { unified_switch_case_modifier(this, i, result) } /** Gets the node corresponding to the field `pattern`. */ - final Pattern getPattern(int i) { unified_switch_case_pattern(this, i, result) } + final Pattern getPattern() { unified_switch_case_pattern(this, result) } /** Gets a field or child node of this node. */ final override AstNode getAFieldOrChild() { unified_switch_case_def(this, result) or unified_switch_case_guard(this, result) or unified_switch_case_modifier(this, _, result) or - unified_switch_case_pattern(this, _, result) + unified_switch_case_pattern(this, result) } } @@ -1654,6 +1671,10 @@ module Unified { i = -1 and name = "getPrecedence" or + result = node.(OrPattern).getModifier(i) and name = "getModifier" + or + result = node.(OrPattern).getPattern(i) and name = "getPattern" + or result = node.(Parameter).getDefault() and i = -1 and name = "getDefault" or result = node.(Parameter).getExternalName() and i = -1 and name = "getExternalName" @@ -1682,7 +1703,7 @@ module Unified { or result = node.(SwitchCase).getModifier(i) and name = "getModifier" or - result = node.(SwitchCase).getPattern(i) and name = "getPattern" + result = node.(SwitchCase).getPattern() and i = -1 and name = "getPattern" or result = node.(SwitchExpr).getCase(i) and name = "getCase" or diff --git a/unified/ql/lib/unified.dbscheme b/unified/ql/lib/unified.dbscheme index 3d9e5cddae00..e957e303c22f 100644 --- a/unified/ql/lib/unified.dbscheme +++ b/unified/ql/lib/unified.dbscheme @@ -716,6 +716,24 @@ unified_operator_syntax_declaration_def( int name: @unified_token_identifier ref ); +#keyset[unified_or_pattern, index] +unified_or_pattern_modifier( + int unified_or_pattern: @unified_or_pattern ref, + int index: int ref, + unique int modifier: @unified_token_modifier ref +); + +#keyset[unified_or_pattern, index] +unified_or_pattern_pattern( + int unified_or_pattern: @unified_or_pattern ref, + int index: int ref, + unique int pattern: @unified_pattern ref +); + +unified_or_pattern_def( + unique int id: @unified_or_pattern +); + unified_parameter_default( unique int unified_parameter: @unified_parameter ref, unique int default: @unified_expr ref @@ -747,7 +765,7 @@ unified_parameter_def( unique int id: @unified_parameter ); -@unified_pattern = @unified_bulk_importing_pattern | @unified_constructor_pattern | @unified_expr_equality_pattern | @unified_name_pattern | @unified_token_ignore_pattern | @unified_token_unsupported_node | @unified_tuple_pattern +@unified_pattern = @unified_bulk_importing_pattern | @unified_constructor_pattern | @unified_expr_equality_pattern | @unified_name_pattern | @unified_or_pattern | @unified_token_ignore_pattern | @unified_token_unsupported_node | @unified_tuple_pattern unified_pattern_element_key( unique int unified_pattern_element: @unified_pattern_element ref, @@ -795,10 +813,8 @@ unified_switch_case_modifier( unique int modifier: @unified_token_modifier ref ); -#keyset[unified_switch_case, index] unified_switch_case_pattern( - int unified_switch_case: @unified_switch_case ref, - int index: int ref, + unique int unified_switch_case: @unified_switch_case ref, unique int pattern: @unified_pattern ref ); @@ -1056,7 +1072,7 @@ unified_trivia_tokeninfo( string value: string ref ); -@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_constructor_declaration | @unified_constructor_pattern | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_equality_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_function_type_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_name_pattern | @unified_named_type_expr | @unified_operator_syntax_declaration | @unified_parameter | @unified_pattern_element | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_tuple_pattern | @unified_tuple_type_element | @unified_tuple_type_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_type_test_pattern | @unified_unary_expr | @unified_variable_declaration | @unified_while_stmt +@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_constructor_declaration | @unified_constructor_pattern | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_equality_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_function_type_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_name_expr | @unified_name_pattern | @unified_named_type_expr | @unified_operator_syntax_declaration | @unified_or_pattern | @unified_parameter | @unified_pattern_element | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_tuple_pattern | @unified_tuple_type_element | @unified_tuple_type_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_type_test_pattern | @unified_unary_expr | @unified_variable_declaration | @unified_while_stmt unified_ast_node_location( unique int node: @unified_ast_node ref,