Skip to content

Commit 0455baf

Browse files
committed
Unified: Type inference for nested types
1 parent 4215c8a commit 0455baf

10 files changed

Lines changed: 624 additions & 118 deletions

File tree

‎unified/ql/lib/codeql/unified/internal/AstExtra.qll‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ module Public {
138138
final class EnumCaseClass extends ClassLikeDeclaration {
139139
EnumCaseClass() { this.hasModifier("enum_case") }
140140

141+
/** Gets the enum declaration that this case class belongs to. */
142+
ClassLikeDeclaration getEnum() { this = result.getAMember() }
143+
141144
/** Gets the constructor of this algebraic data type. */
142145
EnumCaseConstructor getConstructor() { result = this.getAMember() }
143146
}
@@ -168,5 +171,31 @@ module Public {
168171

169172
/** Gets the class containing this constructor. */
170173
EnumCaseClass getEnumCaseClass() { result.getAMember() = this }
174+
175+
/** Gets the enum declaration that this constructor belongs to. */
176+
ClassLikeDeclaration getEnum() { result = this.getEnumCaseClass().getEnum() }
177+
}
178+
179+
/**
180+
* A field of an enum case in an algebraic data type. For example `a` and
181+
* `b` in
182+
*
183+
* ```swift
184+
* enum E {
185+
* case a
186+
* case b
187+
* }
188+
* ```
189+
*/
190+
final class EnumField extends VariableDeclaration {
191+
private ClassLikeDeclaration enum;
192+
193+
EnumField() {
194+
this = enum.getAMember() and
195+
this.hasModifier("enum_case")
196+
}
197+
198+
/** Gets the enum declaration that this enum field belongs to. */
199+
ClassLikeDeclaration getEnum() { result = enum }
171200
}
172201
}

‎unified/ql/lib/codeql/unified/internal/typeinference/Type.qll‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ predicate associatedTypeParameterInherited(
3838
name = a.getName()
3939
}
4040

41+
private predicate isNestedType(ClassLikeDeclaration nested, ClassLikeDeclaration parent) {
42+
exists(nested.getNameNode()) and
43+
exists(parent.getNameNode()) and
44+
parent.getAMemberIncludingExtensions() = nested
45+
}
46+
4147
cached
4248
newtype TType =
4349
TClassLikeDeclarationType(ClassLikeDeclaration c) {
@@ -56,6 +62,7 @@ newtype TType =
5662
) {
5763
associatedTypeParameter(c, a, inherited)
5864
} or
65+
TParentTypeParameterType(ClassLikeDeclaration c) { isNestedType(c, _) } or
5966
TUnknownType() or
6067
TUnknownTypeTypeParameter(int i) { i in [0 .. 20] }
6168

@@ -112,9 +119,11 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
112119
result = super.getATypeParameter()
113120
or
114121
result = TAssociatedTypeParameterType(c, _, _)
122+
or
123+
result = TParentTypeParameterType(c)
115124
}
116125

117-
override string toString() { result = c.getName() }
126+
override string toString() { result = c.getQualifiedName() }
118127

119128
override Location getLocation() { result = c.getLocation() }
120129
}
@@ -214,6 +223,25 @@ class AssociatedTypeParameterType extends TypeParameter, TAssociatedTypeParamete
214223
}
215224
}
216225

226+
/**
227+
* A special type parameter used to represent the parent type of a nested class.
228+
*/
229+
class ParentTypeParameterType extends TypeParameter, TParentTypeParameterType {
230+
private Unified::ClassLikeDeclaration c;
231+
232+
ParentTypeParameterType() { this = TParentTypeParameterType(c) }
233+
234+
ClassLikeDeclaration getParent() { isNestedType(c, result) }
235+
236+
override ClassLikeDeclaration getDeclaringItem() { result = c }
237+
238+
override Identifier getNameNode() { none() }
239+
240+
override string toString() { result = "(parent:" + c.getName() + ")" }
241+
242+
override Location getLocation() { result = c.getLocation() }
243+
}
244+
217245
/**
218246
* A type parameter of the special `UnknownType`.
219247
*/

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll‎

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ private module Input1 implements InputSig1<Location> {
6464
kind = 3 and
6565
tp0 = TUnknownTypeTypeParameter(id1) and
6666
id2 = 0
67+
or
68+
kind = 4 and
69+
exists(ClassLikeDeclaration c |
70+
tp0 = TParentTypeParameterType(c) and
71+
id1 = idOfTypeParameterAstNode(c) and
72+
id2 = 0
73+
)
6774
|
6875
tp0 order by kind, id1, id2
6976
)
@@ -389,9 +396,6 @@ module Input3 implements InputSig3 {
389396
Unified::Callable getCallable() { result = c }
390397

391398
override TypeMention getDeclaringType() {
392-
result = c.(EnumConstructor).getEnum().getNameNode()
393-
or
394-
not c instanceof EnumConstructor and
395399
exists(ClassLikeDeclaration cls |
396400
c = cls.getAMember() and
397401
result = getClassLikeDeclarationTypeMention(cls)
@@ -405,9 +409,9 @@ module Input3 implements InputSig3 {
405409
or
406410
result = c.(FunctionDeclaration).getNameNode()
407411
or
408-
result = c.(EnumConstructor).getEnclosingClass().getNameNode()
412+
result = c.(EnumCaseConstructor).getEnumCaseClass().getNameNode()
409413
or
410-
not c instanceof EnumConstructor and
414+
not c instanceof EnumCaseConstructor and
411415
result = c.(ConstructorDeclaration).getNameNode()
412416
}
413417

@@ -659,10 +663,20 @@ module Input3 implements InputSig3 {
659663
result = getQualifierTypeMention(invocation)
660664
}
661665

662-
private predicate constructionReceiver(Invocation i, Callable constructor, Expr receiver) {
663-
constructor = i.getTargetImpl(_) and
664-
constructor.asCallable() instanceof ConstructorDeclaration and
665-
receiver = i.getArgument(0)
666+
private predicate constructionReceiver(
667+
Invocation i, Callable constructor, Expr receiver, TypePath enumPrefixPath
668+
) {
669+
exists(ConstructorDeclaration cd |
670+
constructor = i.getTargetImpl(_) and
671+
cd = constructor.asCallable() and
672+
receiver = i.getArgument(0)
673+
|
674+
enumPrefixPath =
675+
TypePath::singleton(TParentTypeParameterType(cd.(EnumCaseConstructor).getEnumCaseClass()))
676+
or
677+
not cd instanceof EnumCaseConstructor and
678+
enumPrefixPath.isEmpty()
679+
)
666680
}
667681

668682
/**
@@ -696,11 +710,15 @@ module Input3 implements InputSig3 {
696710
M3::ContextualTyping::ContextualReturnType<ContextualConstructorReturnTypeInput>;
697711

698712
private predicate needsContextualTyping(Invocation i, TypePath path) {
699-
exists(Callable constructor, Expr receiver, ClassLikeDeclarationType receiverType |
700-
constructionReceiver(i, constructor, receiver) and
701-
receiverType = inferType(receiver)
713+
exists(
714+
Callable constructor, Expr receiver, TypePath enumPrefixPath,
715+
ClassLikeDeclarationType receiverType, TypePath path0
716+
|
717+
constructionReceiver(i, constructor, receiver, enumPrefixPath) and
718+
receiverType = inferType(receiver) and
719+
path = path0.stripPrefix(enumPrefixPath)
702720
|
703-
ContextualConstructorReturnType::needsContextualTyping(i, path) and
721+
ContextualConstructorReturnType::needsContextualTyping(i, path0) and
704722
receiverType = constructor.getDeclaringType().getType()
705723
or
706724
// inherited constructor
@@ -710,18 +728,18 @@ module Input3 implements InputSig3 {
710728
|
711729
declType = constructor.getDeclaringType().getType() and
712730
BaseTypes::rootTypesSatisfaction(receiverType, declType, abs, _, constraint) and
713-
path = TypePath::singleton(tp)
731+
path0 = TypePath::singleton(tp)
714732
|
715733
// `A<T> : B<T>`
716-
exists(TypePath path0 |
717-
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path0, tp) and
718-
ContextualConstructorReturnType::needsContextualTyping(i, path0)
734+
exists(TypePath path1 |
735+
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path1, tp) and
736+
ContextualConstructorReturnType::needsContextualTyping(i, path1)
719737
)
720738
or
721739
// `A<T> : B`
722740
tp = receiverType.getATypeParameter() and
723741
not BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, _, tp) and
724-
not exists(Type t | t = i.getTypeQualifier(path) and not t instanceof PseudoType)
742+
not exists(Type t | t = i.getTypeQualifier(path0) and not t instanceof PseudoType)
725743
)
726744
)
727745
}
@@ -734,8 +752,7 @@ module Input3 implements InputSig3 {
734752
* as well as calls receivers, `self.init(0)` or `super.init(0)`.
735753
*/
736754
predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
737-
constructionReceiver(n2, _, n1) and
738-
prefix1.isEmpty() and
755+
constructionReceiver(n2, _, n1, prefix1) and
739756
prefix2.isEmpty()
740757
}
741758

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeInferencePlugin.qll‎

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,6 @@ class TypeInferencePlugin extends Unit {
8181
/** Holds if `field` is a field of the tuple type `tuple` with the given `name`. */
8282
abstract predicate tupleField(Type tuple, string name, VariableDeclaration field);
8383

84-
/** Holds if `case` is an enum case constructor of `enum` with identifier `i`. */
85-
abstract predicate isEnumConstructor(
86-
ClassLikeDeclaration enum, ConstructorDeclaration case, Identifier i
87-
);
88-
89-
/** Holds if `field` is an enum field of `enum`. */
90-
abstract predicate isEnumField(ClassLikeDeclaration enum, VariableDeclaration field);
91-
9284
/** Gets the inferred type for the given AST node and type path. */
9385
Type inferType(AstNode n, TypePath path) { none() }
9486
}
@@ -139,25 +131,6 @@ class TupleType extends Type {
139131
}
140132
}
141133

142-
class EnumConstructor extends ConstructorDeclaration {
143-
private ClassLikeDeclaration enum;
144-
private Identifier id;
145-
146-
EnumConstructor() { any(TypeInferencePlugin p).isEnumConstructor(enum, this, id) }
147-
148-
ClassLikeDeclaration getEnum() { result = enum }
149-
150-
Identifier getNameNode() { result = id }
151-
}
152-
153-
class EnumField extends VariableDeclaration {
154-
private ClassLikeDeclaration enum;
155-
156-
EnumField() { any(TypeInferencePlugin p).isEnumField(enum, this) }
157-
158-
ClassLikeDeclaration getEnum() { result = enum }
159-
}
160-
161134
Type inferType(AstNode n, TypePath path) { result = any(TypeInferencePlugin p).inferType(n, path) }
162135

163136
Callable getFunctionInvoke(Type t) { result = any(TypeInferencePlugin p).getFunctionInvoke(t) }

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeInferencePluginSwift.qll‎

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,6 @@ private class EnumConstructor extends EnumCaseConstructor {
108108
Enum getEnum() { result = e }
109109
}
110110

111-
private class EnumField extends VariableDeclaration {
112-
private Enum e;
113-
114-
EnumField() {
115-
this = e.getAMember() and
116-
this.getAModifier().getValue() = "enum_case"
117-
}
118-
119-
Enum getEnum() { result = e }
120-
}
121-
122111
private predicate inferTypeAlias = inferType/2;
123112

124113
private class SwiftTypeInferencePlugin extends Plugin::TypeInferencePlugin {
@@ -169,20 +158,6 @@ private class SwiftTypeInferencePlugin extends Plugin::TypeInferencePlugin {
169158
field = tuple.(TupleType).getField(name)
170159
}
171160

172-
override predicate isEnumConstructor(
173-
ClassLikeDeclaration enum, ConstructorDeclaration case, Identifier i
174-
) {
175-
case =
176-
any(EnumConstructor ec |
177-
enum = ec.getEnum() and
178-
i = ec.getNameNode()
179-
)
180-
}
181-
182-
override predicate isEnumField(ClassLikeDeclaration enum, VariableDeclaration field) {
183-
enum = field.(EnumField).getEnum()
184-
}
185-
186161
override Type inferType(AstNode n, TypePath path) {
187162
// todo: approximation for now
188163
n instanceof BinaryExpr and

‎unified/ql/lib/codeql/unified/internal/typeinference/TypeMention.qll‎

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,11 @@ private class NonAliasExprTypeMention extends ExprTypeMentionImpl {
6363

6464
private Type getRootType0() {
6565
exists(Identifier i | i = getTypeFromRef(this) |
66-
// for now, we resolve enum cases to their enclosing class
67-
exists(EnumCaseClass c |
68-
i = c.getNameNode() and
69-
result.getNameNode() = c.getEnclosingClass().getNameNode()
70-
)
71-
or
72-
not i = any(EnumCaseClass c).getNameNode() and
7366
result.getNameNode() = i and
7467
(
7568
not result instanceof AssociatedTypeParameterType
7669
or
77-
result = TAssociatedTypeParameterType(this.getEnclosingClass(), _, _)
70+
result = TAssociatedTypeParameterType(this.getEnclosingClass().resolve(), _, _)
7871
)
7972
)
8073
or
@@ -148,6 +141,55 @@ private class NonAliasExprTypeMention extends ExprTypeMentionImpl {
148141
path = TypePath::cons(tt.getPositionalTypeParameter(i), suffix)
149142
)
150143
)
144+
or
145+
this =
146+
any(MemberAccessExpr mae |
147+
exists(TypePath suffix |
148+
result = mae.getBase().(TypeMention).getTypeAt(suffix) and
149+
path =
150+
TypePath::cons(TParentTypeParameterType(this.getRootType()
151+
.(ClassLikeDeclarationType)
152+
.getClassLikeDeclaration()), suffix)
153+
)
154+
)
155+
or
156+
exists(ClassLikeDeclarationType encl |
157+
encl = this.getRootType() and
158+
encl.getClassLikeDeclaration() = this.getEnclosingClass().resolve().getEnclosingClass*()
159+
|
160+
// A mention of a generic type without explicit type arguments and within
161+
// the scope of the type, implicitly mentions all type parameters (including
162+
// at the identifer that introduces the type):
163+
//
164+
// ```swift
165+
// class Generic<T> {
166+
// let x: Generic // short-hand for `Generic<T>
167+
// }
168+
// ```
169+
not this = any(GenericTypeExpr g).getBase() and
170+
result = encl.getATypeParameter() and
171+
not result instanceof ParentTypeParameterType and
172+
path = TypePath::singleton(result)
173+
or
174+
// A mention of a nested type without an explicit qualifier and within the
175+
// scope of the type, implicitly mentions the parent type (including
176+
// at the identifer that introduces the type)
177+
//
178+
// ```swift
179+
// class Outer {
180+
// class Inner {
181+
// let y: Inner // short-hand for `Outer.Inner`
182+
// }
183+
// }
184+
// ```
185+
not this = any(MemberAccessExpr mae).getMemberNameNode() and
186+
not this instanceof MemberAccessExpr and
187+
exists(ParentTypeParameterType tp, TypePath suffix |
188+
tp = TParentTypeParameterType(encl.getClassLikeDeclaration()) and
189+
path = TypePath::cons(tp, suffix) and
190+
result = tp.getParent().getNameNode().(TypeMention).getTypeAt(suffix)
191+
)
192+
)
151193
}
152194
}
153195

@@ -172,25 +214,6 @@ private class GenericTypeExprTypeMention extends TypeMentionImpl, GenericTypeExp
172214
}
173215
}
174216

175-
/** A class declaration mentions itself. */
176-
private class ClassLikeDeclarationTypeMention extends TypeMentionImpl, Identifier {
177-
private ClassLikeDeclaration c;
178-
179-
ClassLikeDeclarationTypeMention() { this = c.getNameNode() }
180-
181-
ClassLikeDeclarationType getRootType() { c = result.getClassLikeDeclaration() }
182-
183-
ClassLikeDeclaration getClassLikeDeclaration() { result = c }
184-
185-
override Type getTypeAt(TypePath path) {
186-
result = this.getRootType() and
187-
path.isEmpty()
188-
or
189-
result = this.getRootType().getATypeParameter() and
190-
path = TypePath::singleton(result)
191-
}
192-
}
193-
194217
TypeMention getClassLikeDeclarationTypeMention(ClassLikeDeclaration c) {
195218
result = c.getNameNode()
196219
or

0 commit comments

Comments
 (0)