Skip to content

Commit 5be6d86

Browse files
authored
Merge pull request #22796 from hvitved/unified/type-inference-constructors
Unified: Improve type inference for constructor calls
2 parents fb0ba8c + 781a8b1 commit 5be6d86

9 files changed

Lines changed: 722 additions & 150 deletions

File tree

‎shared/typeinference/codeql/typeinference/internal/TypeInference.qll‎

Lines changed: 133 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,19 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
26672667
*/
26682668
predicate stepLanguageSpecific(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2);
26692669

2670+
/**
2671+
* Like `stepLanguageSpecific`, but allows for recursion through type inference, which
2672+
* has the consequence that it cannot be used in the logic for inferring certain type
2673+
* information.
2674+
*
2675+
* Only use this predicate when using `stepLanguageSpecific` results in non-monotonicity.
2676+
*/
2677+
default predicate stepLanguageSpecificExtra(
2678+
AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2
2679+
) {
2680+
none()
2681+
}
2682+
26702683
/**
26712684
* Gets the inferred type of `n` at `path`.
26722685
*
@@ -2684,6 +2697,13 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
26842697
* in `inferTypeLanguageSpecific`; if in doubt, use `inferTypeLanguageSpecific` instead.
26852698
*/
26862699
default Type inferTypeCertainLanguageSpecific(AstNode n, TypePath path) { none() }
2700+
2701+
/**
2702+
* Holds if contextual inference is allowed for the node `n` at the type path
2703+
* `path`. This has the same effect as assigning `UnknownType` to `n` at `path`,
2704+
* but prevents propagation of `UnknownType` from `n` to other nodes.
2705+
*/
2706+
default predicate allowContextualInference(AstNode n, TypePath path) { none() }
26872707
}
26882708

