Skip to content

Commit 4e45e79

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

18 files changed

Lines changed: 1424 additions & 132 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: 8 additions & 1 deletion
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
@@ -240,14 +245,16 @@ module Unified {
240245
string getName() { result = this.getNameNode().getValue() }
241246

242247
/** Gets a direct base class of this class. */
248+
pragma[nomagic]
243249
ClassLikeDeclaration getABaseClass() {
244250
result.getNameNode() =
245-
NameBinding::getStaticBindingTargetFromRef(this.getABaseType().getType())
251+
NameBinding::getTypeFromRefAliasesResolved(this.getABaseType().getType())
246252
}
247253

248254
/**
249255
* Gets the target of this extension class, or this class itself if it is not an extension.
250256
*/
257+
pragma[nomagic]
251258
ClassLikeDeclaration resolve() {
252259
not exists(this.getExtensionTarget()) and
253260
result = this

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,22 @@ 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+
pragma[nomagic]
833+
Identifier getTypeFromRefAliasesResolved(AstNode access) {
834+
exists(Identifier i | i = getTypeFromRef(access) |
835+
not i = any(TypeAliasDeclaration t).getNameNode() and
836+
result = i
837+
or
838+
exists(TypeAliasDeclaration alias |
839+
i = alias.getNameNode() and
840+
result = getTypeFromRefAliasesResolved(alias.getType())
841+
)
842+
)
843+
}
844+
829845
/**
830846
* Gets the name of the implicit receiver parameter in scope at `callable` (possibly declared by an outer callable).
831847
*

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import TypeInference::Consistency
1010

1111
query predicate illFormedTypeMention(TypeMention tm) {
1212
Consistency::illFormedTypeMention(tm) and
13+
not tm.(AliasExprTypeMention).isTypeParameterAlias(_) and
1314
tm.fromSource()
1415
}
1516

0 commit comments

Comments
 (0)