Skip to content

Always check ConstArgHasType even when otherwise ignoring#152931

Open
khyperia wants to merge 1 commit intorust-lang:mainfrom
khyperia:always-check-constarghastype
Open

Always check ConstArgHasType even when otherwise ignoring#152931
khyperia wants to merge 1 commit intorust-lang:mainfrom
khyperia:always-check-constarghastype

Conversation

@khyperia
Copy link
Contributor

fixes #149774

helping @BoxyUwU finish up #150322, this is a simple tweak/finishing-up of that PR.

this is a breaking change that crater detected has some issues with in Boxy's PR, and hence needs a t-types FCP. I can go and help fix those crates if/once the break is signed off on.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 21, 2026
{
let filtered_obligations =
unnormalized_obligations.into_iter().filter(|o| {
matches!(o.predicate.kind().skip_binder(),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
matches!(o.predicate.kind().skip_binder(),
matches!(o.predicate.kind().no_bound_vars().unwrap(),

I think this is safer in that we won't silently skip stuff if we unexpectedly have higher ranked const arg has type goals. Instead we'll loudly explode and can fix that 😌

@BoxyUwU
Copy link
Member

BoxyUwU commented Feb 21, 2026

thx for taking this on for me

@rustbot
Copy link
Collaborator

rustbot commented Feb 24, 2026

changes to the core type system

cc @lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Feb 24, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 24, 2026

r? @nnethercote

rustbot has assigned @nnethercote.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 68 candidates
  • Random selection from 13 candidates

@khyperia
Copy link
Contributor Author

Background Context

First, it's important to note that:

  • We do not check that the RHS of free type aliases are WF: type Foo = Vec<[u32]>; is valid
  • We do not check trait objects' where clauses: having trait Object<T: Sized> then let x: &dyn Object<[u32]>; is valid
  • We do not check WF related things for lifetimes from a for<'a>

This PR does not fundamentally change the above.

Second, in the past, we lowered many things to anon consts. In doing so, we implicitly caused a typecheck of constants to happen, by enforcing that the body of the anon const matched with the type of the anon const.

This typecheck always happend, regardless of if the usage of the anon const was inside a free type alias or trait object reference. In other words, these used to cause compiler errors:

  • struct Struct<const N: usize> {}
    type Foo<const B: bool> = Struct<B>;
  • trait Object<const N: usize> {}
    fn f<const B: bool>(a: &dyn Object<B>);

However, recently (a few months ago) we changed how some constants are desugared, no longer generating anon consts for them, and instead representing them immediately. This caused consts in these locations to stop being typechecked.

Actual Change

Eventually, we would like to begin wfchecking free type aliases and trait objects. Today is not that day, so instead, this PR is reintroducing typechecking for constants, to minimize the eventual breakage that will be caused by wfchecking type aliases and trait objects.

  • Look at wf obligations for free type aliases, discard all of them except ConstArgHasType, and prove those.
  • Look at wf obligations for trait objects, discard all of them except ConstArgHasType, and prove those.

Important to note is that ConstArgHasType cannot interact with lifetimes from a for<'a> on stable, because we do not allow generic parameters (including lifetimes) in the types of const generics, so we are free to continue ignoring them.

Crater Breakage

A few crates have already slipped in some "invalid" code, here's a crater run: #150322 (comment)

There are no crater breakages around the trait objects part of this PR.

Here's an example breakage of the free type alias part of this PR:

error: the constant `MODE` is not of type `usize`
  --> src/lfstd.rs:92:1
   |
92 | type AllocScqRing<const MODE: bool> = ScqRing<Box<[CachePadded<AtomicUsize>]>, MODE>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `bool`
   |
note: required by a const generic parameter in `ScqRing`
  --> src/scq.rs:47:23
   |
47 | pub struct ScqRing<I, const MODE: usize> {
   |                       ^^^^^^^^^^^^^^^^^ required by this const generic parameter in `ScqRing`

Note that even though these type aliases are clearly incorrect, they are still useable by downstream code - this code compiles successfully before this PR, but fails after it:

struct Struct<const N: usize> {}
type Foo<const B: bool> = Struct<B>;
type Bar<const N: usize> = Foo<N>;
let x: Bar<1_usize>;

The breakage is small enough that one person (me) could easily help fix up broken crates. However, the longer we wait, the more breakage will creep in. We've waited a few months already so it's not desperately urgent, but still.

@BoxyUwU
Copy link
Member

BoxyUwU commented Feb 24, 2026

r? BoxyUwU

@rustbot rustbot assigned BoxyUwU and unassigned nnethercote Feb 24, 2026
@BoxyUwU BoxyUwU added T-types Relevant to the types team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 24, 2026
@BoxyUwU
Copy link
Member

BoxyUwU commented Feb 24, 2026

cc @rust-lang/types
@rfcbot merge types

See: #152931 (comment)

@rust-rfcbot
Copy link
Collaborator

rust-rfcbot commented Feb 24, 2026

Team member @BoxyUwU has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Feb 24, 2026
@BoxyUwU BoxyUwU added S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 24, 2026
@lcnr
Copy link
Contributor

lcnr commented Mar 2, 2026

reintroducing typechecking for constants,

This PR checks that the type of constant arguments matches the generic parameter instantiated by them when checking that the containing item is well-formed. We do not go back to calling HIR typeck for them. Is that correct?

// cannot be referenced by a ConstArgHasType clause. However,
// under `generic_const_parameter_types`, it can. Ignore those
// predicates for now, to not have HKT-ConstArgHasTypes.
!kind.has_escaping_bound_vars()
Copy link
Contributor

Choose a reason for hiding this comment

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

this should always return true as we assert o.predicate.kind().no_bound_vars() being Some above, maybe also rewrite to assert!(o.predicate.kind().no_bound_vars().is_some());

Copy link
Contributor Author

@khyperia khyperia Mar 2, 2026

Choose a reason for hiding this comment

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

hmm? I don't think so:

#![feature(generic_const_parameter_types)]
Trait<'a, const N: &'a u32>
&dyn for<'b> Trait<'b, false>

This has o.predicate.kind().no_bound_vars().unwrap() be Some, as the ConstArgHasType doesn't have a Binder with vars, but there's an escaping bound var 'b, that we skipped via principal.skip_binder().

Unfortunately I'm not able to write a test for this due to #153039

Perhaps nominal_obligations actually should return a Binder with vars in it and stuff in this case, but that's all unimplemented as of now AFAIK (and will be implemented in the work to wfcheck dyn types in general, not just ConstArgHasType).

Copy link
Contributor

@lcnr lcnr Mar 2, 2026

Choose a reason for hiding this comment

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

Your example is incomplete and I don't fully get what you mean? Do you want const N: &'a u32 and then the const arg type is higher ranked 🤔

In this case the assert o.predicate.kind().no_bound_vars().unwrap(); would trigger, would it not?

The check !kind.has_escaping_bound_vars() is just o.predicate.kind().skip_binder().has_escaping_bound_vars()?

fn no_bound_vars is implemented if self.value.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) }, so the assertion and the boolean returned afterwards are the same

Copy link
Contributor Author

@khyperia khyperia Mar 2, 2026

Choose a reason for hiding this comment

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

Aah, shoot, I mucked this up in a refactoring, I think.

I meant to assert that o.predicate.kind() introduces no bound vars of its own, that it's an empty for<>. I didn't realize that .no_bound_vars() checks has_escaping_bound_vars, it doesn't check if self.bound_vars is empty.

(edit: and oh, yeah, sorry, typing too fast, I did mean const N: &'a u32 - the full example is in the linked issue)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In other words, the construct that we could have, in pseudocode:

for<'b> ( for<> ConstArgHasType(_, &'b u32) )
^ this binder is from the syntax &dyn for<'b> Trait<'b, false>
          ^ this binder is from the return type of nominal_obligations

I want to assert that the inner binder is empty, because ConstArgHasType obligations should not be higher-ranked, and we need to reevaluate this code if they are.

But if the outer binder is non-empty, and the ConstArgHasType obligation references that parameter (only possible with feature(generic_const_parameter_types), not possible on stable rust), we should ignore the obligation, as a sort of "WIP, implement this in the work for generic_const_parameter_types, etc."

Copy link
Member

Choose a reason for hiding this comment

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

ah I guess isntead of using no_bound_vars above it should be bound_vars().is_empty() or sth like that.

@khyperia
Copy link
Contributor Author

khyperia commented Mar 2, 2026

This PR checks that the type of constant arguments matches the generic parameter instantiated by them when checking that the containing item is well-formed. We do not go back to calling HIR typeck for them. Is that correct?

doublechecked with boxy (was only 90% sure of exact definitions of words), that is correct!

"most" constants still lower to anonconsts still, it's just very simple stuff that is now directly represented (as of a few months ago), without lowering to an anon const. So, this PR is just covering those simple things that are no longer anonconsts. AFAIK, anonconsts always have been and continue to be HIR typechecked.

@rust-rfcbot rust-rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Mar 4, 2026
@rust-rfcbot
Copy link
Collaborator

🔔 This is now entering its final comment period, as per the review above. 🔔

@khyperia khyperia force-pushed the always-check-constarghastype branch from 620edeb to c48caa2 Compare March 4, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. T-types Relevant to the types team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Not using anon consts for uses of const parameters caused us to accept more code for free type aliases

6 participants