Content-Length: 625580 | pFad | http://github.com/github/codeql/pull/20041

C6 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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

paldepind
Copy link
Contributor

@paldepind paldepind commented Jul 14, 2025

Implements type inference for tuples. More specifically tuple expressions (e.g., (1,2)), tuple indexing (e.g., tuple.1), and pattern matching on tuples.

Pattern matches with .. in them are not supported.

DCA shows a 1.12% point increase in resolved method calls, type inference inconsistencies is down, and performance seem unaffected.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Jul 14, 2025
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.
@paldepind paldepind marked this pull request as ready for review July 15, 2025 09:11
@Copilot Copilot AI review requested due to automatic review settings July 15, 2025 09:11
@paldepind paldepind requested a review from a team as a code owner July 15, 2025 09:11
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements type inference for tuples in Rust, extending the type system to handle tuple expressions, tuple indexing, and pattern matching on tuples. The changes enable the type inference engine to correctly track type information through tuple creation, field access, and destructuring patterns.

  • Adds support for tuple types and tuple type parameters in the type system
  • Implements type inference for tuple expressions, indexing, and pattern matching
  • Updates type mentions to handle tuple type representations

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
rust/ql/lib/codeql/rust/internal/Type.qll Defines tuple types and tuple type parameters
rust/ql/lib/codeql/rust/internal/TypeInference.qll Implements tuple type inference logic
rust/ql/lib/codeql/rust/internal/TypeMention.qll Adds tuple type representation mentions
rust/ql/test/library-tests/type-inference/type-inference.expected Test file with .expected extension
rust/ql/test/library-tests/type-inference/pattern_matching.rs Updates test expectations for tuple patterns
rust/ql/test/library-tests/type-inference/main.rs Updates test expectations for tuple expressions
rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected Test file with .expected extension
rust/ql/test/library-tests/dataflow/local/CONSISTENCY/PathResolutionConsistency.expected Test file with .expected extension
rust/ql/src/change-notes/2025-07-15-type-inference-tuples.md Changelog entry

Copy link
Contributor

@aibaars aibaars left a comment

Choose a reason for hiding this comment

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

Looks good to me overall. I have some questions and suggestions. Also could you add a test case like the following where the type information has to flow from a pattern match to other uses and the definition:

        let pair = 1.into();
        match pair {
            (0,0) => print!("unexpected");
        }
        let x = pair.0;

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
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.

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 👍

@@ -56,8 +60,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 {

Comment on lines 374 to 375
/** 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) }

Comment on lines 12 to 15
TTuple(int arity) {
arity = any(TupleTypeRepr t).getNumberOfFields() and
Stages::TypeInferenceStage::ref()
} or
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we also include tuple patterns and tuple expression in addition to tuple type reprs?

Suggested change
TTuple(int arity) {
arity = any(TupleTypeRepr t).getNumberOfFields() and
Stages::TypeInferenceStage::ref()
} or
TTuple(int arity) {
(
arity = any(TupleTypeRepr t).getNumberOfFields() or
arity = any(TupleExpr e).getNumberOfFields() or
arity = any(TuplePat p).getNumberOfFields()
) and
Stages::TypeInferenceStage::ref()
} or

* their positional index.
*/
class TupleTypeParameter extends TypeParameter, TTupleTypeParameter {
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.


/** 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.

@@ -1055,6 +1079,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.

@paldepind
Copy link
Contributor Author

Thanks for the review. I think I've addressed the suggestions and questions.

Also could you add a test case like the following where the type information has to flow from a pattern match to other uses and the definition:

Done. This also uncovered a gap in the implementation. We never inferred the root tuple type itself from a tuple pattern, only the type of the elements.

Copy link
Contributor

@geoffw0 geoffw0 left a comment

Choose a reason for hiding this comment

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

Looks great, I'll have a look at the DCA results as well when that's done...

* their positional index.
*/
class TupleTypeParameter extends TypeParameter, TTupleTypeParameter {
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.

Thanks, I was wondering this as well.

Co-authored-by: Arthur Baars <aibaars@github.com>
@paldepind
Copy link
Contributor Author

The status of this PR is that it's currently blocked as it causes analysis on databend to explode. I'm investigating the performance issue which is either caused or exposed by this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Rust Pull requests that update Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants








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

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy