Merge pull request #19829 from paldepind/rust/type-tree-constraint

Rust: Add `SatisfiesConstraintInput` module in shared type inference
This commit is contained in:
Tom Hvitved
2025-06-23 17:17:23 +02:00
committed by GitHub
4 changed files with 130 additions and 182 deletions

View File

@@ -1132,7 +1132,7 @@ pragma[nomagic]
private predicate crateDependencyEdge(SourceFileItemNode file, string name, CrateItemNode dep) {
exists(CrateItemNode c | dep = c.(Crate).getDependency(name) | file = c.getASourceFile())
or
// Give builtin files, such as `await.rs`, access to `std`
// Give builtin files access to `std`
file instanceof BuiltinSourceFile and
dep.getName() = name and
name = "std"
@@ -1501,7 +1501,7 @@ private predicate preludeEdge(SourceFile f, string name, ItemNode i) {
exists(Crate stdOrCore, ModuleLikeNode mod, ModuleItemNode prelude, ModuleItemNode rust |
f = any(Crate c0 | stdOrCore = c0.getDependency(_) or stdOrCore = c0).getASourceFile()
or
// Give builtin files, such as `await.rs`, access to the prelude
// Give builtin files access to the prelude
f instanceof BuiltinSourceFile
|
stdOrCore.getName() = ["std", "core"] and

View File

@@ -997,79 +997,6 @@ private AssociatedTypeTypeParameter getFutureOutputTypeParameter() {
result.getTypeAlias() = any(FutureTrait ft).getOutputType()
}
/**
* A matching configuration for resolving types of `.await` expressions.
*/
private module AwaitExprMatchingInput implements MatchingInputSig {
private newtype TDeclarationPosition =
TSelfDeclarationPosition() or
TOutputPos()
class DeclarationPosition extends TDeclarationPosition {
predicate isSelf() { this = TSelfDeclarationPosition() }
predicate isOutput() { this = TOutputPos() }
string toString() {
this.isSelf() and
result = "self"
or
this.isOutput() and
result = "(output)"
}
}
private class BuiltinsAwaitFile extends File {
BuiltinsAwaitFile() {
this.getBaseName() = "await.rs" and
this.getParentContainer() instanceof Builtins::BuiltinsFolder
}
}
class Declaration extends Function {
Declaration() {
this.getFile() instanceof BuiltinsAwaitFile and
this.getName().getText() = "await_type_matching"
}
TypeParameter getTypeParameter(TypeParameterPosition ppos) {
typeParamMatchPosition(this.getGenericParamList().getATypeParam(), result, ppos)
}
Type getDeclaredType(DeclarationPosition dpos, TypePath path) {
dpos.isSelf() and
result = this.getParam(0).getTypeRepr().(TypeMention).resolveTypeAt(path)
or
dpos.isOutput() and
result = this.getRetType().getTypeRepr().(TypeMention).resolveTypeAt(path)
}
}
class AccessPosition = DeclarationPosition;
class Access extends AwaitExpr {
Type getTypeArgument(TypeArgumentPosition apos, TypePath path) { none() }
AstNode getNodeAt(AccessPosition apos) {
result = this.getExpr() and
apos.isSelf()
or
result = this and
apos.isOutput()
}
Type getInferredType(AccessPosition apos, TypePath path) {
result = inferType(this.getNodeAt(apos), path)
}
Declaration getTarget() { exists(this) and exists(result) }
}
predicate accessDeclarationPositionMatch(AccessPosition apos, DeclarationPosition dpos) {
apos = dpos
}
}
pragma[nomagic]
private TraitType inferAsyncBlockExprRootType(AsyncBlockExpr abe) {
// `typeEquality` handles the non-root case
@@ -1077,21 +1004,24 @@ private TraitType inferAsyncBlockExprRootType(AsyncBlockExpr abe) {
result = getFutureTraitType()
}
private module AwaitExprMatching = Matching<AwaitExprMatchingInput>;
final class AwaitTarget extends Expr {
AwaitTarget() { this = any(AwaitExpr ae).getExpr() }
Type getTypeAt(TypePath path) { result = inferType(this, path) }
}
private module AwaitSatisfiesConstraintInput implements SatisfiesConstraintInputSig<AwaitTarget> {
predicate relevantConstraint(AwaitTarget term, Type constraint) {
exists(term) and
constraint.(TraitType).getTrait() instanceof FutureTrait
}
}
pragma[nomagic]
private Type inferAwaitExprType(AstNode n, TypePath path) {
exists(AwaitExprMatchingInput::Access a, AwaitExprMatchingInput::AccessPosition apos |
n = a.getNodeAt(apos) and
result = AwaitExprMatching::inferAccessType(a, apos, path)
)
or
// This case is needed for `async` functions and blocks, where we assign
// the type `Future<Output = T>` directly instead of `impl Future<Output = T>`
//
// TODO: It would be better if we could handle this in the shared library
exists(TypePath exprPath |
result = inferType(n.(AwaitExpr).getExpr(), exprPath) and
SatisfiesConstraint<AwaitTarget, AwaitSatisfiesConstraintInput>::satisfiesConstraintType(n.(AwaitExpr)
.getExpr(), _, exprPath, result) and
exprPath.isCons(getFutureOutputTypeParameter(), path)
)
}

View File

@@ -1,7 +0,0 @@
use std::future::Future;
fn await_type_matching<T1, T2: Future<Output = T1>>(x: T2) -> T1 {
panic!(
"This function exists only in order to implement type inference for `.await` expressions."
);
}

View File

@@ -866,6 +866,108 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
private import BaseTypes
signature module SatisfiesConstraintInputSig<HasTypeTreeSig HasTypeTree> {
/** Holds if it is relevant to know if `term` satisfies `constraint`. */
predicate relevantConstraint(HasTypeTree term, Type constraint);
}
module SatisfiesConstraint<
HasTypeTreeSig HasTypeTree, SatisfiesConstraintInputSig<HasTypeTree> Input>
{
private import Input
private module IsInstantiationOfInput implements IsInstantiationOfInputSig<HasTypeTree> {
predicate potentialInstantiationOf(HasTypeTree tt, TypeAbstraction abs, TypeMention cond) {
exists(Type constraint, Type type |
type = tt.getTypeAt(TypePath::nil()) and
relevantConstraint(tt, constraint) and
rootTypesSatisfaction(type, constraint, abs, cond, _) and
// We only need to check instantiations where there are multiple candidates.
countConstraintImplementations(type, constraint) > 1
)
}
predicate relevantTypeMention(TypeMention constraint) {
rootTypesSatisfaction(_, _, _, constraint, _)
}
}
/** Holds if the type tree has the type `type` and should satisfy `constraint`. */
pragma[nomagic]
private predicate hasTypeConstraint(HasTypeTree term, Type type, Type constraint) {
type = term.getTypeAt(TypePath::nil()) and
relevantConstraint(term, constraint)
}
/**
* Holds if `tt` satisfies `constraint` through `abs`, `sub`, and `constraintMention`.
*/
pragma[nomagic]
private predicate hasConstraintMention(
HasTypeTree tt, TypeAbstraction abs, TypeMention sub, Type constraint,
TypeMention constraintMention
) {
exists(Type type | hasTypeConstraint(tt, 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<HasTypeTree, IsInstantiationOfInput>::isInstantiationOf(tt, abs, sub)
else any()
)
}
pragma[nomagic]
private predicate satisfiesConstraintTypeMention0(
HasTypeTree tt, Type constraint, TypeAbstraction abs, TypeMention sub, TypePath path, Type t
) {
exists(TypeMention constraintMention |
hasConstraintMention(tt, abs, sub, constraint, constraintMention) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, path, t)
)
}
pragma[nomagic]
private predicate satisfiesConstraintTypeMention1(
HasTypeTree tt, Type constraint, TypePath path, TypePath pathToTypeParamInSub
) {
exists(TypeAbstraction abs, TypeMention sub, TypeParameter tp |
satisfiesConstraintTypeMention0(tt, constraint, abs, sub, path, tp) and
tp = abs.getATypeParameter() and
sub.resolveTypeAt(pathToTypeParamInSub) = tp
)
}
/**
* Holds if the type tree at `tt` satisfies the constraint `constraint`
* with the type `t` at `path`.
*/
pragma[nomagic]
predicate satisfiesConstraintType(HasTypeTree tt, Type constraint, TypePath path, Type t) {
exists(TypeAbstraction abs |
satisfiesConstraintTypeMention0(tt, constraint, abs, _, path, t) and
not t = abs.getATypeParameter()
)
or
exists(TypePath prefix0, TypePath pathToTypeParamInSub, TypePath suffix |
satisfiesConstraintTypeMention1(tt, constraint, prefix0, pathToTypeParamInSub) and
tt.getTypeAt(pathToTypeParamInSub.appendInverse(suffix)) = t and
path = prefix0.append(suffix)
)
or
tt.getTypeAt(TypePath::nil()) = constraint and
t = tt.getTypeAt(path)
}
}
/** Provides the input to `Matching`. */
signature module MatchingInputSig {
/**
@@ -1129,11 +1231,8 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
adjustedAccessType(a, apos, target, path.appendInverse(suffix), result)
}
/** Holds if this relevant access has the type `type` and should satisfy `constraint`. */
predicate hasTypeConstraint(Type type, Type constraint) {
adjustedAccessType(a, apos, target, path, type) and
relevantAccessConstraint(a, target, apos, path, constraint)
}
/** Holds if this relevant access should satisfy `constraint`. */
Type getConstraint() { relevantAccessConstraint(a, target, apos, path, result) }
string toString() {
result = a.toString() + ", " + apos.toString() + ", " + path.toString()
@@ -1142,94 +1241,20 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
Location getLocation() { result = a.getLocation() }
}
private module IsInstantiationOfInput implements IsInstantiationOfInputSig<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
)
}
predicate relevantTypeMention(TypeMention constraint) {
rootTypesSatisfaction(_, _, _, constraint, _)
private module SatisfiesConstraintInput implements
SatisfiesConstraintInputSig<RelevantAccess>
{
predicate relevantConstraint(RelevantAccess at, Type constraint) {
constraint = at.getConstraint()
}
}
/**
* Holds if `at` satisfies `constraint` through `abs`, `sub`, and `constraintMention`.
*/
pragma[nomagic]
private predicate hasConstraintMention(
RelevantAccess at, TypeAbstraction abs, TypeMention sub, Type constraint,
TypeMention constraintMention
) {
exists(Type type | 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()
)
}
pragma[nomagic]
predicate satisfiesConstraintTypeMention0(
RelevantAccess at, Access a, AccessPosition apos, TypePath prefix, Type constraint,
TypeAbstraction abs, TypeMention sub, TypePath path, Type t
) {
exists(TypeMention constraintMention |
at = MkRelevantAccess(a, _, apos, prefix) and
hasConstraintMention(at, abs, sub, constraint, constraintMention) and
conditionSatisfiesConstraintTypeAt(abs, sub, constraintMention, path, t)
)
}
pragma[nomagic]
predicate satisfiesConstraintTypeMention1(
RelevantAccess at, Access a, AccessPosition apos, TypePath prefix, Type constraint,
TypePath path, TypePath pathToTypeParamInSub
) {
exists(TypeAbstraction abs, TypeMention sub, TypeParameter tp |
satisfiesConstraintTypeMention0(at, a, apos, prefix, constraint, abs, sub, path, tp) and
tp = abs.getATypeParameter() and
sub.resolveTypeAt(pathToTypeParamInSub) = tp
)
}
/**
* Holds if the type at `a`, `apos`, and `path` satisfies the constraint
* `constraint` with the type `t` at `path`.
*/
pragma[nomagic]
predicate satisfiesConstraintTypeMention(
predicate satisfiesConstraintType(
Access a, AccessPosition apos, TypePath prefix, Type constraint, TypePath path, Type t
) {
exists(TypeAbstraction abs |
satisfiesConstraintTypeMention0(_, a, apos, prefix, constraint, abs, _, path, t) and
not t = abs.getATypeParameter()
)
or
exists(
RelevantAccess at, TypePath prefix0, TypePath pathToTypeParamInSub, TypePath suffix
|
satisfiesConstraintTypeMention1(at, a, apos, prefix, constraint, prefix0,
pathToTypeParamInSub) and
at.getTypeAt(pathToTypeParamInSub.appendInverse(suffix)) = t and
path = prefix0.append(suffix)
exists(RelevantAccess at | at = MkRelevantAccess(a, _, apos, prefix) |
SatisfiesConstraint<RelevantAccess, SatisfiesConstraintInput>::satisfiesConstraintType(at,
constraint, path, t)
)
}
}
@@ -1366,7 +1391,7 @@ module Make1<LocationSig Location, InputSig1<Location> Input1> {
accessDeclarationPositionMatch(apos, dpos) and
typeParameterConstraintHasTypeParameter(target, dpos, pathToTp2, _, constraint, pathToTp,
tp) and
AccessConstraint::satisfiesConstraintTypeMention(a, apos, pathToTp2, constraint,
AccessConstraint::satisfiesConstraintType(a, apos, pathToTp2, constraint,
pathToTp.appendInverse(path), t)
)
}