diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 000601b7215c..ccc1cc7f725d 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -185,6 +185,20 @@ fn translation_rules() -> Vec> { // the target AST has no expression-level discard (only `ignore_pattern`, // which is a pattern), so it becomes a `name_expr` over the `_` token. rule!((discardAssignmentExpr wildcard: @@w) => (name_expr identifier: (identifier #{w}))), + // A generic specialization in expression position (`C`, + // `Array`) is represented by swift-syntax as a + // `genericSpecializationExpr`. When used as a call target + // (`C()`), this should become a `call_expr` whose callee is a + // `generic_type_expr`, so we map it directly to that shape. + rule!( + (genericSpecializationExpr + expression: (declReferenceExpr baseName: @name) + genericArgumentClause: (genericArgumentClause arguments: (genericArgument argument: @args)*)) + => + (generic_type_expr + base: (named_type_expr name: (identifier #{name})) + type_argument: {args}) + ), // ---- Operators ---- // The parser front-end folds operator chains into nested // `infixOperatorExpr`s by precedence (see swift-syntax-rs), so @@ -568,6 +582,7 @@ fn translation_rules() -> Vec> { rule!( (functionDecl name: @name + genericParameterClause: (genericParameterClause parameters: _* @type_params)? signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params) returnClause: (returnClause type: @ret)?) @@ -575,6 +590,7 @@ fn translation_rules() -> Vec> { => (function_declaration name: (identifier #{name}) + type_parameter: {type_params} parameter: {params} return_type: {ret} body: (block stmt: {body})) @@ -582,12 +598,14 @@ fn translation_rules() -> Vec> { rule!( (functionDecl name: @name + genericParameterClause: (genericParameterClause parameters: _* @type_params)? signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params) returnClause: (returnClause type: @ret)?)) => (function_declaration name: (identifier #{name}) + type_parameter: {type_params} parameter: {params} return_type: {ret} body: (block)) @@ -1065,19 +1083,51 @@ fn translation_rules() -> Vec> { // A nominal type's `inheritanceClause` (`: Base, Proto`) becomes a list // of `base_type`s, one per inherited type. Each declaration keyword // gets its own rule; the bodies are identical but for the keyword. + rule!( + (genericParameter + attributes: _* @attrs + specifier: _? @@spec + name: @@name + inheritedType: _? @bound) + => + (type_parameter + modifier: {attrs} + modifier: (modifier #{spec})? + name: (identifier #{name}) + bound: {bound}) + ), + rule!( + (genericRequirement + requirement: (conformanceRequirement leftType: @ty rightType: @bound)) + => + (bound_type_constraint type: {ty} bound: {bound}) + ), + rule!( + (genericRequirement + requirement: (sameTypeRequirement leftType: @left rightType: @right)) + => + (equality_type_constraint left: {left} right: {right}) + ), // Class declaration with body containing members rule!( (classDecl classKeyword: @kind modifiers: _* @mods name: @name + genericParameterClause: (genericParameterClause + parameters: _* @params + genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + genericWhereClause: (genericWhereClause requirements: _* @declaration_constraints)? memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + type_parameter: {params} + type_constraint: {parameter_constraints} + type_constraint: {declaration_constraints} base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), @@ -1087,13 +1137,20 @@ fn translation_rules() -> Vec> { enumKeyword: @kind modifiers: _* @mods name: @name + genericParameterClause: (genericParameterClause + parameters: _* @params + genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + genericWhereClause: (genericWhereClause requirements: _* @declaration_constraints)? memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + type_parameter: {params} + type_constraint: {parameter_constraints} + type_constraint: {declaration_constraints} base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), @@ -1103,13 +1160,20 @@ fn translation_rules() -> Vec> { structKeyword: @kind modifiers: _* @mods name: @name + genericParameterClause: (genericParameterClause + parameters: _* @params + genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + genericWhereClause: (genericWhereClause requirements: _* @declaration_constraints)? memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + type_parameter: {params} + type_constraint: {parameter_constraints} + type_constraint: {declaration_constraints} base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), @@ -1119,13 +1183,17 @@ fn translation_rules() -> Vec> { protocolKeyword: @kind modifiers: _* @mods name: @name + genericParameterClause: (genericParameterClause parameters: _* @params)? inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + genericWhereClause: (genericWhereClause requirements: _* @declaration_constraints)? memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + type_parameter: {params} + type_constraint: {declaration_constraints} base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), @@ -1180,11 +1248,13 @@ fn translation_rules() -> Vec> { (typeAliasDecl modifiers: _* @mods name: @@name + genericParameterClause: (genericParameterClause parameters: _* @type_params)? initializer: (typeInitializerClause value: @val)) => (type_alias_declaration modifier: {mods} name: (identifier #{name}) + type_parameter: {type_params} r#type: {val}) ), // Associated type declaration (with optional bound) diff --git a/unified/extractor/tests/corpus/swift/expressions/generic-specialization-expression.output b/unified/extractor/tests/corpus/swift/expressions/generic-specialization-expression.output index 4f3bd0214411..94cdf978b9d2 100644 --- a/unified/extractor/tests/corpus/swift/expressions/generic-specialization-expression.output +++ b/unified/extractor/tests/corpus/swift/expressions/generic-specialization-expression.output @@ -53,4 +53,11 @@ top_level identifier: identifier "numbers" value: call_expr - callee: unsupported_node "Array" + callee: + generic_type_expr + base: + named_type_expr + name: identifier "Array" + type_argument: + named_type_expr + name: identifier "Int" diff --git a/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.output b/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.output new file mode 100644 index 000000000000..37afdfa0d639 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.output @@ -0,0 +1,106 @@ +class Foo {} +class C {} +let x = C() + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Foo" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class + codeBlockItem + item: + classDecl + attributes: + name: identifier "C" + genericParameterClause: + genericParameterClause + parameters: + genericParameter + attributes: + name: identifier "T" + leftAngle: < + rightAngle: > + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + additionalTrailingClosures: + calledExpression: + genericSpecializationExpr + expression: + declReferenceExpr + baseName: identifier "C" + genericArgumentClause: + genericArgumentClause + arguments: + genericArgument + argument: + identifierType + name: identifier "Foo" + leftAngle: < + rightAngle: > + pattern: + identifierPattern + identifier: identifier "x" + +--- + +top_level + body: + block + stmt: + class_like_declaration + modifier: modifier "class" + name: identifier "Foo" + class_like_declaration + modifier: modifier "class" + name: identifier "C" + type_parameter: + type_parameter + name: identifier "T" + variable_declaration + modifier: modifier "let" + pattern: + name_pattern + identifier: identifier "x" + value: + call_expr + callee: + generic_type_expr + base: + named_type_expr + name: identifier "C" + type_argument: + named_type_expr + name: identifier "Foo" diff --git a/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.swift b/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.swift new file mode 100644 index 000000000000..c5a122cb3806 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/functions/constructor-call-with-type-arguments.swift @@ -0,0 +1,3 @@ +class Foo {} +class C {} +let x = C() diff --git a/unified/extractor/tests/corpus/swift/functions/generic-function.output b/unified/extractor/tests/corpus/swift/functions/generic-function.output index d09aac2744a2..dc7831410e23 100644 --- a/unified/extractor/tests/corpus/swift/functions/generic-function.output +++ b/unified/extractor/tests/corpus/swift/functions/generic-function.output @@ -65,6 +65,9 @@ top_level stmt: function_declaration name: identifier "identity" + type_parameter: + type_parameter + name: identifier "T" parameter: parameter external_name: identifier "_" diff --git a/unified/extractor/tests/corpus/swift/functions/generic-type-alias.output b/unified/extractor/tests/corpus/swift/functions/generic-type-alias.output new file mode 100644 index 000000000000..36737bd95bda --- /dev/null +++ b/unified/extractor/tests/corpus/swift/functions/generic-type-alias.output @@ -0,0 +1,77 @@ +typealias Box = Dictionary + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + typeAliasDecl + attributes: + name: identifier "Box" + genericParameterClause: + genericParameterClause + parameters: + genericParameter + colon: : + attributes: + name: identifier "T" + trailingComma: , + inheritedType: + identifierType + name: identifier "Equatable" + genericParameter + attributes: + name: identifier "U" + leftAngle: < + rightAngle: > + modifiers: + initializer: + typeInitializerClause + equal: = + value: + identifierType + name: identifier "Dictionary" + genericArgumentClause: + genericArgumentClause + arguments: + genericArgument + trailingComma: , + argument: + identifierType + name: identifier "T" + genericArgument + argument: + identifierType + name: identifier "U" + leftAngle: < + rightAngle: > + typealiasKeyword: typealias + +--- + +top_level + body: + block + stmt: + type_alias_declaration + name: identifier "Box" + type_parameter: + type_parameter + name: identifier "T" + bound: + named_type_expr + name: identifier "Equatable" + type_parameter + name: identifier "U" + type: + generic_type_expr + base: + named_type_expr + name: identifier "Dictionary" + type_argument: + named_type_expr + name: identifier "T" + named_type_expr + name: identifier "U" diff --git a/unified/extractor/tests/corpus/swift/functions/generic-type-alias.swift b/unified/extractor/tests/corpus/swift/functions/generic-type-alias.swift new file mode 100644 index 000000000000..831f552bab1a --- /dev/null +++ b/unified/extractor/tests/corpus/swift/functions/generic-type-alias.swift @@ -0,0 +1 @@ +typealias Box = Dictionary diff --git a/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.output b/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.output new file mode 100644 index 000000000000..26b17599ae00 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.output @@ -0,0 +1,94 @@ +class Box where U: Equatable, U == T { +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Box" + genericParameterClause: + genericParameterClause + parameters: + genericParameter + colon: : + attributes: + name: identifier "T" + trailingComma: , + inheritedType: + identifierType + name: identifier "Equatable" + genericParameter + attributes: + name: identifier "U" + leftAngle: < + rightAngle: > + genericWhereClause: + genericWhereClause + requirements: + genericRequirement + trailingComma: , + requirement: + conformanceRequirement + colon: : + leftType: + identifierType + name: identifier "U" + rightType: + identifierType + name: identifier "Equatable" + genericRequirement + requirement: + sameTypeRequirement + equal: binaryOperator "==" + leftType: + identifierType + name: identifier "U" + rightType: + identifierType + name: identifier "T" + whereKeyword: where + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class + +--- + +top_level + body: + block + stmt: + class_like_declaration + modifier: modifier "class" + name: identifier "Box" + type_parameter: + type_parameter + name: identifier "T" + bound: + named_type_expr + name: identifier "Equatable" + type_parameter + name: identifier "U" + type_constraint: + bound_type_constraint + type: + named_type_expr + name: identifier "U" + bound: + named_type_expr + name: identifier "Equatable" + equality_type_constraint + left: + named_type_expr + name: identifier "U" + right: + named_type_expr + name: identifier "T" diff --git a/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.swift b/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.swift new file mode 100644 index 000000000000..80c882308a51 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/types/generic-class-parameters-and-constraints.swift @@ -0,0 +1,2 @@ +class Box where U: Equatable, U == T { +} diff --git a/unified/ql/lib/codeql/unified/internal/AstExtra.qll b/unified/ql/lib/codeql/unified/internal/AstExtra.qll index e14043dc46d2..378712b86cb9 100644 --- a/unified/ql/lib/codeql/unified/internal/AstExtra.qll +++ b/unified/ql/lib/codeql/unified/internal/AstExtra.qll @@ -52,4 +52,9 @@ module Public { result = this.getValue().regexpCapture("(?s)/\\*(.*)\\*/", 1) } } + + /** A `Stmt` at the top-level. */ + final class TopLevelStmt extends Stmt { + TopLevelStmt() { this = any(TopLevel t).getBody().getAStmt() } + } } diff --git a/unified/ql/lib/codeql/unified/internal/Variables.qll b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll similarity index 62% rename from unified/ql/lib/codeql/unified/internal/Variables.qll rename to unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll index 49cad56d05db..8c78a187b766 100644 --- a/unified/ql/lib/codeql/unified/internal/Variables.qll +++ b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll @@ -1,5 +1,5 @@ /** - * Provides classes for reasoning about lexically scoped variables and references to these. + * Provides classes for reasoning about lexically scoped names and references to these. */ private import unified @@ -90,6 +90,20 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig; module Public { /** - * A local variable. + * A representative for a lexically scoped entity, such as a local variable, type name, or module name. */ - class Variable extends LocalNameBindingOutput::Local { - VariableAccess getAnAccess() { result.getVariable() = this } - } + class LocalName instanceof LocalNameBindingOutput::Local { + /** Gets the name of this local, as a string. */ + string toString() { result = super.toString() } - /** - * An AST node that is a reference to a local variable. - */ - class VariableAccess extends AstNode instanceof LocalNameBindingOutput::LocalAccess { - Variable getVariable() { result = super.getLocal() } + /** Gets the location of this local name's first declaration */ + Location getLocation() { result = super.getLocation() } - Identifier getIdentifier() { - result = this.(NameExpr).getIdentifier() - or - result = this.(NamePattern).getIdentifier() - or - result = this - } + /** Gets the name of this local, as a string. */ + string getName() { result = super.getName() } + } +} - string getName() { result = this.getIdentifier().getValue() } +/** + * An identifier node that is possibly a reference to a local name, but could also refer to a member + * visible through imports or inheritance. + * + * For example, the type annotation `C` below is a potential access to `class C`, but could + * also refer to `B.C` if such a class exists: + * ```swift + * class C {} + * class A : B { + * let x : C + * } + * ``` + */ +class PotentialLocalNameAccess extends Identifier { + PotentialLocalNameAccess() { + this = any(NameExpr e).getIdentifier() + or + this = any(NamePattern e).getIdentifier() + or + this = any(NamedTypeExpr e | not exists(e.getQualifier())).getName() + or + LocalNameBindingInput::bindingContext(this, _) } + + LocalName getLocalName() { result = this.(LocalNameBindingOutput::LocalAccess).getLocal() } + + string getName() { result = this.getValue() } + + /** Holds if this is one of the declaration sites for a name, such as the `x` in `let x = 123`. */ + predicate isDeclarationSite() { LocalNameBindingInput::bindingContext(this, _) } } diff --git a/unified/ql/lib/unified.qll b/unified/ql/lib/unified.qll index fad4b3ef9e67..67d6ce7558ea 100644 --- a/unified/ql/lib/unified.qll +++ b/unified/ql/lib/unified.qll @@ -6,4 +6,4 @@ import codeql.Locations import codeql.files.FileSystem import codeql.unified.internal.Ast::UnifiedFinal import codeql.unified.internal.AstExtra::Public -import codeql.unified.internal.Variables::Public +import codeql.unified.internal.LocalNameBinding::Public diff --git a/unified/ql/test/library-tests/local-name-binding/class_scope.swift b/unified/ql/test/library-tests/local-name-binding/class_scope.swift new file mode 100644 index 000000000000..5e251b8e6a0d --- /dev/null +++ b/unified/ql/test/library-tests/local-name-binding/class_scope.swift @@ -0,0 +1,82 @@ +class A { + static func static_before() { + print(staticVar) // $ access=staticVar + B(); // $ access=A.B + let b: B = nil // $ access=A.B + let c: C = nil // $ access=A.C + } + func instance_before() { + print(instanceVar) // $ access=instanceVar + B(); // $ access=A.B + let b: B = nil // $ access=A.B + let c: C = nil // $ access=A.C + } + + private static let staticVar = 123 + private let instanceVar = 456 + + class B {} // name=A.B + typealias C = B // $ access=A.B // name=A.C + + static func static_after() { + print(staticVar) // $ access=staticVar + B(); // $ access=A.B + let b: B = nil // $ access=A.B + let c: C = nil // $ access=A.C + + } + func instance_after() { + print(instanceVar) // $ access=instanceVar + B(); // $ access=A.B + let b: B = nil // $ access=A.B + let c: C = nil // $ access=A.C + } +} + +class Base {} // name=top.Base + +// Base types and type parameter bounds can't see members in the class body +class C : Base { // $ access=top.Base + class Base {} // name=C.Base +} + +class D { // $ access=top.Base + class Base {} // name=D.Base +} + +class E where T : Base { // $ access=T access=top.Base + class Base {} // name=E.Base +} + +// Base types and type parameter bounds can see type parameters +class F : + D { // $ access=D access=TypeParamF +} + +class G> { // $ access=D access=TypeParamG +} + +class H where + TypeParamH : Base { // $ access=TypeParamH access=top.Base +} + +// Type parameter bounds can see other type parameters, even if declared later +class I< + T1 : D, // $ access=D access=I.T2 + T2> { // name=I.T2 +} + +// Members can see type parameters. +class J { + let x: TypeParamI; // $ access=TypeParamI +} + +typealias Alias = + D; // $ access=D access=TypeParamAlias + +func foo(x: FooT) // $ access=FooT + -> D { // $ access=D access=FooT + let x: FooT = nil // $ access=FooT + return D() // $ access=D access=FooT +} diff --git a/unified/ql/test/library-tests/variables/variables.expected b/unified/ql/test/library-tests/local-name-binding/test.expected similarity index 100% rename from unified/ql/test/library-tests/variables/variables.expected rename to unified/ql/test/library-tests/local-name-binding/test.expected diff --git a/unified/ql/test/library-tests/variables/variables.ql b/unified/ql/test/library-tests/local-name-binding/test.ql similarity index 68% rename from unified/ql/test/library-tests/variables/variables.ql rename to unified/ql/test/library-tests/local-name-binding/test.ql index cf653afebd9d..0fcae36dd31e 100644 --- a/unified/ql/test/library-tests/variables/variables.ql +++ b/unified/ql/test/library-tests/local-name-binding/test.ql @@ -1,5 +1,6 @@ import unified import utils.test.InlineExpectationsTest +import codeql.unified.internal.LocalNameBinding /** Holds if a comment with `text` appears at `filepath:line`, excluding the text in a `$` section. */ predicate plainCommentAt(string filepath, int line, string text) { @@ -13,7 +14,7 @@ predicate plainCommentAt(string filepath, int line, string text) { predicate keyValueCommentAt(string filepath, int line, string key, string value) { exists(string text, string regexp, string match | plainCommentAt(filepath, line, text) and - regexp = "(\\w+)=(\\w+)" and + regexp = "(\\w+)=([\\w.]+)" and match = text.regexpFind(regexp, _, _) and key = match.regexpCapture(regexp, 1) and value = match.regexpCapture(regexp, 2) @@ -23,11 +24,11 @@ predicate keyValueCommentAt(string filepath, int line, string key, string value) module VariableAccessTest implements TestSig { string getARelevantTag() { result = "access" } - additional predicate declAt(Variable v, string filepath, int line) { + additional predicate declAt(LocalName v, string filepath, int line) { v.getLocation().hasLocationInfo(filepath, line, _, _, _) } - private predicate decl(Variable v, string alias) { + private predicate decl(LocalName v, string alias) { exists(string filepath, int line | declAt(v, filepath, line) | keyValueCommentAt(filepath, line, "name", alias) or @@ -36,10 +37,15 @@ module VariableAccessTest implements TestSig { ) } + private PotentialLocalNameAccess getUniqueDeclarationSite(LocalName name) { + result = + unique(PotentialLocalNameAccess ac | ac.isDeclarationSite() and ac.getLocalName() = name) + } + predicate hasActualResult(Location location, string element, string tag, string value) { - exists(VariableAccess va, Variable v | - v = va.getVariable() and - not va = v.getDefiningNode() and + exists(PotentialLocalNameAccess va, LocalName v | + v = va.getLocalName() and + not va = getUniqueDeclarationSite(v) and // no need to annotate declaration site, if there is only one location = va.getLocation() and element = va.toString() and decl(v, value) and @@ -50,12 +56,12 @@ module VariableAccessTest implements TestSig { import MakeTest -private Variable getVariableAt(string name, string filepath, int line) { +private LocalName getVariableAt(string name, string filepath, int line) { VariableAccessTest::declAt(result, filepath, line) and result.getName() = name } -query predicate ambiguousVariable(Variable v, string name, string filepath, int line) { +query predicate ambiguousVariable(LocalName v, string name, string filepath, int line) { v = getVariableAt(name, filepath, line) and strictcount(getVariableAt(name, filepath, line)) >= 2 } diff --git a/unified/ql/test/library-tests/variables/test.swift b/unified/ql/test/library-tests/local-name-binding/test.swift similarity index 99% rename from unified/ql/test/library-tests/variables/test.swift rename to unified/ql/test/library-tests/local-name-binding/test.swift index e4280dc5b408..b5d0469e8a44 100644 --- a/unified/ql/test/library-tests/variables/test.swift +++ b/unified/ql/test/library-tests/local-name-binding/test.swift @@ -343,7 +343,7 @@ enum E38 { } // Switch with a multi-pattern case that binds 'x' in each pattern -func t38(value: E38) { +func t38(value: E38) { // $ access=E38 switch value { // $ access=value case .a(let x), // $ access=x1 // name=x1 .b(let x): // $ access=x1 diff --git a/unified/ql/test/library-tests/local-name-binding/top_level.swift b/unified/ql/test/library-tests/local-name-binding/top_level.swift new file mode 100644 index 000000000000..a7ea16ef10b8 --- /dev/null +++ b/unified/ql/test/library-tests/local-name-binding/top_level.swift @@ -0,0 +1,17 @@ +func use_before() { + print(x) // $ access=top.x + B(); // $ access=top.B + let b: B = nil // $ access=top.B + let c: C = nil // $ access=top.C +} + +let x = 123 // name=top.x +class B {} // name=top.B +typealias C = B // $ access=top.B // name=top.C + +func use_after() { + print(x) // $ access=top.x + B(); // $ access=top.B + let b: B = nil // $ access=top.B + let c: C = nil // $ access=top.C +}