Rust: Implement type inference support for non-universal impl blocks

This commit is contained in:
Simon Friis Vindum
2025-04-28 15:14:33 +02:00
parent 22407cad44
commit e45b5c557d
7 changed files with 1872 additions and 1174 deletions

View File

@@ -448,61 +448,6 @@ class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
TraitItemNode resolveTraitTy() { result = resolvePath(this.getTraitPath()) }
pragma[nomagic]
private TypeRepr getASelfTyArg() {
result =
this.getSelfPath().getSegment().getGenericArgList().getAGenericArg().(TypeArg).getTypeRepr()
}
/**
* Holds if this `impl` block is not fully parametric. That is, the implementing
* type is generic and the implementation is not parametrically polymorphic in all
* the implementing type's arguments.
*
* Examples:
*
* ```rust
* impl Foo { ... } // fully parametric
*
* impl<T> Foo<T> { ... } // fully parametric
*
* impl Foo<i64> { ... } // not fully parametric
*
* impl<T> Foo<Foo<T>> { ... } // not fully parametric
*
* impl<T: Trait> Foo<T> { ... } // not fully parametric
*
* impl<T> Foo<T> where T: Trait { ... } // not fully parametric
* ```
*/
pragma[nomagic]
predicate isNotFullyParametric() {
exists(TypeRepr arg | arg = this.getASelfTyArg() |
not exists(resolveTypeParamPathTypeRepr(arg))
or
resolveTypeParamPathTypeRepr(arg).hasTraitBound()
)
}
/**
* Holds if this `impl` block is fully parametric. Examples:
*
* ```rust
* impl Foo { ... } // fully parametric
*
* impl<T> Foo<T> { ... } // fully parametric
*
* impl Foo<i64> { ... } // not fully parametric
*
* impl<T> Foo<Foo<T>> { ... } // not fully parametric
*
* impl<T: Trait> Foo<T> { ... } // not fully parametric
*
* impl<T> Foo<T> where T: Trait { ... } // not fully parametric
* ```
*/
predicate isFullyParametric() { not this.isNotFullyParametric() }
override AssocItemNode getAnAssocItem() { result = super.getAssocItemList().getAnAssocItem() }
override string getName() { result = "(impl)" }

View File

@@ -27,10 +27,6 @@ newtype TType =
* types, such as traits and implementation blocks.
*/
abstract class Type extends TType {
/** Gets the method `name` belonging to this type, if any. */
pragma[nomagic]
abstract Function getMethod(string name);
/** Gets the struct field `name` belonging to this type, if any. */
pragma[nomagic]
abstract StructField getStructField(string name);
@@ -45,25 +41,6 @@ abstract class Type extends TType {
/** Gets a type parameter of this type. */
final TypeParameter getATypeParameter() { result = this.getTypeParameter(_) }
/**
* Gets an AST node that mentions a base type of this type, if any.
*
* Although Rust doesn't have traditional OOP-style inheritance, we model trait
* bounds and `impl` blocks as base types. Example:
*
* ```rust
* trait T1 {}
*
* trait T2 {}
*
* trait T3 : T1, T2 {}
* // ^^ `this`
* // ^^ `result`
* // ^^ `result`
* ```
*/
abstract TypeMention getABaseTypeMention();
/** Gets a textual representation of this type. */
abstract string toString();
@@ -73,21 +50,6 @@ abstract class Type extends TType {
abstract private class StructOrEnumType extends Type {
abstract ItemNode asItemNode();
final override Function getMethod(string name) {
result = this.asItemNode().getASuccessor(name) and
exists(ImplOrTraitItemNode impl | result = impl.getAnAssocItem() |
impl instanceof Trait
or
impl.(ImplItemNode).isFullyParametric()
)
}
/** Gets all of the fully parametric `impl` blocks that target this type. */
final override ImplMention getABaseTypeMention() {
this.asItemNode() = result.resolveSelfTy() and
result.isFullyParametric()
}
}
/** A struct type. */
@@ -138,8 +100,6 @@ class TraitType extends Type, TTrait {
TraitType() { this = TTrait(trait) }
override Function getMethod(string name) { result = trait.(ItemNode).getASuccessor(name) }
override StructField getStructField(string name) { none() }
override TupleField getTupleField(int i) { none() }
@@ -151,14 +111,6 @@ class TraitType extends Type, TTrait {
any(AssociatedTypeTypeParameter param | param.getTrait() = trait and param.getIndex() = i)
}
pragma[nomagic]
private TypeReprMention getABoundMention() {
result = trait.getTypeBoundList().getABound().getTypeRepr()
}
/** Gets any of the trait bounds of this trait. */
override TypeMention getABaseTypeMention() { result = this.getABoundMention() }
override string toString() { result = trait.toString() }
override Location getLocation() { result = trait.getLocation() }
@@ -220,8 +172,6 @@ class ImplType extends Type, TImpl {
ImplType() { this = TImpl(impl) }
override Function getMethod(string name) { result = impl.(ItemNode).getASuccessor(name) }
override StructField getStructField(string name) { none() }
override TupleField getTupleField(int i) { none() }
@@ -230,9 +180,6 @@ class ImplType extends Type, TImpl {
result = TTypeParamTypeParameter(impl.getGenericParamList().getTypeParam(i))
}
/** Get the trait implemented by this `impl` block, if any. */
override TypeMention getABaseTypeMention() { result = impl.getTrait() }
override string toString() { result = impl.toString() }
override Location getLocation() { result = impl.getLocation() }
@@ -247,8 +194,6 @@ class ImplType extends Type, TImpl {
class ArrayType extends Type, TArrayType {
ArrayType() { this = TArrayType() }
override Function getMethod(string name) { none() }
override StructField getStructField(string name) { none() }
override TupleField getTupleField(int i) { none() }
@@ -257,8 +202,6 @@ class ArrayType extends Type, TArrayType {
none() // todo
}
override TypeMention getABaseTypeMention() { none() }
override string toString() { result = "[]" }
override Location getLocation() { result instanceof EmptyLocation }
@@ -273,8 +216,6 @@ class ArrayType extends Type, TArrayType {
class RefType extends Type, TRefType {
RefType() { this = TRefType() }
override Function getMethod(string name) { none() }
override StructField getStructField(string name) { none() }
override TupleField getTupleField(int i) { none() }
@@ -284,8 +225,6 @@ class RefType extends Type, TRefType {
i = 0
}
override TypeMention getABaseTypeMention() { none() }
override string toString() { result = "&" }
override Location getLocation() { result instanceof EmptyLocation }
@@ -293,8 +232,6 @@ class RefType extends Type, TRefType {
/** A type parameter. */
abstract class TypeParameter extends Type {
override TypeMention getABaseTypeMention() { none() }
override StructField getStructField(string name) { none() }
override TupleField getTupleField(int i) { none() }
@@ -318,19 +255,9 @@ class TypeParamTypeParameter extends TypeParameter, TTypeParamTypeParameter {
TypeParam getTypeParam() { result = typeParam }
override Function getMethod(string name) {
// NOTE: If the type parameter has trait bounds, then this finds methods
// on the bounding traits.
result = typeParam.(ItemNode).getASuccessor(name)
}
override string toString() { result = typeParam.toString() }
override Location getLocation() { result = typeParam.getLocation() }
final override TypeMention getABaseTypeMention() {
result = typeParam.getTypeBoundList().getABound().getTypeRepr()
}
}
/**
@@ -377,19 +304,13 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara
int getIndex() { traitAliasIndex(_, result, typeAlias) }
override Function getMethod(string name) { none() }
override string toString() { result = typeAlias.getName().getText() }
override Location getLocation() { result = typeAlias.getLocation() }
override TypeMention getABaseTypeMention() { none() }
}
/** An implicit reference type parameter. */
class RefTypeParameter extends TypeParameter, TRefTypeParameter {
override Function getMethod(string name) { none() }
override string toString() { result = "&T" }
override Location getLocation() { result instanceof EmptyLocation }
@@ -409,15 +330,34 @@ class SelfTypeParameter extends TypeParameter, TSelfTypeParameter {
Trait getTrait() { result = trait }
override TypeMention getABaseTypeMention() { result = trait }
override Function getMethod(string name) {
// The `Self` type parameter is an implementation of the trait, so it has
// all the trait's methods.
result = trait.(ItemNode).getASuccessor(name)
}
override string toString() { result = "Self [" + trait.toString() + "]" }
override Location getLocation() { result = trait.getLocation() }
}
/** A type abstraction. */
abstract class TypeAbstraction extends AstNode {
abstract TypeParameter getATypeParameter();
}
final class ImplTypeAbstraction extends TypeAbstraction, Impl {
override TypeParamTypeParameter getATypeParameter() {
result.getTypeParam() = this.getGenericParamList().getATypeParam()
}
}
final class TraitTypeAbstraction extends TypeAbstraction, Trait {
override TypeParamTypeParameter getATypeParameter() {
result.getTypeParam() = this.getGenericParamList().getATypeParam()
}
}
final class TypeBoundTypeAbstraction extends TypeAbstraction, TypeBound {
override TypeParamTypeParameter getATypeParameter() { none() }
}
final class SelfTypeBoundTypeAbstraction extends TypeAbstraction, Name {
SelfTypeBoundTypeAbstraction() { any(Trait trait).getName() = this }
override TypeParamTypeParameter getATypeParameter() { none() }
}

View File

@@ -19,6 +19,8 @@ private module Input1 implements InputSig1<Location> {
class TypeParameter = T::TypeParameter;
class TypeAbstraction = T::TypeAbstraction;
private newtype TTypeArgumentPosition =
// method type parameters are matched by position instead of by type
// parameter entity, to avoid extra recursion through method call resolution
@@ -108,7 +110,45 @@ private module Input2 implements InputSig2 {
class TypeMention = TM::TypeMention;
TypeMention getABaseTypeMention(Type t) { result = t.getABaseTypeMention() }
TypeMention getABaseTypeMention(Type t) { none() }
TypeMention getTypeParameterConstraint(TypeParameter tp) {
result = tp.(TypeParamTypeParameter).getTypeParam().getTypeBoundList().getABound().getTypeRepr()
or
result = tp.(SelfTypeParameter).getTrait()
}
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, TypeMention condition, TypeMention constraint
) {
// `impl` blocks implementing traits
exists(Impl impl |
abs = impl and
condition = impl.getSelfTy() and
constraint = impl.getTrait()
)
or
// supertraits
exists(Trait trait |
abs = trait and
condition = trait and
constraint = trait.getTypeBoundList().getABound().getTypeRepr()
)
or
// trait bounds on type parameters
exists(TypeParam param |
abs = param.getTypeBoundList().getABound() and
condition = param and
constraint = param.getTypeBoundList().getABound().getTypeRepr()
)
or
// the implicit `Self` type parameter satisfies the trait
exists(SelfTypeParameterMention self |
abs = self and
condition = self and
constraint = self.getTrait()
)
}
}
private module M2 = Make2<Input2>;
@@ -227,7 +267,7 @@ private Type getRefAdjustImplicitSelfType(SelfParam self, TypePath suffix, Type
}
pragma[nomagic]
private Type inferImplSelfType(Impl i, TypePath path) {
private Type resolveImplSelfType(Impl i, TypePath path) {
result = i.getSelfTy().(TypeReprMention).resolveTypeAt(path)
}
@@ -239,7 +279,7 @@ private Type inferImplicitSelfType(SelfParam self, TypePath path) {
self = f.getParamList().getSelfParam() and
result = getRefAdjustImplicitSelfType(self, suffix, t, path)
|
t = inferImplSelfType(i, suffix)
t = resolveImplSelfType(i, suffix)
or
t = TSelfTypeParameter(i) and suffix.isEmpty()
)
@@ -911,36 +951,94 @@ private module Cached {
)
}
pragma[nomagic]
private Type receiverRootType(Expr e) {
any(MethodCallExpr mce).getReceiver() = e and
result = inferType(e)
}
pragma[nomagic]
private Type inferReceiverType(Expr e, TypePath path) {
exists(Type root | root = receiverRootType(e) |
// for reference types, lookup members in the type being referenced
if root = TRefType()
then result = inferType(e, TypePath::cons(TRefTypeParameter(), path))
else result = inferType(e, path)
)
}
private class ReceiverExpr extends Expr {
MethodCallExpr mce;
ReceiverExpr() { mce.getReceiver() = this }
string getField() { result = mce.getIdentifier().getText() }
Type resolveTypeAt(TypePath path) { result = inferReceiverType(this, path) }
}
private module IsInstantiationOfInput implements IsInstantiationOfSig<ReceiverExpr> {
predicate potentialInstantiationOf(ReceiverExpr receiver, TypeAbstraction impl, TypeMention sub) {
sub.resolveType() = receiver.resolveTypeAt(TypePath::nil()) and
sub = impl.(ImplTypeAbstraction).getSelfTy().(TypeReprMention) and
exists(impl.(ImplItemNode).getASuccessor(receiver.getField()))
}
}
bindingset[item, name]
pragma[inline_late]
private Function getMethodSuccessor(ItemNode item, string name) {
result = item.getASuccessor(name)
}
bindingset[tp, name]
pragma[inline_late]
private Function getTypeParameterMethod(TypeParameter tp, string name) {
result = getMethodSuccessor(tp.(TypeParamTypeParameter).getTypeParam(), name)
or
result = getMethodSuccessor(tp.(SelfTypeParameter).getTrait(), name)
}
/**
* Gets the method from an `impl` block with an implementing type that matches
* the type of `receiver` and with a name of the method call in which
* `receiver` occurs, if any.
*/
private Function getMethodFromImpl(ReceiverExpr receiver) {
exists(Impl impl |
IsInstantiationOf<ReceiverExpr, IsInstantiationOfInput>::isInstantiationOf(receiver, impl,
impl.(ImplTypeAbstraction).getSelfTy().(TypeReprMention)) and
result = getMethodSuccessor(impl, receiver.getField())
)
}
/** Gets a method that the method call `mce` resolves to, if any. */
cached
Function resolveMethodCallExpr(MethodCallExpr mce) {
exists(ReceiverExpr receiver | mce.getReceiver() = receiver |
// The method comes from an `impl` block targeting the type of `receiver`.
result = getMethodFromImpl(receiver)
or
// The type of `receiver` is a type parameter and the method comes from a
// trait bound on the type parameter.
result = getTypeParameterMethod(receiver.resolveTypeAt(TypePath::nil()), receiver.getField())
)
}
pragma[inline]
private Type getLookupType(AstNode n) {
private Type inferRootTypeDeref(AstNode n) {
exists(Type t |
t = inferType(n) and
// for reference types, lookup members in the type being referenced
if t = TRefType()
then
// for reference types, lookup members in the type being referenced
result = inferType(n, TypePath::singleton(TRefTypeParameter()))
then result = inferType(n, TypePath::singleton(TRefTypeParameter()))
else result = t
)
}
pragma[nomagic]
private Type getMethodCallExprLookupType(MethodCallExpr mce, string name) {
result = getLookupType(mce.getReceiver()) and
name = mce.getIdentifier().getText()
}
/**
* Gets a method that the method call `mce` resolves to, if any.
*/
cached
Function resolveMethodCallExpr(MethodCallExpr mce) {
exists(string name | result = getMethodCallExprLookupType(mce, name).getMethod(name))
}
pragma[nomagic]
private Type getFieldExprLookupType(FieldExpr fe, string name) {
result = getLookupType(fe.getContainer()) and
name = fe.getIdentifier().getText()
result = inferRootTypeDeref(fe.getContainer()) and name = fe.getIdentifier().getText()
}
/**

View File

@@ -248,3 +248,19 @@ class TraitMention extends TypeMention, TraitItemNode {
override Type resolveType() { result = TTrait(this) }
}
// NOTE: Since the implicit type parameter for the self type parameter never
// appears in the AST, we (somewhat arbitrarily) choose the name of a trait as a
// type mention. This works because there is a one-to-one correspondence between
// a trait and its name.
class SelfTypeParameterMention extends TypeMention, Name {
Trait trait;
SelfTypeParameterMention() { trait.getName() = this }
Trait getTrait() { result = trait }
override Type resolveType() { result = TSelfTypeParameter(trait) }
override TypeReprMention getTypeArgument(int i) { none() }
}

View File

@@ -129,8 +129,8 @@ mod method_non_parametric_impl {
println!("{:?}", x.a); // $ fieldof=MyThing
println!("{:?}", y.a); // $ fieldof=MyThing
println!("{:?}", x.m1()); // $ MISSING: method=MyThing<S1>::m1
println!("{:?}", y.m1().a); // $ MISSING: method=MyThing<S2>::m1 fieldof=MyThing
println!("{:?}", x.m1()); // $ method=MyThing<S1>::m1
println!("{:?}", y.m1().a); // $ method=MyThing<S2>::m1 fieldof=MyThing
let x = MyThing { a: S1 };
let y = MyThing { a: S2 };
@@ -295,22 +295,22 @@ mod method_non_parametric_trait_impl {
// Tests for method resolution
println!("{:?}", thing_s1.m1()); // $ MISSING: method=MyThing<S1>::m1
println!("{:?}", thing_s2.m1().a); // $ MISSING: method=MyThing<S2>::m1 fieldof=MyThing
let s3: S3 = thing_s3.m1(); // $ MISSING: method=MyThing<S3>::m1
println!("{:?}", thing_s1.m1()); // $ method=MyThing<S1>::m1
println!("{:?}", thing_s2.m1().a); // $ method=MyThing<S2>::m1 fieldof=MyThing
let s3: S3 = thing_s3.m1(); // $ method=MyThing<S3>::m1
println!("{:?}", s3);
let p1 = MyPair { p1: S1, p2: S1 };
println!("{:?}", p1.m1()); // $ MISSING: method=MyTrait<I>::m1
println!("{:?}", p1.m1()); // $ method=MyTrait<I>::m1
let p2 = MyPair { p1: S1, p2: S2 };
println!("{:?}", p2.m1()); // $ MISSING: method=MyTrait<S3>::m1
println!("{:?}", p2.m1()); // $ method=MyTrait<S3>::m1
let p3 = MyPair {
p1: MyThing { a: S1 },
p2: S3,
};
println!("{:?}", p3.m1()); // $ MISSING: method=MyTrait<TT>::m1
println!("{:?}", p3.m1()); // $ method=MyTrait<TT>::m1
// These calls go to the first implementation of `MyProduct` for `MyPair`
let a = MyPair { p1: S1, p2: S1 };
@@ -324,17 +324,17 @@ mod method_non_parametric_trait_impl {
// parameters of the implementation enforce that the two generics must
// be equal.
let b = MyPair { p1: S2, p2: S1 };
let x = b.fst(); // $ MISSING: method=MyPair<S2,S1>::fst SPURIOUS: method=MyPair<A,A>::fst
let x = b.fst(); // $ method=MyPair<S2,S1>::fst
println!("{:?}", x);
let y = b.snd(); // $ MISSING: method=MyPair<S2,S1>::snd SPURIOUS: method=MyPair<A,A>::snd
let y = b.snd(); // $ method=MyPair<S2,S1>::snd
println!("{:?}", y);
// Tests for inference of type parameters based on trait implementations.
let x = call_trait_m1(thing_s1); // $ MISSING: type=x:S1
let x = call_trait_m1(thing_s1); // $ type=x:S1
println!("{:?}", x);
let y = call_trait_m1(thing_s2); // $ MISSING: type=y:MyThing type=y.A:S2
println!("{:?}", y.a); // $ MISSING: fieldof=MyThing
let y = call_trait_m1(thing_s2); // $ type=y:MyThing type=y:A.S2
println!("{:?}", y.a); // $ fieldof=MyThing
// First implementation
let a = MyPair { p1: S1, p2: S1 };
@@ -345,20 +345,20 @@ mod method_non_parametric_trait_impl {
// Second implementation
let b = MyPair { p1: S2, p2: S1 };
let x = get_fst(b); // $ type=x:S1 SPURIOUS: type=x:S2
let x = get_fst(b); // $ type=x:S1
println!("{:?}", x);
let y = get_snd(b); // $ type=y:S2 SPURIOUS: type=y:S1
let y = get_snd(b); // $ type=y:S2
println!("{:?}", y);
let c = MyPair {
p1: S3,
p2: MyPair { p1: S2, p2: S1 },
};
let x = get_snd_fst(c); // $ type=x:S1 SPURIOUS: type=x:S2
let x = get_snd_fst(c); // $ type=x:S1
let thing = MyThing { a: S1 };
let i = thing.convert_to(); // $ MISSING: type=i:S1 MISSING: method=T::convert_to
let j = convert_to(thing); // $ MISSING: type=j:S1
let i = thing.convert_to(); // $ MISSING: type=i:S1 method=T::convert_to
let j = convert_to(thing); // $ type=j:S1
}
}
@@ -936,7 +936,7 @@ mod option_methods {
println!("{:?}", x4);
let x5 = MyOption::MySome(MyOption::<S>::MyNone());
println!("{:?}", x5.flatten()); // $ MISSING: method=flatten
println!("{:?}", x5.flatten()); // $ method=flatten
let x6 = MyOption::MySome(MyOption::<S>::MyNone());
println!("{:?}", MyOption::<MyOption<S>>::flatten(x6));

View File

@@ -32,6 +32,33 @@ signature module InputSig1<LocationSig Location> {
/** A type parameter. */
class TypeParameter extends Type;
/**
* A type abstraction. I.e., a place in the program where type variables are
* introduced.
*
* Example in C#:
* ```csharp
* class C<A, B> : D<A, B> { }
* // ^^^^^^ a type abstraction
* ```
*
* Example in Rust:
* ```rust
* impl<A, B> Foo<A, B> { }
* // ^^^^^^ a type abstraction
* ```
*/
class TypeAbstraction {
/** Gets a type parameter introduced by this abstraction. */
TypeParameter getATypeParameter();
/** Gets a textual representation of this type abstraction. */
string toString();
/** Gets the location of this type abstraction. */
Location getLocation();
}
/**
* Gets the unique identifier of type parameter `tp`.
*
@@ -91,11 +118,9 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
predicate getRank = getTypeParameterId/1;
}
private int getTypeParameterRank(TypeParameter tp) {
tp = DenseRank<DenseRankInput>::denseRank(result)
}
int getRank(TypeParameter tp) { tp = DenseRank<DenseRankInput>::denseRank(result) }
string encode(TypeParameter tp) { result = getTypeParameterRank(tp).toString() }
string encode(TypeParameter tp) { result = getRank(tp).toString() }
bindingset[s]
TypeParameter decode(string s) { encode(result) = s }
@@ -212,6 +237,17 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
TypePath cons(TypeParameter tp, TypePath suffix) { result = singleton(tp).append(suffix) }
}
/** A class that represents a type tree. */
signature class TypeTreeSig {
Type resolveTypeAt(TypePath path);
/** Gets a textual representation of this type abstraction. */
string toString();
/** Gets the location of this type abstraction. */
Location getLocation();
}
/** Provides the input to `Make2`. */
signature module InputSig2 {
/** A type mention, for example a type annotation in a local variable declaration. */
@@ -253,6 +289,62 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
* ```
*/
TypeMention getABaseTypeMention(Type t);
/**
* Gets a type constraint on the type parameter `tp`, if any. All
* instantiations of the type parameter must satisfy the constraint.
*
* For example, in
* ```csharp
* class GenericClass<T> : IComparable<GenericClass<T>>
* // ^ `tp`
* where T : IComparable<T> { }
* // ^^^^^^^^^^^^^^ `result`
* ```
* the type parameter `T` has the constraint `IComparable<T>`.
*/
TypeMention getTypeParameterConstraint(TypeParameter tp);
/**
* Holds if
* - `abs` is a type abstraction that introduces type variables that are
* free in `condition` and `constraint`,
* - and for every instantiation of the type parameters the resulting
* `condition` satisifies the constraint given by `constraint`.
*
* Example in C#:
* ```csharp
* class C<T> : IComparable<C<T>> { }
* // ^^^ `abs`
* // ^^^^ `condition`
* // ^^^^^^^^^^^^^^^^^ `constraint`
* ```
*
* Example in Rust:
* ```rust
* impl<A> Trait<i64, B> for Type<String, A> { }
* // ^^^ `abs` ^^^^^^^^^^^^^^^ `condition`
* // ^^^^^^^^^^^^^ `constraint`
* ```
*
* Note that the type parameters in `abs` significantly change the meaning
* of type parameters that occur in `condition`. For instance, in the Rust
* example
* ```rust
* fn foo<T: Trait>() { }
* ```
* we have that the type parameter `T` satisfies the constraint `Trait`. But,
* only that specific `T` satisfy the constraint. Hence we would not have
* `T` in `abs`. On the other hand, in the Rust example
* ```rust
* impl<T> Trait for T { }
* ```
* the constraint `Trait` is in fact satisfied for all types, and we would
* have `T` in `abs` to make it free in the condition.
*/
predicate conditionSatisfiesConstraint(
TypeAbstraction abs, TypeMention condition, TypeMention constraint
);
}
module Make2<InputSig2 Input2> {
@@ -265,8 +357,239 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
result = tm.resolveTypeAt(TypePath::nil())
}
signature module IsInstantiationOfSig<TypeTreeSig App> {
/**
* Holds if `abs` is a type abstraction under which `tm` occurs and if
* `app` is potentially the result of applying the abstraction to type
* some type argument.
*/
predicate potentialInstantiationOf(App app, TypeAbstraction abs, TypeMention tm);
}
module IsInstantiationOf<TypeTreeSig App, IsInstantiationOfSig<App> Input> {
private import Input
/** Gets the `i`th path in `tm` per some arbitrary order. */
private TypePath getNthPath(TypeMention tm, int i) {
result = rank[i + 1](TypePath path | exists(tm.resolveTypeAt(path)) | path)
}
/**
* Holds if `app` is a possible instantiation of `tm` at `path`. That is
* the type at `path` in `tm` is either a type parameter or equal to the
* type at the same path in `app`.
*/
bindingset[app, abs, tm, path]
private predicate satisfiesConcreteTypeAt(
App app, TypeAbstraction abs, TypeMention tm, TypePath path
) {
exists(Type t |
tm.resolveTypeAt(path) = t and
if t = abs.getATypeParameter() then any() else app.resolveTypeAt(path) = t
)
}
private predicate satisfiesConcreteTypesFromIndex(
App app, TypeAbstraction abs, TypeMention tm, int i
) {
potentialInstantiationOf(app, abs, tm) and
satisfiesConcreteTypeAt(app, abs, tm, getNthPath(tm, i)) and
// Recurse unless we are at the first path
if i = 0 then any() else satisfiesConcreteTypesFromIndex(app, abs, tm, i - 1)
}
pragma[inline]
private predicate satisfiesConcreteTypes(App app, TypeAbstraction abs, TypeMention tm) {
satisfiesConcreteTypesFromIndex(app, abs, tm, max(int i | exists(getNthPath(tm, i))))
}
private TypeParameter getNthTypeParameter(TypeAbstraction abs, int i) {
result =
rank[i + 1](TypeParameter tp |
tp = abs.getATypeParameter()
|
tp order by TypeParameter::getRank(tp)
)
}
/**
* Gets the path to the `i`th occurrence of `tp` within `tm` per some
* arbitrary order, if any.
*/
private TypePath getNthTypeParameterPath(TypeMention tm, TypeParameter tp, int i) {
result = rank[i + 1](TypePath path | tp = tm.resolveTypeAt(path) | path)
}
private predicate typeParametersEqualFromIndex(
App app, TypeAbstraction abs, TypeMention tm, TypeParameter tp, int i
) {
potentialInstantiationOf(app, abs, tm) and
exists(TypePath path, TypePath nextPath |
path = getNthTypeParameterPath(tm, tp, i) and
nextPath = getNthTypeParameterPath(tm, tp, i - 1) and
app.resolveTypeAt(path) = app.resolveTypeAt(nextPath) and
if i = 1 then any() else typeParametersEqualFromIndex(app, abs, tm, tp, i - 1)
)
}
private predicate typeParametersEqual(
App app, TypeAbstraction abs, TypeMention tm, TypeParameter tp
) {
potentialInstantiationOf(app, abs, tm) and
tp = getNthTypeParameter(abs, _) and
(
not exists(getNthTypeParameterPath(tm, tp, _))
or
exists(int n | n = max(int i | exists(getNthTypeParameterPath(tm, tp, i))) |
// If the largest index is 0, then there are no equalities to check as
// the type parameter only occurs once.
if n = 0 then any() else typeParametersEqualFromIndex(app, abs, tm, tp, n)
)
)
}
/** Holds if all the concrete types in `tm` also occur in `app`. */
private predicate typeParametersHaveEqualInstantiationFromIndex(
App app, TypeAbstraction abs, TypeMention tm, int i
) {
exists(TypeParameter tp | tp = getNthTypeParameter(abs, i) |
typeParametersEqual(app, abs, tm, tp) and
if i = 0
then any()
else typeParametersHaveEqualInstantiationFromIndex(app, abs, tm, i - 1)
)
}
/** All the places where the same type parameter occurs in `tm` are equal in `app. */
pragma[inline]
private predicate typeParametersHaveEqualInstantiation(
App app, TypeAbstraction abs, TypeMention tm
) {
potentialInstantiationOf(app, abs, tm) and
(
not exists(getNthTypeParameter(abs, _))
or
exists(int n | n = max(int i | exists(getNthTypeParameter(abs, i))) |
typeParametersHaveEqualInstantiationFromIndex(app, abs, tm, n)
)
)
}
/**
* Holds if `app` is a possible instantiation of `tm`. That is, by making
* appropriate substitutions for the free type parameters in `tm` given by
* `abs`, it is possible to obtain `app`.
*
* For instance, if `A` and `B` are free type parameters we have:
* - `Pair<int, string>` is an instantiation of `A`
* - `Pair<int, string>` is an instantiation of `Pair<A, B>`
* - `Pair<int, int>` is an instantiation of `Pair<A, A>`
* - `Pair<int, bool>` is _not_ an instantiation of `Pair<A, A>`
* - `Pair<int, string>` is _not_ an instantiation of `Pair<string, string>`
*/
predicate isInstantiationOf(App app, TypeAbstraction abs, TypeMention tm) {
potentialInstantiationOf(app, abs, tm) and
satisfiesConcreteTypes(app, abs, tm) and
typeParametersHaveEqualInstantiation(app, abs, tm)
}
}
/** Provides logic related to base types. */
private module BaseTypes {
/**
* Holds if, when `tm1` is considered an instantiation of `tm2`, then at
* the type parameter `tp` is has the type `t` at `path`.
*
* For instance, if the type `Map<int, List<int>>` is considered an
* instantion of `Map<K, V>` then it has the type `int` at `K` and the
* type `List<int>` at `V`.
*/
bindingset[tm1, tm2]
predicate instantiatesWith(
TypeMention tm1, TypeMention tm2, TypeParameter tp, TypePath path, Type t
) {
exists(TypePath prefix |
tm2.resolveTypeAt(prefix) = tp and t = tm1.resolveTypeAt(prefix.append(path))
)
}
module IsInstantiationOfInput implements IsInstantiationOfSig<TypeMention> {
pragma[nomagic]
private predicate typeCondition(Type type, TypeAbstraction abs, TypeMention lhs) {
conditionSatisfiesConstraint(abs, lhs, _) and type = resolveTypeMentionRoot(lhs)
}
pragma[nomagic]
private predicate typeConstraint(Type type, TypeMention rhs) {
conditionSatisfiesConstraint(_, _, rhs) and type = resolveTypeMentionRoot(rhs)
}
predicate potentialInstantiationOf(
TypeMention condition, TypeAbstraction abs, TypeMention constraint
) {
exists(Type type |
typeConstraint(type, condition) and typeCondition(type, abs, constraint)
)
}
}
// The type mention `condition` satisfies `constraint` with the type `t` at the path `path`.
predicate conditionSatisfiesConstraintTypeAt(
TypeAbstraction abs, TypeMention condition, TypeMention constraint, TypePath path, Type t
) {
// base case
conditionSatisfiesConstraint(abs, condition, constraint) and
constraint.resolveTypeAt(path) = t
or
// recursive case
exists(TypeAbstraction midAbs, TypeMention midSup, TypeMention midSub |
conditionSatisfiesConstraint(abs, condition, midSup) and
// NOTE: `midAbs` describe the free type variables in `midSub`, hence
// we use that for instantiation check.
IsInstantiationOf<TypeMention, IsInstantiationOfInput>::isInstantiationOf(midSup, midAbs,
midSub) and
(
conditionSatisfiesConstraintTypeAt(midAbs, midSub, constraint, path, t) and
not t = abs.getATypeParameter()
or
exists(TypePath prefix, TypePath suffix, TypeParameter tp |
tp = abs.getATypeParameter() and
conditionSatisfiesConstraintTypeAt(midAbs, midSub, constraint, prefix, tp) and
instantiatesWith(midSup, midSub, tp, suffix, t) and
path = prefix.append(suffix)
)
)
)
}
/**
* Holds if its possible for a type with `conditionRoot` at the root to
* satisfy a constraint with `constraintRoot` at the root through `abs`,
* `condition`, and `constraint`.
*/
predicate rootTypesSatisfaction(
Type conditionRoot, Type constraintRoot, TypeAbstraction abs, TypeMention condition,
TypeMention constraint
) {
conditionSatisfiesConstraintTypeAt(abs, condition, constraint, _, _) and
conditionRoot = resolveTypeMentionRoot(condition) and
constraintRoot = resolveTypeMentionRoot(constraint)
}
/**
* Gets the number of ways in which it is possible for a type with
* `conditionRoot` at the root to satisfy a constraint with
* `constraintRoot` at the root.
*/
int countConstraintImplementations(Type conditionRoot, Type constraintRoot) {
result =
strictcount(TypeAbstraction abs, TypeMention tm, TypeMention constraint |
rootTypesSatisfaction(conditionRoot, constraintRoot, abs, tm, constraint)
|
constraint
)
}
/**
* Holds if `baseMention` is a (transitive) base type mention of `sub`,
* and `t` is mentioned (implicitly) at `path` inside `baseMention`. For
@@ -528,24 +851,19 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
* Holds if inferring types at `a` might depend on the type at `path` of
* `apos` having `base` as a transitive base type.
*/
private predicate relevantAccess(Access a, AccessPosition apos, TypePath path, Type base) {
private predicate relevantAccess(Access a, AccessPosition apos, Type base) {
exists(Declaration target, DeclarationPosition dpos |
adjustedAccessType(a, apos, target, _, _) and
accessDeclarationPositionMatch(apos, dpos)
|
path.isEmpty() and declarationBaseType(target, dpos, base, _, _)
or
typeParameterConstraintHasTypeParameter(target, dpos, path, _, base, _, _)
accessDeclarationPositionMatch(apos, dpos) and
declarationBaseType(target, dpos, base, _, _)
)
}
pragma[nomagic]
private Type inferTypeAt(
Access a, AccessPosition apos, TypePath prefix, TypeParameter tp, TypePath suffix
) {
relevantAccess(a, apos, prefix, _) and
private Type inferTypeAt(Access a, AccessPosition apos, TypeParameter tp, TypePath suffix) {
relevantAccess(a, apos, _) and
exists(TypePath path0 |
result = a.getInferredType(apos, prefix.append(path0)) and
result = a.getInferredType(apos, path0) and
path0.isCons(tp, suffix)
)
}
@@ -581,24 +899,128 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
* `Base<C<T3>>` | `"T2.T1"` | ``C`1``
* `Base<C<T3>>` | `"T2.T1.T1"` | `int`
*/
pragma[nomagic]
predicate hasBaseTypeMention(
Access a, AccessPosition apos, TypePath pathToSub, TypeMention baseMention, TypePath path,
Type t
Access a, AccessPosition apos, TypeMention baseMention, TypePath path, Type t
) {
relevantAccess(a, apos, pathToSub, resolveTypeMentionRoot(baseMention)) and
exists(Type sub | sub = a.getInferredType(apos, pathToSub) |
relevantAccess(a, apos, resolveTypeMentionRoot(baseMention)) and
exists(Type sub | sub = a.getInferredType(apos, TypePath::nil()) |
baseTypeMentionHasNonTypeParameterAt(sub, baseMention, path, t)
or
exists(TypePath prefix, TypePath suffix, TypeParameter tp |
baseTypeMentionHasTypeParameterAt(sub, baseMention, prefix, tp) and
t = inferTypeAt(a, apos, pathToSub, tp, suffix) and
t = inferTypeAt(a, apos, tp, suffix) and
path = prefix.append(suffix)
)
)
}
}
private module AccessConstraint {
private newtype TTRelevantAccess =
TRelevantAccess(Access a, AccessPosition apos, TypePath path, Type constraint) {
exists(DeclarationPosition dpos |
accessDeclarationPositionMatch(apos, dpos) and
typeParameterConstraintHasTypeParameter(a.getTarget(), dpos, path, _, constraint, _, _)
)
}
/**
* If the access `a` for `apos` and `path` has the inferred root type
* `type` and type inference requires it to satisfy the constraint
* `constraint`.
*/
private class RelevantAccess extends TTRelevantAccess {
Access a;
AccessPosition apos;
TypePath path;
Type constraint0;
RelevantAccess() { this = TRelevantAccess(a, apos, path, constraint0) }
Type resolveTypeAt(TypePath suffix) {
a.getInferredType(apos, path.append(suffix)) = result
}
/** Holds if this relevant access has the type `type` and should satisfy `constraint`. */
predicate hasTypeConstraint(Type type, Type constraint) {
type = a.getInferredType(apos, path) and
constraint = constraint0
}
string toString() {
result = a.toString() + ", " + apos.toString() + ", " + path.toString()
}
Location getLocation() { result = a.getLocation() }
}
private module IsInstantiationOfInput implements IsInstantiationOfSig<RelevantAccess> {
predicate potentialInstantiationOf(
RelevantAccess at, TypeAbstraction abs, TypeMention cond
) {
exists(Type constraint, Type type |
at.hasTypeConstraint(type, constraint) and
rootTypesSatisfaction(type, constraint, abs, cond, _) and
// We only need to check instantiations where there are multiple candidates.
countConstraintImplementations(type, constraint) > 1
)
}
}
/**
* Holds if `at` satisfies `constraint` through `abs`, `sub`, and `constraintMention`.
*/
private predicate hasConstraintMention(
RelevantAccess at, TypeAbstraction abs, TypeMention sub, TypeMention constraintMention
) {
exists(Type type, Type constraint | at.hasTypeConstraint(type, constraint) |
not exists(countConstraintImplementations(type, constraint)) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, _, _) and
resolveTypeMentionRoot(sub) = abs.getATypeParameter() and
constraint = resolveTypeMentionRoot(constraintMention)
or
countConstraintImplementations(type, constraint) > 0 and
rootTypesSatisfaction(type, constraint, abs, sub, constraintMention) and
// When there are multiple ways the type could implement the
// constraint we need to find the right implementation, which is the
// one where the type instantiates the precondition.
if countConstraintImplementations(type, constraint) > 1
then
IsInstantiationOf<RelevantAccess, IsInstantiationOfInput>::isInstantiationOf(at, abs,
sub)
else any()
)
}
/**
* Holds if the type at `a`, `apos`, and `path` satisfies the constraint
* `constraint` with the type `t` at `path`.
*/
pragma[nomagic]
predicate satisfiesConstraintTypeMention(
Access a, AccessPosition apos, TypePath prefix, Type constraint, TypePath path, Type t
) {
exists(
RelevantAccess at, TypeAbstraction abs, TypeMention sub, Type t0, TypePath prefix0,
TypeMention constraintMention
|
at = TRelevantAccess(a, apos, prefix, constraint) and
hasConstraintMention(at, abs, sub, constraintMention) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, prefix0, t0) and
(
not t0 = abs.getATypeParameter() and t = t0 and path = prefix0
or
t0 = abs.getATypeParameter() and
exists(TypePath path3, TypePath suffix |
sub.resolveTypeAt(path3) = t0 and
at.resolveTypeAt(path3.append(suffix)) = t and
path = prefix0.append(suffix)
)
)
)
}
}
/**
* Holds if the type of `a` at `apos` has the base type `base`, and when
* viewed as an element of that type has the type `t` at `path`.
@@ -608,7 +1030,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
Access a, AccessPosition apos, Type base, TypePath path, Type t
) {
exists(TypeMention tm |
AccessBaseType::hasBaseTypeMention(a, apos, TypePath::nil(), tm, path, t) and
AccessBaseType::hasBaseTypeMention(a, apos, tm, path, t) and
base = resolveTypeMentionRoot(tm)
)
}
@@ -712,7 +1134,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
tp1 != tp2 and
tp1 = target.getDeclaredType(dpos, path1) and
exists(TypeMention tm |
tm = getABaseTypeMention(tp1) and
tm = getTypeParameterConstraint(tp1) and
tm.resolveTypeAt(path2) = tp2 and
constraint = resolveTypeMentionRoot(tm)
)
@@ -725,13 +1147,14 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
not exists(getTypeArgument(a, target, tp, _)) and
target = a.getTarget() and
exists(
TypeMention base, AccessPosition apos, DeclarationPosition dpos, TypePath pathToTp,
Type constraint, AccessPosition apos, DeclarationPosition dpos, TypePath pathToTp,
TypePath pathToTp2
|
accessDeclarationPositionMatch(apos, dpos) and
typeParameterConstraintHasTypeParameter(target, dpos, pathToTp2, _,
resolveTypeMentionRoot(base), pathToTp, tp) and
AccessBaseType::hasBaseTypeMention(a, apos, pathToTp2, base, pathToTp.append(path), t)
typeParameterConstraintHasTypeParameter(target, dpos, pathToTp2, _, constraint, pathToTp,
tp) and
AccessConstraint::satisfiesConstraintTypeMention(a, apos, pathToTp2, constraint,
pathToTp.append(path), t)
)
}
@@ -749,7 +1172,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
// We can infer the type of `tp` by going up the type hiearchy
baseTypeMatch(a, target, path, t, tp)
or
// We can infer the type of `tp` by a type bound
// We can infer the type of `tp` by a type constraint
typeConstraintBaseTypeMatch(a, target, path, t, tp)
}
@@ -811,7 +1234,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
}
}
/** Provides consitency checks. */
/** Provides consistency checks. */
module Consistency {
query predicate missingTypeParameterId(TypeParameter tp) {
not exists(getTypeParameterId(tp))