Rust: Disambiguate types inferred from trait bounds

This commit is contained in:
Tom Hvitved
2026-03-17 20:02:01 +01:00
parent 98d8cd1d6d
commit 7fc1d53ede
7 changed files with 469 additions and 171 deletions

View File

@@ -13,68 +13,105 @@ private import TypeMention
private import TypeInference
private import FunctionType
pragma[nomagic]
private Type resolveNonTypeParameterTypeAt(TypeMention tm, TypePath path) {
result = tm.getTypeAt(path) and
not result instanceof TypeParameter
}
bindingset[t1, t2]
private predicate typeMentionEqual(TypeMention t1, TypeMention t2) {
forex(TypePath path, Type type | resolveNonTypeParameterTypeAt(t1, path) = type |
resolveNonTypeParameterTypeAt(t2, path) = type
)
}
pragma[nomagic]
private predicate implSiblingCandidate(
Impl impl, TraitItemNode trait, Type rootType, TypeMention selfTy
) {
trait = impl.(ImplItemNode).resolveTraitTy() and
selfTy = impl.getSelfTy() and
rootType = selfTy.getType()
}
pragma[nomagic]
private predicate blanketImplSiblingCandidate(ImplItemNode impl, Trait trait) {
impl.isBlanketImplementation() and
trait = impl.resolveTraitTy()
}
private signature Type resolveTypeMentionAtSig(AstNode tm, TypePath path);
/**
* Holds if `impl1` and `impl2` are a sibling implementations of `trait`. We
* consider implementations to be siblings if they implement the same trait for
* the same type. In that case `Self` is the same type in both implementations,
* and method calls to the implementations cannot be resolved unambiguously
* based only on the receiver type.
* Provides logic for identifying sibling implementations, parameterized over
* how to resolve type mentions (`PreTypeMention` vs. `TypeMention`).
*/
pragma[inline]
private predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
impl1 != impl2 and
(
exists(Type rootType, TypeMention selfTy1, TypeMention selfTy2 |
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
// In principle the second conjunct below should be superflous, but we still
// have ill-formed type mentions for types that we don't understand. For
// those checking both directions restricts further. Note also that we check
// syntactic equality, whereas equality up to renaming would be more
// correct.
typeMentionEqual(selfTy1, selfTy2) and
typeMentionEqual(selfTy2, selfTy1)
private module MkSiblingImpls<resolveTypeMentionAtSig/2 resolveTypeMentionAt> {
pragma[nomagic]
private Type resolveNonTypeParameterTypeAt(AstNode tm, TypePath path) {
result = resolveTypeMentionAt(tm, path) and
not result instanceof TypeParameter
}
bindingset[t1, t2]
private predicate typeMentionEqual(AstNode t1, AstNode t2) {
forex(TypePath path, Type type | resolveNonTypeParameterTypeAt(t1, path) = type |
resolveNonTypeParameterTypeAt(t2, path) = type
)
or
blanketImplSiblingCandidate(impl1, trait) and
blanketImplSiblingCandidate(impl2, trait)
)
}
pragma[nomagic]
private predicate implSiblingCandidate(
Impl impl, TraitItemNode trait, Type rootType, AstNode selfTy
) {
trait = impl.(ImplItemNode).resolveTraitTy() and
selfTy = impl.getSelfTy() and
rootType = resolveTypeMentionAt(selfTy, TypePath::nil())
}
pragma[nomagic]
private predicate blanketImplSiblingCandidate(ImplItemNode impl, Trait trait) {
impl.isBlanketImplementation() and
trait = impl.resolveTraitTy()
}
/**
* Holds if `impl1` and `impl2` are sibling implementations of `trait`. We
* consider implementations to be siblings if they implement the same trait for
* the same type. In that case `Self` is the same type in both implementations,
* and method calls to the implementations cannot be resolved unambiguously
* based only on the receiver type.
*/
pragma[inline]
predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
impl1 != impl2 and
(
exists(Type rootType, AstNode selfTy1, AstNode selfTy2 |
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
// In principle the second conjunct below should be superfluous, but we still
// have ill-formed type mentions for types that we don't understand. For
// those checking both directions restricts further. Note also that we check
// syntactic equality, whereas equality up to renaming would be more
// correct.
typeMentionEqual(selfTy1, selfTy2) and
typeMentionEqual(selfTy2, selfTy1)
)
or
blanketImplSiblingCandidate(impl1, trait) and
blanketImplSiblingCandidate(impl2, trait)
)
}
/**
* Holds if `impl` is an implementation of `trait` and if another implementation
* exists for the same type.
*/
pragma[nomagic]
predicate implHasSibling(ImplItemNode impl, Trait trait) { implSiblings(trait, impl, _) }
pragma[nomagic]
predicate implHasAmbiguousSiblingAt(ImplItemNode impl, Trait trait, TypePath path) {
exists(ImplItemNode impl2, Type t1, Type t2 |
implSiblings(trait, impl, impl2) and
t1 = resolveTypeMentionAt(impl.getTraitPath(), path) and
t2 = resolveTypeMentionAt(impl2.getTraitPath(), path) and
t1 != t2
|
not t1 instanceof TypeParameter or
not t2 instanceof TypeParameter
)
}
}
/**
* Holds if `impl` is an implementation of `trait` and if another implementation
* exists for the same type.
*/
pragma[nomagic]
private predicate implHasSibling(ImplItemNode impl, Trait trait) { implSiblings(trait, impl, _) }
private Type resolvePreTypeMention(AstNode tm, TypePath path) {
result = tm.(PreTypeMention).getTypeAt(path)
}
private module PreSiblingImpls = MkSiblingImpls<resolvePreTypeMention/2>;
predicate preImplHasAmbiguousSiblingAt = PreSiblingImpls::implHasAmbiguousSiblingAt/3;
private Type resolveTypeMention(AstNode tm, TypePath path) {
result = tm.(TypeMention).getTypeAt(path)
}
private module SiblingImpls = MkSiblingImpls<resolveTypeMention/2>;
import SiblingImpls
/**
* Holds if `f` is a function declared inside `trait`, and the type of `f` at

View File

@@ -30,7 +30,7 @@ private newtype TTypeArgumentPosition =
} or
TTypeParamTypeArgumentPosition(TypeParam tp)
private module Input implements InputSig1<Location>, InputSig2<PreTypeMention> {
private module Input1 implements InputSig1<Location> {
private import Type as T
private import codeql.rust.elements.internal.generated.Raw
private import codeql.rust.elements.internal.generated.Synth
@@ -122,12 +122,28 @@ private module Input implements InputSig1<Location>, InputSig2<PreTypeMention> {
tp0 order by kind, id1, id2
)
}
}
int getTypePathLimit() { result = 10 }
private import Input1
PreTypeMention getABaseTypeMention(Type t) { none() }
private module M1 = Make1<Location, Input1>;
PreTypeMention getATypeParameterConstraint(TypeParameter tp) {
import M1
predicate getTypePathLimit = Input1::getTypePathLimit/0;
predicate getTypeParameterId = Input1::getTypeParameterId/1;
class TypePath = M1::TypePath;
module TypePath = M1::TypePath;
/**
* Provides shared logic for implementing `InputSig2<PreTypeMention>` and
* `InputSig2<TypeMention>`.
*/
private module Input2Common {
AstNode getATypeParameterConstraint(TypeParameter tp) {
result = tp.(TypeParamTypeParameter).getTypeParam().getATypeBound().getTypeRepr() or
result = tp.(SelfTypeParameter).getTrait() or
result =
@@ -146,7 +162,7 @@ private module Input implements InputSig1<Location>, InputSig2<PreTypeMention> {
* inference module for more information.
*/
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, PreTypeMention condition, PreTypeMention constraint, boolean transitive
TypeAbstraction abs, AstNode condition, AstNode constraint, boolean transitive
) {
// `impl` blocks implementing traits
transitive = false and
@@ -194,23 +210,64 @@ private module Input implements InputSig1<Location>, InputSig2<PreTypeMention> {
)
)
}
predicate typeParameterIsFunctionallyDetermined(TypeParameter tp) {
tp instanceof AssociatedTypeTypeParameter
}
}
private import Input
private module PreInput2 implements InputSig2<PreTypeMention> {
PreTypeMention getABaseTypeMention(Type t) { none() }
private module M1 = Make1<Location, Input>;
PreTypeMention getATypeParameterConstraint(TypeParameter tp) {
result = Input2Common::getATypeParameterConstraint(tp)
}
import M1
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, PreTypeMention condition, PreTypeMention constraint, boolean transitive
) {
Input2Common::conditionSatisfiesConstraint(abs, condition, constraint, transitive)
}
predicate getTypePathLimit = Input::getTypePathLimit/0;
predicate typeAbstractionHasAmbiguousConstraintAt(
TypeAbstraction abs, Type constraint, TypePath path
) {
FunctionOverloading::preImplHasAmbiguousSiblingAt(abs, constraint.(TraitType).getTrait(), path)
}
predicate getTypeParameterId = Input::getTypeParameterId/1;
predicate typeParameterIsFunctionallyDetermined =
Input2Common::typeParameterIsFunctionallyDetermined/1;
}
class TypePath = M1::TypePath;
/** Provides an instantiation of the shared type inference library for `PreTypeMention`s. */
module PreM2 = Make2<PreTypeMention, PreInput2>;
module TypePath = M1::TypePath;
private module Input2 implements InputSig2<TypeMention> {
TypeMention getABaseTypeMention(Type t) { none() }
private module M2 = Make2<PreTypeMention, Input>;
TypeMention getATypeParameterConstraint(TypeParameter tp) {
result = Input2Common::getATypeParameterConstraint(tp)
}
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, TypeMention condition, TypeMention constraint, boolean transitive
) {
Input2Common::conditionSatisfiesConstraint(abs, condition, constraint, transitive)
}
predicate typeAbstractionHasAmbiguousConstraintAt(
TypeAbstraction abs, Type constraint, TypePath path
) {
FunctionOverloading::implHasAmbiguousSiblingAt(abs, constraint.(TraitType).getTrait(), path)
}
predicate typeParameterIsFunctionallyDetermined =
Input2Common::typeParameterIsFunctionallyDetermined/1;
}
private import Input2
private module M2 = Make2<TypeMention, Input2>;
import M2
@@ -596,17 +653,18 @@ module CertainTypeInference {
}
/**
* Holds if `n` has complete and certain type information at _some_ type path.
* Holds if `n` has complete and certain type information at `path`.
*/
pragma[nomagic]
predicate hasInferredCertainType(AstNode n) { exists(inferCertainType(n, _)) }
predicate hasInferredCertainType(AstNode n, TypePath path) { exists(inferCertainType(n, path)) }
/**
* Holds if `n` having type `t` at `path` conflicts with certain type information.
* Holds if `n` having type `t` at `path` conflicts with certain type information
* at `prefix`.
*/
bindingset[n, path, t]
bindingset[n, prefix, path, t]
pragma[inline_late]
predicate certainTypeConflict(AstNode n, TypePath path, Type t) {
predicate certainTypeConflict(AstNode n, TypePath prefix, TypePath path, Type t) {
inferCertainType(n, path) != t
or
// If we infer that `n` has _some_ type at `T1.T2....Tn`, and we also
@@ -615,7 +673,7 @@ module CertainTypeInference {
// otherwise there is a conflict.
//
// Below, `prefix` is `T1.T2...Ti` and `tp` is `T(i+1)`.
exists(TypePath prefix, TypePath suffix, TypeParameter tp, Type certainType |
exists(TypePath suffix, TypeParameter tp, Type certainType |
path = prefix.appendInverse(suffix) and
tp = suffix.getHead() and
inferCertainType(n, prefix) = certainType and
@@ -1054,9 +1112,12 @@ private module ContextTyping {
pragma[nomagic]
private predicate hasUnknownType(AstNode n) { hasUnknownTypeAt(n, _) }
signature Type inferCallTypeSig(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
);
newtype FunctionPositionKind =
SelfKind() or
ReturnKind() or
PositionalKind()
signature Type inferCallTypeSig(AstNode n, FunctionPositionKind kind, TypePath path);
/**
* Given a predicate `inferCallType` for inferring the type of a call at a given
@@ -1064,35 +1125,28 @@ private module ContextTyping {
* predicate and checks that types are only propagated into arguments when they
* are context-typed.
*/
module CheckContextTyping<inferCallTypeSig/4 inferCallType> {
module CheckContextTyping<inferCallTypeSig/3 inferCallType> {
pragma[nomagic]
private Type inferCallNonReturnType(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
AstNode n, FunctionPositionKind kind, TypePath prefix, TypePath path
) {
result = inferCallType(n, pos, hasReceiver, path) and
not pos.isReturn()
}
pragma[nomagic]
private Type inferCallNonReturnType(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath prefix, TypePath path
) {
result = inferCallNonReturnType(n, pos, hasReceiver, path) and
result = inferCallType(n, kind, path) and
hasUnknownType(n) and
kind != ReturnKind() and
prefix = path.getAPrefix()
}
pragma[nomagic]
Type check(AstNode n, TypePath path) {
result = inferCallType(n, any(FunctionPosition pos | pos.isReturn()), _, path)
result = inferCallType(n, ReturnKind(), path)
or
exists(FunctionPosition pos, boolean hasReceiver, TypePath prefix |
result = inferCallNonReturnType(n, pos, hasReceiver, prefix, path) and
exists(FunctionPositionKind kind, TypePath prefix |
result = inferCallNonReturnType(n, kind, prefix, path) and
hasUnknownTypeAt(n, prefix)
|
// Never propagate type information directly into the receiver, since its type
// must already have been known in order to resolve the call
if pos.asPosition() = 0 and hasReceiver = true then not prefix.isEmpty() else any()
if kind = SelfKind() then not prefix.isEmpty() else any()
)
}
}
@@ -2877,17 +2931,20 @@ private Type inferFunctionCallTypeSelf(
}
private Type inferFunctionCallTypePreCheck(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
AstNode n, ContextTyping::FunctionPositionKind kind, TypePath path
) {
result = inferFunctionCallTypeNonSelf(n, pos, path) and
hasReceiver = false
exists(FunctionPosition pos |
result = inferFunctionCallTypeNonSelf(n, pos, path) and
if pos.isPosition()
then kind = ContextTyping::PositionalKind()
else kind = ContextTyping::ReturnKind()
)
or
exists(FunctionCallMatchingInput::Access a |
result = inferFunctionCallTypeSelf(a, n, DerefChain::nil(), path) and
pos.asPosition() = 0 and
if a.(AssocFunctionResolution::AssocFunctionCall).hasReceiver()
then hasReceiver = true
else hasReceiver = false
then kind = ContextTyping::SelfKind()
else kind = ContextTyping::PositionalKind()
)
}
@@ -2896,7 +2953,7 @@ private Type inferFunctionCallTypePreCheck(
* argument/receiver of a function call.
*/
private predicate inferFunctionCallType =
ContextTyping::CheckContextTyping<inferFunctionCallTypePreCheck/4>::check/2;
ContextTyping::CheckContextTyping<inferFunctionCallTypePreCheck/3>::check/2;
abstract private class Constructor extends Addressable {
final TypeParameter getTypeParameter(TypeParameterPosition ppos) {
@@ -3055,10 +3112,14 @@ private module ConstructionMatching = Matching<ConstructionMatchingInput>;
pragma[nomagic]
private Type inferConstructionTypePreCheck(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
AstNode n, ContextTyping::FunctionPositionKind kind, TypePath path
) {
hasReceiver = false and
exists(ConstructionMatchingInput::Access a | n = a.getNodeAt(pos) |
exists(ConstructionMatchingInput::Access a, FunctionPosition pos |
n = a.getNodeAt(pos) and
if pos.isPosition()
then kind = ContextTyping::PositionalKind()
else kind = ContextTyping::ReturnKind()
|
result = ConstructionMatching::inferAccessType(a, pos, path)
or
a.hasUnknownTypeAt(pos, path) and
@@ -3067,7 +3128,7 @@ private Type inferConstructionTypePreCheck(
}
private predicate inferConstructionType =
ContextTyping::CheckContextTyping<inferConstructionTypePreCheck/4>::check/2;
ContextTyping::CheckContextTyping<inferConstructionTypePreCheck/3>::check/2;
/**
* A matching configuration for resolving types of operations like `a + b`.
@@ -3133,17 +3194,22 @@ private module OperationMatching = Matching<OperationMatchingInput>;
pragma[nomagic]
private Type inferOperationTypePreCheck(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
AstNode n, ContextTyping::FunctionPositionKind kind, TypePath path
) {
exists(OperationMatchingInput::Access a |
exists(OperationMatchingInput::Access a, FunctionPosition pos |
n = a.getNodeAt(pos) and
result = OperationMatching::inferAccessType(a, pos, path) and
hasReceiver = true
if pos.asPosition() = 0
then kind = ContextTyping::SelfKind()
else
if pos.isPosition()
then kind = ContextTyping::PositionalKind()
else kind = ContextTyping::ReturnKind()
)
}
private predicate inferOperationType =
ContextTyping::CheckContextTyping<inferOperationTypePreCheck/4>::check/2;
ContextTyping::CheckContextTyping<inferOperationTypePreCheck/3>::check/2;
pragma[nomagic]
private Type getFieldExprLookupType(FieldExpr fe, string name, DerefChain derefChain) {
@@ -3900,10 +3966,11 @@ private module Cached {
or
// Don't propagate type information into a node which conflicts with certain
// type information.
(
if CertainTypeInference::hasInferredCertainType(n)
then not CertainTypeInference::certainTypeConflict(n, path, result)
else any()
forall(TypePath prefix |
CertainTypeInference::hasInferredCertainType(n, prefix) and
prefix.isPrefixOf(path)
|
not CertainTypeInference::certainTypeConflict(n, prefix, path, result)
) and
(
result = inferAssignmentOperationType(n, path)
@@ -3970,7 +4037,7 @@ private module Debug {
TypeAbstraction abs, TypeMention condition, TypeMention constraint, boolean transitive
) {
abs = getRelevantLocatable() and
Input::conditionSatisfiesConstraint(abs, condition, constraint, transitive)
Input2::conditionSatisfiesConstraint(abs, condition, constraint, transitive)
}
predicate debugInferShorthandSelfType(ShorthandSelfParameterMention self, TypePath path, Type t) {

View File

@@ -205,6 +205,13 @@ private module MkTypeMention<getAdditionalPathTypeAtSig/2 getAdditionalPathTypeA
)
or
exists(TypeParamItemNode tp | this = tp.getABoundPath() and result = tp)
or
// `<result as this>::...`
exists(PathTypeRepr typeRepr, PathTypeRepr traitRepr |
pathTypeAsTraitAssoc(_, typeRepr, traitRepr, _, _) and
this = traitRepr.getPath() and
result = typeRepr.getPath()
)
}
pragma[nomagic]
@@ -696,16 +703,26 @@ private module PreTypeMention = MkTypeMention<preGetAdditionalPathTypeAt/2>;
class PreTypeMention = PreTypeMention::TypeMention;
private class TraitOrTmTrait extends AstNode {
Type getTypeAt(TypePath path) {
pathTypeAsTraitAssoc(_, _, this, _, _) and
result = this.(PreTypeMention).getTypeAt(path)
or
result = TTrait(this) and
path.isEmpty()
}
}
/**
* Holds if `path` accesses an associated type `alias` from `trait` on a
* concrete type given by `tm`.
*
* `implOrTmTrait` is either the mention that resolves to `trait` when `path`
* is of the form `<Type as Trait>::AssocType`, or the enclosing `impl` block
* when `path` is of the form `Self::AssocType`.
* `traitOrTmTrait` is either the mention that resolves to `trait` when `path`
* is of the form `<Type as Trait>::AssocType`, or the trait being implemented
* when `path` is of the form `Self::AssocType` within an `impl` block.
*/
private predicate pathConcreteTypeAssocType(
Path path, PreTypeMention tm, TraitItemNode trait, AstNode implOrTmTrait, TypeAlias alias
Path path, PreTypeMention tm, TraitItemNode trait, TraitOrTmTrait traitOrTmTrait, TypeAlias alias
) {
exists(Path qualifier |
qualifier = path.getQualifier() and
@@ -713,31 +730,34 @@ private predicate pathConcreteTypeAssocType(
|
// path of the form `<Type as Trait>::AssocType`
// ^^^ tm ^^^^^^^^^ name
// ^^^^^ traitOrTmTrait
exists(string name |
pathTypeAsTraitAssoc(path, tm, implOrTmTrait, trait, name) and
pathTypeAsTraitAssoc(path, tm, traitOrTmTrait, trait, name) and
getTraitAssocType(trait, name) = alias
)
or
// path of the form `Self::AssocType` within an `impl` block
// tm ^^^^ ^^^^^^^^^ name
implOrTmTrait =
any(ImplItemNode impl |
alias = resolvePath(path) and
qualifier = impl.getASelfPath() and
tm = impl.(Impl).getSelfTy() and
trait.getAnAssocItem() = alias
)
exists(ImplItemNode impl |
alias = resolvePath(path) and
qualifier = impl.getASelfPath() and
tm = impl.(Impl).getSelfTy() and
trait.getAnAssocItem() = alias and
traitOrTmTrait = trait
)
)
}
private module PathSatisfiesConstraintInput implements SatisfiesTypeInputSig<PreTypeMention> {
predicate relevantConstraint(PreTypeMention tm, Type constraint) {
pathConcreteTypeAssocType(_, tm, constraint.(TraitType).getTrait(), _, _)
private module PathSatisfiesConstraintInput implements
PreM2::SatisfiesConstraintInputSig<PreTypeMention, TraitOrTmTrait>
{
predicate relevantConstraint(PreTypeMention tm, TraitOrTmTrait constraint) {
pathConcreteTypeAssocType(_, tm, _, constraint, _)
}
}
private module PathSatisfiesConstraint =
SatisfiesType<PreTypeMention, PathSatisfiesConstraintInput>;
PreM2::SatisfiesConstraint<PreTypeMention, TraitOrTmTrait, PathSatisfiesConstraintInput>;
/**
* Gets the type of `path` at `typePath` when `path` accesses an associated type
@@ -745,26 +765,12 @@ private module PathSatisfiesConstraint =
*/
private Type getPathConcreteAssocTypeAt(Path path, TypePath typePath) {
exists(
PreTypeMention tm, ImplItemNode impl, TraitItemNode trait, TraitType t, AstNode implOrTmTrait,
PreTypeMention tm, ImplItemNode impl, TraitItemNode trait, TraitOrTmTrait traitOrTmTrait,
TypeAlias alias, TypePath path0
|
pathConcreteTypeAssocType(path, tm, trait, implOrTmTrait, alias) and
t = TTrait(trait) and
PathSatisfiesConstraint::satisfiesConstraintTypeThrough(tm, impl, t, path0, result) and
pathConcreteTypeAssocType(path, tm, trait, traitOrTmTrait, alias) and
PathSatisfiesConstraint::satisfiesConstraintTypeThrough(tm, impl, traitOrTmTrait, path0, result) and
path0.isCons(TAssociatedTypeTypeParameter(trait, alias), typePath)
|
implOrTmTrait instanceof Impl
or
// When `path` is of the form `<Type as Trait>::AssocType` we need to check
// that `impl` is not more specific than the mentioned trait
implOrTmTrait =
any(PreTypeMention tmTrait |
not exists(TypePath path1, Type t1 |
t1 = impl.getTraitPath().(PreTypeMention).getTypeAt(path1) and
not t1 instanceof TypeParameter and
t1 != tmTrait.getTypeAt(path1)
)
)
)
}

View File

@@ -509,15 +509,15 @@ mod trait_bound_impl_overlap {
fn test() {
let x = S(0);
let y = call_f(x); // $ target=call_f type=y:i32 $ SPURIOUS: type=y:i64
let y = call_f(x); // $ target=call_f type=y:i32
let z: i32 = y;
let x = S(0);
let y = call_f::<i32, _>(x); // $ target=call_f type=y:i32
let x = S(0);
let y = call_f2(S(0i32), x); // $ target=call_f2 type=y:i32 $ SPURIOUS: type=y:i64
let y = call_f2(S(0i32), x); // $ target=call_f2 type=y:i32
let x = S(0);
let y = call_f2(S(0i64), x); // $ target=call_f2 type=y:i64 $ SPURIOUS: type=y:i32
let y = call_f2(S(0i64), x); // $ target=call_f2 type=y:i64
}
}

View File

@@ -10775,7 +10775,6 @@ inferType
| main.rs:2032:56:2034:9 | { ... } | | {EXTERNAL LOCATION} | & |
| main.rs:2032:56:2034:9 | { ... } | TRef | main.rs:2028:10:2028:10 | T |
| main.rs:2033:13:2033:29 | &... | | {EXTERNAL LOCATION} | & |
| main.rs:2033:13:2033:29 | &... | TRef | {EXTERNAL LOCATION} | u8 |
| main.rs:2033:13:2033:29 | &... | TRef | main.rs:2028:10:2028:10 | T |
| main.rs:2033:14:2033:17 | self | | {EXTERNAL LOCATION} | & |
| main.rs:2033:14:2033:17 | self | TRef | main.rs:2013:5:2016:5 | MyVec |
@@ -10783,7 +10782,6 @@ inferType
| main.rs:2033:14:2033:22 | self.data | | {EXTERNAL LOCATION} | Vec |
| main.rs:2033:14:2033:22 | self.data | A | {EXTERNAL LOCATION} | Global |
| main.rs:2033:14:2033:22 | self.data | T | main.rs:2028:10:2028:10 | T |
| main.rs:2033:14:2033:29 | ...[index] | | {EXTERNAL LOCATION} | u8 |
| main.rs:2033:14:2033:29 | ...[index] | | main.rs:2028:10:2028:10 | T |
| main.rs:2033:24:2033:28 | index | | {EXTERNAL LOCATION} | usize |
| main.rs:2037:22:2037:26 | slice | | {EXTERNAL LOCATION} | & |
@@ -12931,14 +12929,11 @@ inferType
| overloading.rs:511:17:511:20 | S(...) | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:511:19:511:19 | 0 | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:512:13:512:13 | y | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:512:13:512:13 | y | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:512:17:512:25 | call_f(...) | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:512:17:512:25 | call_f(...) | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:512:24:512:24 | x | | overloading.rs:464:5:464:19 | S |
| overloading.rs:512:24:512:24 | x | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:513:13:513:13 | z | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:513:22:513:22 | y | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:513:22:513:22 | y | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:515:13:515:13 | x | | overloading.rs:464:5:464:19 | S |
| overloading.rs:515:13:515:13 | x | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:515:17:515:20 | S(...) | | overloading.rs:464:5:464:19 | S |
@@ -12954,9 +12949,7 @@ inferType
| overloading.rs:518:17:518:20 | S(...) | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:518:19:518:19 | 0 | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:519:13:519:13 | y | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:519:13:519:13 | y | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:519:17:519:35 | call_f2(...) | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:519:17:519:35 | call_f2(...) | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:519:25:519:31 | S(...) | | overloading.rs:464:5:464:19 | S |
| overloading.rs:519:25:519:31 | S(...) | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:519:27:519:30 | 0i32 | | {EXTERNAL LOCATION} | i32 |
@@ -12967,9 +12960,7 @@ inferType
| overloading.rs:520:17:520:20 | S(...) | | overloading.rs:464:5:464:19 | S |
| overloading.rs:520:17:520:20 | S(...) | T | {EXTERNAL LOCATION} | i32 |
| overloading.rs:520:19:520:19 | 0 | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:521:13:521:13 | y | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:521:13:521:13 | y | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:521:17:521:35 | call_f2(...) | | {EXTERNAL LOCATION} | i32 |
| overloading.rs:521:17:521:35 | call_f2(...) | | {EXTERNAL LOCATION} | i64 |
| overloading.rs:521:25:521:31 | S(...) | | overloading.rs:464:5:464:19 | S |
| overloading.rs:521:25:521:31 | S(...) | T | {EXTERNAL LOCATION} | i64 |

View File

@@ -385,6 +385,45 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, TypeMention condition, TypeMention constraint, boolean transitive
);
/**
* Holds if the constraint belonging to `abs` with root type `constraint` is
* ambiguous at `path`, meaning that there is _some_ other abstraction `abs2`
* with a structurally identical condition and same root constraint type
* `constraint`, and where the constraints differ at `path`.
*
* Example:
*
* ```rust
* trait Trait<T1, T2> { }
*
* impl<T> Trait<i32, bool> for Foo<T> { ... }
* // ^^^ `abs`
* // ^^^^^ `constraint`
* // ^^^^^^ `condition`
*
* impl<T> Trait<i64, bool> for Foo<T> { }
* // ^^^ `abs2`
* // ^^^^^ `constraint`
* // ^^^^^^ `condition2`
* ```
*
* In the above, `abs` and `abs2` have structurally identical conditions,
* `condition` and `condition2`, and they differ at the path `"T1"`, but
* not at the path `"T2"`.
*/
predicate typeAbstractionHasAmbiguousConstraintAt(
TypeAbstraction abs, Type constraint, TypePath path
);
/**
* Holds if all instantiations of `tp` are functionally determined by the
* instantiations of the other type parameters in the same abstraction.
*
* For example, in Rust all associated types act as functionally determined
* type parameters.
*/
predicate typeParameterIsFunctionallyDetermined(TypeParameter tp);
}
module Make2<HasTypeTreeSig TypeMention, InputSig2<TypeMention> Input2> {
@@ -661,6 +700,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
* Holds if the type mention `condition` satisfies `constraint` with the
* type `t` at the path `path`.
*/
pragma[nomagic]
predicate conditionSatisfiesConstraintTypeAt(
TypeAbstraction abs, TypeMention condition, TypeMention constraint, TypePath path, Type t
) {
@@ -820,15 +860,30 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
private import BaseTypes
/** Provides the input to `SatisfiesConstraint`. */
signature module SatisfiesConstraintInputSig<HasTypeTreeSig Term, HasTypeTreeSig Constraint> {
/** Provides the input to `SatisfiesConstraintWithTypeMatching`. */
signature module SatisfiesConstraintWithTypeMatchingInputSig<
HasTypeTreeSig Term, HasTypeTreeSig Constraint>
{
/** Holds if it is relevant to know if `term` satisfies `constraint`. */
predicate relevantConstraint(Term term, Constraint constraint);
/** A context in which a type parameter can be matched with an instantiation. */
class TypeMatchingContext;
/** Gets the type matching context for `t`. */
TypeMatchingContext getTypeMatchingContext(Term t);
/**
* Holds if `tp` can be matched with the type `t` at `path` in the context `ctx`.
*
* This may be used to disambiguate between multiple constraints that a term may satisfy.
*/
predicate typeMatch(TypeMatchingContext ctx, TypeParameter tp, TypePath path, Type t);
}
module SatisfiesConstraint<
module SatisfiesConstraintWithTypeMatching<
HasTypeTreeSig Term, HasTypeTreeSig Constraint,
SatisfiesConstraintInputSig<Term, Constraint> Input>
SatisfiesConstraintWithTypeMatchingInputSig<Term, Constraint> Input>
{
private import Input
@@ -944,12 +999,103 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
pragma[nomagic]
private predicate satisfiesConstraintTypeMention0(
Term term, Constraint constraint, TypeMention constraintMention, TypeAbstraction abs,
TypeMention sub, TypePath path, Type t, boolean ambiguous
) {
exists(Type constraintRoot |
hasConstraintMention(term, abs, sub, constraint, constraintRoot, constraintMention) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, path, t) and
if
exists(TypePath prefix |
typeAbstractionHasAmbiguousConstraintAt(abs, constraintRoot, prefix) and
prefix.isPrefixOf(path)
)
then ambiguous = true
else ambiguous = false
)
}
pragma[nomagic]
private predicate conditionSatisfiesConstraintTypeAtForDisambiguation(
TypeAbstraction abs, TypeMention condition, TypeMention constraint, TypePath path, Type t
) {
conditionSatisfiesConstraintTypeAt(abs, condition, constraint, path, t) and
not t instanceof TypeParameter and
not typeParameterIsFunctionallyDetermined(path.getHead())
}
pragma[nomagic]
private predicate constraintTypeMatchForDisambiguation0(
Term term, Constraint constraint, TypePath path, TypePath suffix, TypeParameter tp
) {
exists(
TypeMention constraintMention, TypeAbstraction abs, TypeMention sub, TypePath prefix
|
satisfiesConstraintTypeMention0(term, constraint, constraintMention, abs, sub, _, _, true) and
conditionSatisfiesConstraintTypeAtForDisambiguation(abs, sub, constraintMention, path, _) and
tp = constraint.getTypeAt(prefix) and
path = prefix.appendInverse(suffix)
)
}
pragma[nomagic]
private predicate constraintTypeMatchForDisambiguation1(
Term term, Constraint constraint, TypePath path, TypeMatchingContext ctx, TypePath suffix,
TypeParameter tp
) {
constraintTypeMatchForDisambiguation0(term, constraint, path, suffix, tp) and
ctx = getTypeMatchingContext(term)
}
/**
* Holds if the type of `constraint` at `path` is `t` because it is possible
* to match some type parameter that occurs in `constraint` at a prefix of
* `path` in the context of `term`.
*
* For example, if we have
*
* ```rust
* fn f<T1, T2: SomeTrait<T1>>(x: T1, y: T2) -> T2::Output { ... }
* ```
*
* then at a call like `f(true, ...)` the constraint `SomeTrait<T1>` has the
* type `bool` substituted for `T1`.
*/
pragma[nomagic]
private predicate constraintTypeMatchForDisambiguation(
Term term, Constraint constraint, TypePath path, Type t
) {
exists(TypeMatchingContext ctx, TypeParameter tp, TypePath suffix |
constraintTypeMatchForDisambiguation1(term, constraint, path, ctx, suffix, tp) and
typeMatch(ctx, tp, suffix, t)
)
}
pragma[nomagic]
private predicate satisfiesConstraintTypeMention1(
Term term, Constraint constraint, TypeAbstraction abs, TypeMention sub, TypePath path,
Type t
) {
exists(Type constraintRoot, TypeMention constraintMention |
hasConstraintMention(term, abs, sub, constraint, constraintRoot, constraintMention) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, path, t)
exists(TypeMention constraintMention, boolean ambiguous |
satisfiesConstraintTypeMention0(term, constraint, constraintMention, abs, sub, path, t,
ambiguous)
|
if ambiguous = true
then
// When the constraint is not uniquely satisfied, we check that the satisfying
// abstraction is not more specific than the constraint to be satisfied. For example,
// if the constraint is `MyTrait<i32>` and there is both `impl MyTrait<i32> for ...` and
// `impl MyTrait<i64> for ...`, then the latter will be filtered away
forall(TypePath path1, Type t1 |
conditionSatisfiesConstraintTypeAtForDisambiguation(abs, sub, constraintMention,
path1, t1)
|
t1 = constraint.getTypeAt(path1)
or
// The constraint may contain a type parameter, which we can match to the right type
constraintTypeMatchForDisambiguation(term, constraint, path1, t1)
)
else any()
)
}
@@ -959,7 +1105,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
TypePath pathToTypeParamInSub
) {
exists(TypeMention sub, TypeParameter tp |
satisfiesConstraintTypeMention0(term, constraint, abs, sub, path, tp) and
satisfiesConstraintTypeMention1(term, constraint, abs, sub, path, tp) and
tp = abs.getATypeParameter() and
sub.getTypeAt(pathToTypeParamInSub) = tp
)
@@ -984,7 +1130,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
private predicate satisfiesConstraintTypeNonTypeParamInline(
Term term, TypeAbstraction abs, Constraint constraint, TypePath path, Type t
) {
satisfiesConstraintTypeMention0(term, constraint, abs, _, path, t) and
satisfiesConstraintTypeMention1(term, constraint, abs, _, path, t) and
not t = abs.getATypeParameter()
}
@@ -1048,12 +1194,41 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
predicate dissatisfiesConstraint(Term term, Constraint constraint) {
hasNotConstraintMention(term, constraint, _) and
exists(Type t, Type constraintRoot |
hasTypeConstraint(term, t, constraint, constraintRoot) and // todo
hasTypeConstraint(term, t, constraint, constraintRoot) and
t != constraintRoot
)
}
}
/** Provides the input to `SatisfiesConstraint`. */
signature module SatisfiesConstraintInputSig<HasTypeTreeSig Term, HasTypeTreeSig Constraint> {
/** Holds if it is relevant to know if `term` satisfies `constraint`. */
predicate relevantConstraint(Term term, Constraint constraint);
}
module SatisfiesConstraint<
HasTypeTreeSig Term, HasTypeTreeSig Constraint,
SatisfiesConstraintInputSig<Term, Constraint> Input>
{
private module Inp implements SatisfiesConstraintWithTypeMatchingInputSig<Term, Constraint> {
private import codeql.util.Void
predicate relevantConstraint(Term term, Constraint constraint) {
Input::relevantConstraint(term, constraint)
}
class TypeMatchingContext = Void;
TypeMatchingContext getTypeMatchingContext(Term t) { none() }
predicate typeMatch(TypeMatchingContext ctx, TypeParameter tp, TypePath path, Type t) {
none()
}
}
import SatisfiesConstraintWithTypeMatching<Term, Constraint, Inp>
}
/** Provides the input to `SatisfiesType`. */
signature module SatisfiesTypeInputSig<HasTypeTreeSig Term> {
/** Holds if it is relevant to know if `term` satisfies `type`. */
@@ -1306,7 +1481,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
}
private module AccessConstraint {
predicate relevantAccessConstraint(
private predicate relevantAccessConstraint(
Access a, AccessEnvironment e, Declaration target, AccessPosition apos, TypePath path,
TypeMention constraint
) {
@@ -1331,6 +1506,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
RelevantAccess() { this = MkRelevantAccess(a, apos, e, path) }
pragma[nomagic]
Type getTypeAt(TypePath suffix) {
result = a.getInferredType(e, apos, path.appendInverse(suffix))
}
@@ -1348,16 +1524,29 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
}
private module SatisfiesTypeParameterConstraintInput implements
SatisfiesConstraintInputSig<RelevantAccess, TypeMention>
SatisfiesConstraintWithTypeMatchingInputSig<RelevantAccess, TypeMention>
{
predicate relevantConstraint(RelevantAccess at, TypeMention constraint) {
constraint = at.getConstraint(_)
}
class TypeMatchingContext = Access;
TypeMatchingContext getTypeMatchingContext(RelevantAccess at) {
at = MkRelevantAccess(result, _, _, _)
}
pragma[nomagic]
predicate typeMatch(TypeMatchingContext ctx, TypeParameter tp, TypePath path, Type t) {
typeMatch(ctx, _, _, path, t, tp)
}
}
private module SatisfiesTypeParameterConstraint =
SatisfiesConstraint<RelevantAccess, TypeMention, SatisfiesTypeParameterConstraintInput>;
SatisfiesConstraintWithTypeMatching<RelevantAccess, TypeMention,
SatisfiesTypeParameterConstraintInput>;
pragma[nomagic]
predicate satisfiesConstraintType(
Access a, AccessEnvironment e, Declaration target, AccessPosition apos, TypePath prefix,
TypeMention constraint, TypePath path, Type t

View File

@@ -167,6 +167,14 @@ module Make<LocationSig Location, InputSig<Location> Input> {
*/
bindingset[this]
UnboundList getAPrefix() { result = [this, this.getAProperPrefix()] }
/**
* Holds if this list is a prefix of `other`.
*
* This is equivalent to `this = other.getAPrefix()`, but more performant.
*/
bindingset[this, other]
predicate isPrefixOf(UnboundList other) { this = other.prefix(this.stringLength()) }
}
/** Provides predicates for constructing `UnboundList`s. */