-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Rust: Type inference for tuples #20041
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
21c030f
03a9a16
7c04c9f
97e7794
8858f21
a508089
bbd7ed5
7f8829a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,14 +9,23 @@ private import codeql.rust.elements.internal.generated.Synth | |
|
||
cached | ||
newtype TType = | ||
TUnit() or | ||
TStruct(Struct s) { Stages::TypeInferenceStage::ref() } or | ||
TTuple(int arity) { | ||
arity = | ||
[ | ||
any(TupleTypeRepr t).getNumberOfFields(), | ||
any(TupleExpr e).getNumberOfFields(), | ||
any(TuplePat p).getNumberOfFields() | ||
] and | ||
Stages::TypeInferenceStage::ref() | ||
} or | ||
TStruct(Struct s) or | ||
TEnum(Enum e) or | ||
TTrait(Trait t) or | ||
TArrayType() or // todo: add size? | ||
TRefType() or // todo: add mut? | ||
TImplTraitType(ImplTraitTypeRepr impl) or | ||
TSliceType() or | ||
TTupleTypeParameter(int arity, int i) { exists(TTuple(arity)) and i in [0 .. arity - 1] } or | ||
TTypeParamTypeParameter(TypeParam t) or | ||
TAssociatedTypeTypeParameter(TypeAlias t) { any(TraitItemNode trait).getAnAssocItem() = t } or | ||
TArrayTypeParameter() or | ||
|
@@ -55,21 +64,33 @@ abstract class Type extends TType { | |
abstract Location getLocation(); | ||
} | ||
|
||
/** The unit type `()`. */ | ||
class UnitType extends Type, TUnit { | ||
UnitType() { this = TUnit() } | ||
/** A tuple type `(T, ...)`. */ | ||
class TupleType extends Type, TTuple { | ||
private int arity; | ||
|
||
TupleType() { this = TTuple(arity) } | ||
|
||
override StructField getStructField(string name) { none() } | ||
|
||
override TupleField getTupleField(int i) { none() } | ||
|
||
override TypeParameter getTypeParameter(int i) { none() } | ||
override TypeParameter getTypeParameter(int i) { result = TTupleTypeParameter(arity, i) } | ||
|
||
override string toString() { result = "()" } | ||
/** Gets the arity of this tuple type. */ | ||
int getArity() { result = arity } | ||
|
||
override string toString() { result = "(T_" + arity + ")" } | ||
|
||
override Location getLocation() { result instanceof EmptyLocation } | ||
} | ||
|
||
/** The unit type `()`. */ | ||
class UnitType extends TupleType, TTuple { | ||
UnitType() { this = TTuple(0) } | ||
|
||
override string toString() { result = "()" } | ||
} | ||
|
||
abstract private class StructOrEnumType extends Type { | ||
abstract ItemNode asItemNode(); | ||
} | ||
|
@@ -329,6 +350,30 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara | |
override Location getLocation() { result = typeAlias.getLocation() } | ||
} | ||
|
||
/** | ||
* A tuple type parameter. For instance the `T` in `(T, U)`. | ||
* | ||
* Since tuples are structural their type parameters can be represented as their | ||
* positional index. The type inference library requires that type parameters | ||
* belong to a single type, so we also include the arity of the tuple type. | ||
*/ | ||
class TupleTypeParameter extends TypeParameter, TTupleTypeParameter { | ||
private int arity; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it really necessary to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unintuitively the answer is yes. Before 7c04c9f arity was not in I've noted this in the QLdoc for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I was wondering this as well. |
||
private int index; | ||
|
||
TupleTypeParameter() { this = TTupleTypeParameter(arity, index) } | ||
|
||
override string toString() { result = index.toString() + "(" + arity + ")" } | ||
|
||
override Location getLocation() { result instanceof EmptyLocation } | ||
|
||
/** Gets the index of this tuple type parameter. */ | ||
int getIndex() { result = index } | ||
|
||
/** Gets the tuple type that corresponds to this tuple type parameter. */ | ||
TupleType getTupleType() { result = TTuple(arity) } | ||
} | ||
|
||
/** An implicit array type parameter. */ | ||
class ArrayTypeParameter extends TypeParameter, TArrayTypeParameter { | ||
override string toString() { result = "[T;...]" } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,6 +103,13 @@ private module Input1 implements InputSig1<Location> { | |
node = tp0.(SelfTypeParameter).getTrait() or | ||
node = tp0.(ImplTraitTypeTypeParameter).getImplTraitTypeRepr() | ||
) | ||
or | ||
exists(TupleTypeParameter ttp, int maxArity | | ||
maxArity = max(int i | i = any(TupleType tt).getArity()) and | ||
tp0 = ttp and | ||
kind = 2 and | ||
id = ttp.getTupleType().getArity() * maxArity + ttp.getIndex() | ||
) | ||
| | ||
tp0 order by kind, id | ||
) | ||
|
@@ -229,7 +236,7 @@ private Type inferLogicalOperationType(AstNode n, TypePath path) { | |
private Type inferAssignmentOperationType(AstNode n, TypePath path) { | ||
n instanceof AssignmentOperation and | ||
path.isEmpty() and | ||
result = TUnit() | ||
result instanceof UnitType | ||
} | ||
|
||
pragma[nomagic] | ||
|
@@ -321,6 +328,17 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat | |
prefix1.isEmpty() and | ||
prefix2 = TypePath::singleton(TRefTypeParameter()) | ||
or | ||
exists(int i, int arity | | ||
prefix1.isEmpty() and | ||
prefix2 = TypePath::singleton(TTupleTypeParameter(arity, i)) | ||
| | ||
arity = n2.(TupleExpr).getNumberOfFields() and | ||
n1 = n2.(TupleExpr).getField(i) | ||
or | ||
arity = n2.(TuplePat).getTupleArity() and | ||
n1 = n2.(TuplePat).getField(i) | ||
) | ||
or | ||
exists(BlockExpr be | | ||
n1 = be and | ||
n2 = be.getStmtList().getTailExpr() and | ||
|
@@ -534,6 +552,12 @@ private Type inferStructExprType(AstNode n, TypePath path) { | |
) | ||
} | ||
|
||
pragma[nomagic] | ||
private Type inferTupleRootType(AstNode n) { | ||
// `typeEquality` handles the non-root cases | ||
result = TTuple([n.(TupleExpr).getNumberOfFields(), n.(TuplePat).getTupleArity()]) | ||
} | ||
|
||
pragma[nomagic] | ||
private Type inferPathExprType(PathExpr pe, TypePath path) { | ||
// nullary struct/variant constructors | ||
|
@@ -1055,6 +1079,42 @@ private Type inferFieldExprType(AstNode n, TypePath path) { | |
) | ||
} | ||
|
||
pragma[nomagic] | ||
private Type inferTupleIndexExprType(FieldExpr fe, TypePath path) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks fine to me, although I had expected something more similar to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked into that, but |
||
exists(int i, TypePath path0 | | ||
fe.getIdentifier().getText() = i.toString() and | ||
result = inferType(fe.getContainer(), path0) and | ||
path0.isCons(TTupleTypeParameter(_, i), path) and | ||
fe.getIdentifier().getText() = i.toString() | ||
) | ||
} | ||
|
||
/** Infers the type of `t` in `t.n` when `t` is a tuple. */ | ||
private Type inferTupleContainerExprType(Expr e, TypePath path) { | ||
// NOTE: For a field expression `t.n` where `n` is a number `t` might be a | ||
// tuple as in: | ||
// ```rust | ||
// let t = (Default::default(), 2); | ||
// let s: String = t.0; | ||
// ``` | ||
// But it could also be a tuple struct as in: | ||
// ```rust | ||
// struct T(String, u32); | ||
// let t = T(Default::default(), 2); | ||
// let s: String = t.0; | ||
// ``` | ||
// We need type information to flow from `t.n` to tuple type parameters of `t` | ||
// in the former case but not the latter case. Hence we include the condition | ||
// that the root type of `t` must be a tuple type. | ||
exists(int i, TypePath path0, FieldExpr fe, int arity | | ||
e = fe.getContainer() and | ||
fe.getIdentifier().getText() = i.toString() and | ||
arity = inferType(fe.getContainer()).(TupleType).getArity() and | ||
result = inferType(fe, path0) and | ||
path = TypePath::cons(TTupleTypeParameter(arity, i), path0) | ||
) | ||
} | ||
|
||
/** Gets the root type of the reference node `ref`. */ | ||
pragma[nomagic] | ||
private Type inferRefNodeType(AstNode ref) { | ||
|
@@ -1943,12 +2003,19 @@ private module Cached { | |
or | ||
result = inferStructExprType(n, path) | ||
or | ||
result = inferTupleRootType(n) and | ||
path.isEmpty() | ||
or | ||
result = inferPathExprType(n, path) | ||
or | ||
result = inferCallExprBaseType(n, path) | ||
or | ||
result = inferFieldExprType(n, path) | ||
or | ||
result = inferTupleIndexExprType(n, path) | ||
or | ||
result = inferTupleContainerExprType(n, path) | ||
or | ||
result = inferRefNodeType(n) and | ||
path.isEmpty() | ||
or | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
category: minorAnalysis | ||
--- | ||
* Type inference now supports tuple types. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
multipleCallTargets | ||
| main.rs:445:18:445:24 | n.len() | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way of defining
arity
would makeTType
's definition recursive, right? Not sure if that is a problem, but we might want to avoid it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. No recursion markers show up in VScode, so I think what's happening is that
TTuple
is materialized first and thenTTupleTypeParameter
is materialized. SoTTupleTypeParameter
depends onTTuple
but there is no recursion. I'm just guessing though so maybe I'm wrong.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine with me, @hvitved once commented on a case where I defined a recursive type where it wasn't necessary. Not sure if it is a problem here though. In any case if it's easy to rewrite if it turns out to be a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Let's keep it and change it if it's actually suboptimal.