Skip to content

Commit e55b569

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

13 files changed

Lines changed: 687 additions & 121 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/ExprPositions.qll‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ private import NameBinding as NameBinding
33
private import ExprPositionsPlugin
44

55
/**
6-
* Holds if `expr` appears in a context where it must refer to a type.
6+
* Holds if `expr` appears in a type annotation, where omission of type
7+
* arguments implicitly refer to the type parameters of the type.
78
*/
8-
predicate isInTypeContext(Expr expr) {
9+
predicate isInTypeContextWithImplicitTypeArgs(Expr expr) {
910
expr = any(TypeCastExpr n).getType()
1011
or
1112
expr = any(TypeTestExpr n).getType()
@@ -36,6 +37,17 @@ predicate isInTypeContext(Expr expr) {
3637
or
3738
expr.getParent() instanceof TypeConstraint
3839
or
40+
any(ExprPositionsPlugin p).isInTypeContextWithImplicitTypeArgs(expr)
41+
or
42+
isInTypeContextWithImplicitTypeArgs(expr.getEnclosingExpr())
43+
}
44+
45+
/**
46+
* Holds if `expr` appears in a context where it must refer to a type.
47+
*/
48+
predicate isInTypeContext(Expr expr) {
49+
isInTypeContextWithImplicitTypeArgs(expr)
50+
or
3951
exists(NameBinding::getTypeFromRef(expr))
4052
or
4153
any(ExprPositionsPlugin p).isInTypeContext(expr)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ private module Plugins {
77

88
class ExprPositionsPlugin extends Unit {
99
predicate isInTypeContext(Expr e) { none() }
10+
11+
predicate isInTypeContextWithImplicitTypeArgs(Expr e) { none() }
1012
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ private import unified
22
private import ExprPositionsPlugin
33

44
private class ExprPositionsPluginSwift extends ExprPositionsPlugin {
5-
override predicate isInTypeContext(Expr e) { e = any(GenericTypeExpr g).getBase() }
5+
override predicate isInTypeContextWithImplicitTypeArgs(Expr e) {
6+
e = any(GenericTypeExpr g).getBase()
7+
}
68
}

‎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

0 commit comments

Comments
 (0)