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

Trait bounds are not yet enforced in type definitions #21903

Open
Marwes opened this issue Feb 3, 2015 · 28 comments
Open

Trait bounds are not yet enforced in type definitions #21903

Marwes opened this issue Feb 3, 2015 · 28 comments
Assignees
Labels
A-traits Area: Trait system A-typesystem Area: The type system C-future-compatibility Category: Future-compatibility lints E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. I-needs-decision Issue: In need of a decision. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Marwes
Copy link
Contributor

Marwes commented Feb 3, 2015

Opening an issue since I could not find any information on when/if this will be enforced. Would be really useful for some generalizing I am trying for parser-combinators which I can't do currently without complicating the API.

Specifically I'd like to use a trait bound in a type definition to access an associated type to avoid passing it seperately but due to this issue I simply get a compiler error.

Simplified code which exhibits the error:

trait Stream {
    type Item;
}

trait Parser {
    type Input: Stream;
}

fn test<T>(u: T) -> T
    where T: Parser {
    panic!()
}

type Res<I: Stream> = (I, <I as Stream>::Item);

impl <'a> Stream for &'a str { type Item = char; }
impl <I> Parser for fn (I) -> Res<I> where I: Stream { type Input = I; }

//Works
//fn f(_: &str) -> (&str, char) {

//Errors
fn f(_: &str) -> Res<&str> {
    panic!()
}

fn main() {
    let _ = test(f as fn (_) -> _);
}
@jroesch
Copy link
Member

jroesch commented Feb 3, 2015

At first glance this is probably related to the internal messy-ness of bounds vs. where-clauses. I'm not sure if the predicate constraints for type aliases are propagated correctly. You should be able to use a newtype as a work around:

struct Res<I: Stream>((I, <I as Stream>::Item))

Seems to work just fine on playground with the newtype: http://is.gd/krOTZv.

cc @nikomatsakis

@Marwes
Copy link
Contributor Author

Marwes commented Feb 3, 2015

I would use a newtype but since the type I want to define is a specialized Result type that isn't possible in my case. I didn't really mention that since it is tangential to the bug itself.

@kmcallister kmcallister added A-typesystem Area: The type system A-traits Area: Trait system labels Feb 4, 2015
@tamird
Copy link
Contributor

tamird commented Apr 22, 2015

Looks like warnings are now generated for this case, but not consistently[0].

Should this be a compile-time error instead?

[0] #20222 (comment)

@Marwes
Copy link
Contributor Author

Marwes commented Aug 9, 2015

What is the status on trait bounds in type definitions? I worked around it the best I could for combine but it won't be as clean as it could be until bounds are allowed.

@bluss
Copy link
Member

bluss commented Nov 26, 2015

Where bounds are allowed, they're not checked properly.

I'm using this one and it compiles from Rust 1.0 or later

pub type MapFn<I, B> where I: Iterator = iter::Map<I, fn(I::Item) -> B>;

@nikomatsakis nikomatsakis added I-nominated T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 15, 2015
@nikomatsakis
Copy link
Contributor

Nominating for discussion. Seems like backcompat hazard we should consider trying to address with a feature gate.

@bluss
Copy link
Member

bluss commented Dec 15, 2015

Since the bounds provide writing types you couldn't do otherwise, they are very useful. A feature gate would be a breaking change without recourse(?).

@nikomatsakis
Copy link
Contributor

triage: P-medium

I think this might be not that hard to fix though!

@nixpulvis
Copy link

Unless I'm missing something, this isn't a huge problem because bad things will still fail to type check right? For example the following doesn't compile.

trait Foo {
    fn foo(&self) -> u32 {
        1
    }
}

type Bar<F: Foo> = F;

fn foo(f: Bar<&str>) -> Bar<&str> {
    f
}

fn main() {
    let bar: Bar<&str> = "";
    println!("{:?}", foo(bar.foo()));
}

@pnkfelix
Copy link
Member

pnkfelix commented Feb 9, 2016

@nixpulvis I think the argument for checking type definitions is analogous to our philosophy about other polymorphic code: we don't do C++ style post template-instantiation type-checking; instead we check the polymorphic code at the definition site (even though both strategies are sound)

Or maybe you are just saying the backwards incompatibility hit won't be that bad? (To which I say: famous last words. .. ;)

@nixpulvis
Copy link

@pnkfelix I was really just posting to confirm that I wasn't missing something. The warning sounds a bit scarier than it is in reality. I think this is tangental to #30503, as I do feel having type aliases which aren't check could be useful.

@brson brson added E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. I-needs-decision Issue: In need of a decision. labels Aug 25, 2016
@nikomatsakis
Copy link
Contributor

So this issue is a regular annoyance. It's worth trying to figure out just what we want to do, however!

The key problem is that type aliases are "invisible" to the type checker. You could almost just remove where-clauses from them altogether, but that we need them to resolve associated types in things like type Foo<I: Iterator> = I::Item. Interestingly, in cases like that, if in fact Foo<X> is applied to some type X where X: Iterator does not hold, the code should still fail to compile today, despite that warning that the trait bound is "unenforced". This is because the trait bound is implied by the type itself, which expands to <I as Iterator>::Item.

Enforcing all type bounds could be tricky, but may well be readily achievable. There is some backwards compatibility to worry about. I guess how we would implement it would be to extend the "item type" associated with a type item. This might be a small patch, actually.

Another option would be to detect if the bound is needed (i.e., consumed by an associated type), and warn only if that is not the case. That would just be accepting that we allow where-clauses here even when they are not needed and will not be unenforced. Doesn't feel very good though.

Seems like we might want to start by trying for the "better" fix, and then measuring the impact...?

ncalexan added a commit to ncalexan/combine that referenced this issue Feb 17, 2017
It's possible to `map_err_range` for `ParseResult<>` too, but it's
awkward because the output type needs to be a compatible `StreamOnce`.
As suggested in
Marwes#86 (comment),
it's probably best to either change the parse result type entirely, or
wait for rust-lang/rust#21903.

This at least helps consumers convert `ParseError<>` into something
that can implement `std::fmt::Display`.
ncalexan added a commit to ncalexan/combine that referenced this issue Feb 17, 2017
It's possible to `map_err_range` for `ParseResult<>` too, but it's
awkward because the output type needs to be a compatible `StreamOnce`.
As suggested in
Marwes#86 (comment),
it's probably best to either change the parse result type entirely, or
wait for rust-lang/rust#21903.

This at least helps consumers convert `ParseError<>` into something
that can implement `std::fmt::Display`.
@RReverser
Copy link
Contributor

Interestingly, in cases like that, if in fact Foo is applied to some type X where X: Iterator does not hold, the code should still fail to compile today, despite that warning that the trait bound is "unenforced". This is because the trait bound is implied by the type itself, which expands to ::Item.

@nikomatsakis Would it be possible to remove that warning for these cases then? It's quite confusing.

@nikomatsakis
Copy link
Contributor

@RReverser I agree. I just added a to do item to try and investigate disabling the warnings.

@nikomatsakis
Copy link
Contributor

The best option I can think of currently is to make people write <T as Trait>::Assoc. That seems to be the best practice as of now. It is slightly surprising that you can use T as Trait without having T: Trait, but that is consistent with being able to write

Yes, that's probably the most forwards compatible thing you can do, and yes it's somewhat surprising (but consistent). I tend to make an alias in practice just so it is shorter (and to not get warnings):

type Assoc<T> = <T as Trait>::Assoc;
type SomethingElse<T> = Vec<Assoc<T>>;

@RalfJung
Copy link
Member

(1) Implement lazy normalization (underway)
(2) In Epoch 2018 (hopefully we get this done in time...), handle type aliases not as "eagerly normalizing" in the type-checker, but rather as "lazy normalizing", just like an associated type

Yes, that's probably the most forwards compatible thing you can do, and yes it's somewhat surprising

Okay, so given that, do you think the approach I took in #48909 makes sense?

@nikomatsakis
Copy link
Contributor

@RalfJung are you asking if your suggestion to use <T as Trait>::Foo form makes sense? I think it does, I mean -- as you said -- we can't exactly break that usage, at least not without some kind of opt-in, for back-compat reasons. I can take a look at the PR in terms of giving an improved suggestion.

@RalfJung
Copy link
Member

@RalfJung are you asking if your suggestion to use ::Foo form makes sense?

Yes. Originally I intended this to be a step towards turning the lint into a hard error eventually, but of course if this gets implemented properly that's even better. :) I can't imagine how to implement this in a nice backwards-compatible way, but well, you seem to have some idea :D

alexcrichton added a commit to alexcrichton/rust that referenced this issue Mar 23, 2018
…chenkov

Improve lint for type alias bounds

