Skip to content

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rust: Store arity in tuple type parameters
Type parameters are required to belong to a single type only. Since we store the arity for tuple types, we need to store the arity in tuple type parameters as well such that we can associate them to the tuple type of the same arity.
  • Loading branch information
paldepind committed Jul 15, 2025
commit 7c04c9f969ccfc85d03e938d2d44bd436a4d7b90
19 changes: 14 additions & 5 deletions rust/ql/lib/codeql/rust/internal/Type.qll
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ private import codeql.rust.elements.internal.generated.Synth
cached
newtype TType =
TTuple(int arity) {
exists(any(TupleTypeRepr t).getField(arity)) and Stages::TypeInferenceStage::ref()
arity = any(TupleTypeRepr t).getNumberOfFields() and
Stages::TypeInferenceStage::ref()
} or
TStruct(Struct s) or
TEnum(Enum e) or
Expand All @@ -19,7 +20,7 @@ newtype TType =
TRefType() or // todo: add mut?
TImplTraitType(ImplTraitTypeRepr impl) or
TSliceType() or
TTupleTypeParameter(int i) { exists(TTuple(i)) } or
TTupleTypeParameter(int arity, int i) { exists(TTuple(arity)) and i in [0 .. arity - 1] } or
Copy link
Contributor

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 make TType's definition recursive, right? Not sure if that is a problem, but we might want to avoid it.

Copy link
Contributor Author

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 then TTupleTypeParameter is materialized. So TTupleTypeParameter depends on TTuple but there is no recursion. I'm just guessing though so maybe I'm wrong.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

