Repository navigation
Fix - const parameters rejected when identical - #162908
Conversation
|
Hmm, this is definitely a good step in the correct direction, but I think it might be nice to fully resolve this underlying issue rather than just doing the first step. In particular, I think the equality check here ought to be a full trait solver equality comparison - stuff like this ought to compile (currently, it does not): const FOO: usize = core::direct_const_arg!(5); // I thiiink a regular const ought to work as well, maybe(?)
const BAR: usize = core::direct_const_arg!(5);
pub trait Trait {
fn method<const A: [usize; FOO]>();
}
pub struct Foo;
impl Trait for Foo {
fn method<const A: [usize; BAR]>() {}
}(and then more complex scenarios involving type aliases, const aliases, generic aliases, and the like, too) Other places in this file create a new Also, I don't think perf stuff is super necessary to do here - should be fine to just unconditionally create a new Also, minor code style nit thing, probably good to just remove the Feel free to poke me on zulip if you have questions! @rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
a666a48 to
24d3fed
Compare
|
I've added that as another test case (happy to add more if there are some obvious ones 😃 ). I've phased the check so hopefully some fast paths will still be hit. This heavily borrows ideas from the surrounding code in other functions. Rather than try and split bits of what I've done into other functions or refactor the surrounding code so I could call helpers, it's deliberately a fairly hefty lambda that I think reads fairly well from top to bottom. Though I'm happy to gut some of the borrowed logic into separate functions if you think that a good idea in this PR? @rustbot ready |
This comment has been minimized.
This comment has been minimized.
e659c19 to
4ec6962
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
4ec6962 to
ba3320b
Compare
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
This is turning into a bit of a mess. I'm wondering if it might be good to split this into two functions: compare_generic_param_kinds compares the, well, kinds of the generic params (only checks if types are matched with types, consts are matched with consts, etc.) and then add another compare_generic_const_param_types (called in whatever method after compare_generic_param_kinds is called) that actually drills into the types of the consts.
|
|
||
| match ocx.eq(&cause, param_env, trait_ty, impl_ty) { | ||
| Ok(_) => { | ||
| let errors = ocx.evaluate_obligations_error_on_ambiguity(); |
There was a problem hiding this comment.
If the ocx is created outside the loop, evaluate_obligations_error_on_ambiguity probably could/should be called at the end of the loop, not after every eq. (It's technically correct, but unnecessary, to do it here)
| infcx.err_ctxt().note_type_err( | ||
| &mut diag, | ||
| &cause, | ||
| Some((param_impl_span, Cow::from("type in trait"), false)), |
There was a problem hiding this comment.
Should this be param_trait_span, not param_impl_span?
1316f91 to
3d311db
Compare
This comment has been minimized.
This comment has been minimized.
3c28b9f to
255da88
Compare
This comment has been minimized.
This comment has been minimized.
I've tried splitting this into separate functions a couple of times, but the extracted const-param logic ends up needing quite a lot of surrounding context. For example, the helper ends up looking roughly like: fn const_helper_fn(
ocx: &ObligationCtxt,
tcx: TyCtxt<'tcx>,
impl_item: ty::AssocItem,
impl_trait: ty::AssocItem,
trait_to_impl_args: &ty::GenericArgs,
param_impl: &GenericParamDef,
param_trait: &GenericParamDef,
) -> Result<(), ErrorGuaranteed> {
// Roughly the current `(Const { .. }, Const { .. }) => {}` branch.
}The alternative I considered was putting Neither option felt like it made the code substantially easier to follow; it mostly seemed to move the complexity elsewhere. A fair amount of the logic here is also heavily based on similar code in other functions. I'm slightly wary of extracting helpers that are only useful at this one call site when there may instead be an opportunity to factor out something genuinely reusable across those implementations. Would you be OK with keeping this together for this PR, and doing a follow-up that looks at extracting reusable utilities and reducing the duplicated logic? |
255da88 to
fd6b352
Compare
Apologies for not being clear! That's not quite what I meant. There are two things being checked here in a single function right now:
I was suggesting these two checks be two different functions instead of being combined into one. The one that checks the kinds of generic parameters (i.e. whether the The diff in this PR to fn compare_generic_const_param_types<'tcx>(
tcx: TyCtxt<'tcx>,
impl_item: ty::AssocItem,
trait_item: ty::AssocItem,
impl_trait_ref: ty::TraitRef<'tcx>,
delay: bool,
) -> Result<(), ErrorGuaranteed>;This new function would be called in the same places Conflating these two operations (checking the kinds of generic parameters, and checking the types of const generic parameters) seems to be producing very messy code, as evidenced by the current PR being quite difficult and headache-inducing to follow.
Hm, I'm not quite sure I follow - I'm definitely not suggesting extracting helpers to deduplicate code, this suggestion indeed creates a bit more code duplication. However, my guess would be that code is significantly easier to follow. In any case, sure, it was a suggestion that I'm not sure about, I don't feel particularly strongly about it. |
fd6b352 to
24eb09a
Compare
This comment has been minimized.
This comment has been minimized.
24eb09a to
bd75f0c
Compare
This comment has been minimized.
This comment has been minimized.
bd75f0c to
a65bf43
Compare
| tcx.dcx(), | ||
| param_impl_span, | ||
| E0053, | ||
| "{} `{}` has an incompatible generic parameter for trait `{}`", |
There was a problem hiding this comment.
nit: I would prefer this to specifically talk about the type of const generic parameters, rather than reusing the message from compare_generic_param_kinds. perhaps just adding the word const:
"{}
{}has an incompatible const generic parameter for trait{}"
| Ok(_) => {} | ||
| Err(terr) => { | ||
| let param_impl_span = tcx.def_span(param_impl.def_id); | ||
| let param_trait_span = tcx.def_span(param_trait.def_id); |
There was a problem hiding this comment.
I think it would be nice if we use ty_span here instead of def_span. Here's my thoughts:
- extract out
let param_impl_ty_span = tcx.ty_span(param_impl.def_id.expect_local());to the top of this for-loop - use it for the span in
ObligationCause::new - also use it here
- for the trait side, it is not necessarily local, so do
let param_trait_ty_span = param_trait.def_id.as_local().map(|def_id| tcx.ty_span(def_id)); - pass in
Nonetonote_type_err'ssecondary_spanif the trait is nonlocal, i.e.param_trait_ty_span.map(|span| (span, Cow::from("type in trait"), false))
(I'm basing this a bit off of what compare_const_clause_entailment does)
perhaps we want a test with an err in an impl for a nonlocal trait too, if one doesn't already exist. also I'm unsure of the behavior of ty_span when the type is malformed, e.g. <const N: > or <const N> so maybe a test for that too? but we might not even get to this point if the type is borked, unsure.
| tcx.type_of(param_trait.def_id).instantiate(tcx, trait_to_impl_args), | ||
| ); | ||
|
|
||
| match ocx.eq(&cause, param_env, trait_ty, impl_ty) { |
There was a problem hiding this comment.
@BoxyUwU I just realized that compare_const_clause_entailment does an ocx.sup here, not ocx.eq. Do we want ocx.eq here or ocx.sub? (for future thoughts of const generic references and whatnot - I suppose we could start with eq and expand later if desired)
relatedly, compare_const_clause_entailment does both evaluate_obligations_error_on_ambiguity and resolve_regions_and_report_errors at the end, this PR does only evaluate_obligations_error_on_ambiguity. forgive me for being a bit lazy and just asking whether we want to do so as well, instead of investigating myself (guess is nah if we do eq, but probably yes if we do sub)
| let ty_const_of = |def_id| { | ||
| tcx.generics_of(def_id).own_params.iter().filter(|param| matches!(param.kind, Const { .. })) | ||
| }; |
There was a problem hiding this comment.
nit: my guess is you copied and edited the name of this from compare_generic_param_kinds's ty_const_params_of. That variable is called that because it is the "type and const parameters of". This variable here is just consts, i.e. is the "const parameters of", so should be called const_params_of, not ty_const_of (which is a bit nonsensical~)
| Ok(()) | ||
| } | ||
|
|
||
| fn check_const_type_comparison<'tcx>( |
There was a problem hiding this comment.
Nit: This is not "checking a comparison", so the name check_const_type_comparison is a bit odd. Rather, it is comparing the types of const parameters between a trait and an impl, so perhaps compare_const_generic_param_types?
There was a problem hiding this comment.
Nit: This doc comment is now incorrect, this particular error is no longer checked here ✨ - could you update this comment? (and optionally adding a doc comment to your new method too if you want to, shrug, either way works)
| "{} `{}` has an incompatible generic parameter for trait `{}`", | ||
| impl_item.descr(), | ||
| trait_item.name(), | ||
| &tcx.def_path_str(tcx.parent(trait_item.def_id)) |
There was a problem hiding this comment.
very nit: this & character can be removed :3
| fn foo<const N: Self>() {} | ||
| //~^ ERROR cannot use `Self` in const parameter type | ||
| //~| ERROR associated function `foo` has an incompatible generic parameter for trait `MyTrait` | ||
| //~^ HELP add `#![feature(min_adt_const_params)]` to the crate attributes to enable `Self` as a const parameter type |
There was a problem hiding this comment.
Nit: IMO we don't need to check this HELP here, just the ERROR is enough
There was a problem hiding this comment.
Looking good! two little mistakes I noticed though, then this should be good. Would also be nice to have tests for these two things, too:
perhaps we want a test with an err in an impl for a nonlocal trait too, if one doesn't already exist. also I'm unsure of the behavior of
ty_spanwhen the type is malformed, e.g.<const N: >or<const N>so maybe a test for that too? but we might not even get to this point if the type is borked, unsure.
| param_impl_span, | ||
| E0053, | ||
| "{} `{}` has an incompatible generic parameter for trait `{}`", | ||
| "{} `{}` has an incompatible const generic parameter for trait `{}`", |
There was a problem hiding this comment.
hmm, I think you did an oopsie here!
There was a problem hiding this comment.
mm, still an oopsie here! this is checking generic param kinds, not const generics, so this diff should probably be reverted~
| iter::zip(const_params_of(impl_item.def_id), const_params_of(trait_item.def_id)); | ||
|
|
||
| for (param_impl, param_trait) in param_iter { | ||
| let param_impl_ty_span = tcx.def_span(param_impl.def_id); |
There was a problem hiding this comment.
hmm, bit of an oopsie here too, this is not the span of the type, despite the variable name!
| param_impl_span, | ||
| E0053, | ||
| "{} `{}` has an incompatible generic parameter for trait `{}`", | ||
| "{} `{}` has an incompatible const generic parameter for trait `{}`", |
There was a problem hiding this comment.
mm, still an oopsie here! this is checking generic param kinds, not const generics, so this diff should probably be reverted~
| iter::zip(const_params_of(impl_item.def_id), const_params_of(trait_item.def_id)); | ||
|
|
||
| for (param_impl, param_trait) in param_iter { | ||
| let param_impl_span = tcx.def_span(param_impl.def_id); |
There was a problem hiding this comment.
whoops - my earlier review was asking you to use ty_span here, but looks like you renamed the variable back to param_impl_span instead of changing it to use ty_span!
1bd1637 to
901cebd
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
fantastic, thanks so much! ❤️ @bors r+ rollup |
…, r=khyperia Fix - const parameters rejected when identical This keeps a series of cheaper checks before doing a more heavyweight comparison. Used the example in the issue as the test case. r? @khyperia Fixes rust-lang#162897
…, r=khyperia Fix - const parameters rejected when identical This keeps a series of cheaper checks before doing a more heavyweight comparison. Used the example in the issue as the test case. r? @khyperia Fixes rust-lang#162897
…uwer Rollup of 24 pull requests Successful merges: - #161998 ( Support type-relative assoc item paths in generic param defaults & const param types) - #162106 (Helpful suggestions for incorrect address-of mutability (2)) - #162652 (Syntactically reject leading parenthesized precise capturing lists in bare trait object types (`(use<…>)+`)) - #163337 (MIR move elimination [3/6]: PreciseLiveness) - #163938 (-Zassumptions-on-binders: rewrite alias outlives constraints more goodly) - #163939 (Better debug impls for some assumptions on binders types) - #163954 (fix(bootstrap/darwin): fix rpath for distributed LLD) - #163956 (Pass the unremapped path to the `rustc` invocation for doctests) - #164042 (Allow testing cg-gcc on any target) - #162443 (Do not retain `Normalization` goal errors in nested goals for `BestObligationVisitor:: non_trivial_candidates `) - #162908 (Fix - const parameters rejected when identical) - #163193 (cfi: mangle `f128` as `e` rather than `g` on platforms without `_Float128`) - #163634 (move overflow lint computation into decorator) - #163666 (Updates the expect message library/core/src/time.rs) - #163727 (rigid aliases to non-rigid for fully normalized check) - #163745 (replace `fully_monomorphized` with `cx.typing_env()`) - #163912 (Fix debug assert failure in `note_obligation_cause_code_inner`) - #163950 (don't treat inherited opaques as defining) - #163972 (const-eval: ICE when we hit a non-const fn) - #164000 (When mentioning that closure doesn't implement trait, point at closure) - #164007 ([rustdoc] Prefer local paths over remote ones when foreign item is locally reexported) - #164008 (properly ignore the current goal's usages) - #164017 (cg_llvm: Avoid some explicit casts to `*const c_char`) - #164025 (Less `CanonicalVarValues`)
|
Note This PR was benchmarked as part of triage of its containing rollup: triage URL. Finished benchmarking commit (5f8b748): comparison URL. Overall result: ❌ regressions - please read:Our benchmarks found a performance regression caused by this PR. Next Steps:
@rustbot label: +perf-regression Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)This perf run didn't have relevant results for this metric. CyclesThis perf run didn't have relevant results for this metric. Binary sizeResults (primary -0.1%, secondary -0.1%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Artifact size: 406.49 MiB -> 406.38 MiB (-0.03%) |
|
A small perf regression is to be expected when compiling code that frequently implements traits with const generics, as this PR implements a more expensive, thorough, and correct check for comparing trait vs. impl const generic parameters. The |
View all comments
This keeps a series of cheaper checks before doing a more heavyweight comparison. Used the example in the issue as the test case.
r? @khyperia
Fixes #162897