26892709
module Make3<InputSig3 Input3> {
@@ -2893,6 +2913,8 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
28932913
private predicate step(AstNode n1, TypePath prefix1, AstNode n2, TypePath prefix2) {
28942914
stepLanguageSpecific(n1, prefix1, n2, prefix2)
28952915
or
2916+
stepLanguageSpecificExtra(n1, prefix1, n2, prefix2)
2917+
or
28962918
Certain::stepCertain(n1, prefix1, n2, prefix2)
28972919
or
28982920
prefix1.isEmpty() and
@@ -3234,70 +3256,97 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
32343256
* This module identifies calls where the return type may need to be inferred from the
32353257
* context, and also implements logic for performing contextual inference.
32363258
*/
3237-
private module ContextualTyping {
3238-
pragma[nomagic]
3239-
private TypeParameter getAConstrained(TypeParameter tp) {
3240-
result = getATypeParameterConstraint(tp).getTypeAt(_)
3241-
}
3242-
3259+
module ContextualTyping {
32433260
/**
3244-
* Holds if callable `c` mentions type parameter `tp` at some parameter,
3245-
* possibly via a constraint on another mentioned type parameter.
3261+
* Provides the input to `ContextualReturnType`.
32463262
*/
3247-
pragma[nomagic]
3248-
private predicate mentionsTypeParameterAtParameter(Callable c, TypeParameter tp) {
3249-
tp = getAConstrained*(c.getParameter(_).getType().getTypeAt(_))
3263+
signature module ContextualReturnTypeInputSig {
3264+
/** Holds if `c` has the type parameter `tp` at its return type at `path`. */
3265+
default predicate callableHasTypeParameterAtReturnType(
3266+
Callable c, TypePath path, TypeParameter tp
3267+
) {
3268+
tp = getCallableReturnType(c, path)
3269+
}
3270+
3271+
/** Gets a parameter of `c`. */
3272+
default Parameter getACallableParameter(Callable c) { result = c.getParameter(_) }
32503273
}
32513274

32523275
/**
3253-
* Holds if the return type of the callable `c` at `path` is type parameter
3254-
* `tp`, and `tp` does not appear in the type of any parameter of `c`.
3255-
*
3256-
* In this case, the context in which `p` is called may be needed to infer
3257-
* the instantiation of `tp`.
3258-
*
3259-
* This covers functions like `Default::default` and `Vec::new` in Rust.
3276+
* Provide logic for identifying calls where the return type may need to be inferred from the context.
32603277
*/
3261-
pragma[nomagic]
3262-
private predicate callableReturnContextTypedAt(Callable c, TypePath path, TypeParameter tp) {
3263-
tp = getCallableReturnType(c, path) and
3264-
not mentionsTypeParameterAtParameter(c, tp)
3265-
}
3278+
module ContextualReturnType<ContextualReturnTypeInputSig Input> {
3279+
pragma[nomagic]
3280+
private TypeParameter getAConstrained(TypeParameter tp) {
3281+
result = getATypeParameterConstraint(tp).getTypeAt(_)
3282+
}
32663283

3267-
bindingset[invocation, target]
3268-
pragma[inline_late]
3269-
private predicate hasTypeArgument(Invocation invocation, Callable target, TypeParameter tp) {
3270-
exists(Type t |
3271-
InvocationTypeQualifierMatching::typeMatch(invocation, _, _, _, t, tp) and
3272-
not t instanceof PseudoType
3273-
)
3274-
or
3275-
exists(InvocationMatching::getTypeArgument(invocation, target, tp, _))
3276-
}
3284+
/**
3285+
* Holds if callable `c` mentions type parameter `tp` at some parameter,
3286+
* possibly via a constraint on another mentioned type parameter.
3287+
*/
3288+
pragma[nomagic]
3289+
private predicate mentionsTypeParameterAtParameter(Callable c, TypeParameter tp) {
3290+
tp = getAConstrained*(Input::getACallableParameter(c).getType().getTypeAt(_))
3291+
}
32773292

3278-
/**
3279-
* Holds if `invocation` resolves to some target where the return type at `path`
3280-
* may have to be inferred from the context.
3281-
*/
3282-
pragma[nomagic]
3283-
predicate needsContextualTyping(Invocation invocation, TypePath path) {
3284-
exists(Callable target, TypeParameter tp |
3285-
target = invocation.getATargetForTypeQualifierMatching()
3293+
/**
3294+
* Holds if the return type of the callable `c` at `path` is type parameter
3295+
* `tp`, and `tp` does not appear in the type of any parameter of `c`.
3296+
*
3297+
* In this case, the context in which `p` is called may be needed to infer
3298+
* the instantiation of `tp`.
3299+
*
3300+
* This covers functions like `Default::default` and `Vec::new` in Rust.
3301+
*/
3302+
pragma[nomagic]
3303+
private predicate callableReturnContextTypedAt(Callable c, TypePath path, TypeParameter tp) {
3304+
Input::callableHasTypeParameterAtReturnType(c, path, tp) and
3305+
not mentionsTypeParameterAtParameter(c, tp)
3306+
}
3307+
3308+
bindingset[invocation, target]
3309+
pragma[inline_late]
3310+
private predicate hasTypeArgument(Invocation invocation, Callable target, TypeParameter tp) {
3311+
exists(Type t |
3312+
InvocationTypeQualifierMatching::typeMatch(invocation, _, _, _, t, tp) and
3313+
not t instanceof PseudoType
3314+
)
32863315
or
3287-
target = invocation.getTarget(_)
3288-
|
3289-
callableReturnContextTypedAt(target, path, tp) and
3290-
// check that no explicit type arguments have been supplied that bind `tp`
3291-
not exists(TypeParameter supplied |
3292-
tp = getAConstrained*(supplied) and
3293-
hasTypeArgument(invocation, target, supplied)
3316+
exists(InvocationMatching::getTypeArgument(invocation, target, tp, _))
3317+
}
3318+
3319+
/**
3320+
* Holds if `invocation` resolves to some target where the return type at `path`
3321+
* may have to be inferred from the context.
3322+
*/
3323+
pragma[nomagic]
3324+
predicate needsContextualTyping(Invocation invocation, TypePath path) {
3325+
exists(Callable target, TypeParameter tp |
3326+
target = invocation.getATargetForTypeQualifierMatching()
3327+
or
3328+
target = invocation.getTarget(_)
3329+
|
3330+
callableReturnContextTypedAt(target, path, tp) and
3331+
// check that no explicit type arguments have been supplied that bind `tp`
3332+
not exists(TypeParameter supplied |
3333+
tp = getAConstrained*(supplied) and
3334+
hasTypeArgument(invocation, target, supplied)
3335+
)
32943336
)
3295-
)
3337+
}
32963338
}
32973339

3340+
private module DefaultContextualReturnTypeInput implements ContextualReturnTypeInputSig { }
3341+
3342+
predicate needsContextualTyping =
3343+
ContextualReturnType<DefaultContextualReturnTypeInput>::needsContextualTyping/2;
3344+
32983345
pragma[nomagic]
32993346
predicate hasUnknownTypeAt(AstNode n, TypePath path) {
33003347
inferType(n, path) instanceof UnknownType
3348+
or
3349+
allowContextualInference(n, path)
33013350
}
33023351

33033352
pragma[nomagic]
@@ -3326,10 +3375,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
33263375
private Type inferTypeContextualCand1(AstNode n, TypePath prefix, TypePath path) {
33273376
result = inferTypeContextualCand0(n, path) and
33283377
hasUnknownType(n) and
3329-
prefix = path.getAPrefix() and
3330-
// no need to propagate `UnknownType`s contextually; `n` must already have an
3331-
// `UnknownType` at some prefix of `path`
3332-
not result instanceof UnknownType
3378+
prefix = path.getAPrefix()
33333379
}
33343380

33353381
pragma[nomagic]
@@ -3465,6 +3511,38 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
34653511
}
34663512
}
34673513

3514+
/**
3515+
* Holds if the type of `n` at `path` is `t` and the `j`th element of `path`
3516+
* corresponds to the `i`th type parameter of the unknown type.
3517+
*/
3518+
pragma[nomagic]
3519+
private predicate infersUnknownTypeArg0(AstNode n, int i, int j, TypePath path, Type t) {
3520+
exists(TypeParameter tp |
3521+
ContextualTyping::hasUnknownType(n) and
3522+
tp = any(UnknownType ut).getPositionalTypeParameter(i) and
3523+
t = inferType(n, path) and
3524+
not t instanceof UnknownType and
3525+
tp = path.getElement(j)
3526+
)
3527+
}
3528+
3529+
/**
3530+
* Holds if the type of `n` at `path` is `t`, where
3531+
* `path = prefix ++ tp_i ++ suffix` and `tp_i` is the `i`th type parameter
3532+
* of the unknown type.
3533+
*/
3534+
pragma[nomagic]
3535+
private predicate infersUnknownTypeArg1(
3536+
AstNode n, TypePath prefix, int i, TypePath suffix, Type t
3537+
) {
3538+
exists(TypePath path, int j, TypePath suffix0 |
3539+
infersUnknownTypeArg0(n, i, j, path, t) and
3540+
prefix = path.getProperPrefix(j) and
3541+
path = prefix.appendInverse(suffix0) and
3542+
suffix0.isCons(_, suffix)
3543+
)
3544+
}
3545+
34683546
/**
34693547
* Holds if `n` has unknown type at `prefix`, but is still able to
34703548
* infer a known type at `suffix` for the `i`th type parameter of whatever
@@ -3486,13 +3564,8 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
34863564
private predicate infersUnknownTypeArg(
34873565
AstNode n, TypePath prefix, int i, TypePath suffix, Type t
34883566
) {
3489-
exists(TypeParameter tp, TypePath suffix0 |
3490-
ContextualTyping::hasUnknownTypeAt(n, prefix) and
3491-
suffix0.isCons(tp, suffix) and
3492-
tp = any(UnknownType ut).getPositionalTypeParameter(i) and
3493-
t = inferType(n, prefix.appendInverse(suffix0)) and
3494-
not t instanceof UnknownType
3495-
)
3567+
ContextualTyping::hasUnknownTypeAt(n, prefix) and
3568+
infersUnknownTypeArg1(n, prefix, i, suffix, t)
34963569
}
34973570

34983571
pragma[nomagic]

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

0 commit comments

Comments
 (0)