Rust: Unify handling of struct and tuple constructors

This commit is contained in:
Simon Friis Vindum
2026-03-17 16:32:04 +01:00
parent 8e19b05a25
commit 97670b3674
5 changed files with 154 additions and 297 deletions

View File

@@ -50,5 +50,10 @@ module Impl {
or
result = this.getVariant().getStructField(name)
}
/** Gets the `i`th struct field of the instantiated struct or variant. */
StructField getNthStructField(int i) {
result = [this.getStruct().getNthStructField(i), this.getVariant().getNthStructField(i)]
}
}
}

View File

@@ -35,6 +35,12 @@ module Impl {
/** Gets a record field, if any. */
StructField getAStructField() { result = this.getStructField(_) }
/** Gets the `i`th struct field, if any. */
pragma[nomagic]
StructField getNthStructField(int i) {
result = this.getFieldList().(StructFieldList).getField(i)
}
/** Gets the `i`th tuple field, if any. */
pragma[nomagic]
TupleField getTupleField(int i) { result = this.getFieldList().(TupleFieldList).getField(i) }

View File

@@ -42,6 +42,13 @@ module Impl {
)
}
/** Gets the `i`th struct field of the instantiated struct or variant. */
StructField getNthStructField(int i) {
exists(PathResolution::ItemNode item | item = this.getResolvedPath(_) |
result = [item.(Struct).getNthStructField(i), item.(Variant).getNthStructField(i)]
)
}
/** Gets the struct pattern for the field `name`. */
pragma[nomagic]
StructPatField getPatField(string name) {

View File

@@ -32,6 +32,12 @@ module Impl {
result.getName().getText() = name
}
/** Gets the `i`th struct field, if any. */
pragma[nomagic]
StructField getNthStructField(int i) {
result = this.getFieldList().(StructFieldList).getField(i)
}
/** Gets the `i`th tuple field, if any. */
pragma[nomagic]
TupleField getTupleField(int i) { result = this.getFieldList().(TupleFieldList).getField(i) }

View File

@@ -872,188 +872,6 @@ private Type inferTypeEquality(AstNode n, TypePath path) {
)
}
/**
* A matching configuration for resolving types of struct expressions
* like `Foo { bar = baz }`.
*
* This also includes nullary struct expressions like `None`.
*/
private module StructExprMatchingInput implements MatchingInputSig {
private newtype TPos =
TFieldPos(string name) { exists(any(Declaration decl).getField(name)) } or
TStructPos()
class DeclarationPosition extends TPos {
string asFieldPos() { this = TFieldPos(result) }
predicate isStructPos() { this = TStructPos() }
string toString() {
result = this.asFieldPos()
or
this.isStructPos() and
result = "(struct)"
}
}
abstract class Declaration extends AstNode {
final TypeParameter getTypeParameter(TypeParameterPosition ppos) {
typeParamMatchPosition(this.getTypeItem().getGenericParamList().getATypeParam(), result, ppos)
}
abstract StructField getField(string name);
abstract TypeItem getTypeItem();
Type getDeclaredType(DeclarationPosition dpos, TypePath path) {
// type of a field
exists(TypeMention tp |
tp = this.getField(dpos.asFieldPos()).getTypeRepr() and
result = tp.getTypeAt(path)
)
or
// type parameter of the struct itself
dpos.isStructPos() and
result = this.getTypeParameter(_) and
path = TypePath::singleton(result)
or
// type of the struct or enum itself
dpos.isStructPos() and
path.isEmpty() and
result = TDataType(this.getTypeItem())
}
}
private class StructDecl extends Declaration, Struct {
StructDecl() { this.isStruct() or this.isUnit() }
override StructField getField(string name) { result = this.getStructField(name) }
override TypeItem getTypeItem() { result = this }
}
private class StructVariantDecl extends Declaration, Variant {
StructVariantDecl() { this.isStruct() or this.isUnit() }
override StructField getField(string name) { result = this.getStructField(name) }
override TypeItem getTypeItem() { result = this.getEnum() }
}
class AccessPosition = DeclarationPosition;
abstract class Access extends AstNode {
pragma[nomagic]
abstract AstNode getNodeAt(AccessPosition apos);
pragma[nomagic]
Type getInferredType(AccessPosition apos, TypePath path) {
result = inferType(this.getNodeAt(apos), path)
}
pragma[nomagic]
abstract Path getStructPath();
pragma[nomagic]
Declaration getTarget() { result = resolvePath(this.getStructPath()) }
pragma[nomagic]
Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
// Handle constructions that use `Self {...}` syntax
exists(TypeMention tm, TypePath path0 |
tm = this.getStructPath() and
result = tm.getTypeAt(path0) and
path0.isCons(TTypeParamTypeParameter(apos.asTypeParam()), path)
)
}
/**
* Holds if the return type of this struct expression at `path` may have to
* be inferred from the context.
*/
pragma[nomagic]
predicate hasUnknownTypeAt(DeclarationPosition pos, TypePath path) {
exists(Declaration d, TypeParameter tp |
d = this.getTarget() and
pos.isStructPos() and
tp = d.getDeclaredType(pos, path) and
not exists(DeclarationPosition fieldPos |
not fieldPos.isStructPos() and
tp = d.getDeclaredType(fieldPos, _)
) and
// check that no explicit type arguments have been supplied for `tp`
not exists(TypeArgumentPosition tapos |
exists(this.getTypeArgument(tapos, _)) and
TTypeParamTypeParameter(tapos.asTypeParam()) = tp
)
)
}
}
private class StructExprAccess extends Access, StructExpr {
override Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
result = super.getTypeArgument(apos, path)
or
exists(TypePath suffix |
suffix.isCons(TTypeParamTypeParameter(apos.asTypeParam()), path) and
result = CertainTypeInference::inferCertainType(this, suffix)
)
}
override AstNode getNodeAt(AccessPosition apos) {
result = this.getFieldExpr(apos.asFieldPos()).getExpr()
or
result = this and
apos.isStructPos()
}
override Path getStructPath() { result = this.getPath() }
}
/**
* A potential nullary struct/variant construction such as `None`.
*/
private class PathExprAccess extends Access, PathExpr {
PathExprAccess() { not exists(CallExpr ce | this = ce.getFunction()) }
override AstNode getNodeAt(AccessPosition apos) {
result = this and
apos.isStructPos()
}
override Path getStructPath() { result = this.getPath() }
}
predicate accessDeclarationPositionMatch(AccessPosition apos, DeclarationPosition dpos) {
apos = dpos
}
}
private module StructExprMatching = Matching<StructExprMatchingInput>;
pragma[nomagic]
private Type inferStructExprType0(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
) {
exists(StructExprMatchingInput::Access a, StructExprMatchingInput::AccessPosition apos |
n = a.getNodeAt(apos) and
hasReceiver = false and
if apos.isStructPos() then pos.isReturn() else pos.asPosition() = 0 // the actual position doesn't matter, as long as it is positional
|
result = StructExprMatching::inferAccessType(a, apos, path)
or
a.hasUnknownTypeAt(apos, path) and
result = TUnknownType()
)
}
/**
* Gets the type of `n` at `path`, where `n` is either a struct expression or
* a field expression of a struct expression.
*/
private predicate inferStructExprType =
ContextTyping::CheckContextTyping<inferStructExprType0/4>::check/2;
pragma[nomagic]
private TupleType inferTupleRootType(AstNode n) {
// `typeEquality` handles the non-root cases
@@ -3083,14 +2901,14 @@ private Type inferFunctionCallTypePreCheck(
private predicate inferFunctionCallType =
ContextTyping::CheckContextTyping<inferFunctionCallTypePreCheck/4>::check/2;
abstract private class TupleLikeConstructor extends Addressable {
abstract private class Constructor extends Addressable {
final TypeParameter getTypeParameter(TypeParameterPosition ppos) {
typeParamMatchPosition(this.getTypeItem().getGenericParamList().getATypeParam(), result, ppos)
}
abstract TypeItem getTypeItem();
abstract TupleField getTupleField(int i);
abstract TypeRepr getParameterTypeRepr(int pos);
Type getReturnType(TypePath path) {
result = TDataType(this.getTypeItem()) and
@@ -3105,65 +2923,59 @@ abstract private class TupleLikeConstructor extends Addressable {
or
pos.isReturn() and
result = this.getReturnType(path)
or
pos.isTypeQualifier() and
result = this.getReturnType(path)
}
Type getParameterType(int pos, TypePath path) {
result = this.getTupleField(pos).getTypeRepr().(TypeMention).getTypeAt(path)
result = this.getParameterTypeRepr(pos).(TypeMention).getTypeAt(path)
}
}
private class TupleLikeStruct extends TupleLikeConstructor instanceof Struct {
TupleLikeStruct() { this.isTuple() }
private class StructConstructor extends Constructor instanceof Struct {
override TypeItem getTypeItem() { result = this }
override TupleField getTupleField(int i) { result = Struct.super.getTupleField(i) }
override TypeRepr getParameterTypeRepr(int i) {
result = [super.getTupleField(i).getTypeRepr(), super.getNthStructField(i).getTypeRepr()]
}
}
private class TupleLikeVariant extends TupleLikeConstructor instanceof Variant {
TupleLikeVariant() { this.isTuple() }
private class VariantConstructor extends Constructor instanceof Variant {
override TypeItem getTypeItem() { result = super.getEnum() }
override TupleField getTupleField(int i) { result = Variant.super.getTupleField(i) }
override TypeRepr getParameterTypeRepr(int i) {
result = [super.getTupleField(i).getTypeRepr(), super.getNthStructField(i).getTypeRepr()]
}
}
/**
* A matching configuration for resolving types of tuple-like variants and tuple
* structs such as `Result::Ok(42)`.
* A matching configuration for resolving types of constructors of enums and
* structs, such as `Result::Ok(42)`, `Foo { bar = 1 }` and `None`.
*/
private module TupleLikeConstructionMatchingInput implements MatchingInputSig {
private module ConstructorMatchingInput implements MatchingInputSig {
import FunctionPositionMatchingInput
class Declaration = TupleLikeConstructor;
class Declaration = Constructor;
class Access extends NonAssocCallExpr, ContextTyping::ContextTypedCallCand {
Access() {
this instanceof CallExprImpl::TupleStructExpr or
this instanceof CallExprImpl::TupleVariantExpr
}
abstract class Access extends AstNode {
abstract Type getInferredType(FunctionPosition pos, TypePath path);
override Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
result = NonAssocCallExpr.super.getTypeArgument(apos, path)
}
abstract Declaration getTarget();
Declaration getTarget() { result = this.resolveCallTargetViaPathResolution() }
abstract AstNode getNodeAt(AccessPosition apos);
abstract Type getTypeArgument(TypeArgumentPosition apos, TypePath path);
/**
* Holds if the return type of this tuple-like construction at `path` may have to be inferred
* from the context, for example in `Result::Ok(42)` the error type has to be inferred from the
* context.
* Holds if the return type of this constructor expression at `path` may
* have to be inferred from the context. For example in `Result::Ok(42)` the
* error type has to be inferred from the context.
*/
pragma[nomagic]
predicate hasUnknownTypeAt(FunctionPosition pos, TypePath path) {
exists(TupleLikeConstructor tc, TypeParameter tp |
tc = this.getTarget() and
exists(Declaration d, TypeParameter tp |
d = this.getTarget() and
pos.isReturn() and
tp = tc.getReturnType(path) and
not tp = tc.getParameterType(_, _) and
tp = d.getDeclaredType(pos, path) and
not exists(FunctionPosition pos2 | not pos2.isReturn() and tp = d.getDeclaredType(pos2, _)) and
// check that no explicit type arguments have been supplied for `tp`
not exists(TypeArgumentPosition tapos |
exists(this.getTypeArgument(tapos, _)) and
@@ -3172,25 +2984,93 @@ private module TupleLikeConstructionMatchingInput implements MatchingInputSig {
)
}
}
private class NonAssocCallAccess extends Access, NonAssocCallExpr,
ContextTyping::ContextTypedCallCand
{
NonAssocCallAccess() {
this instanceof CallExprImpl::TupleStructExpr or
this instanceof CallExprImpl::TupleVariantExpr
}
override Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
result = NonAssocCallExpr.super.getTypeArgument(apos, path)
}
override AstNode getNodeAt(AccessPosition apos) {
result = NonAssocCallExpr.super.getNodeAt(apos)
}
override Type getInferredType(FunctionPosition pos, TypePath path) {
result = NonAssocCallExpr.super.getInferredType(pos, path)
}
override Declaration getTarget() { result = this.resolveCallTargetViaPathResolution() }
}
abstract private class StructAccess extends Access instanceof PathAstNode {
pragma[nomagic]
override Type getInferredType(AccessPosition apos, TypePath path) {
result = inferType(this.getNodeAt(apos), path)
}
pragma[nomagic]
override Declaration getTarget() { result = resolvePath(super.getPath()) }
pragma[nomagic]
override Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
// Handle constructions that use `Self {...}` syntax
exists(TypeMention tm, TypePath path0 |
tm = super.getPath() and
result = tm.getTypeAt(path0) and
path0.isCons(TTypeParamTypeParameter(apos.asTypeParam()), path)
)
}
}
private class StructExprAccess extends StructAccess, StructExpr {
override Type getTypeArgument(TypeArgumentPosition apos, TypePath path) {
result = super.getTypeArgument(apos, path)
or
exists(TypePath suffix |
suffix.isCons(TTypeParamTypeParameter(apos.asTypeParam()), path) and
result = CertainTypeInference::inferCertainType(this, suffix)
)
}
override AstNode getNodeAt(AccessPosition apos) {
result =
this.getFieldExpr(this.getNthStructField(apos.asPosition()).getName().getText()).getExpr()
or
result = this and apos.isReturn()
}
}
/** A potential nullary struct/variant construction such as `None`. */
private class PathExprAccess extends StructAccess, PathExpr {
PathExprAccess() { not exists(CallExpr ce | this = ce.getFunction()) }
override AstNode getNodeAt(AccessPosition apos) { result = this and apos.isReturn() }
}
}
private module TupleLikeConstructionMatching = Matching<TupleLikeConstructionMatchingInput>;
private module ConstructorMatching = Matching<ConstructorMatchingInput>;
pragma[nomagic]
private Type inferTupleLikeConstructionTypePreCheck(
private Type inferConstructorTypePreCheck(
AstNode n, FunctionPosition pos, boolean hasReceiver, TypePath path
) {
hasReceiver = false and
exists(TupleLikeConstructionMatchingInput::Access a | n = a.getNodeAt(pos) |
result = TupleLikeConstructionMatching::inferAccessType(a, pos, path)
exists(ConstructorMatchingInput::Access a | n = a.getNodeAt(pos) |
result = ConstructorMatching::inferAccessType(a, pos, path)
or
a.hasUnknownTypeAt(pos, path) and
result = TUnknownType()
)
}
private predicate inferTupleLikeConstructionType =
ContextTyping::CheckContextTyping<inferTupleLikeConstructionTypePreCheck/4>::check/2;
private predicate inferConstructorType =
ContextTyping::CheckContextTyping<inferConstructorTypePreCheck/4>::check/2;
/**
* A matching configuration for resolving types of operations like `a + b`.
@@ -3676,71 +3556,27 @@ private Type inferDereferencedExprPtrType(AstNode n, TypePath path) {
}
/**
* A matching configuration for resolving types of struct patterns
* like `let Foo { bar } = ...`.
* A matching configuration for resolving types of constructor patterns like
* `let Foo { bar } = ...` or `let Some(x) = ...`.
*/
private module StructPatMatchingInput implements MatchingInputSig {
class DeclarationPosition = StructExprMatchingInput::DeclarationPosition;
class Declaration = StructExprMatchingInput::Declaration;
class AccessPosition = DeclarationPosition;
class Access extends StructPat {
Type getTypeArgument(TypeArgumentPosition apos, TypePath path) { none() }
AstNode getNodeAt(AccessPosition apos) {
result = this.getPatField(apos.asFieldPos()).getPat()
or
result = this and
apos.isStructPos()
}
Type getInferredType(AccessPosition apos, TypePath path) {
result = inferType(this.getNodeAt(apos), path)
or
// The struct/enum type is supplied explicitly as a type qualifier, e.g.
// `let Foo<Bar>::Variant { ... } = ...`.
apos.isStructPos() and
result = this.getPath().(TypeMention).getTypeAt(path)
}
Declaration getTarget() { result = resolvePath(this.getPath()) }
}
predicate accessDeclarationPositionMatch(AccessPosition apos, DeclarationPosition dpos) {
apos = dpos
}
}
private module StructPatMatching = Matching<StructPatMatchingInput>;
/**
* Gets the type of `n` at `path`, where `n` is either a struct pattern or
* a field pattern of a struct pattern.
*/
pragma[nomagic]
private Type inferStructPatType(AstNode n, TypePath path) {
exists(StructPatMatchingInput::Access a, StructPatMatchingInput::AccessPosition apos |
n = a.getNodeAt(apos) and
result = StructPatMatching::inferAccessType(a, apos, path)
)
}
/**
* A matching configuration for resolving types of tuple struct patterns
* like `let Some(x) = ...`.
*/
private module TupleStructPatMatchingInput implements MatchingInputSig {
private module ConstructorPatMatchingInput implements MatchingInputSig {
import FunctionPositionMatchingInput
class Declaration = TupleLikeConstructor;
class Declaration = ConstructorMatchingInput::Declaration;
class Access extends Pat instanceof PathAstNode {
Access() { this instanceof TupleStructPat or this instanceof StructPat }
class Access extends TupleStructPat {
Type getTypeArgument(TypeArgumentPosition apos, TypePath path) { none() }
AstNode getNodeAt(AccessPosition apos) {
result = this.getField(apos.asPosition())
this =
any(StructPat sp |
result =
sp.getPatField(sp.getNthStructField(apos.asPosition()).getName().getText()).getPat()
)
or
result = this.(TupleStructPat).getField(apos.asPosition())
or
result = this and
apos.isReturn()
@@ -3750,26 +3586,27 @@ private module TupleStructPatMatchingInput implements MatchingInputSig {
result = inferType(this.getNodeAt(apos), path)
or
// The struct/enum type is supplied explicitly as a type qualifier, e.g.
// `let Foo<Bar>::Variant { ... } = ...` or
// `let Option::<Foo>::Some(x) = ...`.
apos.isTypeQualifier() and
result = this.getPath().(TypeMention).getTypeAt(path)
apos.isReturn() and
result = super.getPath().(TypeMention).getTypeAt(path)
}
Declaration getTarget() { result = resolvePath(this.getPath()) }
Declaration getTarget() { result = resolvePath(super.getPath()) }
}
}
private module TupleStructPatMatching = Matching<TupleStructPatMatchingInput>;
private module ConstructorPatMatching = Matching<ConstructorPatMatchingInput>;
/**
* Gets the type of `n` at `path`, where `n` is either a tuple struct pattern or
* a positional pattern of a tuple struct pattern.
* Gets the type of `n` at `path`, where `n` is a pattern for a constructor,
* either a struct pattern or a tuple-struct pattern.
*/
pragma[nomagic]
private Type inferTupleStructPatType(AstNode n, TypePath path) {
exists(TupleStructPatMatchingInput::Access a, TupleStructPatMatchingInput::AccessPosition apos |
private Type inferConstructorPatType(AstNode n, TypePath path) {
exists(ConstructorPatMatchingInput::Access a, FunctionPosition apos |
n = a.getNodeAt(apos) and
result = TupleStructPatMatching::inferAccessType(a, apos, path)
result = ConstructorPatMatching::inferAccessType(a, apos, path)
)
}
@@ -4080,11 +3917,9 @@ private module Cached {
or
result = inferTypeEquality(n, path)
or
result = inferStructExprType(n, path)
or
result = inferFunctionCallType(n, path)
or
result = inferTupleLikeConstructionType(n, path)
result = inferConstructorType(n, path)
or
result = inferOperationType(n, path)
or
@@ -4106,9 +3941,7 @@ private module Cached {
or
result = inferClosureExprType(n, path)
or
result = inferStructPatType(n, path)
or
result = inferTupleStructPatType(n, path)
result = inferConstructorPatType(n, path)
)
}
}
@@ -4157,9 +3990,9 @@ private module Debug {
t = inferFunctionCallType(n, path)
}
predicate debugInferTupleLikeConstructionType(AstNode n, TypePath path, Type t) {
predicate debugInferConstructorType(AstNode n, TypePath path, Type t) {
n = getRelevantLocatable() and
t = inferTupleLikeConstructionType(n, path)
t = inferConstructorType(n, path)
}
predicate debugTypeMention(TypeMention tm, TypePath path, Type type) {