Skip to content

Rust: Type inference for for loops and array expressions #19754

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 17 commits into
base: main
Choose a base branch
from

Conversation

geoffw0
Copy link
Contributor

@geoffw0 geoffw0 commented Jun 13, 2025

Adds type inference for for loops and array expressions. Currently for for loops this is limited to loops iterating through arrays.

@hvitved please advise.

  • I think for the more general cases we will need to decode the Iterator type and perhaps understand IntoIterator.
  • I'm not sure whether I should be attempting to model implicit dereferences, or perhaps a more general solution for that problem might be in the works?

@geoffw0 geoffw0 added the Rust Pull requests that update Rust code label Jun 13, 2025
@geoffw0
Copy link
Contributor Author

geoffw0 commented Jun 17, 2025

@hvitved do you think this is heading in the right direction?

Copy link
Contributor

@hvitved hvitved left a comment

Choose a reason for hiding this comment

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

Great start!

@@ -1124,6 +1124,37 @@ private Type inferIndexExprType(IndexExpr ie, TypePath path) {
)
}

pragma[nomagic]
private Type inferArrayExprType(ArrayExpr ae, 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.

I think this should be implemented inside the typeEquality predicate instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

You also need a

/** Gets the root type of the array expression `ae`. */
pragma[nomagic]
private Type inferArrayExprType(ArrayExpr ae) { exists(ae) and result = TArrayType() }

predicate, similar to inferRefExprType.

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 see. Done. Got a couple of extra expected results.

(
iterablePath.isCons(any(ArrayTypeParameter tp), path) and
result = iterableType
// TODO: iterables (containers, ranges etc)
Copy link
Contributor

Choose a reason for hiding this comment

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

We may as well pull out the logic from inferIndexExprType that handles arrays, slices, and Vecs, and reuse it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also done, but it doesn't catch any additional test cases. I think at some point we're going to need to cover the general case for iterables.

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason why we are not seeing results for Vec is that we cannot yet infer the type of e.g. vec![1, 2, 3], which desugars to

<[_]>::into_vec(
    #[rustc_box]
    alloc::boxed::Box::new([1, 2, 3]),
)

and we cannot currently resolve the into_vec call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, then I'll add a simpler test case that we will hopefully cover.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you add an explicit : Vec<i32> type annotation it ought to work.

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've added some variations with Vec and we get ... none of them. There's probably something very narrow I could write that we would get a Vec type for, but there's clearly not enough here to work on most normal code involving Vecs.

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 read your comment again and added a case with an explicit type annotation. That does work, though it feels narrow as people usually don't seem to use explicit types much when they don't need to.


// for loops with containers

let vals3 = vec![1, 2, 3]; // $ MISSING: type=vals3:Vec<i32>
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not how to make expected type annotations for constructed types, instead you should use the format type=<element>:<path>.<type> (see

else value = element + ":" + path.toString() + "." + t.toString()
). So in this case it should instead by type=vals3:T.i32.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I think I was relying on the test runner to start suggesting correct syntax, which it doesn't really do for optional results. I've updated the expectations now as best I can - there may still be add mistakes.

@geoffw0 geoffw0 marked this pull request as ready for review June 19, 2025 13:19
@geoffw0 geoffw0 requested a review from a team as a code owner June 19, 2025 13:19
@geoffw0
Copy link
Contributor Author

geoffw0 commented Jun 19, 2025

Ready for review: there are gaps (and apparently a conflict with main), but we get some useful results and I'd like to push towards merging this ASAP.

Copy link
Contributor

@paldepind paldepind left a comment

Choose a reason for hiding this comment

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

Looks really great 👍. I've left a few minor comments.

Comment on lines +1155 to +1158
exists(TypePath path0 |
iterablePath.isCons(any(RefTypeParameter tp), path0) and
path0.isCons(any(SliceTypeParameter tp), path)
)
Copy link
Contributor

@paldepind paldepind Jun 19, 2025

Choose a reason for hiding this comment

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

This makes the last case look more like the prior cases:

Suggested change
exists(TypePath path0 |
iterablePath.isCons(any(RefTypeParameter tp), path0) and
path0.isCons(any(SliceTypeParameter tp), path)
)
iterablePath
.stripPrefix(TypePath::cons(TRefTypeParameter(),
TypePath::singleton(any(SliceTypeParameter tp)))) = path

@@ -1881,6 +1881,109 @@ mod method_determined_by_argument_type {
}
}

mod loops {
struct MyCallable {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The type inference tests are currently formatted with rustfmt and I think it would be nice to keep doing that.

map1.insert(1, Box::new("one")); // $ method=insert
map1.insert(2, Box::new("two")); // $ method=insert
for key in map1.keys() { } // $ method=keys MISSING: type=key:i32
for value in map1.values() { } // $ method=values MISSING: type=value:Box type=value:Box.T:&T.str
Copy link
Contributor

Choose a reason for hiding this comment

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

This type annotation has two :s. I think the later should be a . or tweaked in some other way.

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

Successfully merging this pull request may close these issues.

3 participants