-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Implement unification of const abstract impls #104803
Conversation
r? @oli-obk (rustbot has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
a55f7d1
to
31f6475
Compare
This comment has been minimized.
This comment has been minimized.
31f6475
to
1195864
Compare
@@ -976,6 +976,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { | |||
stack: &TraitObligationStack<'o, 'tcx>, | |||
candidate: &SelectionCandidate<'tcx>, | |||
) -> Result<EvaluationResult, OverflowError> { | |||
if matches!(candidate, SelectionCandidate::LazyCandidate) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should not select any candidates in assemble_candidates_from_unified_impls, and instead of bailing out successfully here, actually go into confirmation. Confirmation can then generate obligations for all the variants of the enum/bool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what you're saying is instead of 1 single fake candidates, generate a new obligation that looks for all candidates of the variants?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll still want one candidate, so that we first look for a single generic impl with the existing candidates, and if that fails, we process your new candidate in confirmation by requiring an obligation per variant
This comment has been minimized.
This comment has been minimized.
5fd771b
to
6daa0f6
Compare
@oli-obk I've implemented a very sketchy version of what I think you're asking for. If this is the general idea, I'll clean it up and make it a bit more general, but please let me know if this is generally what you're thinking of. |
a18b72d
to
cdcc486
Compare
cdcc486
to
5c927b8
Compare
if self.tcx().features().impl_unified_exhaustive_const_traits && candidates.is_empty() { | ||
let mut out = vec![]; | ||
self.assemble_candidates_from_unified_impls(stack.obligation, &mut out); | ||
if !out.is_empty() { | ||
assert!(out.len() == 1); | ||
candidates.push(EvaluatedCandidate { | ||
candidate: out.pop().unwrap(), | ||
evaluation: crate::traits::EvaluationResult::EvaluatedToOk, | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping we could insert it unconditionally if the candidates list is empty after
rust/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
Lines 329 to 333 in dd12cd6
// Auto implementations have lower priority, so we only | |
// consider triggering a default if there is no other impl that can apply. | |
if candidates.vec.is_empty() { | |
self.assemble_candidates_from_auto_impls(obligation, &mut candidates); | |
} |
Did you try that?
} | ||
|
||
let tcx = self.tcx(); | ||
for v in [true, false] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been giving this scheme some thought. Instead of generating all possible variants and proving them, maybe we could let the trait solver handle such "questions". Basically you are asking the trait solver whether
Type<X>: Trait
can be satisfied (whereX
is a constant)- which is basically
for<const X: bool> Type<X>: Trait
- which gets turned into something like
Type<PlaceholderConst>: Trait
- which we could then treat differently from
Type<ParamConst>
orType<true>
orType<false>
When we try to prove that, we could add some special handling for that case that essentially makes a query to list all possible impls for Type<_>
even if normally it would avoid trying to match inference vars against possible impls. Once we got the list of all impls that match (for bools there should be either one (a generic one) or two (one true
and one false
)). I'm not sure this works out, but we should probably explore it before going with a feature that just lists all values. Especially because the listing would get annoying for enums with values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense, as extending this to enums is also a pain. There are also cases which are inductive such as 0usize
, and N.saturating_sub(1)
. Where would that kind of impl go?
As a counterpoint, this PR was meant to be a small part of this larger feature, but I think most use cases would be for bools and not enums with many variants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think most use cases would be for bools and not enums with many variants.
I'm not worried about enums with many variants. I was mainly thinking about an impl for None
and one for Some(T)
.
I get that this is supposed to be a minimal impl, but I don't know if we should land a minimal impl if we are sure it's going to get removed entirely and replaced by a rewrite (fwiw: I don't think we are sure yet, but I also don't know if anyone'll have the capacity to work on my explorative alternative proposed above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can definitely make something which can have more cases added, but I am not entirely sure how to add something like that without explicit checks for certain traits. I've mostly been able to follow the trait selection documentation, since it mostly exists for candidate assembly, but beyond that point there isn't much.
Would it require building or refactoring a large part of the existing obligation system?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't believe so. but i also don't know how to implement it exactly. this would be more exploratory work.
we could do a sync session on eploring the existing implementation and coming up with more concrete ideas.
71a580a
to
7f4c883
Compare
This comment has been minimized.
This comment has been minimized.
☔ The latest upstream changes (presumably #105070) made this pull request unmergeable. Please resolve the merge conflicts. |
5b30b2a
to
6467470
Compare
@oli-obk this is the closest thing to what I think you want. For an exhaustive impl, it will add obligations for each implementation for a specific const, rather than exhaustively search for all of them. I didn't add ADTs yet, because I'm not entirely sure how to construct them from valtrees, but it's easy to fill in later. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This is still the design you basically had originally. You are still iterating over the possible values of |
For that, what I could do is push an additional candidate in Ultimately, I don't think that checking exhaustively can be avoided, as ultimately there will need to be some check to see it is actually generic over all values. |
2bd3187
to
1c797b4
Compare
This comment has been minimized.
This comment has been minimized.
My issue with this approach is that it creates a second exhaustiveness checker that a) works very differently from the one we have (pattern exhaustiveness collects all patterns and fills out a matrix with the patterns that are there). I'd be ok without reusing pattern exhaustiveness checking in an initial MVP that we can land, but the approach should be designed in a way that works as if we were using it. I am very aware how painful that design would be to implement in the old solver, and I don't know how to implement it in the new solver without doing a deep dive myself. Since it has been lost in the discussion and wasn't very well explained to begin with, I'll try to explain the high level steps that I think should be done, without exactly knowing how the steps can be done currently (they likely need some extra work, or may only work in the new solver) if you have an predicate of the form |
☔ The latest upstream changes (presumably #113393) made this pull request unmergeable. Please resolve the merge conflicts. |
saw this PR because of #113393. Even when using pattern exhaustiveness checking and while being unstable, I would prefer to not currently experiment on this. Having a kind of working, but not not-appropriate for stabilization, impl here will end up causing issues or conflict in the future. Having something that kinda works makes it harder to rework the surrounding code because we don't want to unnecessarily break peoples projects, even if unstable. It also causes bad vibes™ in the community if we do break it. This has happened when adding There are a lot of subtle details here: e.g. should Any such involved changes to the type system should also require a t-types FCP before being landed, even if unstably. |
a25359e
to
9a165d7
Compare
@oli-obk ah, I finally understand what you're saying. @lcnr I'll try to make this a more suitable version using exhaustiveness checking, but it seems that the methodology is not the issue, but that there are many unanswered questions about types. I'm not sure if this is a satisfying enough answer, but I think that this feature could be restricted to a subset of types, where the implementor must be a Since I don't understand all the minutiae of |
☔ The latest upstream changes (presumably #114134) made this pull request unmergeable. Please resolve the merge conflicts. |
If two impls cover the entire set of possibilities: i.e. ``` impl TraitName for Struct<true> {} impl TraitName for Struct<false> {} ``` Then it would be expected that: ``` ... <const N: usize> ... where Struct<{N > 1}>: TraitName { ``` Should compile. This allows for such by generating a pseudo-impl for conditions which are exhaustive. Since these impls will never be instantiated as is, it is fine to have a fake impl. For now, this checks for a single bool condition, and whether both bool implementations are present. Change some of the api surface This adds separate functionality for checking for recursive traits versus exhaustive traits. But is still a WIP.
9a165d7
to
1a27a84
Compare
☔ The latest upstream changes (presumably #114781) made this pull request unmergeable. Please resolve the merge conflicts. |
Ping from triage, @JulianKnodt any updates on resolving the conflicts? Thanks |
@tranquillity-codes |
Looks like it has been marked as waiting-for-author since #104803 (comment) |
I'm gonna go ahead and close this. I think T-types is not really free at the moment to take on any major new const generic features, since we're busy working on revamping the base support for const generics (Boxy is leading all that wonderful work!). This PR has sat in an unresolved state for quite some time (sorry about that from the types side!), so I don't want to give the impression that this PR is still open for review sake. |
If two impls cover the entire set of possibilities:
i.e.
Then it would be expected that:
Should compile.
This allows for such by generating a pseudo-impl for conditions which are exhaustive.
Since these impls will never be instantiated as is, it is fine to have a fake impl.
For now, this checks for a single bool condition, and whether both bool implementations are present.
Not sure who to
r?
but cc @BoxyUwUAddresses rust-lang/project-const-generics#26