Skip to content

Commit e60275c

Browse files
committed
Rust: Refine implSiblings
Consider two implementations of the same trait to be siblings when the type being implemented by one is an instantiation of the type being implemented by the other.
1 parent bca51a9 commit e60275c

5 files changed

Lines changed: 115 additions & 58 deletions

File tree

rust/ql/lib/codeql/rust/internal/typeinference/FunctionOverloading.qll

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,8 @@ private signature Type resolveTypeMentionAtSig(AstNode tm, TypePath path);
2222
* how to resolve type mentions (`PreTypeMention` vs. `TypeMention`).
2323
*/
2424
private module MkSiblingImpls<resolveTypeMentionAtSig/2 resolveTypeMentionAt> {
25-
pragma[nomagic]
26-
private Type resolveNonTypeParameterTypeAt(AstNode tm, TypePath path) {
27-
result = resolveTypeMentionAt(tm, path) and
28-
not result instanceof TypeParameter
29-
}
30-
31-
bindingset[t1, t2]
32-
private predicate typeMentionEqual(AstNode t1, AstNode t2) {
33-
forex(TypePath path, Type type | resolveNonTypeParameterTypeAt(t1, path) = type |
34-
resolveNonTypeParameterTypeAt(t2, path) = type
35-
)
25+
private class Tm extends AstNode {
26+
Type getTypeAt(TypePath path) { result = resolveTypeMentionAt(this, path) }
3627
}
3728

3829
pragma[nomagic]
@@ -50,54 +41,91 @@ private module MkSiblingImpls<resolveTypeMentionAtSig/2 resolveTypeMentionAt> {
5041
trait = impl.resolveTraitTy()
5142
}
5243

44+
private module ImplIsInstantiationOfSiblingInput implements IsInstantiationOfInputSig<Tm, Tm> {
45+
predicate potentialInstantiationOf(Tm cond, TypeAbstraction abs, Tm constraint) {
46+
exists(TraitItemNode trait, Type rootType |
47+
implSiblingCandidate(_, trait, rootType, cond) and
48+
implSiblingCandidate(abs, trait, rootType, constraint) and
49+
cond != constraint
50+
)
51+
}
52+
}
53+
54+
private module ImplIsInstantiationOfSibling =
55+
IsInstantiationOf<Tm, Tm, ImplIsInstantiationOfSiblingInput>;
56+
5357
/**
5458
* Holds if `impl1` and `impl2` are sibling implementations of `trait`. We
55-
* consider implementations to be siblings if they implement the same trait for
56-
* the same type. In that case `Self` is the same type in both implementations,
57-
* and method calls to the implementations cannot be resolved unambiguously
58-
* based only on the receiver type.
59+
* consider implementations to be siblings if they implement the same trait and
60+
* the type being implemented by one of the implementations is an instantiation
61+
* of the type being implemented by the other.
62+
*
63+
* For example, in
64+
*
65+
* ```rust
66+
* trait MyTrait<T> { ... }
67+
* impl MyTrait<i64> for i64 { ... } // I1
68+
* impl MyTrait<u64> for i64 { ... } // I2
69+
*
70+
* impl MyTrait<i64> for S<i64> { ... } // I3
71+
* impl MyTrait<u64> for S<u64> { ... } // I4
72+
* impl MyTrait<bool> for S<T> { ... } // I5
73+
* ```
74+
*
75+
* the pairs `(I1, I2)`, `(I3, I5)`, and `(I4, I5)` are siblings, but not `(I3, I4)`.
76+
*
77+
* Whenever an implementation has a sibling, calls to the implementations cannot be
78+
* resolved unambiguously based only on the `Self` type alone.
5979
*/
60-
pragma[inline]
61-
predicate implSiblings(TraitItemNode trait, Impl impl1, Impl impl2) {
62-
impl1 != impl2 and
63-
(
64-
exists(Type rootType, AstNode selfTy1, AstNode selfTy2 |
65-
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
66-
implSiblingCandidate(impl2, trait, rootType, selfTy2) and
67-
// In principle the second conjunct below should be superfluous, but we still
68-
// have ill-formed type mentions for types that we don't understand. For
69-
// those checking both directions restricts further. Note also that we check
70-
// syntactic equality, whereas equality up to renaming would be more
71-
// correct.
72-
typeMentionEqual(selfTy1, selfTy2) and
73-
typeMentionEqual(selfTy2, selfTy1)
74-
)
75-
or
76-
blanketImplSiblingCandidate(impl1, trait) and
77-
blanketImplSiblingCandidate(impl2, trait)
80+
pragma[nomagic]
81+
predicate implSiblings(TraitItemNode trait, ImplItemNode impl1, ImplItemNode impl2) {
82+
exists(Type rootType, AstNode selfTy1, AstNode selfTy2 |
83+
implSiblingCandidate(impl1, trait, rootType, selfTy1) and
84+
implSiblingCandidate(impl2, trait, rootType, selfTy2)
85+
|
86+
ImplIsInstantiationOfSibling::isInstantiationOf(selfTy1, impl2, selfTy2) or
87+
ImplIsInstantiationOfSibling::isInstantiationOf(selfTy2, impl1, selfTy1)
7888
)
89+
or
90+
blanketImplSiblingCandidate(impl1, trait) and
91+
blanketImplSiblingCandidate(impl2, trait) and
92+
impl1 != impl2
7993
}
8094

8195
/**
8296
* Holds if `impl` is an implementation of `trait` and if another implementation
8397
* exists for the same type.
8498
*/
85-
pragma[nomagic]
8699
predicate implHasSibling(ImplItemNode impl, Trait trait) { implSiblings(trait, impl, _) }
87100

101+
pragma[nomagic]
102+
predicate implSiblings(TraitItemNode trait, ImplItemNode impl1, Tm traitMention1, Tm traitMention2) {
103+
exists(ImplItemNode impl2 |
104+
implSiblings(trait, impl1, impl2) and
105+
traitMention1 = impl1.getTraitPath() and
106+
traitMention2 = impl2.getTraitPath()
107+
)
108+
}
109+
110+
bindingset[t1, t2]
111+
pragma[inline_late]
112+
private predicate differentTypes(Type t1, Type t2) {
113+
t1 != t2 and
114+
(not t1 instanceof TypeParameter or not t2 instanceof TypeParameter)
115+
}
116+
88117
pragma[nomagic]
89118
predicate implHasAmbiguousSiblingAt(ImplItemNode impl, Trait trait, TypePath path) {
90-
exists(ImplItemNode impl2, Type t1, Type t2 |
91-
implSiblings(trait, impl, impl2) and
92-
t1 = resolveTypeMentionAt(impl.getTraitPath(), path) and
93-
t2 = resolveTypeMentionAt(impl2.getTraitPath(), path) and
94-
t1 != t2
95-
|
96-
not t1 instanceof TypeParameter or
97-
not t2 instanceof TypeParameter
119+
exists(Tm traitMention, Tm traitMention2, Type t1, Type t2 |
120+
implSiblings(trait, impl, traitMention, traitMention2) and
121+
t1 = traitMention.getTypeAt(path) and
122+
t2 = traitMention2.getTypeAt(path) and
123+
differentTypes(t1, t2)
98124
)
99125
or
100-
// todo: handle blanket/non-blanket siblings in `implSiblings`
126+
// Since we cannot resolve the `Output` types of certain built-in `Index` trait
127+
// implementations, we need to ensure that the type-specialized versions that we
128+
// ship do not apply unless there is an exact type match
101129
trait =
102130
any(IndexTrait it |
103131
implSiblingCandidate(impl, it, _, _) and
@@ -152,19 +180,14 @@ private predicate functionResolutionDependsOnArgumentCand(
152180
* ```rust
153181
* trait MyTrait<T> {
154182
* fn method(&self, value: Foo<T>) -> Self;
155-
* // ^^^^^^^^^^^^^ `pos` = 0
183+
* // ^^^^^^^^^^^^^ `pos` = 1
156184
* // ^ `path` = "T"
157185
* }
158186
* impl MyAdd<i64> for i64 {
159187
* fn method(&self, value: Foo<i64>) -> Self { ... }
160188
* // ^^^ `type` = i64
161189
* }
162190
* ```
163-
*
164-
* Note that we only check the root type symbol at the position. If the type
165-
* at that position is a type constructor (for instance `Vec<..>`) then
166-
* inspecting the entire type tree could be necessary to disambiguate the
167-
* method. In that case we will still resolve several methods.
168191
*/
169192

170193
exists(TraitItemNode trait |

rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ private module Input1 implements InputSig1<Location> {
3737

3838
class Type = T::Type;
3939

40+
predicate isPseudoType(Type t) {
41+
t instanceof UnknownType or
42+
t instanceof NeverType
43+
}
44+
4045
class TypeParameter = T::TypeParameter;
4146

4247
class TypeAbstraction = TA::TypeAbstraction;

rust/ql/test/library-tests/type-inference/regressions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ mod regression5 {
149149

150150
fn foo() -> S2<S1> {
151151
let x = S1.into(); // $ target=into
152-
x // $ SPURIOUS: type=x:T2.TRef.S1 -- happens because we currently do not consider the two `impl` blocks to be siblings
152+
x // $ type=x:T2.S1
153153
}
154154
}
155155

rust/ql/test/library-tests/type-inference/type-inference.expected

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15818,18 +15818,12 @@ inferType
1581815818
| regressions.rs:150:24:153:5 | { ... } | | regressions.rs:136:5:136:22 | S2 |
1581915819
| regressions.rs:150:24:153:5 | { ... } | T2 | regressions.rs:135:5:135:14 | S1 |
1582015820
| regressions.rs:151:13:151:13 | x | | regressions.rs:136:5:136:22 | S2 |
15821-
| regressions.rs:151:13:151:13 | x | T2 | {EXTERNAL LOCATION} | & |
1582215821
| regressions.rs:151:13:151:13 | x | T2 | regressions.rs:135:5:135:14 | S1 |
15823-
| regressions.rs:151:13:151:13 | x | T2.TRef | regressions.rs:135:5:135:14 | S1 |
1582415822
| regressions.rs:151:17:151:18 | S1 | | regressions.rs:135:5:135:14 | S1 |
1582515823
| regressions.rs:151:17:151:25 | S1.into() | | regressions.rs:136:5:136:22 | S2 |
15826-
| regressions.rs:151:17:151:25 | S1.into() | T2 | {EXTERNAL LOCATION} | & |
1582715824
| regressions.rs:151:17:151:25 | S1.into() | T2 | regressions.rs:135:5:135:14 | S1 |
15828-
| regressions.rs:151:17:151:25 | S1.into() | T2.TRef | regressions.rs:135:5:135:14 | S1 |
1582915825
| regressions.rs:152:9:152:9 | x | | regressions.rs:136:5:136:22 | S2 |
15830-
| regressions.rs:152:9:152:9 | x | T2 | {EXTERNAL LOCATION} | & |
1583115826
| regressions.rs:152:9:152:9 | x | T2 | regressions.rs:135:5:135:14 | S1 |
15832-
| regressions.rs:152:9:152:9 | x | T2.TRef | regressions.rs:135:5:135:14 | S1 |
1583315827
| regressions.rs:164:16:164:19 | SelfParam | | regressions.rs:158:5:158:19 | S |
1583415828
| regressions.rs:164:16:164:19 | SelfParam | T | regressions.rs:160:10:160:10 | T |
1583515829
| regressions.rs:164:22:164:25 | _rhs | | regressions.rs:158:5:158:19 | S |

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ signature module InputSig1<LocationSig Location> {
145145
Location getLocation();
146146
}
147147

148+
/**
149+
* Holds if `t` is a pseudo type. Pseudo types are skipped when checking for
150+
* non-instantiations in `isNotInstantiationOf`.
151+
*/
152+
predicate isPseudoType(Type t);
153+
148154
/** A type parameter. */
149155
class TypeParameter extends Type;
150156

@@ -624,6 +630,26 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
624630
)
625631
}
626632

633+
pragma[nomagic]
634+
private predicate hasTypeParameterAt(
635+
App app, TypeAbstraction abs, Constraint constraint, TypePath path, TypeParameter tp
636+
) {
637+
tp = getTypeAt(app, abs, constraint, path) and
638+
tp = abs.getATypeParameter()
639+
}
640+
641+
private Type getNonPseudoTypeAt(App app, TypePath path) {
642+
result = app.getTypeAt(path) and not isPseudoType(result)
643+
}
644+
645+
pragma[nomagic]
646+
private Type getNonPseudoTypeAtTypeParameter(
647+
App app, TypeAbstraction abs, Constraint constraint, TypeParameter tp, TypePath path
648+
) {
649+
hasTypeParameterAt(app, abs, constraint, path, tp) and
650+
result = getNonPseudoTypeAt(app, path)
651+
}
652+
627653
/**
628654
* Holds if `app` is _not_ a possible instantiation of `constraint`, because `app`
629655
* and `constraint` differ on concrete types at `path`.
@@ -643,13 +669,22 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
643669
predicate isNotInstantiationOf(
644670
App app, TypeAbstraction abs, Constraint constraint, TypePath path
645671
) {
646-
// `app` and `constraint` differ on a concrete type
672+
// `app` and `constraint` differ on a non-pseudo concrete type
647673
exists(Type t, Type t2 |
648674
t = getTypeAt(app, abs, constraint, path) and
649675
not t = abs.getATypeParameter() and
650-
app.getTypeAt(path) = t2 and
676+
t2 = getNonPseudoTypeAt(app, path) and
651677
t2 != t
652678
)
679+
or
680+
// `app` has different instantiations of a type parameter mentioned at two
681+
// different paths
682+
exists(TypeParameter tp, TypePath path2, Type t, Type t2 |
683+
t = getNonPseudoTypeAtTypeParameter(app, abs, constraint, tp, path) and
684+
t2 = getNonPseudoTypeAtTypeParameter(app, abs, constraint, tp, path2) and
685+
t != t2 and
686+
path != path2
687+
)
653688
}
654689
}
655690

0 commit comments

Comments
 (0)