-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Tracking issue for RFC #2056: Allow trivial constraints to appear in where clauses #48214
Comments
I'm working on this. |
@matthewjasper ok. I've not thought hard about what it will take to support this. I had thought about doing it after making some more progress on general trait refactoring, but if it can be easily supported today seems fine. |
Oh, I meant to add, please ping me on IRC/gitter with any questions! I can try to carve out some time to think about it as well. |
Implement RFC 2056 trivial constraints in where clauses This is an implementation of the new behaviour for #48214. Tests are mostly updated to show the effects of this. Feature gate hasn't been added yet. Some things that are worth noting and are maybe not want we want * `&mut T: Copy` doesn't allow as much as someone might expect because there is often an implicit reborrow. * ~There isn't a check that a where clause is well-formed any more, so `where Vec<str>: Debug` is now allowed (without a `str: Sized` bound).~ r? @nikomatsakis
Prevent main from having a where clause. Closes #50714 Should this have a crater run? cc #48557, #48214 r? @nikomatsakis
…atsakis Re-enable trivial bounds cc #50825 Remove implementations from global bounds in winnowing when there is ambiguity. This results in the reverse of #24066 happening sometimes. I'm not sure if anything can be done about that though. cc #48214 r? @nikomatsakis
So with #51042 merged this feature is implemented on nightly, but the implementation isn't particularly clean and there are some edge cases that don't work as one would expect: #![feature(trivial_bounds)]
struct A;
trait X<T> {
fn test(&self, t: T) {}
}
impl X<i32> for A {}
fn foo(a: A) where A: X<i64> {
a.test(1i64); // expected i32, found i64
} Since Chalk might be able to fix some of these issues, I think that this feature is now waiting for Chalk to be integrated. edit: As of 2018-12-28 |
As another concrete example where trivially false constraints arise due to macros, see taiki-e/pin-project#102 (comment). That crate now carries a weird hack to work around this limitation. What is the current status of this feature? |
Not much has changed since my last comment. |
The current implementation generates a warning when using the pre-existing impl trait syntax, which, reading the RFC, I assume is a false-positive. #![allow(dead_code, unused_variables,)]
#![feature(trait_alias, trivial_bounds)]
trait T = Fn(&i32, &i32) -> bool;
fn impl_trait_fn() -> impl T {
|a: &i32, b: &i32| true
} This code generates the warning |
I think this change could cause problems with some proc-macros that rely on struct _AssertCopy where String: ::core::marker::Copy; to not compile, like @ExpHP wrote in rust-lang/rfcs#2056
but I can not comprehend why this should be warn by default as this would break existing proc-macros that rely on this behavior? I can say for a fact that there are definitely crates that rely on this behavior, because I recently published my first proc-macro ( I think most of those assertions are only used to make better error messages. For example my proc-macro emits struct _AssertCopy where String: ::core::marker::Copy; which is spanned around the field of a struct and this creates this error message:
I would say this lint should be deny by default to keep backwards compatibility. |
@Luro02 What else do you generate? Does your proc macro use If so why not put the bound on some That would actually protect it, unlike an unrelated and unused type definition. (Alternatively, just use |
@eddyb Nope, in my case it is not using any unsafe code and it would not do the wrong thing, but who knows some macro might use this as a guard. For example one could have an attribute like this: #[only(copy)]
struct Example {
field: String, // error because not copy
} this would silently be accepted.
does this mean, that trivial bounds do not compile in unsafe code? 🤔 The unrelated/unused type definiton is there to get a specific error message (like
I think this would still compile/ignore the bound |
I think the suggestion to "just use the bound somewhere in code" means emitting something like: struct _AssertCopy where String: ::core::marker::Copy;
let _: _AssertCopy; There is no way this would compile with an unsatisfied bound. |
No, @dtolnay explained what I meant. I was expecting you had
If it doesn't error when compiling the function, it will error when trying to use it. |
Just ran into this issue while implementing a derive macro
That caused an overflow in the compiler, with compile errors like this one:
As you can see, the compiler gave me a suggestion to use |
Oh ok I think I understand now!
Hmm I guess the choice is then currently between having the issues listed in #26925 and being unable to derive on recursive structs, until someone designs a way to filter only on types that depend on actual generic arguments or this issue is fixed. Or to use the wrapper type trick to delay execution, but that probably can't be generalized to any crate. I'm curious, do you know why the default with proc-macro + syn seems to be "unable to derive on recursive structs"? (I'm referring to the fact that this simple code, where |
Well, even if the filter were implemented, I don't think the approach of using field types at the bounds of the where clause would work in some cases. e.g., that approach still does not interact well with structs that have private types. (see #48054 for more. there is a workaround for that problem as well, but IIRC that workaround only works with auto-traits) EDIT: see also dtolnay/syn#370 Those lines are not causing the problem. The problem is that the way of changing the where clause in the |
Just to clarify the space here a bit,
It becomes breaking because code generation could rely on code not compiling to enforce soundness constraints. However, this applies to literally any change which results in more code compiling; the code generation which is broken by such a change was only tenuously sound in the first place, as it was relying on a satisfiable bound actually being unsatisfiable (an error). The former case is provably sound and does not break; the impl is bounded on the condition which makes the impl sound, and if that condition does not hold, the impl does not apply. The "minor breaking" change is that the unsatisfiable impl goes from an immediate error on definition to only an error when trying to use the impl; this delayed communication is why I've suggested that trivially false bounds could be a deny-by-default lint. |
Oh… I'm stupid I had missed the fact that (And so, for @CAD97 this explains why I was saying it was a breaking change in my message: I was thinking this behavior was coming from either syn or rustc, which was a wrong assumption) |
I'm trying to figure out the current state of this feature. What's the current status of The last update I found about blocking concerns, across the several issues about this, seems to be from 2019 talking about limitations in chalk. |
Just to add, I approve of the attributes here: #48214 (comment) I want to conditionally implement a Trait in a derive macro on a "transparent" wrapper style new-type depending on if a given Trait is implemented on the inner type. Naively making use of a I'd love to be able to add the where clause and for the above to just work - and the two annotations would allow this to work as-expected:
This would allow an easy way for macro-writers to avoid the problem of handling generics and avoiding #26925 As a work-around in my case, I'll try introducing some new trait
EDIT: Looks like this workaround should work: #48214 (comment) |
ran into this issue when trying to serialize my type with:
|
Chiming in to provide another use case: zerocopy also falls into the category of crates which rely on trivially-false bounds failing in order to guarantee that our derive-emitted code is sound. E.g., all of the derives in this file should fail, but with One potential concern is that we already have versions of our crate published which don't use |
@joshlf To clarify, with |
@joshlf if what you really want is "check if type fn test_trait(_x: impl Trait) {}
fn test_t_trait(x: T) { test_trait(x) } It doesn't seem necessary to rely on trivial trait bounds for that? |
Apologies, it looks like I misunderstood what this feature did: I was incorrectly assuming that trivial bounds were simply removed. I probably should have realized that that behavior makes no sense lol. In case it's useful, here's the error message that I saw that made me think "oh, if I enable
|
I may have found a bug. I changed the PR linked above to use * There is one error in the error output; that's due to the fact that we derive |
How does |
It calls macro_rules! assert_impl {
(for($($generic:tt)*) $ty:ty: $($rest:tt)*) => {
const _: () = {
fn assert_impl<$($generic)*>() {
// Construct an expression using `True`/`False` and their
// operators, that corresponds to the provided expression.
let _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
}
};
};
($ty:ty: $($rest:tt)*) => {
// Construct an expression using `True`/`False` and their operators,
// that corresponds to the provided expression.
const _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
};
}
/// Returns `True` or `False` depending on whether the given type implements the
/// given trait boolean expression. Can be used in const contexts if it doesn't
/// depend on outer generic parameters.
///
/// This is the core of `assert_impl`.
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! _does_impl {
($ty:ty: $($rest:tt)*) => {{
#[allow(unused_imports)]
use $crate::{
_bool::{True, False},
_core::{marker::PhantomData, ops::Deref},
};
// Fallback trait that returns false if the type does not implement a
// given trait.
trait DoesntImpl {
const DOES_IMPL: False = False;
}
impl<T: ?Sized> DoesntImpl for T {}
// Construct an expression using `True`/`False` and their operators,
// that corresponds to the provided expression.
*_does_impl!(@boolexpr($ty,) $($rest)*)
}};
(@boolexpr($($args:tt)*) ($($expr:tt)*)) => {
_does_impl!(@boolexpr($($args)*) $($expr)*)
};
(@boolexpr($($args:tt)*) !($($expr:tt)*)) => {
_does_impl!(@boolexpr($($args)*) $($expr)*).not()
};
(@boolexpr($($args:tt)*) ($($left:tt)*) | $($right:tt)*) => {{
let left = _does_impl!(@boolexpr($($args)*) $($left)*);
let right = _does_impl!(@boolexpr($($args)*) $($right)*);
left.or(right)
}};
(@boolexpr($($args:tt)*) ($($left:tt)*) & $($right:tt)*) => {{
let left = _does_impl!(@boolexpr($($args)*) $($left)*);
let right = _does_impl!(@boolexpr($($args)*) $($right)*);
left.and(right)
}};
(@boolexpr($($args:tt)*) !($($left:tt)*) | $($right:tt)*) => {{
_does_impl!(@boolexpr($($args)*) (!($($left)*)) | $($right)*)
}};
(@boolexpr($($args:tt)*) !($($left:tt)*) & $($right:tt)*) => {{
_does_impl!(@boolexpr($($args)*) (!($($left)*)) & $($right)*)
}};
(@boolexpr($($args:tt)*) !$left:ident | $($right:tt)*) => {{
_does_impl!(@boolexpr($($args)*) !($left) | $($right)*)
}};
(@boolexpr($($args:tt)*) !$left:ident & $($right:tt)*) => {{
_does_impl!(@boolexpr($($args)*) !($left) & $($right)*)
}};
(@boolexpr($($args:tt)*) $left:ident | $($right:tt)*) => {
_does_impl!(@boolexpr($($args)*) ($left) | $($right)*)
};
(@boolexpr($($args:tt)*) $left:ident & $($right:tt)*) => {{
_does_impl!(@boolexpr($($args)*) ($left) & $($right)*)
}};
(@boolexpr($($args:tt)*) !$expr:ident) => {
_does_impl!(@boolexpr($($args)*) !($expr))
};
(@boolexpr($($args:tt)*) !$expr:path) => {
_does_impl!(@boolexpr($($args)*) !($expr))
};
(@boolexpr($($args:tt)*) $expr:ident) => {
_does_impl!(@base($($args)*) $expr)
};
(@boolexpr($($args:tt)*) $expr:path) => {
_does_impl!(@base($($args)*) $expr)
};
(@base($ty:ty, $($args:tt)*) $($trait:tt)*) => {{
// Base case: computes whether `ty` implements `trait`.
struct Wrapper<T: ?Sized>(PhantomData<T>);
#[allow(dead_code)]
impl<T: ?Sized + $($trait)*> Wrapper<T> {
const DOES_IMPL: True = True;
}
// If `$type: $trait`, the `_does_impl` inherent method on `Wrapper`
// will be called, and return `True`. Otherwise, the trait method will
// be called, which returns `False`.
&<Wrapper<$ty>>::DOES_IMPL
}};
} |
That macro looks... interesting: const _: () = {
fn assert_impl<$($generic)*>() {
// Construct an expression using `True`/`False` and their
// operators, that corresponds to the provided expression.
let _: $crate::True = $crate::_does_impl!($ty: $($rest)*);
}
}; This is a generic function that is never called. Looks like with this feature we no longer type-check it properly? I don't know what we guarantee about how much we type-check never-called generic functions. It would help to distill the behavior you are seeing to a small macro-free example. |
Is the "Chalk" mentioned in the original post related to/the same as the new solver enabled by |
Yes, I've updated op for that |
This comment has been minimized.
This comment has been minimized.
|
This is a tracking issue for the RFC "Allow trivial constraints to appear in where clauses " (rust-lang/rfcs#2056).
Steps:
-Znext-solver
)Unresolved questions:
The text was updated successfully, but these errors were encountered: