-
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
infer array len from pattern #70562
infer array len from pattern #70562
Conversation
This comment has been minimized.
This comment has been minimized.
r? @Centril |
cc @rust-lang/lang This changes slice pattern type checking such that we can infer the type of the pattern in |
use std::convert::TryInto;
fn main() {
let s: &[u32] = &[0, 1];
let [x, y] = s.try_into().unwrap();
} Haven't checked, but I suspect this would compile with this PR (and currently requires type annotation, presumably because |
Ah yeah, that might work. @lcnr Please add that to the set of tests. :) |
This comment has been minimized.
This comment has been minimized.
src/librustc_typeck/check/pat.rs
Outdated
// We have a pattern with a fixed length, | ||
// which we can use to infer the length of the array. | ||
// of the array. | ||
let updated_arr = self.tcx.mk_array(inner_ty, min_len); | ||
self.demand_eqtype(span, updated_arr, arr_ty); | ||
return Some(PatLenOrArray::Array(updated_arr)); |
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.
Is this extra complexity (compared to just unifying the constant) in order to be able to use demand_eqtype
?
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.
tbh I wasn't able to find a good way to unify consts.
The only thing I found was unify_const_variable
, which requires a ConstVid
, which I do not fully understand yet. At this point I gave up and just used type equality 😆
So while this is the least complex thing I found there could easily be a better approach.
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.
The current approach might do some extra work, but it seems fairly clean, and it can't go wrong. We could always improve this later imo.
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.
@lcnr That's mostly because there aren't specific functions, it's all done through traits.
demand_eqtype
, for example, passes actual
and expected
to a function that can take just about anything.
use std::convert::TryInto;
fn main() {
let s: &[u32] = &[0, 1];
let [x, y] = s.try_into().unwrap();
} and similar implementations do not compile with this, as Similar example, which fails for the same reason: struct A;
impl From<A> for [u8; 2] {
fn from(a: A) -> Self {
[0; 2]
}
}
impl From<A> for [u8; 3] {
fn from(a: A) -> Self {
[0; 3]
}
}
fn main() {
let a = A;
let [_, _] = a.into();
} This seems to be the related to the following, which does not depend on const generics at all. struct A;
impl From<A> for Vec<u8> {
fn from(a: A) -> Self {
vec![0]
}
}
impl From<A> for Vec<u16> {
fn from(a: A) -> Self {
vec![1]
}
}
fn main() {
let a = A;
let mut vec = a.into();
//~^ ERROR type annotations needed
vec.push(2u16);
} |
Presumably because we are using |
Co-Authored-By: Mazdak Farrokhzad <twingoow@gmail.com>
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.
Thanks for doing this! 💖 The implementation looks very clean. 🎉
r=me unless @eddyb wants to tweak the unification bits, but imo it looks good as-is.
.map_or(err, |len| self.tcx.mk_array(inner_ty, len)); | ||
(inner_ty, slice_ty, expected) | ||
let (slice_ty, expected) = | ||
self.check_array_pat_len(span, element_ty, expected, slice, len, min); |
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.
Is this the only caller? It feels like a bunch of stuff is muddled by the fact that it's a separate function.
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.
it is. Could inline this in a followup pr if you want 🤷♂️
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.
Please don't. I disagree with eddy here and I think the method provides separation of concerns from the rest of type checking of slice patterns (the structural parts of checking before/slice/after once you've extracted the slice_ty
and inferred
). At most, I think this method should be split in two, one for the slice.is_none()
and one for the other bits.
@bors r+ |
📌 Commit a3df1db has been approved by |
🌲 The tree is currently closed for pull requests below priority 1000, this pull request will be tested once the tree is reopened |
infer array len from pattern closes rust-lang#70529 This still errors in the following case ```rust #![feature(const_generics)] fn arr<const N: usize>() -> [u8; N] { todo!() } fn main() { match arr() { [5, ..] => (), //~^ ERROR cannot pattern-match on an array without a fixed length [_, _] => (), } } ``` Considering that this should be rare and is harder to implement I would merge this PR without *fixing* the above.
infer array len from pattern closes rust-lang#70529 This still errors in the following case ```rust #![feature(const_generics)] fn arr<const N: usize>() -> [u8; N] { todo!() } fn main() { match arr() { [5, ..] => (), //~^ ERROR cannot pattern-match on an array without a fixed length [_, _] => (), } } ``` Considering that this should be rare and is harder to implement I would merge this PR without *fixing* the above.
Rollup of 9 pull requests Successful merges: - rust-lang#69784 (Optimize strip_prefix and strip_suffix with str patterns) - rust-lang#70548 (Add long error code for error E0226) - rust-lang#70555 (resolve, `try_resolve_as_non_binding`: use `delay_span_bug` due to parser recovery) - rust-lang#70561 (remove obsolete comment) - rust-lang#70562 (infer array len from pattern) - rust-lang#70585 (std: Fix over-aligned allocations on wasm32-wasi) - rust-lang#70587 (Add `Rust` to the code snippet) - rust-lang#70588 (Fix incorrect documentation for `str::{split_at, split_at_mut}`) - rust-lang#70613 (more clippy fixes) Failed merges: r? @ghost
…ination, r=varkor stabilize `#![feature(min_const_generics)]` in 1.51 *A new Kind* *A Sort long Prophesized* *Once Fragile, now Eternal* blocked on rust-lang#79073. # Stabilization report This is the stabilization report for `#![feature(min_const_generics)]` (tracking issue rust-lang#74878), a subset of `#![feature(const_generics)]` (tracking issue rust-lang#44580), based on rust-lang/rfcs#2000. The [version target](https://forge.rust-lang.org/#current-release-versions) is ~~1.50 (2020-12-31 => beta, 2021-02-11 => stable)~~ 1.51 (2021-02-111 => beta, 2021-03-25 => stable). This report is a collaborative effort of `@varkor,` `@shepmaster` and `@lcnr.` ## Summary It is currently possible to parameterize functions, type aliases, types, traits and implementations by types and lifetimes. With `#![feature(min_const_generics)]`, it becomes possible, in addition, to parameterize these by constants. This is done using the syntax `const IDENT: Type` in the parameter listing. Unlike full const generics, `min_const_generics` is limited to parameterization by integers, and constants of type `char` or `bool`. We already use `#![feature(min_const_generics)]` on stable to implement many common traits for arrays. See [the documentation](https://doc.rust-lang.org/nightly/std/primitive.array.html) for specific examples. Generic const arguments, for now, are not permitted to involve computations depending on generic parameters. This means that const parameters may only be instantiated using either: 1. const expressions that do not depend on any generic parameters, e.g. `{ foo() + 1 }`, where `foo` is a `const fn` 1. standalone const parameters, e.g. `{N}` ### Example ```rust #![feature(min_const_generics)] trait Foo<const N: usize> { fn method<const M: usize>(&mut self, arr: [[u8; M]; N]); } struct Bar<T, const N: usize> { inner: [T; N], } impl<const N: usize> Foo<N> for Bar<u8, N> { fn method<const M: usize>(&mut self, arr: [[u8; M]; N]) { for (elem, s) in self.inner.iter_mut().zip(arr.iter()) { for &x in s { *elem &= x; } } } } fn function<const N: u16>() -> u16 { // Const parameters can be used freely inside of functions. (N + 1) / 2 * N } fn main() { let mut bar = Bar { inner: [0xff; 3] }; // This infers the value of `M` from the type of the function argument. bar.method([[0b11_00, 0b01_00], [0b00_11, 0b00_01], [0b11_00, 0b00_11]]); assert_eq!(bar.inner, [0b01_00, 0b00_01, 0b00_00]); // You can also explicitly specify the value of `N`. assert_eq!(function::<17>(), 153); } ``` ## Motivation Rust has the built-in array type, which is parametric over a constant. Without const generics, this type can be quite cumbersome to use as it is not possible to generically implement a trait for arrays of different lengths. For example, this meant that, for a long time, the standard library only contained trait implementations for arrays up to a length of 32. This restriction has since been lifted through the use of const generics. Const parameters allow users to naturally specify variants of a generic type which are more naturally parameterized by values, rather than by types. For example, using const generics, many of the uses of the crate [typenum](https://crates.io/crates/typenum) may now be replaced with const parameters, improving compilation time as well as code readability and diagnostics. The subset described by `min_const_generics` is self-contained, but extensive enough to help with the most frequent issues: implementing traits for arrays and using arbitrarily-sized arrays inside of other types. Furthermore, it extends naturally to full `const_generics` once the remaining design and implementation questions have been resolved. ## In-depth feature description ### Declaring const parameters *Const parameters* are allowed in all places where types and lifetimes are supported. They use the syntax `const IDENT: Type`. Currently, const parameters must be declared after lifetime and type parameters. Their scope is equal to the scope of other generic parameters. They live in the value namespace. `Type` must be one of `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `char` and `bool`. This restriction is implemented in two places: 1. during name resolution, where we forbid generic parameters 1. during well-formedness checking, where we only allow the types listed above The updated syntax of parameter listings is: ``` GenericParams: (OuterAttr* LifetimeParam),* (OuterAttr* TypeParam),* (OuterAttr* ConstParam),* OuterAttr: '#[' ... ']' LifetimeParam: ... TypeParam: ... ConstParam: 'const' IDENT ':' Type ``` Unlike type and lifetime parameters, const parameters of types can be used without being mentioned inside of a parameterized type because const parameters do not have issues concerning variance. This means that the following types are allowed: ```rust struct Foo<const N: usize>; enum Bar<const M: usize> { A, B } ``` ### Const arguments Const parameters are instantiated using *const arguments*. Any concrete const expression or const parameter as a standalone argument can be used. When applying an expression as const parameter, most expressions must be contained within a block, with two exceptions: 1. literals and single-segment path expressions 1. array lengths This syntactic restriction is necessary to avoid ambiguity, or requiring infinite lookahead when parsing an expression as a generic argument. In the cases where a generic argument could be resolved as either a type or const argument, we always interpret it as a type. This causes the following test to fail: ```rust type N = u32; struct Foo<const N: usize>; fn foo<const N: usize>() -> Foo<N> { todo!() } // ERR ``` To circumvent this, the user may wrap the const parameter with braces, at which point it is unambiguously accepted. ```rust type N = u32; struct Foo<const N: usize>; fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ok ``` Operations depending on generic parameters are **not** allowed, which is enforced during well-formedness checking. Allowing generic unevaluated constants would require a way to check if they would always evaluate successfully to prevent errors that are not caught at declaration time. This ability forms part of `#![feature(const_evaluatable_checked)]`, which is not yet being stabilised. Since we are not yet stabilizing `#![feature(lazy_normalization_consts)]`, we must not supply the parent generics to anonymous constants except for repeat expressions. Doing so can cause cycle errors for arrays used in `where`-bounds. Not supplying the parent generics can however lead to ICEs occurring before well-formedness checking when trying to use a generic parameter. See rust-lang#56445 for details. Since we expect cases like this to occur more frequently once `min_const_generics` is stabilized, we have chosen to forbid generic parameters in anonymous constants during name resolution. While this changes the ICE in the situation above to an ordinary error, this is theoretically a breaking change, as early-bound lifetimes were previously permitted in repeat expressions but now are disallowed, causing the following snippet to break: ```rust fn late_bound<'a>() { let _ = [0; { let _: &'a (); // ICE ==> ERR 3 }]; } fn early_bound<'a>() where &'a (): Sized { let _ = [0; { let _: &'a (); // ok ==> ERR 3 }]; } ``` ### Using const parameters Const parameters can be used almost everywhere ordinary constants are allowed, except that they may not be used in the construction of consts, statics, functions, or types inside a function body and are subject to the generic argument restrictions mentioned above. Expressions containing const parameters are eligible for promotion: ```rust fn test<const N: usize>() -> &'static usize { &(3 + N) } ``` ### Symbol mangling See the [Rust symbol name mangling RFC](https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html) for an overview. Generic const parameters take the form `K[type][value]` when the value is known, or `Kp` where the value is not known, where: - `[type]` is any integral type, `bool`, or `char`. - `[value]` is the unsigned hex value for integers, preceded by `n` when negative; is `0` or `1` for `bool`; is the hex value for `char`. ### Exhaustiveness checking We do not check the exhaustiveness of impls, meaning that the following example does **not** compile: ```rust struct Foo<const B: bool>; trait Bar {} impl Bar for Foo<true> {} impl Bar for Foo<false> {} fn needs_bar(_: impl Bar) {} fn generic<const B: bool>() { let v = Foo::<B>; needs_bar(v); } ``` ### Type inference The value of const parameters can be inferred during typeck. One interesting case is the length of generic arrays, which can also be inferred from patterns (implemented in rust-lang#70562). Practical usage of this can be seen in rust-lang#76825. ### Equality of constants `#![feature(min_const_generics)]` only permits generic parameters to be used as standalone generic arguments. We compare two parameters to be equal if they are literally the same generic parameter. ### Associated constants Associated constants can use const parameters without restriction, see rust-lang#79135 (comment) for more details. ## Future work As this is a limited subset of rust-lang/rfcs#2000, there are quite a few extensions we will be looking into next. ### Lazy normalization of constants Stabilizing `#![feature(lazy_normalization_consts)]` (tracking issue rust-lang#72219) will remove some special cases that are currently necessary for `min_const_generics`, and unblocks operations on const parameters. ### Relaxing ordering requirements between const and type parameters We currently restrict the order of generic parameters so that types must come before consts. We could relax this, as is currently done with `const_generics`. Without this it is not possible to use both type defaults and const parameters at the same time. Unrestricting the order will require us to improve some diagnostics that expect there to be a strict order between type and const parameters. ### Allowing more parameter types We would like to support const parameters of more types, especially`&str` and user-defined types. Both are blocked on [valtrees]. There are also open questions regarding the design of `structural_match` concerning the latter. Supporting generic const parameter types such as `struct Foo<T, const N: T>` will be a lot harder and is unlikely to be implemented in the near future. ### Default values of const parameters We do not yet support default values for const parameters. There is work in progress to enable this on nightly (see rust-lang#75384). ### Generic const operations With `#![feature(min_const_generics)]`, only concrete const expressions and parameters as standalone arguments are allowed in types and repeat expressions. However, supporting generic const operations, such as `N + 1` or `std::mem::size_of::<T>()` is highly desirable. This feature is in early development under `#![feature(const_evaluatable_checked)]`. ## Implementation history Many people have contributed to the design and implementation of const generics over the last three years. See rust-lang#44580 (comment) for a summary. Once again thank you to everybody who helped out here! [valtrees]: rust-lang#72396 --- r? `@varkor`
closes #70529
This still errors in the following case
Considering that this should be rare and is harder to implement I would merge this PR without fixing the above.