Skip to content
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

Should enum discriminants have generics in scope? #70453

Closed
eddyb opened this issue Mar 27, 2020 · 18 comments · Fixed by #70825
Closed

Should enum discriminants have generics in scope? #70453

eddyb opened this issue Mar 27, 2020 · 18 comments · Fixed by #70825
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@eddyb
Copy link
Member

eddyb commented Mar 27, 2020

What are our options?

  1. we teach name resolution (rustc_resolve) to hide enum generic parameters from the discriminant expression
    • this effectively enshrines that we don't want the parameterization to happen
    • I'm not sure this can be backwards-compatible with 2.
  2. we keep the current name resolution support but error if we can't evaluate to concrete values

Examples of current behavior:

Note: #![feature(const_generics)] is used below so that this special-case kicks in:

// FIXME(#43408) enable this always when we get lazy normalization.
Node::AnonConst(_) => {
// HACK(eddyb) this provides the correct generics when

Also, note that the reason for the uniform current treatment (which doesn't consider enum parameters as not in scope of discriminants), is because rustc_resolve treats surrounding generics as being in scope of AnonConsts, always - all the bugs later in compilation are due to the lack of lazy normalization (see #43408).


This ICEs currently (playground):

#![feature(const_generics)]

#[repr(usize)]
enum Foo<T> {
    Zero = 0,
    SizeOf = std::mem::size_of::<T>(),
}

with

error: internal compiler error: src/librustc/ty/mod.rs:2538: enum discriminant depends on generic arguments
 --> src/lib.rs:6:14
  |
6 |     SizeOf = std::mem::size_of::<T>(),
  |              ^^^^^^^^^^^^^^^^^^^^^^^^

Same thing happens if a const parameter is used instead of size_of::<T>().

If we choose option 2, this ICE will turn into a regular error.


EDIT: and this currently compiles on nightly (playground):

#![feature(const_generics, arbitrary_enum_discriminant)]

#[repr(usize)]
enum MyWeirdOption<T> {
    None = 0,
    Some(T) = std::mem::size_of::<*mut T>(),
}

And using *mut *mut T instead of *mut T would make it work even when T: Sized isn't known.
(If not for the substitution failure, you could remove #![feature(const_generics)])


cc @rust-lang/compiler @rust-lang/lang

@eddyb eddyb added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Mar 27, 2020
@varkor varkor added A-const-generics Area: const generics (parameters and arguments) F-const_generics `#![feature(const_generics)]` labels Mar 27, 2020
@petrochenkov
Copy link
Contributor

This seems equivalent to something like

type A<T> = [u8; std::mem::size_of::<T>()]; // ERROR the size for values of type `T` cannot be known at compilation time

where T is "obviously" in scope, it's just not concrete enough for a use in a constant (the current error is weird though).

I don't like the idea of hiding the name T, but if this is going to be done in resolve, we could setup a "barrier" making it an error to use it, like in this case

fn f<T>() {
    fn f() {
        std::mem::size_of::<T>(); // ERROR can't use generic parameters from outer function
    }
}

, but I'd still prefer the option 2.

@eddyb
Copy link
Member Author

eddyb commented Mar 27, 2020

where T is "obviously" in scope, it's just not concrete enough for a use in a constant (the current error is weird though).

No, that's #43408, which is why I added #![feature(const_generics)] to enable the fix above.
This is how it should work and maybe we should just apply an approach like #70452 to as many cases that won't cause query cycle errors, as we can, even before lazy normalization is ready with the full fix.

but if this is going to be done in resolve, we could setup a "barrier" making it an error to use it

Right, this matches my intuition for 1., another example of that error being:

fn f<T>() {
    const _: usize = std::mem::size_of::<T>(); // ERROR can't use generic parameters from outer function
}

but I'd still prefer the option 2.

Me too, if for no other reason that we shouldn't paint ourselves into a corner if we can avoid it.

@Centril Centril added I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ requires-nightly This issue requires a nightly compiler in some way. labels Apr 1, 2020
@spastorino
Copy link
Member

spastorino commented Apr 1, 2020

We've discussed this briefly during pre triage, I think this was I-nominated in order to be discussed during some T-lang and/or T-compiler meeting. Prioritizing it as P-medium mainly given that it requires nightly.

@spastorino spastorino added the P-medium Medium priority label Apr 1, 2020
@eddyb
Copy link
Member Author

eddyb commented Apr 1, 2020

@spastorino "requires nightly" is misleading, I'm only using #![feature(const_generics)] to bypass #43408 (enum discriminant expressions are in the same boat as array lengths today).
I'll adjust the labels to match reality and reduce confusion.

More to the point: if we apply the #70452 treatment to enum discriminants (as a partial #43408 fix), then #![feature(const_generics)] won't be necessary.


Also, here's an example that compiles on nightly, I'll add it to the issue description:

#![feature(const_generics, arbitrary_enum_discriminant)]

#[repr(usize)]
enum MyWeirdOption<T> {
    None = 0,
    Some(T) = std::mem::size_of::<*mut T>(),
}

And using *mut *mut T instead of *mut T would make it work even when T: Sized isn't known.
(If not for the substitution failure, you could remove #![feature(const_generics)])

@eddyb eddyb removed A-const-generics Area: const generics (parameters and arguments) F-const_generics `#![feature(const_generics)]` requires-nightly This issue requires a nightly compiler in some way. I-ICE Issue: The compiler panicked, giving an Internal Compilation Error (ICE) ❄️ labels Apr 1, 2020
@eddyb
Copy link
Member Author

eddyb commented Apr 1, 2020

I've just removed the ICE label, after reorganizing the issue description to indicate that the question in the title is the important part, and the ICE is just an example (bonus: the ICE becomes a regular error if we choose option 2, it's not exactly a bug, it's just not printed as a regular diagnostic).

@nikomatsakis
Copy link
Contributor

@eddyb

I'm not sure this can be backwards-compatible with 2.

Is the reason for this that there might be some type in scope that would otherwise be shadowed?

e.g.,

struct Foo { }
impl Foo { const Constant: usize = 22; }
enum Bar<Foo> { X = Foo::Constant }

@eddyb
Copy link
Member Author

eddyb commented Apr 3, 2020

@nikomatsakis Shadowing is one of the theoretical concerns, sure. Note that you can also do this:

enum Bar<Foo> {
    X = {
        struct Foo { }
        impl Foo { const Constant: isize = 22; }
        Foo::Constant
    }
}

But even scarier than shadowing is this:

enum Bar<FOO> {
    X = {
        const FOO: isize = 3;
        struct Baz<T>(T);
        Baz::<FOO>;
        0
    }
}

Currently errors with a #43408 error (which #![feature(const_generics)] makes it go away), but if we suddenly remove the type parameter, const FOO resolves instead (playground):

enum Bar {
    X = {
        const FOO: isize = 3;
        struct Baz<T>(T);
        Baz::<FOO>;
        0
    }
}
error[E0107]: wrong number of const arguments: expected 0, found 1
 --> src/lib.rs:5:15
  |
5 |         Baz::<FOO>;
  |               ^^^ unexpected const argument

error[E0107]: wrong number of type arguments: expected 1, found 0
 --> src/lib.rs:5:9
  |
5 |         Baz::<FOO>;
  |         ^^^^^^^^^^ expected 1 type argument

@eddyb
Copy link
Member Author

eddyb commented Apr 3, 2020

More to the point, this compiles today (playground), and relies only on the simplest subset of const generics, so it might be stable in a year or two, but would break if FOO resolved as a type parameter:

#![feature(const_generics)]

struct IsizeVal<const X: isize>;
impl<const X: isize> IsizeVal<X> {
    const VAL: isize = X;
}

enum Bar</*FOO*/> {
    X = {
        const FOO: isize = 3;
        IsizeVal::<FOO>::VAL
    }
}

@nikomatsakis
Copy link
Contributor

Personally, I think we should make the type parameters in scope, but I'm fine with being conservative in terms of how they may be used. I'd be ok with it being an error to even reference them, actually, but I think limiting ourselves to constant expressions that can be statically resolved is pretty reasonable. (Long term, I'd probably also be ok with moving these errors to be "post-monomorphization errors", perhaps with an opt-in, in exchange for looser rules, but I think we don't have to discuss that now.)

@Centril
Copy link
Contributor

Centril commented Apr 4, 2020

2 we keep the current name resolution support but error if we can't evaluate to concrete values

I'm also fine with this option with the restrictions to known concrete values given the compatibility hazards that the first option is introducing. Presumably, "making it an error to even reference them" as an added restriction would be implemented in resolve. I'm also not opposed to such an additional restriction if it is not too complicated to implement. (I'll just note that I'm not OK with more post-monomorphization errors -- I see it as a great strength of Rust that its model of generics works like Haskell and ML, not C++, but let's indeed defer that discussion.)

@eddyb
Copy link
Member Author

eddyb commented Apr 4, 2020

Note that the WF precautions we're taking with constants-in-types (see #70107), would result in "prove that this evaluates successfully" requirements bubbling up (when we get a syntax for said bubbling up, i.e. #68436), if we switch enum discriminants from early evaluation to that system in the future.

So whichever part of the code passes in types/consts concrete enough for the value to evaluate, will get an error if it fails to evaluate (that's our general constants-in-types WF approach)

@eddyb
Copy link
Member Author

eddyb commented Apr 5, 2020

  1. we keep the current name resolution support but error if we can't evaluate to concrete values

Opened #70825 for this alternative (+ #70452-like treatment for explicit enum discriminant expressions).

@spastorino
Copy link
Member

I'm not sure if this should still be nominated but in any case I guess it's nominated for T-lang cc @eddyb

@Centril Centril removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 10, 2020
@Centril
Copy link
Contributor

Centril commented Apr 10, 2020

Based on the discussion thus far, I believe everyone who has commented is in favor of option 2. (see #70453 (comment)) which means that the answer to the question in the title is: "Yes, enum discriminants should have generics in scope."

However, this also makes certain commitments which will be observable on stable, e.g., when #![feature(arbitrary_enum_discriminant)] is stabilized. Therefore, to confirm this choice through the proper process, I would propose that we confirm that we are going with option 2 (#70825).

With that said,
@rfcbot merge

@rfcbot
Copy link

rfcbot commented Apr 10, 2020

Team member @Centril 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.

@rfcbot 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 Apr 10, 2020
@Centril Centril added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. I-nominated and removed I-nominated labels Apr 10, 2020
bors added a commit to rust-lang-ci/rust that referenced this issue Apr 14, 2020
…rent, r=nikomatsakis

typeck: always expose repeat count `AnonConst`s' parent in `generics_of`.

This should reduce some of the confusion around rust-lang#43408, although, if you look at the changed test outputs (for the last commit), they all hit rust-lang#68436, so nothing new will start compiling.

We can let counts of "repeat expressions" (`N` in `[x; N]`) always have the correct generics parenting, because they're always in a body, so nothing in the `where` clauses or `impl` trait/type of the parent can use it, and therefore no query cycles can occur.

<hr/>

Other potential candidates we might want to apply the same approach to, are:
* ~~(easy) `enum` discriminants (see also rust-lang#70453)~~ opened rust-lang#70825
* (trickier) array *type* (not *expression*) lengths nested in:
  * bodies
  * types of (associated or not) `const`/`static`
  * RHS of `type` aliases and associated `type`s
  * `fn` signatures

We should've done so from the start, the only reason we haven't is because I was squeamish about blacklisting some of the cases, but if we whitelist instead we should be fine.
Also, lazy normalization is taking forever 😞.

<hr/>

There's also 5 other commits here:
* "typeck: track any errors injected during writeback and taint tables appropriately." - fixes rust-lang#66706, as the next commit would otherwise trigger an ICE again
* "typeck: workaround WF hole in `to_const`." - its purpose is to emulate most of rust-lang#70107's direct effect, at least in the case of repeat expressions, where the count always goes through `to_const`
  * this is the reason no new code can really compile, as the WF checks require rust-lang#68436 to bypass
  * however, this has more test changes than I hoped, so it should be reviewed separately, and maybe even landed separately (as rust-lang#70107 might take a while, as it's blocked on a few of my PRs)
* "ty: erase lifetimes early in `ty::Const::eval`." - first attempt at fixing rust-lang#70773
  * still useful, I believe the new approach is less likely to cause issues long-term
  * I could take this out or move it into another PR if desired or someone else could take over (cc @Skinny121)
* "traits/query/normalize: add some `debug!` logging for the result." - debugging aid for rust-lang#70773
* "borrow_check/type_check: normalize `Aggregate` and `Call` operands." - actually fixes rust-lang#70773

r? @nikomatsakis cc @pnkfelix @varkor @yodaldevoid @oli-obk @estebank
@rfcbot
Copy link

rfcbot commented Apr 16, 2020

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

@rfcbot 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 Apr 16, 2020
@spastorino
Copy link
Member

Unnominating this one given that it was already discussed in last compiler weekly meeting.

@rfcbot rfcbot added the finished-final-comment-period The final comment period is finished for this PR / Issue. label Apr 26, 2020
@rfcbot
Copy link

rfcbot commented Apr 26, 2020

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

The RFC will be merged soon.

@rfcbot rfcbot removed the final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. label Apr 26, 2020
@bors bors closed this as completed in e5f35df May 3, 2020
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. finished-final-comment-period The final comment period is finished for this PR / Issue. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants