Skip to content

Commit fee52f9

Browse files
committed
Unified: Type inference for nested types
1 parent 9b72529 commit fee52f9

17 files changed

Lines changed: 1108 additions & 135 deletions

‎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: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ 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 context where it must refer to a type,
7+
* and where omission of type arguments implicitly refer to the type
8+
* parameters of the type being accessed (provided that they are in scope).
79
*/
8-
predicate isInTypeContext(Expr expr) {
10+
predicate isInTypeContextWithImplicitTypeArgs(Expr expr) {
911
expr = any(TypeCastExpr n).getType()
1012
or
1113
expr = any(TypeTestExpr n).getType()
@@ -36,11 +38,26 @@ predicate isInTypeContext(Expr expr) {
3638
or
3739
expr.getParent() instanceof TypeConstraint
3840
or
41+
any(ExprPositionsPlugin p).isInTypeContextWithImplicitTypeArgs(expr)
42+
or
43+
isInTypeContextWithImplicitTypeArgs(expr.getEnclosingExpr())
44+
}
45+
46+
private predicate isInTypeContextWithoutImplicitTypeArgs(Expr expr) {
3947
exists(NameBinding::getTypeFromRef(expr))
4048
or
4149
any(ExprPositionsPlugin p).isInTypeContext(expr)
4250
or
43-
isInTypeContext(expr.getEnclosingExpr())
51+
isInTypeContextWithoutImplicitTypeArgs(expr.getEnclosingExpr())
52+
}
53+
54+
/**
55+
* Holds if `expr` appears in a context where it must refer to a type.
56+
*/
57+
predicate isInTypeContext(Expr expr) {
58+
isInTypeContextWithImplicitTypeArgs(expr)
59+
or
60+
isInTypeContextWithoutImplicitTypeArgs(expr)
4461
}
4562

4663
/** Holds if `e` appears in a name-binding position inside `declaration` */

‎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/FacadeAst.qll‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ module Unified {
5050
)
5151
}
5252

53+
/**
54+
* Gets the nearest resolved enclosing class declaration, if any.
55+
*/
56+
ClassLikeDeclaration getEnclosingClassResolved() { result = this.getEnclosingClass().resolve() }
57+
5358
private AstNode overrideEnclosingCallableParent() {
5459
exists(FunctionExpr func |
5560
// Capture declarations are evaluated as part of the outer context, and

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,21 @@ Identifier getTypeFromRef(AstNode access) {
826826
)
827827
}
828828

829+
/**
830+
* Gets the type being accessed by `access`, where type aliases are resolved, if any.
831+
*/
832+
Identifier getTypeFromRefAliasesResolved(AstNode access) {
833+
exists(Identifier i | i = getTypeFromRef(access) |
834+
not i = any(TypeAliasDeclaration t).getNameNode() and
835+
result = i
836+
or
837+
exists(TypeAliasDeclaration alias |
838+
i = alias.getNameNode() and
839+
result = getTypeFromRefAliasesResolved(alias.getType())
840+
)
841+
)
842+
}
843+
829844
/**
830845
* Gets the name of the implicit receiver parameter in scope at `callable` (possibly declared by an outer callable).
831846
*

‎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/TypeAbstraction.qll‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ abstract class TypeAbstraction extends AstNode {
1515
*/
1616
private class ClassLikeDeclarationTypeAbstraction extends TypeAbstraction instanceof Unified::ClassLikeDeclaration
1717
{
18-
override TypeParameter getATypeParameter() { this = result.getDeclaringItem() }
18+
override TypeParameter getATypeParameter() {
19+
// type parameters from outer classes are also in scope
20+
super.resolve().getEnclosingClassResolved*() = result.getDeclaringItem()
21+
}
1922
}

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

Lines changed: 42 additions & 22 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

@@ -492,15 +496,17 @@ module Input3 implements InputSig3 {
492496
}
493497

494498
Callable getTargetViaStaticNameBinding() {
495-
exists(NameBinding b | b = getStaticBindingTargetFromRef(this.getCallee()) |
499+
exists(NameBinding b |
496500
// object creation (including enum constructors): `String(42)`, `Optional.Some(42)`
501+
b = getTypeFromRefAliasesResolved(this.getCallee()) and
497502
exists(ClassLikeDeclaration cls |
498503
cls.getNameNode() = b and
499504
result.isInstanceMemberOf(cls, _) and
500505
result.asCallable() instanceof ConstructorDeclaration
501506
)
502507
or
503508
// call without explicit receiver: `foo(42)`
509+
b = getStaticBindingTargetFromRef(this.getCallee()) and
504510
result.getNameNode() = b
505511
)
506512
}
@@ -659,10 +665,20 @@ module Input3 implements InputSig3 {
659665
result = getQualifierTypeMention(invocation)
660666
}
661667

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)
668+
private predicate constructionReceiver(
669+
Invocation i, Callable constructor, Expr receiver, TypePath enumPrefixPath
670+
) {
671+
exists(ConstructorDeclaration cd |
672+
constructor = i.getTargetImpl(_) and
673+
cd = constructor.asCallable() and
674+
receiver = i.getArgument(0)
675+
|
676+
enumPrefixPath =
677+
TypePath::singleton(TParentTypeParameterType(cd.(EnumCaseConstructor).getEnumCaseClass()))
678+
or
679+
not cd instanceof EnumCaseConstructor and
680+
enumPrefixPath.isEmpty()
681+
)
666682
}
667683

668684
/**
@@ -696,11 +712,15 @@ module Input3 implements InputSig3 {
696712
M3::ContextualTyping::ContextualReturnType<ContextualConstructorReturnTypeInput>;
697713

698714
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)
715+
exists(
716+
Callable constructor, Expr receiver, TypePath enumPrefixPath,
717+
ClassLikeDeclarationType receiverType, TypePath path0
718+
|
719+
constructionReceiver(i, constructor, receiver, enumPrefixPath) and
720+
receiverType = inferType(receiver) and
721+
path = path0.stripPrefix(enumPrefixPath)
702722
|
703-
ContextualConstructorReturnType::needsContextualTyping(i, path) and
723+
ContextualConstructorReturnType::needsContextualTyping(i, path0) and
704724
receiverType = constructor.getDeclaringType().getType()
705725
or
706726
// inherited constructor
@@ -710,18 +730,19 @@ module Input3 implements InputSig3 {
710730
|
711731
declType = constructor.getDeclaringType().getType() and
712732
BaseTypes::rootTypesSatisfaction(receiverType, declType, abs, _, constraint) and
713-
path = TypePath::singleton(tp)
733+
tp =
734+
getClassLikeDeclarationTypeMention(receiverType.getClassLikeDeclaration())
735+
.getTypeAt(path0)
714736
|
715737
// `A<T> : B<T>`
716-
exists(TypePath path0 |
717-
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path0, tp) and
718-
ContextualConstructorReturnType::needsContextualTyping(i, path0)
738+
exists(TypePath path1 |
739+
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path1, tp) and
740+
ContextualConstructorReturnType::needsContextualTyping(i, path1)
719741
)
720742
or
721743
// `A<T> : B`
722-
tp = receiverType.getATypeParameter() and
723744
not BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, _, tp) and
724-
not exists(Type t | t = i.getTypeQualifier(path) and not t instanceof PseudoType)
745+
not exists(Type t | t = i.getTypeQualifier(path0) and not t instanceof PseudoType)
725746
)
726747
)
727748
}
@@ -734,8 +755,7 @@ module Input3 implements InputSig3 {
734755
* as well as calls receivers, `self.init(0)` or `super.init(0)`.
735756
*/
736757
predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
737-
constructionReceiver(n2, _, n1) and
738-
prefix1.isEmpty() and
758+
constructionReceiver(n2, _, n1, prefix1) and
739759
prefix2.isEmpty()
740760
}
741761

‎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) }

0 commit comments

Comments
 (0)