First of all, I learned just today that I was wrong assuming that the bounds in type aliases are entirely ignored: It turns out they are used to resolve associated types in type aliases. So:
```rust
type T1<U: Bound> = U::Assoc; // compiles
type T2<U> = U::Assoc; // fails
type T3<U> = <U as Bound>::Assoc; // "correct" way to write this, maybe?
```
I am sorry for creating this mess.

This PR changes the wording of the lint accordingly. Moreover, since just removing the bound is no longer always a possible fix, I tried to detect cases like `T1` above and show a helpful message to the user:
```
warning: bounds on generic parameters are not enforced in type aliases
  --> $DIR/type-alias-bounds.rs:57:12
   |
LL | type T1<U: Bound> = U::Assoc; //~ WARN not enforced in type aliases
   |            ^^^^^
   |
   = help: the bound will not be checked when the type alias is used, and should be removed
help: use absolute paths (i.e., <T as Trait>::Assoc) to refer to associated types in type aliases
  --> $DIR/type-alias-bounds.rs:57:21
   |
LL | type T1<U: Bound> = U::Assoc; //~ WARN not enforced in type aliases
   |                     ^^^^^^^^
```
I am not sure if I got this entirely right. Ideally, we could provide a suggestion involving the correct trait and type name -- however, while I have access to the HIR in the lint, I do not know how to get access to the resolved name information, like which trait `Assoc` belongs to above. The lint does not even run if that resolution fails, so I assume that information is available *somewhere*...

This is a follow-up for (parts of) rust-lang#48326. Also see rust-lang#21903.

This changes the name of a lint, but that lint was just merged to master yesterday and has never even been on beta.
@alexreg
Copy link
Contributor

alexreg commented Oct 10, 2018

Any progress here lately? (Trying to have a go at implementing trait aliases here.)

CC @nikomatsakis

@WaffleLapkin
Copy link
Member

Is there any chance this will be implemented in edition 2021?

@D3PSI
Copy link

D3PSI commented May 19, 2022

this would be such a juicy feature, is there any chance this will ever be implemented?

@Skallwar
Copy link

Skallwar commented Jun 10, 2022

I just encounter a case where depending on conditional compilation, my type alias could change but I wanted to make sure that whatever concrete type was chosen, at the end it will implement a trait. I posted about it here

I wanted to do something like this:

#[cfg(target_arch = "x86_64")]
type MyAlias: MyTrait = A;
#[cfg(target_arch = "riscv64")]
type MyAlias: MyTrait = B;

notriddle added a commit to notriddle/rust that referenced this issue Aug 25, 2023
Remake of "List matching impls on type aliases"
* 4b1d13d
* 6f552c8
* 2ce7cd9

Partially reverts "Fix infinite loop when retrieving impls for
type alias", but keeps the test case.

This version of the PR avoids the infinite loop by structurally
matching types instead of using full unification. This version
does not support type alias trait bounds, but the compiler does
not enforce those anyway
(rust-lang#21903).
@fmease
Copy link
Member

fmease commented Aug 25, 2023

To give an update to the people subscribed to this issue, the (incomplete) feature lazy_type_alias implements the expected semantics for type aliases:

The feature is nominated for stabilization in a future edition (202X). Its tracking issue #112792 basically supersedes this issue in my humble opinion. See also F-lazy_type_alias `#![feature(lazy_type_alias)]` .

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this issue Sep 8, 2023
…l-list, r=GuillaumeGomez

rustdoc: list matching impls on type aliases

Fixes rust-lang#32077

Fixes rust-lang#99952

Remake of rust-lang#112429

Partially reverts rust-lang#112543, but keeps the test case.

This version of the PR avoids the infinite loop by structurally matching types instead of using full unification. This version does not support type alias trait bounds, but the compiler does not enforce those anyway (rust-lang#21903).

r? `@GuillaumeGomez`

CC `@lcnr`
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Sep 8, 2023
Rollup merge of rust-lang#115201 - notriddle:notriddle/type-alias-impl-list, r=GuillaumeGomez

rustdoc: list matching impls on type aliases

Fixes rust-lang#32077

Fixes rust-lang#99952

Remake of rust-lang#112429

Partially reverts rust-lang#112543, but keeps the test case.

This version of the PR avoids the infinite loop by structurally matching types instead of using full unification. This version does not support type alias trait bounds, but the compiler does not enforce those anyway (rust-lang#21903).

r? `@GuillaumeGomez`

CC `@lcnr`
@fmease fmease self-assigned this Dec 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-traits Area: Trait system A-typesystem Area: The type system C-future-compatibility Category: Future-compatibility lints E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. I-needs-decision Issue: In need of a decision. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.