Skip to content

Rust: Make trait a base type mention of the self type parameter #19149

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 2 commits into from
Apr 3, 2025

Conversation

paldepind
Copy link
Contributor

@paldepind paldepind commented Mar 28, 2025

This PR changes how the Self parameter is handled in trait methods. Suppose some trait Trait<A>. Today a method on this trait is handled like this (where the | is some fictional syntax):

fn before<Self>(self: Self | Trait<A>) -> A;

The self parameter has the type of an implicit Self parameter and the trait type. On a call the passed self is then matched as a "subtype" of Trait<A> to infer A.

With this PR the situration is now like this:

fn after<A, Self : Trait<A>>(self: Self) -> A;

The self parameter only has the Self type and the Self type parameter now has the trait as a trait bound. This works since we can now handle trait bounds on type parameters, and A is inferred through that.

The biggest benefit of this is that we no longer infer superfluous types. For instance, we currently infer multiple return types for calls to trait methods that return Self: both the inferred Self type and the trait type itself. But the latter follows from the former. This reduction in type can be seen in the change to the .expected file where all the trait types and their type arguments are now gone.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Mar 28, 2025
@paldepind paldepind force-pushed the rust-ti-self-param branch from f8b991c to 91b1676 Compare March 28, 2025 15:08
@paldepind paldepind marked this pull request as ready for review March 28, 2025 15:27
@Copilot Copilot AI review requested due to automatic review settings March 28, 2025 15:27
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.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (4)
  • rust/ql/lib/codeql/rust/internal/Type.qll: Language not supported
  • rust/ql/lib/codeql/rust/internal/TypeInference.qll: Language not supported
  • rust/ql/lib/codeql/rust/internal/TypeMention.qll: Language not supported
  • rust/ql/test/library-tests/type-inference/type-inference.expected: Language not supported

Tip: Copilot only keeps its highest confidence comments to reduce noise and keep you focused. Learn more

@paldepind paldepind force-pushed the rust-ti-self-param branch from 91b1676 to 3db773d Compare March 31, 2025 07:23
@paldepind paldepind force-pushed the rust-ti-self-param branch from 3db773d to 8acf9ce Compare April 1, 2025 12:05
@paldepind paldepind requested a review from hvitved April 1, 2025 13:17
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.

Very nice refactor, one small suggestion and question.

Comment on lines 72 to 76
private predicate id(Raw::AstNode x, Raw::AstNode y) { x = y }

private predicate idOfRaw(Raw::TypeParam x, int y) = equivalenceRelation(id/2)(x, y)
private predicate idOfRaw(Raw::AstNode x, int y) = equivalenceRelation(id/2)(x, y)

private int idOf(TypeParam node) { idOfRaw(Synth::convertAstNodeToRaw(node), result) }
private int idOf(AstNode node) { idOfRaw(Synth::convertAstNodeToRaw(node), result) }
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, instead of using AstNode, it would be better to use something like

private import codeql.rust.elements.internal.generated.Synth

private class TTypeParamOrTrait = Synth::TTypeParam or Synth::TTrait;

private class TypeParamOrTrait extends AstNode, TTypeParamOrTrait { }

in order to reduce pressure on the equivalenceRelation HOP.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense. I remember that we also do equivalenceRelation on Raw::AstNode over in the CFG implementation. Perhaps it would make sense to factor that one out and reuse it for both CFG and type inference?

Copy link
Contributor

Choose a reason for hiding this comment

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

In order to be properly shared, we would have to make it cached, I'm not sure we'd want that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it only the type of idOf that should be changed? Not idOfRaw and id?

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 changed idOf now. I picked at different name than TypeParamOrTrait as we'll also have to add TypeAliases later when they become type parameters.

Copy link
Contributor

Choose a reason for hiding this comment

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

idOfRaw and id should be updated as well, otherwise the equivalenceRelation invocation will not be restricted.

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'm not sure how they should be updated to match the suggestion? I've changed idOfRaw now such that it only applies to the relevant extensional predicates, but it doesn't look like the above.

exists(TraitItemNode trait | this = trait.getAnAssocItem() |
typeParamMatchPosition(trait.getTypeParam(_), result, ppos)
or
ppos.isSelf() and result = TSelfTypeParameter(trait)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, the Self type parameter is now moved from the trait into each of the methods, which makes sense. But I wonder if this approach will also work once support for associated type defaults lands? E.g., in something like

trait MyTrait {
  type T = Option<Self>

  fn foo(self) -> T;
}

Perhaps it will Just Work with your other PR that introduced support for type aliases?

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 haven't thought of that. I don't think it will just work, but I think it can be made to work.

Associated types will be type parameters of the trait and the trait's methods, like this:

fn foo<Self : MyTrait<T>, T>(self) -> T

So we have to ensure that whatever makes Self a MyTrait has a type argument for T and we should read that off of the default if nothing else is provided. So for an impl block that uses the default it should behave as if the default was written for T:

impl MyTrait<Option<Foo>> for Foo { ... }

@paldepind paldepind force-pushed the rust-ti-self-param branch from cf3fb23 to 001735b Compare April 2, 2025 13:40
@paldepind paldepind merged commit 52660fa into github:main Apr 3, 2025
16 checks passed
@paldepind paldepind deleted the rust-ti-self-param branch April 3, 2025 06:44
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.

2 participants