Skip to content

Commit b3e63f8

Browse files
committed
Unified: Improve type inference for constructor calls
1 parent bec8c3e commit b3e63f8

8 files changed

Lines changed: 356 additions & 63 deletions

File tree

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,7 @@ predicate isInTypeContext(Expr expr) {
3636
or
3737
expr.getParent() instanceof TypeConstraint
3838
or
39-
exists(Identifier id | id = NameBinding::getStaticBindingTargetFromRef(expr) |
40-
id = any(ClassLikeDeclaration c).getNameNode()
41-
or
42-
id = any(TypeAliasDeclaration t).getNameNode()
43-
or
44-
id = any(TypeParameter t).getNameNode()
45-
)
39+
exists(NameBinding::getTypeFromRef(expr))
4640
or
4741
any(ExprPositionsPlugin p).isInTypeContext(expr)
4842
or

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,22 @@ NameBinding getStaticBindingTargetFromRef(AstNode access) {
810810
result = getStaticBindingTargetFromIdentifier(getIdentifierFromRef(access))
811811
}
812812

813+
/**
814+
* Gets the type being accessed by `access`, if any.
815+
*/
816+
Identifier getTypeFromRef(AstNode access) {
817+
result = getStaticBindingTargetFromRef(access) and
818+
(
819+
result = any(ClassLikeDeclaration c | not c instanceof EnumCaseClass).getNameNode()
820+
or
821+
result = any(TypeAliasDeclaration t).getNameNode()
822+
or
823+
result = any(TypeParameter t).getNameNode()
824+
or
825+
result = any(AssociatedTypeDeclaration t).getNameNode()
826+
)
827+
}
828+
813829
/**
814830
* Gets the name of the implicit receiver parameter in scope at `callable` (possibly declared by an outer callable).
815831
*

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ abstract private class TypeImpl extends TType {
8080
*/
8181
TypeParameter getATypeParameter() { result = this.getPositionalTypeParameter(_) }
8282

83+
abstract Identifier getNameNode();
84+
8385
/** Gets a textual representation of this type. */
8486
abstract string toString();
8587

@@ -99,6 +101,8 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
99101

100102
string getName() { result = c.getName() }
101103

104+
override Identifier getNameNode() { result = c.getNameNode() }
105+
102106
override TypeParameter getPositionalTypeParameter(int i) {
103107
result = TTypeParameterType(c.getTypeParameter(i))
104108
}
@@ -117,7 +121,9 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
117121
/**
118122
* A pseudo type that does not correspond to any concrete type in the source code.
119123
*/
120-
abstract class PseudoType extends TypeImpl { }
124+
abstract class PseudoType extends TypeImpl {
125+
override Identifier getNameNode() { none() }
126+
}
121127

122128
/**
123129
* A type representing an unknown type.
@@ -173,6 +179,8 @@ class TypeParameterType extends TypeParameter, TTypeParameterType {
173179

174180
override ClassLikeDeclaration getDeclaringItem() { typeParam = result.getATypeParameter() }
175181

182+
override Identifier getNameNode() { result = typeParam.getNameNode() }
183+
176184
override string toString() { result = typeParam.getName() }
177185

178186
override Location getLocation() { result = typeParam.getLocation() }
@@ -192,6 +200,8 @@ class AssociatedTypeParameterType extends TypeParameter, TAssociatedTypeParamete
192200

193201
override ClassLikeDeclaration getDeclaringItem() { result = c }
194202

203+
override Identifier getNameNode() { result = assocTypeDecl.getNameNode() }
204+
195205
override string toString() {
196206
if inherited = true
197207
then result = assocTypeDecl.getName() + " (inherited)"
@@ -213,6 +223,8 @@ class UnknownTypeTypeParameter extends TypeParameter, TUnknownTypeTypeParameter
213223

214224
override AstNode getDeclaringItem() { none() }
215225

226+
override Identifier getNameNode() { none() }
227+
216228
override TypeParameter getPositionalTypeParameter(int j) { none() }
217229

218230
override string toString() { result = "unknown type parameter " + i }

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

Lines changed: 174 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,11 @@ module Input3 implements InputSig3 {
135135
class AstNode = Unified::AstNode;
136136

137137
class Expr extends Unified::Expr {
138-
Expr() { isInAssignmentContext(this, _) or hasResultValue(this) }
138+
Expr() {
139+
isInAssignmentContext(this, _) or
140+
hasResultValue(this) or
141+
this = Construction::getSyntheticConstructionReceiver(_)
142+
}
139143
}
140144

141145
class Cast extends Expr, TypeCastExpr {
@@ -270,6 +274,7 @@ module Input3 implements InputSig3 {
270274

271275
pragma[nomagic]
272276
private predicate lookupMember0(MemberAccessExpr mae, ClassLikeDeclaration cls, string name) {
277+
not exists(getStaticBindingTargetFromRef(mae)) and // if a static member can be resolved, use it
273278
cls = inferType(mae.getBase()).(ClassLikeDeclarationType).getClassLikeDeclaration() and
274279
name = mae.getMemberName()
275280
}
@@ -393,12 +398,7 @@ module Input3 implements InputSig3 {
393398
)
394399
}
395400

396-
override TypeMention getType() {
397-
result = c.getReturnType()
398-
or
399-
c instanceof ConstructorDeclaration and
400-
result = this.getDeclaringType()
401-
}
401+
override TypeMention getType() { result = c.getReturnType() }
402402

403403
override Identifier getNameNode() {
404404
result = c.(AccessorDeclaration).getNameNode()
@@ -456,26 +456,29 @@ module Input3 implements InputSig3 {
456456

457457
class InvocationResolutionContext = Unit;
458458

459+
private TypeMention getQualifierTypeMention(CallExpr ce) {
460+
result = ce.getCallee()
461+
or
462+
not ce.getCallee() instanceof TypeMention and
463+
result = ce.getCallee().(MemberAccessExpr).getBase()
464+
}
465+
459466
class Invocation extends Expr, CallExpr {
460-
Type getTypeQualifier(TypePath path) {
461-
exists(TypeMention tm | result = tm.getTypeAt(path) |
462-
tm = super.getCallee()
463-
or
464-
not super.getCallee() instanceof TypeMention and
465-
tm = super.getCallee().(MemberAccessExpr).getBase()
466-
)
467-
}
467+
Type getTypeQualifier(TypePath path) { result = getQualifierTypeMention(this).getTypeAt(path) }
468468

469469
Type getTypeArgument(int i, TypePath path) { none() }
470470

471-
int getNumberOfArguments() { result = CallExpr.super.getNumberOfArguments() + 1 }
472-
473471
Expr getArgument(int i) {
474472
i = 0 and
475-
exists(boolean isFunctionExprInvoke | exists(this.getTargetImpl(isFunctionExprInvoke)) |
476-
if isFunctionExprInvoke = true
477-
then result = CallExpr.super.getCallee()
478-
else result = CallExpr.super.getCallee().(MemberAccessExpr).getBase()
473+
(
474+
result = Construction::getSyntheticConstructionReceiver(this)
475+
or
476+
not exists(Construction::getSyntheticConstructionReceiver(this)) and
477+
exists(boolean isFunctionExprInvoke | exists(this.getTargetImpl(isFunctionExprInvoke)) |
478+
if isFunctionExprInvoke = true
479+
then result = CallExpr.super.getCallee()
480+
else result = CallExpr.super.getCallee().(MemberAccessExpr).getBase()
481+
)
479482
)
480483
or
481484
result = CallExpr.super.getArgument(i - 1).getValue()
@@ -488,13 +491,13 @@ module Input3 implements InputSig3 {
488491
result = getFunctionInvoke(t)
489492
}
490493

491-
private Callable getTargetViaStaticNameBinding() {
494+
Callable getTargetViaStaticNameBinding() {
492495
exists(NameBinding b | b = getStaticBindingTargetFromRef(this.getCallee()) |
493496
// object creation (including enum constructors): `String(42)`, `Optional.Some(42)`
494-
exists(ClassLikeDeclaration cls, ConstructorDeclaration init |
497+
exists(ClassLikeDeclaration cls |
495498
cls.getNameNode() = b and
496-
init = cls.getAMember() and
497-
result.asCallable() = init
499+
result.isInstanceMemberOf(cls, _) and
500+
result.asCallable() instanceof ConstructorDeclaration
498501
)
499502
or
500503
// call without explicit receiver: `foo(42)`
@@ -535,13 +538,13 @@ module Input3 implements InputSig3 {
535538
result = inferType(invocation.(CallExpr).getCallee(), path)
536539
or
537540
exists(TypePath prefix, TypePath suffix, int j |
538-
functionInvokeSignature(t, invocation.getNumberOfArguments() - 1, j, i, prefix) and
541+
functionInvokeSignature(t, invocation.getNumberOfArguments(), j, i, prefix) and
539542
result = inferType(invocation.getArgument(j + 1), suffix) and
540543
path = prefix.append(suffix)
541544
)
542545
)
543546
or
544-
// A call to a regular function or method
547+
// A non-function-expression call
545548
exists(invocation.getTargetImpl(false)) and
546549
(
547550
result = inferType(invocation.getArgument(i), path)
@@ -612,6 +615,144 @@ module Input3 implements InputSig3 {
612615
)
613616
}
614617

618+
predicate stepLanguageSpecificExtra(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
619+
Construction::step(n1, prefix1, n2, prefix2)
620+
}
621+
622+
/**
623+
* Provides logic for resolving types of constructor invocations.
624+
*
625+
* Constructors are treated as instance methods without return types, in order
626+
* to be able to handle inherited constructors similar to ordinary instance
627+
* methods:
628+
*
629+
* ```swift
630+
* class A<Ta> {
631+
* init(x : Ta) { ... }
632+
* }
633+
*
634+
* class B<Tb> : A<Tb?> { }
635+
*
636+
* A.init(0); // (1) has type `A<Int>`
637+
* B.init(.some(0)); // (2) has type `B<Int>`
638+
* B<Int>(.none); // (3) has type `B<Int>`
639+
* ```
640+
*
641+
* Because the constructor calls are treated as normal instance method calls,
642+
* we use the type qualifier to represent the receiver, and then allow for
643+
* contextual type information to flow into the receiver:
644+
*
645+
* (1) The receiver has type `A`, `Ta` is inferred to be instantiated to `Int`,
646+
* which is propagated directly back into the receiver.
647+
* (2) The receiver has type `B`, `Ta` is inferred to be instantiated to `Int?`
648+
* which in turn means `Tb` is inferred to be instantiated to `Int`, which
649+
* is then propagated back into the receiver.
650+
* (3) The receiver has type `B<Int>`, and no further type inference is needed.
651+
*/
652+
private module Construction {
653+
/**
654+
* Gets the AST node that is used to represent the receiver in a constructor
655+
* invocation without an actual receiver.
656+
*/
657+
TypeMention getSyntheticConstructionReceiver(Invocation invocation) {
658+
invocation.getTargetViaStaticNameBinding().asCallable() instanceof ConstructorDeclaration and
659+
result = getQualifierTypeMention(invocation)
660+
}
661+
662+
private predicate constructionReceiver(Invocation i, ConstructorDeclaration cd, Expr receiver) {
663+
cd = i.getTargetImpl(_).asCallable() and
664+
receiver = i.getArgument(0)
665+
}
666+
667+
/**
668+
* Since we handle constructors as instance methods without return types,
669+
* we need to explicitly handle constructors where (parts of) the type of
670+
* the constructed object must be inferred from the context.
671+
*
672+
* Example:
673+
*
674+
* ```swift
675+
* class C<T1, T2> {
676+
* init(x : T1) { ... } // `T2` must be inferred from the context
677+
* }
678+
* ```
679+
*/
680+
private module ContextualConstructorReturnTypeInput implements
681+
M3::ContextualTyping::ContextualReturnTypeInputSig
682+
{
683+
predicate callableHasTypeParameterAtReturnType(Callable c, TypePath path, TypeParameter tp) {
684+
c.asCallable() instanceof ConstructorDeclaration and
685+
tp = c.getDeclaringType().getTypeAt(path)
686+
}
687+
688+
Parameter getACallableParameter(Callable c) {
689+
result = c.getParameter(_) and
690+
not result.getParameterEx().isImplicitReceiverParameter(_)
691+
}
692+
}
693+
694+
private module ContextualConstructorReturnType =
695+
M3::ContextualTyping::ContextualReturnType<ContextualConstructorReturnTypeInput>;
696+
697+
private predicate needsContextualTyping(Invocation i, TypePath path) {
698+
exists(
699+
ConstructorDeclaration cd, Expr receiver, ClassLikeDeclaration declType, TypePath path0
700+
|
701+
constructionReceiver(i, cd, receiver) and
702+
declType = inferType(receiver).(ClassLikeDeclarationType).getClassLikeDeclaration() and
703+
ContextualConstructorReturnType::needsContextualTyping(i, path0)
704+
|
705+
declType.getAMember() = cd and
706+
path = path0
707+
or
708+
// inherited constructor
709+
exists(TypeAbstraction abs, TypeMention constraint, TypeParameter tp |
710+
BaseTypes::rootTypesSatisfaction(TClassLikeDeclarationType(declType), _, abs, _,
711+
constraint) and
712+
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path0, tp) and
713+
path = TypePath::singleton(tp)
714+
)
715+
)
716+
}
717+
718+
/**
719+
* Propagate type information from the receiver of a construction to the construction
720+
* itself.
721+
*
722+
* Note that this works both for calls with synthetic receivers, `A.init(0)` or `A(0)`,
723+
* as well as calls receivers, `self.init(0)` or `super.init(0)`.
724+
*/
725+
predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
726+
constructionReceiver(n2, _, n1) and
727+
prefix1.isEmpty() and
728+
prefix2.isEmpty()
729+
}
730+
731+
Type inferType(AstNode n, TypePath path) {
732+
n =
733+
any(TypeMention tm |
734+
tm = getSyntheticConstructionReceiver(_) and
735+
result = tm.getTypeAt(path)
736+
)
737+
or
738+
needsContextualTyping(n, path) and
739+
result instanceof UnknownType
740+
}
741+
742+
predicate allowFullContextualInference(TypeMention tm, TypePath path) {
743+
// For a construction such as `Optional.some(0)`, we need contextual inference
744+
// to infer the type of the wrapped object
745+
tm = getSyntheticConstructionReceiver(_) and
746+
exists(TypePath prefix, Type t, TypeParameter tp |
747+
t = tm.getTypeAt(prefix) and
748+
not t instanceof UnknownType and
749+
tp = t.getATypeParameter() and
750+
path = TypePath::snoc(prefix, tp) and
751+
not exists(tm.getTypeAt(path))
752+
)
753+
}
754+
}
755+
615756
Type inferTypeLanguageSpecific(AstNode n, TypePath path) {
616757
result = Plugin::inferType(n, path)
617758
or
@@ -650,10 +791,16 @@ module Input3 implements InputSig3 {
650791
n.(ArrayLiteral).getNumberOfElements() = 0 and
651792
path = TypePath::singleton(getArrayElementTypeParameter()) and
652793
result instanceof UnknownType
794+
or
795+
result = Construction::inferType(n, path)
653796
}
654797

655798
pragma[nomagic]
656799
Type inferTypeCertainLanguageSpecific(AstNode n, TypePath path) { none() }
800+
801+
predicate allowFullContextualInference(AstNode n, TypePath path) {
802+
Construction::allowFullContextualInference(n, path)
803+
}
657804
}
658805

659806
private module M3 = Make3<Input3>;

0 commit comments

Comments
 (0)