Content-Length: 732703 | pFad | http://github.com/github/codeql/pull/20041/commits/03a9a1688e18e6c77d4309c0fe7f4bd57bfd3547

01 Rust: Type inference for tuples by paldepind · Pull Request #20041 · github/codeql · GitHub
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

Merged
merged 9 commits into from
Jul 23, 2025
Merged
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: Add type inference for tuples
  • Loading branch information
paldepind committed Jul 14, 2025
commit 03a9a1688e18e6c77d4309c0fe7f4bd57bfd3547
45 changes: 41 additions & 4 deletions rust/ql/lib/codeql/rust/internal/Type.qll
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ private import codeql.rust.elements.internal.generated.Synth

cached
newtype TType =
TUnit() or
TStruct(Struct s) { Stages::TypeInferenceStage::ref() } or
TTuple(int arity) {
exists(any(TupleTypeRepr t).getField(arity)) 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 i) { exists(TTuple(i)) } or
TTypeParamTypeParameter(TypeParam t) or
TAssociatedTypeTypeParameter(TypeAlias t) { any(TraitItemNode trait).getAnAssocItem() = t } or
TArrayTypeParameter() or
Expand Down Expand Up @@ -56,8 +59,8 @@ abstract class Type extends TType {
}

/** The unit type `()`. */
class UnitType extends Type, TUnit {
UnitType() { this = TUnit() }
class UnitType extends Type, TTuple {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class UnitType extends Type, TTuple {
class UnitType extends TupleType, TTuple {

UnitType() { this = TTuple(0) }

override StructField getStructField(string name) { none() }

Expand All @@ -70,6 +73,25 @@ class UnitType extends Type, TUnit {
override Location getLocation() { result instanceof EmptyLocation }
}

/** A tuple type `(T, ...)`. */
class TupleType extends Type, TTuple {
private int arity;

TupleType() { this = TTuple(arity) and arity > 0 }
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the restriction that arity must be non-zero? Isn't () just a 0-arity tuple? See also: https://doc.rust-lang.org/reference/types/tuple.html

Suggested change
TupleType() { this = TTuple(arity) and arity > 0 }
TupleType() { this = TTuple(arity) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, lets do that! I wanted to have a class UnitType since I think it's useful to have that concept named, but making it a subclass of tuple still accomplices that and is nicer 👍


override StructField getStructField(string name) { none() }

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

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

int getArity() { result = arity }

override string toString() { result = "(T_" + arity + ")" }

override Location getLocation() { result instanceof EmptyLocation }
}

abstract private class StructOrEnumType extends Type {
abstract ItemNode asItemNode();
}
Expand Down Expand Up @@ -329,6 +351,21 @@ 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 parameters can be represented simply as
* their positional index.
*/
class TupleTypeParameter extends TypeParameter, TTupleTypeParameter {
override string toString() { result = this.getIndex().toString() }

override Location getLocation() { result instanceof EmptyLocation }

/** Gets the index of this tuple type parameter. */
int getIndex() { this = TTupleTypeParameter(result) }
}

/** An implicit array type parameter. */
class ArrayTypeParameter extends TypeParameter, TArrayTypeParameter {
override string toString() { result = "[T;...]" }
Expand Down
51 changes: 50 additions & 1 deletion rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ private module Input1 implements InputSig1<Location> {
node = tp0.(SelfTypeParameter).getTrait() or
node = tp0.(ImplTraitTypeTypeParameter).getImplTraitTypeRepr()
)
or
kind = 2 and
id = tp0.(TupleTypeParameter).getIndex()
|
tp0 order by kind, id
)
Expand Down Expand Up @@ -229,7 +232,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]
Expand Down Expand Up @@ -321,6 +324,14 @@ private predicate typeEquality(AstNode n1, TypePath prefix1, AstNode n2, TypePat
prefix1.isEmpty() and
prefix2 = TypePath::singleton(TRefTypeParameter())
or
exists(int i |
prefix1.isEmpty() and
prefix2 = TypePath::singleton(TTupleTypeParameter(i))
|
n1 = n2.(TupleExpr).getField(i) or
n1 = n2.(TuplePat).getField(i)
)
or
exists(BlockExpr be |
n1 = be and
n2 = be.getStmtList().getTailExpr() and
Expand Down Expand Up @@ -534,6 +545,12 @@ private Type inferStructExprType(AstNode n, TypePath path) {
)
}

pragma[nomagic]
private Type inferTupleExprRootType(TupleExpr te) {
// `typeEquality` handles the non-root case
result = TTuple(te.getNumberOfFields())
}

pragma[nomagic]
private Type inferPathExprType(PathExpr pe, TypePath path) {
// nullary struct/variant constructors
Expand Down Expand Up @@ -1055,6 +1072,31 @@ private Type inferFieldExprType(AstNode n, TypePath path) {
)
}

pragma[nomagic]
private Type inferTupleIndexExprType(FieldExpr fe, TypePath path) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 inferFieldExprType (or even integrated into that predicate). Or do things really work differently for named fields compared to numeric fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked into that, but inferFieldExprType uses the Matching module which is about propagating type info from declarations. Tuple types need not correspond to any declaration, so they're different enough that I don't see a clear way to handle them in inferFieldExprType.

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 both be
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't really understand this comment. I think the following is perfectly fine in rust

struct Pair (i32, u64) ;
let x = (Pair (1,2)).1;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that is the problem :) In let i: i64 = foo.1 we cannot say from looking at this syntactically that foo has i64 as it's second tuple element, exactly because it could also be a tuple struct. But if foo is a tuple then we want to make that conclusion.

I've expanded on the comment. Let me know if it is still not clear enough.

// 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 |
e = fe.getContainer() and
fe.getIdentifier().getText() = i.toString() and
inferType(fe.getContainer()) instanceof TupleType and
result = inferType(fe, path0) and
path = TypePath::cons(TTupleTypeParameter(i), path0)
)
}

/** Gets the root type of the reference node `ref`. */
pragma[nomagic]
private Type inferRefNodeType(AstNode ref) {
Expand Down Expand Up @@ -1943,12 +1985,19 @@ private module Cached {
or
result = inferStructExprType(n, path)
or
result = inferTupleExprRootType(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
Expand Down
12 changes: 12 additions & 0 deletions rust/ql/lib/codeql/rust/internal/TypeMention.qll
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ abstract class TypeMention extends AstNode {
final Type resolveType() { result = this.resolveTypeAt(TypePath::nil()) }
}

class TupleTypeReprMention extends TypeMention instanceof TupleTypeRepr {
override Type resolveTypeAt(TypePath path) {
path.isEmpty() and
result = TTuple(super.getNumberOfFields())
or
exists(TypePath suffix, int i |
result = super.getField(i).(TypeMention).resolveTypeAt(suffix) and
path = TypePath::cons(TTupleTypeParameter(i), suffix)
)
}
}

class ArrayTypeReprMention extends TypeMention instanceof ArrayTypeRepr {
override Type resolveTypeAt(TypePath path) {
path.isEmpty() and
Expand Down
34 changes: 17 additions & 17 deletions rust/ql/test/library-tests/type-inference/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2334,27 +2334,27 @@ mod tuples {
}

pub fn f() {
let a = S1::get_pair(); // $ target=get_pair MISSING: type=a:(T_2)
let mut b = S1::get_pair(); // $ target=get_pair MISSING: type=b:(T_2)
let (c, d) = S1::get_pair(); // $ target=get_pair MISSING: type=c:S1 type=d:S1
let (mut e, f) = S1::get_pair(); // $ target=get_pair MISSING: type=e:S1 type=f:S1
let (mut g, mut h) = S1::get_pair(); // $ target=get_pair MISSING: type=g:S1 type=h:S1

a.0.foo(); // $ MISSING: target=foo
b.1.foo(); // $ MISSING: target=foo
c.foo(); // $ MISSING: target=foo
d.foo(); // $ MISSING: target=foo
e.foo(); // $ MISSING: target=foo
f.foo(); // $ MISSING: target=foo
g.foo(); // $ MISSING: target=foo
h.foo(); // $ MISSING: target=foo
let a = S1::get_pair(); // $ target=get_pair type=a:(T_2)
let mut b = S1::get_pair(); // $ target=get_pair type=b:(T_2)
let (c, d) = S1::get_pair(); // $ target=get_pair type=c:S1 type=d:S1
let (mut e, f) = S1::get_pair(); // $ target=get_pair type=e:S1 type=f:S1
let (mut g, mut h) = S1::get_pair(); // $ target=get_pair type=g:S1 type=h:S1

a.0.foo(); // $ target=foo
b.1.foo(); // $ target=foo
c.foo(); // $ target=foo
d.foo(); // $ target=foo
e.foo(); // $ target=foo
f.foo(); // $ target=foo
g.foo(); // $ target=foo
h.foo(); // $ target=foo

// Here type information must flow from `pair.0` and `pair.1` into
// `pair` and from `(a, b)` into `a` and `b` in order for the types of
// `a` and `b` to be inferred.
let a = Default::default(); // $ MISSING: target=default type=a:i64
let b = Default::default(); // $ MISSING: target=default MISSING: type=b:bool
let pair = (a, b); // $ MISSING: type=pair:0.i64 type=pair:1.bool
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 i: i64 = pair.0;
let j: bool = pair.1;
}
Expand Down
34 changes: 17 additions & 17 deletions rust/ql/test/library-tests/type-inference/pattern_matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,21 +446,21 @@ pub fn tuple_patterns() {
// TuplePat - Tuple patterns
match tuple {
(1, 2, 3.0) => {
let exact_tuple = tuple; // $ MISSING: type=exact_tuple:?
let exact_tuple = tuple; // $ type=exact_tuple:(T_3)
println!("Exact tuple: {:?}", exact_tuple);
}
(a, b, c) => {
let first_elem = a; // $ MISSING: type=first_elem:i32
let second_elem = b; // $ MISSING: type=second_elem:i64
let third_elem = c; // $ MISSING: type=third_elem:f32
let first_elem = a; // $ type=first_elem:i32
let second_elem = b; // $ type=second_elem:i64
let third_elem = c; // $ type=third_elem:f32
println!("Tuple: ({}, {}, {})", first_elem, second_elem, third_elem);
}
}

// With rest pattern
match tuple {
(first, ..) => {
let tuple_first = first; // $ MISSING: type=tuple_first:i32
let tuple_first = first; // $ type=tuple_first:i32
println!("First element: {}", tuple_first);
}
}
Expand All @@ -469,7 +469,7 @@ pub fn tuple_patterns() {
let unit = ();
match unit {
() => {
let unit_value = unit; // $ MISSING: type=unit_value:?
let unit_value = unit; // $ type=unit_value:()
println!("Unit value: {:?}", unit_value);
}
}
Expand All @@ -478,7 +478,7 @@ pub fn tuple_patterns() {
let single = (42i32,);
match single {
(x,) => {
let single_elem = x; // $ MISSING: type=single_elem:i32
let single_elem = x; // $ type=single_elem:i32
println!("Single element tuple: {}", single_elem);
}
}
Expand All @@ -499,8 +499,8 @@ pub fn parenthesized_patterns() {
let tuple = (1i32, 2i32);
match tuple {
(x, (y)) => {
let paren_x = x; // $ MISSING: type=paren_x:i32
let paren_y = y; // $ MISSING: type=paren_y:i32
let paren_x = x; // $ type=paren_x:i32
let paren_y = y; // $ type=paren_y:i32
println!("Parenthesized in tuple: {}, {}", paren_x, paren_y);
}
}
Expand Down Expand Up @@ -630,7 +630,7 @@ pub fn rest_patterns() {
// RestPat - Rest patterns (..)
match tuple {
(first, ..) => {
let rest_first = first; // $ MISSING: type=rest_first:i32
let rest_first = first; // $ 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; // $ MISSING: type=rest_start:i32
let rest_start = first; // $ 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 @@ -719,9 +719,9 @@ pub fn patterns_in_let_statements() {

let tuple = (1i32, 2i64, 3.0f32);
let (a, b, c) = tuple; // TuplePat in let
let let_a = a; // $ MISSING: type=let_a:i32
let let_b = b; // $ MISSING: type=let_b:i64
let let_c = c; // $ MISSING: type=let_c:f32
let let_a = a; // $ type=let_a:i32
let let_b = b; // $ type=let_b:i64
let let_c = c; // $ type=let_c:f32

let array = [1i32, 2, 3, 4, 5];
let [first, .., last] = array; // SlicePat in let
Expand Down Expand Up @@ -759,8 +759,8 @@ pub fn patterns_in_function_parameters() {
}

fn extract_tuple((first, _, third): (i32, f64, bool)) -> (i32, bool) {
let param_first = first; // $ MISSING: type=param_first:i32
let param_third = third; // $ MISSING: type=param_third:bool
let param_first = first; // $ type=param_first:i32
let param_third = third; // $ type=param_third:bool
(param_first, param_third)
}

Expand All @@ -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 MISSING: type=tuple_extracted:?
let tuple_extracted = extract_tuple(tuple); // $ target=extract_tuple type=tuple_extracted:0.i32 type=tuple_extracted:1.bool
}

#[rustfmt::skip]
Expand Down
Loading








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/github/codeql/pull/20041/commits/03a9a1688e18e6c77d4309c0fe7f4bd57bfd3547

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy