From df5def119f518d55e78e9da2e9bc9e5970fdc5d2 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 8 Oct 2026 11:31:25 +0200 Subject: [PATCH 1/3] Unified: Add more static name binding tests --- .../static-name-binding/inheritance.swift | 109 ++++++++++++++++-- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/unified/ql/test/library-tests/static-name-binding/inheritance.swift b/unified/ql/test/library-tests/static-name-binding/inheritance.swift index fcf1967d4842..2dcfa481bc01 100644 --- a/unified/ql/test/library-tests/static-name-binding/inheritance.swift +++ b/unified/ql/test/library-tests/static-name-binding/inheritance.swift @@ -2,18 +2,113 @@ class A { class B { class C {} } + + static func f() {} +} + +class D: A { // $ access=A + static func f(x: Int) {} // $ access=Int +} +class E: D.B { // $ access=D access=A.B + override init() {} +} + +class F { + private init() {} } -class D: A {} // $ access=A -class E: D.B {} // $ access=D access=A.B +class G: F { // $ access=F + public init() {} +} + +class H: G { // $ access=G + public init(x: String) {} // $ access=String +} + +class I: H {} // $ access=H + +class J: I { // $ access=I + convenience init() { + self.init(x: "") + } +} + +protocol P { + init() +} + +final class K: P {} // $ access=P + +struct S: P {} // $ access=P + +class L { + init(x: Int) {} // $ access=Int // name=L.init1 + + init(x: Int, y: Int) {} // $ access=Int // name=L.init2 + + convenience init() { self.init(x: 0) } // name=L.init_conv +} + +class M: L { // $ access=L + override init(x: Int) { super.init(x: 0) } // $ access=Int + + override init(x: Int, y: Int) { super.init(x: 0, y: 0) } // $ access=Int +} + +class N: L { // $ access=L + override init(x: Int) { super.init(x: 0) } // $ access=Int + + convenience init // name=N.init_conv + (a: Int, b: Int, c: Int) // $ access=Int + { + self.init(x: a + b + c) // $ access=a access=b access=c + } +} + +class O: N { // $ access=N + override init(x: Int) { // $ access=Int + super.init(x: x) // $ access=x + } +} // Members of base classes can be accessed through derived classes func t1() { - let x1: D = nil; // $ access=D - let x2: D.B = nil; // $ access=D access=A.B - let x3: D.B.C = nil; // $ access=D access=A.B access=A.B.C + let x1: D = D() // $ access=D + let x2: D.B = D.B() // $ access=D access=A.B + let x3: D.B.C = D.B.C() // $ access=D access=A.B access=A.B.C // The base class of 'E' is itself resolved through inheritance - let x4: E = nil; // $ access=E - let x5: E.C = nil; // $ access=E access=A.B.C + let x4: E = E() // $ access=E + let x5: E.C = E.C() // $ access=E access=A.B.C + + let x6 = A.init() // $ access=A access=A.init + let x7 = D.init() // $ access=D $ MISSING: access=A.init $ SPURIOUS: access=D.init + + let x8 = A.f() // $ access=A access=A.f + let x9 = D.f() // $ access=D access=D.f -- type inference should filter away the `D.f` target $ MISSING: access=A.f + let x10 = D.f(x: 0) // $ access=D access=D.f $ MISSING: access=A.f -- type inference should filter away the `A.f` target + + let x11 = E.init() // $ access=E access=E.init + + let x12 = G.init() // $ access=G access=G.init + + let x13 = H.init(x: "String") // $ access=H access=H.init + + let x14 = I.init(x: "String") // $ access=I $ MISSING: access=H.init $ SPURIOUS: access=I.init + + let x15 = J.init() // $ access=J access=J.init $ MISSING: access=H.init -- type inference should filter away the `H.init` target + + let x16 = K.init() // $ access=K access=K.init + + let x17 = S.init() // $ access=S access=S.init + + let x18 = M.init(x: 0) // $ access=M access=M.init $ MISSING: access=L.init_conv -- type inference should filter away the `L.init_conv` target + + let x19 = M.init() // $ access=M access=M.init $ MISSING: access=L.init_conv -- type inference should filter away the `M.init` target + + let x20 = N.init(x: 0) // $ access=N access=N.init access=N.init_conv -- type inference should filter away the `N.init_conv` target + + let x21 = O.init(x: 0) // $ access=O access=O.init $ MISSING: access=N.init_conv -- type inference should filter away the `N.init_conv` target + + let x22 = O.init(a: 1, b: 2, c: 3) // $ access=O access=O.init $ MISSING: access=N.init_conv -- type inference should filter away the `O.init` target } From 3aa553875b239a80539e0f70d6e94b25fd4b8975 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 8 Oct 2026 12:42:48 +0200 Subject: [PATCH 2/3] Unified: Add `Member.getNameNode` --- unified/extractor/ast_types.yml | 25 +++++++++++-------- .../ql/lib/codeql/unified/internal/Ast.qll | 25 +++++++++++++------ .../lib/codeql/unified/internal/FacadeAst.qll | 2 +- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index ed3b0181439b..70fe9c09d269 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -80,17 +80,20 @@ supertypes: body?: block # A member is anything that can appear in the body of a class-like declaration member: - - constructor_declaration - - destructor_declaration - - function_declaration - - variable_declaration - - accessor_declaration - - initializer_declaration - - class_like_declaration - - type_alias_declaration - - associated_type_declaration - - unsupported_node - - unhandled_node + subtypes: + - constructor_declaration + - destructor_declaration + - function_declaration + - variable_declaration + - accessor_declaration + - initializer_declaration + - class_like_declaration + - type_alias_declaration + - associated_type_declaration + - unsupported_node + - unhandled_node + fields: + name_node: identifier type_constraint: - equality_type_constraint - bound_type_constraint diff --git a/unified/ql/lib/codeql/unified/internal/Ast.qll b/unified/ql/lib/codeql/unified/internal/Ast.qll index bc4883415661..d3a6e361655b 100644 --- a/unified/ql/lib/codeql/unified/internal/Ast.qll +++ b/unified/ql/lib/codeql/unified/internal/Ast.qll @@ -113,7 +113,7 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_accessor_declaration_def(this, _, result) } + final override F::Identifier getNameNode() { unified_accessor_declaration_def(this, _, result) } /** Gets the node corresponding to the field `parameter`. */ final override F::Parameter getParameter(int i) { @@ -200,7 +200,9 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_associated_type_declaration_def(this, result) } + final override F::Identifier getNameNode() { + unified_associated_type_declaration_def(this, result) + } /** Gets a field or child node of this node. */ final override F::AstNode getAFieldOrChild() { @@ -427,7 +429,9 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_class_like_declaration_name_node(this, result) } + final override F::Identifier getNameNode() { + unified_class_like_declaration_name_node(this, result) + } /** Gets the node corresponding to the field `type_constraint`. */ final F::TypeConstraint getTypeConstraint(int i) { @@ -501,7 +505,9 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_constructor_declaration_name_node(this, result) } + final override F::Identifier getNameNode() { + unified_constructor_declaration_name_node(this, result) + } /** Gets the node corresponding to the field `parameter`. */ final override F::Parameter getParameter(int i) { @@ -704,7 +710,7 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_function_declaration_def(this, result) } + final override F::Identifier getNameNode() { unified_function_declaration_def(this, result) } /** Gets the node corresponding to the field `parameter`. */ final override F::Parameter getParameter(int i) { @@ -979,7 +985,10 @@ module Unified { final override F::AstNode getAFieldOrChild() { unified_map_literal_element(this, _, result) } } - class Member extends @unified_member, F::AstNode { } + class Member extends @unified_member, F::AstNode { + /** Gets the node corresponding to the field `name_node`. */ + F::Identifier getNameNode() { none() } + } /** A class representing `member_access_expr` nodes. */ class MemberAccessExpr extends @unified_member_access_expr, F::Expr { @@ -1341,7 +1350,9 @@ module Unified { final F::Modifier getAModifier() { result = this.getModifier(_) } /** Gets the node corresponding to the field `name_node`. */ - final F::Identifier getNameNode() { unified_type_alias_declaration_def(this, result, _) } + final override F::Identifier getNameNode() { + unified_type_alias_declaration_def(this, result, _) + } /** Gets the node corresponding to the field `type`. */ final F::Expr getType() { unified_type_alias_declaration_def(this, _, result) } diff --git a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll index e8b027066b16..c282928abbc3 100644 --- a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll +++ b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll @@ -193,7 +193,7 @@ module Unified { class VariableDeclaration extends G::VariableDeclaration { /** Gets the name node of this variable declaration, if any. */ - Identifier getNameNode() { result = this.getPattern() } + override Identifier getNameNode() { result = this.getPattern() } /** Gets the name of the variable being declared, if any. */ string getName() { result = this.getNameNode().getValue() } From b1da660bbfb0c10875e6c177d83be78ef6f99831 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 8 Oct 2026 11:31:59 +0200 Subject: [PATCH 3/3] Unified: Improve static name binding involving shadowing --- .../unified/internal/NameBindingPlugin.qll | 41 +++++++ .../internal/NameBindingPluginSwift.qll | 56 ++++++++- .../unified/internal/StaticNameBinding.qll | 106 +++++++++++++----- .../static-name-binding/inheritance.swift | 20 ++-- 4 files changed, 184 insertions(+), 39 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll b/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll index 7afabea20deb..d2320f8cab1b 100644 --- a/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll +++ b/unified/ql/lib/codeql/unified/internal/NameBindingPlugin.qll @@ -1,5 +1,7 @@ private import unified private import codeql.util.Unit +private import codeql.util.Option +private import StaticNameBinding private module Plugins { private import codeql.unified.internal.NameBindingPluginSwift @@ -43,6 +45,23 @@ class NameBindingPlugin extends Unit { bindingset[cls, member] predicate isInheritableMember(ClassLikeDeclaration cls, Member member) { none() } + /** + * Holds if `member` is considered invalid within the namespace `n`. + */ + bindingset[n, member] + predicate isInvalidMember(NamespaceNode n, Member member) { none() } + + /** + * Gets the key used to determine if `m` is shadowed by another declaration + * with the same name and key. + * + * Nodes without shadowing keys will not shadow inherited declarations. + * + * This means that shadowing can be completely disabled by not implementing this + * predicate, and completely enabled by assigning the same key to all members. + */ + string getShadowingKey(Member m) { none() } + /** Gets the name of the implicit receiver parameter in `callable`, if it has one. */ string getImplicitReceiverParameterName(Callable callable) { none() } @@ -89,6 +108,28 @@ predicate isInheritableMember(Member member) { ) } +bindingset[n, member] +predicate isInvalidMember(NamespaceNode n, Member member) { + any(NameBindingPlugin p).isInvalidMember(n, member) +} + +private string getShadowingKey0(NameBindingNode n) { + result = any(NameBindingPlugin p).getShadowingKey(any(Member m | n.isMember(m))) +} + +private class ShadowingKey extends string { + ShadowingKey() { this = getShadowingKey0(_) } +} + +class ShadowingKeyOpt = Option::Option; + +ShadowingKeyOpt getShadowingKey(NameBindingNode n) { + result.asSome() = getShadowingKey0(n) + or + not exists(getShadowingKey0(n)) and + result.isNone() +} + /** * A representative for a module scope. * diff --git a/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll index f2785099b741..eecd22e22aab 100644 --- a/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/NameBindingPluginSwift.qll @@ -3,8 +3,21 @@ */ private import unified +private import codeql.unified.internal.StaticNameBinding private import codeql.unified.internal.NameBindingPlugin +private class GeneratedConstructor extends ConstructorDeclaration { + GeneratedConstructor() { this.hasModifier("generated") } +} + +private class ConvenienceConstructor extends ConstructorDeclaration { + ConvenienceConstructor() { this.hasModifier("convenience") } +} + +private class DesignatedConstructor extends ConstructorDeclaration { + DesignatedConstructor() { not this instanceof ConvenienceConstructor } +} + class NameBindingPluginSwift extends NameBindingPlugin { bindingset[e] override predicate isNonPattern(Expr e) { isUnboundPattern(e.(Identifier)) } @@ -36,7 +49,48 @@ class NameBindingPluginSwift extends NameBindingPlugin { bindingset[cls, member] override predicate isInheritableMember(ClassLikeDeclaration cls, Member member) { exists(cls) and - not member.hasModifier("private") + not member.hasModifier("private") and + not (cls.hasModifier("protocol") and member instanceof ConstructorDeclaration) + } + + bindingset[n, member] + override predicate isInvalidMember(NamespaceNode n, Member member) { + exists(NamespaceNode parent | + parent = n.getAnInheritanceParent() and + not parent + .isInstanceOrStaticMemberNamespace(any(ClassLikeDeclaration p | p.hasModifier("protocol"))) + | + // Remove generated constructors when there are inherited constructors available + // Note: If the base class only has private constructors, this class must have an + // explicit constructor, in which case there is no generated constructor to begin + // with + member instanceof GeneratedConstructor and + n.getOwnMember(_).isMember(member) and + parent.getMemberFull(_, _).isMember(any(ConstructorDeclaration inherited)) + or + // Remove inherited designated constructors (generated or not) when there are + // explicit designated constructors available + // Note: We always inherit convenience constructors, even though it may not actually + // be the case in Swift; this should be OK, since there can then not exist any calls + // that target those constructors + parent.getMemberFull(_, _).isMember(member.(DesignatedConstructor)) and + exists(DesignatedConstructor designated | + n.getOwnMember(_).isMember(designated) and + not designated instanceof GeneratedConstructor + ) + ) + } + + override string getShadowingKey(Member m) { + not m instanceof Callable and result = "" + or + not m instanceof GeneratedConstructor and + result = + concat(int i, Parameter p | + p = m.(Callable).getParameter(i) + | + p.getExternalNameNode().getValue(), "," order by i + ) } override string getImplicitReceiverParameterName(Callable callable) { diff --git a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll index 73ac2d9f8092..219942a3feba 100644 --- a/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/StaticNameBinding.qll @@ -29,6 +29,8 @@ class NameBindingNode extends TNameBindingNode { Identifier asIdentifier() { this.isIdentifier(result) } + predicate isMember(Member m) { this.asIdentifier() = m.getNameNode() } + predicate isBulkImport(BulkImportingPattern p) { this = TBulkImport(p) } predicate isLocalName(LocalName local) { this = TLocalName(local) } @@ -41,6 +43,10 @@ class NameBindingNode extends TNameBindingNode { this = TInstanceMemberNamespace(cls) } + predicate isInstanceOrStaticMemberNamespace(ClassLikeDeclaration cls) { + this.isInstanceMemberNamespace(cls) or this.isStaticMemberNamespace(cls) + } + /** Holds if this represents the set of members that can be accessed unqualified within the given scope. */ predicate isLocalNamespace(AstNode n) { this = TLocalNamespace(n) } @@ -312,6 +318,10 @@ signature module TrackInputSig { /** Holds if the forward-flow of `node` should be tracked. */ predicate shouldTrack(NameBindingNode node); + default NameBindingNode getMember(NamespaceNode namespace, string name) { + result = namespace.getMember(name) + } + default predicate additionalValueStep(NameBindingNode node1, NameBindingNode node2) { none() } } @@ -327,6 +337,25 @@ module Track { exists(NameBindingNode prev | prev = track(node) | valueStepEx(prev, result)) } + pragma[nomagic] + private predicate derivedStoreReadStep0( + NamespaceNode namespace, string name, NameBindingNode node2 + ) { + readStep(namespace.ref(), name, node2) + } + + /** + * Holds if `node1 -> node2` is derived by combining a store and a read step, with zero or more value steps and inheritance steps in-between. + */ + pragma[nomagic] + private predicate derivedStoreReadStep(NameBindingNode node1, NameBindingNode node2) { + exists(NamespaceNode namespace, string name | + node1 = getMember(namespace, name) and // getMember() combines a store step with subsequent inheritance steps + derivedStoreReadStep0(namespace, name, node2) and + node1 != node2 + ) + } + /** Holds if there is an effective value step `node1 -> node2`. */ pragma[inline] private predicate valueStepEx(NameBindingNode node1, NameBindingNode node2) { @@ -338,23 +367,6 @@ module Track { } } -pragma[nomagic] -private predicate derivedStoreReadStep0(NamespaceNode namespace, string name, NameBindingNode node2) { - readStep(namespace.ref(), pragma[only_bind_into](name), node2) -} - -/** - * Holds if `node1 -> node2` is derived by combining a store and a read step, with zero or more value steps and inheritance steps in-between. - */ -pragma[nomagic] -private predicate derivedStoreReadStep(NameBindingNode node1, NameBindingNode node2) { - exists(NamespaceNode namespace, string name | - node1 = namespace.getMember(name) and // getMember() combines a store step with subsequent inheritance steps - derivedStoreReadStep0(namespace, name, node2) and - node1 != node2 - ) -} - /** Holds if the member represented by `node` can be inherited. */ pragma[nomagic] private predicate isInheritableMemberNode(NameBindingNode node) { @@ -382,10 +394,13 @@ class NamespaceNode extends NameBindingNode { NameBindingNode ref() { result = TrackNamespace::track(this) } /** Gets an own (non-inherited) member of this namespace of the given name. */ - NameBindingNode getOwnMember(string name) { storeStep(result, name, this) } + NameBindingNode getOwnMember(string name, ShadowingKeyOpt shadowingKey) { + storeStep(result, name, this) and + shadowingKey = getShadowingKey(result) + } - /** Holds if this namespace has an own-member of the given name */ - predicate hasOwnMember(string name) { exists(this.getOwnMember(name)) } + /** Gets an own (non-inherited) member of this namespace of the given name. */ + NameBindingNode getOwnMember(string name) { result = this.getOwnMember(name, _) } /** If this is the static namespace for a class, gets the corresponding instance namespace. */ NamespaceNode toInstanceNamespace() { @@ -423,23 +438,58 @@ class NamespaceNode extends NameBindingNode { /** Gets a namespace that directly inherits from this one. */ NamespaceNode getAnInheritanceChild() { result.getAnInheritanceParent() = this } - /** Gets a member of this namespace of the given name. */ - cached - NameBindingNode getMember(string name) { - CachedStage::ref() and - result = this.getOwnMember(name) + pragma[nomagic] + private predicate hasOwnMemberWithShadowingKey(string name, string shadowingKey) { + exists(this.getOwnMember(name, any(ShadowingKeyOpt opt | opt.asSome() = shadowingKey))) + } + + /** + * Gets a potential member of this namespace of the given name. + * + * Unlike `getMember`, this predicate does not filter away invalid members. + */ + pragma[nomagic] + NameBindingNode getMemberFull(string name, ShadowingKeyOpt shadowingKey) { + result = this.getOwnMember(name) and + shadowingKey = getShadowingKey(result) or - not this.hasOwnMember(name) and - result = this.getAnInheritanceParent().getMember(name) and + not this.hasOwnMemberWithShadowingKey(name, shadowingKey.asSome()) and + result = this.getAnInheritanceParent().getMemberFull(name, shadowingKey) and isInheritableMemberNode(result) or - result = this.getAnExtension().getMember(name) + result = this.getAnExtension().getMemberFull(name, shadowingKey) + } + + private NameBindingNode getMember(string name, ShadowingKeyOpt shadowingKey) { + CachedStage::ref() and + not exists(Member m | result.isMember(m) | isInvalidMember(this, m)) and + ( + result = this.getOwnMember(name) and + shadowingKey = getShadowingKey(result) + or + result = this.getAnExtension().getMember(name, shadowingKey) + or + exists(NamespaceNode parent | + parent = this.getAnInheritanceParent() and + not this.hasOwnMemberWithShadowingKey(name, shadowingKey.asSome()) and + result = parent.getMember(name, shadowingKey) and + isInheritableMemberNode(result) + ) + ) } + + /** Gets a member of this namespace of the given name. */ + cached + NameBindingNode getMember(string name) { result = this.getMember(name, _) } } private module TrackNamespaceInput implements TrackInputSig { predicate shouldTrack(NameBindingNode node) { node instanceof NamespaceNode } + NameBindingNode getMember(NamespaceNode namespace, string name) { + result = namespace.getMemberFull(name, _) + } + predicate additionalValueStep(NameBindingNode node1, NameBindingNode node2) { // Namespace-tracking goes through aliases, but declaration-tracking does not exists(TypeAliasDeclaration decl | diff --git a/unified/ql/test/library-tests/static-name-binding/inheritance.swift b/unified/ql/test/library-tests/static-name-binding/inheritance.swift index 2dcfa481bc01..c9dcd3bdf240 100644 --- a/unified/ql/test/library-tests/static-name-binding/inheritance.swift +++ b/unified/ql/test/library-tests/static-name-binding/inheritance.swift @@ -82,11 +82,11 @@ func t1() { let x5: E.C = E.C() // $ access=E access=A.B.C let x6 = A.init() // $ access=A access=A.init - let x7 = D.init() // $ access=D $ MISSING: access=A.init $ SPURIOUS: access=D.init + let x7 = D.init() // $ access=D access=A.init let x8 = A.f() // $ access=A access=A.f - let x9 = D.f() // $ access=D access=D.f -- type inference should filter away the `D.f` target $ MISSING: access=A.f - let x10 = D.f(x: 0) // $ access=D access=D.f $ MISSING: access=A.f -- type inference should filter away the `A.f` target + let x9 = D.f() // $ access=D access=A.f access=D.f -- type inference should filter away the `D.f` target + let x10 = D.f(x: 0) // $ access=D access=A.f access=D.f -- type inference should filter away the `A.f` target let x11 = E.init() // $ access=E access=E.init @@ -94,21 +94,21 @@ func t1() { let x13 = H.init(x: "String") // $ access=H access=H.init - let x14 = I.init(x: "String") // $ access=I $ MISSING: access=H.init $ SPURIOUS: access=I.init + let x14 = I.init(x: "String") // $ access=I access=H.init - let x15 = J.init() // $ access=J access=J.init $ MISSING: access=H.init -- type inference should filter away the `H.init` target + let x15 = J.init() // $ access=J access=J.init access=H.init -- type inference should filter away the `H.init` target let x16 = K.init() // $ access=K access=K.init let x17 = S.init() // $ access=S access=S.init - let x18 = M.init(x: 0) // $ access=M access=M.init $ MISSING: access=L.init_conv -- type inference should filter away the `L.init_conv` target + let x18 = M.init(x: 0) // $ access=M access=M.init access=L.init_conv -- type inference should filter away the `L.init_conv` target - let x19 = M.init() // $ access=M access=M.init $ MISSING: access=L.init_conv -- type inference should filter away the `M.init` target + let x19 = M.init() // $ access=M access=M.init access=L.init_conv -- type inference should filter away the `M.init` target - let x20 = N.init(x: 0) // $ access=N access=N.init access=N.init_conv -- type inference should filter away the `N.init_conv` target + let x20 = N.init(x: 0) // $ access=N access=N.init access=N.init_conv access=L.init_conv -- type inference should filter away the `N.init_conv` and `L.init_conv` targets - let x21 = O.init(x: 0) // $ access=O access=O.init $ MISSING: access=N.init_conv -- type inference should filter away the `N.init_conv` target + let x21 = O.init(x: 0) // $ access=O access=O.init access=N.init_conv access=L.init_conv -- type inference should filter away the `N.init_conv` and `L.init_conv` targets - let x22 = O.init(a: 1, b: 2, c: 3) // $ access=O access=O.init $ MISSING: access=N.init_conv -- type inference should filter away the `O.init` target + let x22 = O.init(a: 1, b: 2, c: 3) // $ access=O access=O.init access=N.init_conv access=L.init_conv -- type inference should filter away the `O.init` and `L.init_conv` targets }