TTypeParamTypeParameter(TypeParam t) or
TAssociatedTypeTypeParameter(TypeAlias t) { any(TraitItemNode trait).getAnAssocItem() = t } or
TArrayTypeParameter() or
Expand Down Expand Up @@ -83,7 +84,7 @@ class TupleType extends Type, TTuple {

override TupleField getTupleField(int i) { none() }

override TypeParameter getTypeParameter(int i) { result = TTupleTypeParameter(i) and i < arity }
override TypeParameter getTypeParameter(int i) { result = TTupleTypeParameter(arity, i) }

int getArity() { result = arity }

Expand Down Expand Up @@ -358,12 +359,20 @@ class AssociatedTypeTypeParameter extends TypeParameter, TAssociatedTypeTypePara
* their positional index.
*/
class TupleTypeParameter extends TypeParameter, TTupleTypeParameter {
override string toString() { result = this.getIndex().toString() }
private int arity;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really necessary to have arity as a field? Is it important to distinguish the first element of a pair from the first element of a triple?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unintuitively the answer is yes. Before 7c04c9f arity was not in TupleTypeParameter. But the type inference library relies on the assumption that every type parameter corresponds to exactly one type, so not having the arity caused problems.

I've noted this in the QLdoc for TupleTypeParameter now.

Copy link
Contributor

Choose a reason for hiding this comment

The 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() { this = TTupleTypeParameter(result) }
int getIndex() { result = index }

/** Gets the arity of this tuple type parameter. */
int getArity() { result = arity }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeParameters don't really have an arity, right? Do we need this method for anything? Perhaps it should be replaced by

TypeType getTupleType() { result = TTuple (arity) }

}

/** An implicit array type parameter. */
Expand Down
25 changes: 16 additions & 9 deletions rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ private module Input1 implements InputSig1<Location> {
node = tp0.(ImplTraitTypeTypeParameter).getImplTraitTypeRepr()
)
or
kind = 2 and
id = tp0.(TupleTypeParameter).getIndex()
exists(TupleTypeParameter ttp, int maxArity |
maxArity = max(int i | i = any(TupleType tt).getArity()) and
tp0 = ttp and
kind = 2 and
id = ttp.getArity() * maxArity + ttp.getIndex()
)
|
tp0 order by kind, id
)
Expand Down Expand Up @@ -324,11 +328,14 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat
prefix1.isEmpty() and
prefix2 = TypePath::singleton(TRefTypeParameter())
or
exists(int i |
exists(int i, int arity |
prefix1.isEmpty() and
prefix2 = TypePath::singleton(TTupleTypeParameter(i))
prefix2 = TypePath::singleton(TTupleTypeParameter(arity, i))
|
n1 = n2.(TupleExpr).getField(i) or
arity = n2.(TupleExpr).getNumberOfFields() and
n1 = n2.(TupleExpr).getField(i)
or
arity = n2.(TuplePat).getNumberOfFields() and
n1 = n2.(TuplePat).getField(i)
)
or
Expand Down Expand Up @@ -1077,7 +1084,7 @@ private Type inferTupleIndexExprType(FieldExpr fe, TypePath path) {
exists(int i, TypePath path0 |
fe.getIdentifier().getText() = i.toString() and
result = inferType(fe.getContainer(), path0) and
path0.isCons(TTupleTypeParameter(i), path) and
path0.isCons(TTupleTypeParameter(_, i), path) and
fe.getIdentifier().getText() = i.toString()
)
}
Expand All @@ -1088,12 +1095,12 @@ private Type inferTupleContainerExprType(Expr e, TypePath path) {
// a tuple struct or a tuple. It is only correct to let type information flow
// from `t.n` to tuple type parameters of `t` in 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 |
exists(int i, TypePath path0, FieldExpr fe, int arity |
e = fe.getContainer() and
fe.getIdentifier().getText() = i.toString() and
inferType(fe.getContainer()) instanceof TupleType and
arity = inferType(fe.getContainer()).(TupleType).getArity() and
result = inferType(fe, path0) and
path = TypePath::cons(TTupleTypeParameter(i), path0)
path = TypePath::cons(TTupleTypeParameter(arity, i), path0) // FIXME:
)
}

Expand Down
2 changes: 1 addition & 1 deletion rust/ql/lib/codeql/rust/internal/TypeMention.qll
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TupleTypeReprMention extends TypeMention instanceof TupleTypeRepr {
or
exists(TypePath suffix, int i |
result = super.getField(i).(TypeMention).resolveTypeAt(suffix) and
path = TypePath::cons(TTupleTypeParameter(i), suffix)
path = TypePath::cons(TTupleTypeParameter(super.getNumberOfFields(), i), suffix)
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion rust/ql/test/library-tests/type-inference/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2354,7 +2354,7 @@ mod tuples {
// `a` and `b` to be inferred.
let a = Default::default(); // $ target=default type=a:i64
let b = Default::default(); // $ target=default type=b:bool
let pair = (a, b); // $ type=pair:0.i64 type=pair:1.bool
let pair = (a, b); // $ type=pair:0(2).i64 type=pair:1(2).bool
let i: i64 = pair.0;
let j: bool = pair.1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ pub fn tuple_patterns() {
// With rest pattern
match tuple {
(first, ..) => {
let tuple_first = first; // $ type=tuple_first:i32
let tuple_first = first; // $ MISSING: type=tuple_first:i32
println!("First element: {}", tuple_first);
}
}
Expand Down Expand Up @@ -630,7 +630,7 @@ pub fn rest_patterns() {
// RestPat - Rest patterns (..)
match tuple {
(first, ..) => {
let rest_first = first; // $ type=rest_first:i32
let rest_first = first; // $ MISSING: type=rest_first:i32
println!("First with rest: {}", rest_first);
}
}
Expand All @@ -644,7 +644,7 @@ pub fn rest_patterns() {

match tuple {
(first, .., last) => {
let rest_start = first; // $ type=rest_start:i32
let rest_start = first; // $ MISSING: type=rest_start:i32
let rest_end = last; // $ MISSING: type=rest_end:u8
println!("First and last: {}, {}", rest_start, rest_end);
}
Expand Down Expand Up @@ -772,7 +772,7 @@ pub fn patterns_in_function_parameters() {
let red = extract_color(color); // $ target=extract_color type=red:u8

let tuple = (42i32, 3.14f64, true);
let tuple_extracted = extract_tuple(tuple); // $ target=extract_tuple type=tuple_extracted:0.i32 type=tuple_extracted:1.bool
let tuple_extracted = extract_tuple(tuple); // $ target=extract_tuple type=tuple_extracted:0(2).i32 type=tuple_extracted:1(2).bool
}

#[rustfmt::skip]
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy