Skip to content

Commit a5037ad

Browse files
committed
Unified: Improve type inference for constructor calls
1 parent 835fa35 commit a5037ad

8 files changed

Lines changed: 398 additions & 66 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).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: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ abstract private class TypeImpl extends TType {
8080
*/
8181
TypeParameter getATypeParameter() { result = this.getPositionalTypeParameter(_) }
8282

83+
/** Gets the name node of this type, if any. */
84+
abstract Identifier getNameNode();
85+
8386
/** Gets a textual representation of this type. */
8487
abstract string toString();
8588

@@ -99,6 +102,8 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
99102

100103
string getName() { result = c.getName() }
101104

105+
override Identifier getNameNode() { result = c.getNameNode() }
106+
102107
override TypeParameter getPositionalTypeParameter(int i) {
103108
result = TTypeParameterType(c.getTypeParameter(i))
104109
}
@@ -117,7 +122,9 @@ class ClassLikeDeclarationType extends TypeImpl, TClassLikeDeclarationType {
117122
/**
118123
* A pseudo type that does not correspond to any concrete type in the source code.
119124
*/
120-
abstract class PseudoType extends TypeImpl { }
125+
abstract class PseudoType extends TypeImpl {
126+
override Identifier getNameNode() { none() }
127+
}
121128

122129
/**
123130
* A type representing an unknown type.
@@ -173,6 +180,8 @@ class TypeParameterType extends TypeParameter, TTypeParameterType {
173180

174181
override ClassLikeDeclaration getDeclaringItem() { typeParam = result.getATypeParameter() }
175182

183+
override Identifier getNameNode() { result = typeParam.getNameNode() }
184+
176185
override string toString() { result = typeParam.getName() }
177186

178187
override Location getLocation() { result = typeParam.getLocation() }
@@ -192,6 +201,8 @@ class AssociatedTypeParameterType extends TypeParameter, TAssociatedTypeParamete
192201

193202
override ClassLikeDeclaration getDeclaringItem() { result = c }
194203

204+
override Identifier getNameNode() { result = assocTypeDecl.getNameNode() }
205+
195206
override string toString() {
196207
if inherited = true
197208
then result = assocTypeDecl.getName() + " (inherited)"
@@ -213,6 +224,8 @@ class UnknownTypeTypeParameter extends TypeParameter, TUnknownTypeTypeParameter
213224

214225
override AstNode getDeclaringItem() { none() }
215226

227+
override Identifier getNameNode() { none() }
228+
216229
override TypeParameter getPositionalTypeParameter(int j) { none() }
217230

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

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

Lines changed: 184 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,154 @@ 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, Callable constructor, Expr receiver) {
663+
constructor = i.getTargetImpl(_) and
664+
constructor.asCallable() instanceof ConstructorDeclaration and
665+
receiver = i.getArgument(0)
666+
}
667+
668+
/**
669+
* Since we handle constructors as instance methods without return types,
670+
* we need to explicitly handle constructors where (parts of) the type of
671+
* the constructed object must be inferred from the context.
672+
*
673+
* Example:
674+
*
675+
* ```swift
676+
* class C<T1, T2> {
677+
* init(x : T1) { ... } // `T2` must be inferred from the context
678+
* }
679+
* ```
680+
*/
681+
private module ContextualConstructorReturnTypeInput implements
682+
M3::ContextualTyping::ContextualReturnTypeInputSig
683+
{
684+
predicate callableHasTypeParameterAtReturnType(Callable c, TypePath path, TypeParameter tp) {
685+
c.asCallable() instanceof ConstructorDeclaration and
686+
tp = c.getDeclaringType().getTypeAt(path)
687+
}
688+
689+
Parameter getACallableParameter(Callable c) {
690+
result = c.getParameter(_) and
691+
not result.getParameterEx().isImplicitReceiverParameter(_)
692+
}
693+
}
694+
695+
private module ContextualConstructorReturnType =
696+
M3::ContextualTyping::ContextualReturnType<ContextualConstructorReturnTypeInput>;
697+
698+
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)
702+
|
703+
ContextualConstructorReturnType::needsContextualTyping(i, path) and
704+
receiverType = constructor.getDeclaringType().getType()
705+
or
706+
// inherited constructor
707+
exists(
708+
ClassLikeDeclarationType declType, TypeAbstraction abs, TypeMention constraint,
709+
TypeParameter tp
710+
|
711+
declType = constructor.getDeclaringType().getType() and
712+
BaseTypes::rootTypesSatisfaction(receiverType, declType, abs, _, constraint) and
713+
path = TypePath::singleton(tp)
714+
|
715+
// `A<T> : B<T>`
716+
exists(TypePath path0 |
717+
BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, path0, tp) and
718+
ContextualConstructorReturnType::needsContextualTyping(i, path0)
719+
)
720+
or
721+
// `A<T> : B`
722+
tp = receiverType.getATypeParameter() and
723+
not BaseTypes::conditionSatisfiesConstraintTypeAt(abs, _, constraint, _, tp)
724+
)
725+
)
726+
}
727+
728+
/**
729+
* Propagate type information from the receiver of a construction to the construction
730+
* itself.
731+
*
732+
* Note that this works both for calls with synthetic receivers, `A.init(0)` or `A(0)`,
733+
* as well as calls receivers, `self.init(0)` or `super.init(0)`.
734+
*/
735+
predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
736+
constructionReceiver(n2, _, n1) and
737+
prefix1.isEmpty() and
738+
prefix2.isEmpty()
739+
}
740+
741+
Type inferType(AstNode n, TypePath path) {
742+
n =
743+
any(TypeMention tm |
744+
tm = getSyntheticConstructionReceiver(_) and
745+
result = tm.getTypeAt(path)
746+
)
747+
or
748+
needsContextualTyping(n, path) and
749+
result instanceof UnknownType
750+
}
751+
752+
predicate allowFullContextualInference(TypeMention tm, TypePath path) {
753+
// For a construction such as `Optional.some(0)`, we need contextual inference
754+
// to infer the type of the wrapped object
755+
tm = getSyntheticConstructionReceiver(_) and
756+
exists(TypePath prefix, Type t, TypeParameter tp |
757+
t = tm.getTypeAt(prefix) and
758+
not t instanceof UnknownType and
759+
tp = t.getATypeParameter() and
760+
path = TypePath::snoc(prefix, tp) and
761+
not exists(tm.getTypeAt(path))
762+
)
763+
}
764+
}
765+
615766
Type inferTypeLanguageSpecific(AstNode n, TypePath path) {
616767
result = Plugin::inferType(n, path)
617768
or
@@ -650,10 +801,16 @@ module Input3 implements InputSig3 {
650801
n.(ArrayLiteral).getNumberOfElements() = 0 and
651802
path = TypePath::singleton(getArrayElementTypeParameter()) and
652803
result instanceof UnknownType
804+
or
805+
result = Construction::inferType(n, path)
653806
}
654807

655808
pragma[nomagic]
656809
Type inferTypeCertainLanguageSpecific(AstNode n, TypePath path) { none() }
810+
811+
predicate allowFullContextualInference(AstNode n, TypePath path) {
812+
Construction::allowFullContextualInference(n, path)
813+
}
657814
}
658815

659816
private module M3 = Make3<Input3>;

0 commit comments

Comments
 (0)