diff --git a/src/librustc_typeck/check/pat.rs b/src/librustc_typeck/check/pat.rs index f9dee0e477f79..81e10e7a54010 100644 --- a/src/librustc_typeck/check/pat.rs +++ b/src/librustc_typeck/check/pat.rs @@ -89,6 +89,18 @@ impl<'tcx> FnCtxt<'_, 'tcx> { } } +const INITIAL_BM: BindingMode = BindingMode::BindByValue(hir::Mutability::Not); + +/// Mode for adjusting the expected type and binding mode. +enum AdjustMode { + /// Peel off all immediate reference types. + Peel, + /// Reset binding mode to the inital mode. + Reset, + /// Pass on the input binding mode and expected type. + Pass, +} + impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Type check the given top level pattern against the `expected` type. /// @@ -105,8 +117,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Option, origin_expr: bool, ) { - let def_bm = BindingMode::BindByValue(hir::Mutability::Not); - self.check_pat(pat, expected, def_bm, TopInfo { expected, origin_expr, span }); + self.check_pat(pat, expected, INITIAL_BM, TopInfo { expected, origin_expr, span }); } /// Type check the given `pat` against the `expected` type @@ -123,12 +134,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { debug!("check_pat(pat={:?},expected={:?},def_bm={:?})", pat, expected, def_bm); - let path_resolution = match &pat.kind { + let path_res = match &pat.kind { PatKind::Path(qpath) => Some(self.resolve_ty_and_res_ufcs(qpath, pat.hir_id, pat.span)), _ => None, }; - let is_nrp = self.is_non_ref_pat(pat, path_resolution.map(|(res, ..)| res)); - let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, is_nrp); + let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res)); + let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, adjust_mode); let ty = match pat.kind { PatKind::Wild => expected, @@ -141,7 +152,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, def_bm, ti) } PatKind::Path(ref qpath) => { - self.check_pat_path(pat, path_resolution.unwrap(), qpath, expected) + self.check_pat_path(pat, path_res.unwrap(), qpath, expected) } PatKind::Struct(ref qpath, fields, etc) => { self.check_pat_struct(pat, qpath, fields, etc, expected, def_bm, ti) @@ -223,64 +234,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, def_bm: BindingMode, - is_non_ref_pat: bool, + adjust_mode: AdjustMode, ) -> (Ty<'tcx>, BindingMode) { - if is_non_ref_pat { - debug!("pattern is non reference pattern"); - self.peel_off_references(pat, expected, def_bm) - } else { - // When you encounter a `&pat` pattern, reset to "by - // value". This is so that `x` and `y` here are by value, - // as they appear to be: - // - // ``` - // match &(&22, &44) { - // (&x, &y) => ... - // } - // ``` - // - // See issue #46688. - let def_bm = match pat.kind { - PatKind::Ref(..) => ty::BindByValue(hir::Mutability::Not), - _ => def_bm, - }; - (expected, def_bm) + match adjust_mode { + AdjustMode::Pass => (expected, def_bm), + AdjustMode::Reset => (expected, INITIAL_BM), + AdjustMode::Peel => self.peel_off_references(pat, expected, def_bm), } } - /// Is the pattern a "non reference pattern"? + /// How should the binding mode and expected type be adjusted? + /// /// When the pattern is a path pattern, `opt_path_res` must be `Some(res)`. - fn is_non_ref_pat(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option) -> bool { - match pat.kind { + fn calc_adjust_mode(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option) -> AdjustMode { + match &pat.kind { + // Type checking these product-like types successfully always require + // that the expected type be of those types and not reference types. PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Tuple(..) | PatKind::Box(_) | PatKind::Range(..) - | PatKind::Slice(..) => true, - PatKind::Lit(ref lt) => { - let ty = self.check_expr(lt); - match ty.kind { - ty::Ref(..) => false, - _ => true, - } - } + | PatKind::Slice(..) => AdjustMode::Peel, + // String and byte-string literals result in types `&str` and `&[u8]` respectively. + // All other literals result in non-reference types. + // As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo {}`. + PatKind::Lit(lt) => match self.check_expr(lt).kind { + ty::Ref(..) => AdjustMode::Pass, + _ => AdjustMode::Peel, + }, PatKind::Path(_) => match opt_path_res.unwrap() { - Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => false, - _ => true, + // These constants can be of a reference type, e.g. `const X: &u8 = &0;`. + // Peeling the reference types too early will cause type checking failures. + // Although it would be possible to *also* peel the types of the constants too. + Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => AdjustMode::Pass, + // In the `ValueNS`, we have `SelfCtor(..) | Ctor(_, Const), _)` remaining which + // could successfully compile. The former being `Self` requires a unit struct. + // In either case, and unlike constants, the pattern itself cannot be + // a reference type wherefore peeling doesn't give up any expressivity. + _ => AdjustMode::Peel, }, - // FIXME(or_patterns; Centril | dlrobertson): To keep things compiling - // for or-patterns at the top level, we need to make `p_0 | ... | p_n` - // a "non reference pattern". For example the following currently compiles: + // When encountering a `& mut? pat` pattern, reset to "by value". + // This is so that `x` and `y` here are by value, as they appear to be: + // // ``` - // match &1 { - // e @ &(1...2) | e @ &(3...4) => {} - // _ => {} + // match &(&22, &44) { + // (&x, &y) => ... // } // ``` // - // We should consider whether we should do something special in nested or-patterns. - PatKind::Or(_) | PatKind::Wild | PatKind::Binding(..) | PatKind::Ref(..) => false, + // See issue #46688. + PatKind::Ref(..) => AdjustMode::Reset, + // A `_` pattern works with any expected type, so there's no need to do anything. + PatKind::Wild + // Bindings also work with whatever the expected type is, + // and moreover if we peel references off, that will give us the wrong binding type. + // Also, we can have a subpattern `binding @ pat`. + // Each side of the `@` should be treated independently (like with OR-patterns). + | PatKind::Binding(..) + // An OR-pattern just propagates to each individual alternative. + // This is maximally flexible, allowing e.g., `Some(mut x) | &Some(mut x)`. + // In that example, `Some(mut x)` results in `Peel` whereas `&Some(mut x)` in `Reset`. + | PatKind::Or(_) => AdjustMode::Pass, } } @@ -508,7 +523,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let local_ty = self.local_ty(pat.span, pat.hir_id).decl_ty; let eq_ty = match bm { ty::BindByReference(mutbl) => { - // If the binding is like `ref x | ref const x | ref mut x` + // If the binding is like `ref x | ref mut x`, // then `x` is assigned a value of type `&M T` where M is the // mutability and T is the expected type. // diff --git a/src/test/ui/or-patterns/or-pattern-mismatch.rs b/src/test/ui/or-patterns/or-pattern-mismatch.rs deleted file mode 100644 index 973954bca5a5e..0000000000000 --- a/src/test/ui/or-patterns/or-pattern-mismatch.rs +++ /dev/null @@ -1,4 +0,0 @@ -enum Blah { A(isize, isize, usize), B(isize, isize) } - -fn main() { match Blah::A(1, 1, 2) { Blah::A(_, x, y) | Blah::B(x, y) => { } } } -//~^ ERROR mismatched types diff --git a/src/test/ui/or-patterns/or-pattern-mismatch.stderr b/src/test/ui/or-patterns/or-pattern-mismatch.stderr deleted file mode 100644 index bc288e0625075..0000000000000 --- a/src/test/ui/or-patterns/or-pattern-mismatch.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:3:68 - | -LL | fn main() { match Blah::A(1, 1, 2) { Blah::A(_, x, y) | Blah::B(x, y) => { } } } - | ---------------- this expression has type `Blah` ^ expected `usize`, found `isize` - -error: aborting due to previous error - -For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.rs b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.rs new file mode 100644 index 0000000000000..5ec7dc6962c18 --- /dev/null +++ b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.rs @@ -0,0 +1,68 @@ +// Here we test type checking of bindings when combined with or-patterns. +// Specifically, we ensure that introducing bindings of different types result in type errors. + +#![feature(or_patterns)] + +fn main() { + enum Blah { + A(isize, isize, usize), + B(isize, isize), + } + + match Blah::A(1, 1, 2) { + Blah::A(_, x, y) | Blah::B(x, y) => {} //~ ERROR mismatched types + } + + match Some(Blah::A(1, 1, 2)) { + Some(Blah::A(_, x, y) | Blah::B(x, y)) => {} //~ ERROR mismatched types + } + + match (0u8, 1u16) { + (x, y) | (y, x) => {} //~ ERROR mismatched types + //~^ ERROR mismatched types + } + + match Some((0u8, Some((1u16, 2u32)))) { + Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} + //~^ ERROR mismatched types + //~| ERROR mismatched types + //~| ERROR mismatched types + //~| ERROR mismatched types + _ => {} + } + + if let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2) { + //~^ ERROR mismatched types + } + + if let Some(Blah::A(_, x, y) | Blah::B(x, y)) = Some(Blah::A(1, 1, 2)) { + //~^ ERROR mismatched types + } + + if let (x, y) | (y, x) = (0u8, 1u16) { + //~^ ERROR mismatched types + //~| ERROR mismatched types + } + + if let Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) + //~^ ERROR mismatched types + //~| ERROR mismatched types + //~| ERROR mismatched types + //~| ERROR mismatched types + = Some((0u8, Some((1u16, 2u32)))) + {} + + let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2); + //~^ ERROR mismatched types + + let (x, y) | (y, x) = (0u8, 1u16); + //~^ ERROR mismatched types + //~| ERROR mismatched types + + fn f1((Blah::A(_, x, y) | Blah::B(x, y)): Blah) {} + //~^ ERROR mismatched types + + fn f2(((x, y) | (y, x)): (u8, u16)) {} + //~^ ERROR mismatched types + //~| ERROR mismatched types +} diff --git a/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr new file mode 100644 index 0000000000000..5094f04b9204f --- /dev/null +++ b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr @@ -0,0 +1,183 @@ +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:13:39 + | +LL | match Blah::A(1, 1, 2) { + | ---------------- this expression has type `main::Blah` +LL | Blah::A(_, x, y) | Blah::B(x, y) => {} + | ^ expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:17:44 + | +LL | match Some(Blah::A(1, 1, 2)) { + | ---------------------- this expression has type `std::option::Option` +LL | Some(Blah::A(_, x, y) | Blah::B(x, y)) => {} + | ^ expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:21:19 + | +LL | match (0u8, 1u16) { + | ----------- this expression has type `(u8, u16)` +LL | (x, y) | (y, x) => {} + | ^ expected `u16`, found `u8` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:21:22 + | +LL | match (0u8, 1u16) { + | ----------- this expression has type `(u8, u16)` +LL | (x, y) | (y, x) => {} + | ^ expected `u8`, found `u16` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:26:41 + | +LL | match Some((0u8, Some((1u16, 2u32)))) { + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` +LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} + | ^ expected `u16`, found `u8` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:26:50 + | +LL | match Some((0u8, Some((1u16, 2u32)))) { + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` +LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} + | ^ expected `u8`, found `u16` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:26:59 + | +LL | match Some((0u8, Some((1u16, 2u32)))) { + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` +LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} + | ^ expected `u32`, found `u16` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:26:62 + | +LL | match Some((0u8, Some((1u16, 2u32)))) { + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` +LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} + | ^ expected `u8`, found `u32` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:34:42 + | +LL | if let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2) { + | ^ ---------------- this expression has type `main::Blah` + | | + | expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:38:47 + | +LL | if let Some(Blah::A(_, x, y) | Blah::B(x, y)) = Some(Blah::A(1, 1, 2)) { + | ^ ---------------------- this expression has type `std::option::Option` + | | + | expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:42:22 + | +LL | if let (x, y) | (y, x) = (0u8, 1u16) { + | ^ ----------- this expression has type `(u8, u16)` + | | + | expected `u16`, found `u8` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:42:25 + | +LL | if let (x, y) | (y, x) = (0u8, 1u16) { + | ^ ----------- this expression has type `(u8, u16)` + | | + | expected `u8`, found `u16` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:47:44 + | +LL | if let Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) + | ^ expected `u16`, found `u8` +... +LL | = Some((0u8, Some((1u16, 2u32)))) + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:47:53 + | +LL | if let Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) + | ^ expected `u8`, found `u16` +... +LL | = Some((0u8, Some((1u16, 2u32)))) + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:47:62 + | +LL | if let Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) + | ^ expected `u32`, found `u16` +... +LL | = Some((0u8, Some((1u16, 2u32)))) + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:47:65 + | +LL | if let Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) + | ^ expected `u8`, found `u32` +... +LL | = Some((0u8, Some((1u16, 2u32)))) + | ------------------------------- this expression has type `std::option::Option<(u8, std::option::Option<(u16, u32)>)>` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:55:39 + | +LL | let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2); + | ^ ---------------- this expression has type `main::Blah` + | | + | expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:58:19 + | +LL | let (x, y) | (y, x) = (0u8, 1u16); + | ^ ----------- this expression has type `(u8, u16)` + | | + | expected `u16`, found `u8` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:58:22 + | +LL | let (x, y) | (y, x) = (0u8, 1u16); + | ^ ----------- this expression has type `(u8, u16)` + | | + | expected `u8`, found `u16` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:62:42 + | +LL | fn f1((Blah::A(_, x, y) | Blah::B(x, y)): Blah) {} + | ^ ---- expected due to this + | | + | expected `usize`, found `isize` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:65:22 + | +LL | fn f2(((x, y) | (y, x)): (u8, u16)) {} + | ^ --------- expected due to this + | | + | expected `u16`, found `u8` + +error[E0308]: mismatched types + --> $DIR/or-patterns-binding-type-mismatch.rs:65:25 + | +LL | fn f2(((x, y) | (y, x)): (u8, u16)) {} + | ^ --------- expected due to this + | | + | expected `u8`, found `u16` + +error: aborting due to 22 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/src/test/ui/or-patterns/or-patterns-default-binding-modes.rs b/src/test/ui/or-patterns/or-patterns-default-binding-modes.rs new file mode 100644 index 0000000000000..3b6047c7be47d --- /dev/null +++ b/src/test/ui/or-patterns/or-patterns-default-binding-modes.rs @@ -0,0 +1,132 @@ +// Test that or-patterns are pass-through with respect to default binding modes. + +// check-pass + +#![feature(or_patterns)] +#![allow(irrefutable_let_patterns)] + +fn main() { + // A regression test for a mistake we made at one point: + match &1 { + e @ &(1..=2) | e @ &(3..=4) => {} + _ => {} + } + + match &0 { + 0 | &1 => {} + _ => {} + } + + type R<'a> = &'a Result; + + let res: R<'_> = &Ok(0); + + match res { + // Alternatives propagate expected type / binding mode independently. + Ok(mut x) | &Err(mut x) => drop::(x), + } + match res { + &(Ok(x) | Err(x)) => drop::(x), + } + match res { + Ok(x) | Err(x) => drop::<&u8>(x), + } + if let Ok(mut x) | &Err(mut x) = res { + drop::(x); + } + if let &(Ok(x) | Err(x)) = res { + drop::(x); + } + let Ok(mut x) | &Err(mut x) = res; + drop::(x); + let &(Ok(x) | Err(x)) = res; + drop::(x); + let Ok(x) | Err(x) = res; + drop::<&u8>(x); + for Ok(mut x) | &Err(mut x) in std::iter::once(res) { + drop::(x); + } + for &(Ok(x) | Err(x)) in std::iter::once(res) { + drop::(x); + } + for Ok(x) | Err(x) in std::iter::once(res) { + drop::<&u8>(x); + } + fn f1((Ok(mut x) | &Err(mut x)): R<'_>) { + drop::(x); + } + fn f2(&(Ok(x) | Err(x)): R<'_>) { + drop::(x); + } + fn f3((Ok(x) | Err(x)): R<'_>) { + drop::<&u8>(x); + } + + // Wrap inside another type (a product for a simplity with irrefutable contexts). + #[derive(Copy, Clone)] + struct Wrap(T); + let wres = Wrap(res); + + match wres { + Wrap(Ok(mut x) | &Err(mut x)) => drop::(x), + } + match wres { + Wrap(&(Ok(x) | Err(x))) => drop::(x), + } + match wres { + Wrap(Ok(x) | Err(x)) => drop::<&u8>(x), + } + if let Wrap(Ok(mut x) | &Err(mut x)) = wres { + drop::(x); + } + if let Wrap(&(Ok(x) | Err(x))) = wres { + drop::(x); + } + if let Wrap(Ok(x) | Err(x)) = wres { + drop::<&u8>(x); + } + let Wrap(Ok(mut x) | &Err(mut x)) = wres; + drop::(x); + let Wrap(&(Ok(x) | Err(x))) = wres; + drop::(x); + let Wrap(Ok(x) | Err(x)) = wres; + drop::<&u8>(x); + for Wrap(Ok(mut x) | &Err(mut x)) in std::iter::once(wres) { + drop::(x); + } + for Wrap(&(Ok(x) | Err(x))) in std::iter::once(wres) { + drop::(x); + } + for Wrap(Ok(x) | Err(x)) in std::iter::once(wres) { + drop::<&u8>(x); + } + fn fw1(Wrap(Ok(mut x) | &Err(mut x)): Wrap>) { + drop::(x); + } + fn fw2(Wrap(&(Ok(x) | Err(x))): Wrap>) { + drop::(x); + } + fn fw3(Wrap(Ok(x) | Err(x)): Wrap>) { + drop::<&u8>(x); + } + + // Nest some more: + + enum Tri

{ + A(P), + B(P), + C(P), + } + + let tri = &Tri::A(&Ok(0)); + let Tri::A(Ok(mut x) | Err(mut x)) + | Tri::B(&Ok(mut x) | Err(mut x)) + | &Tri::C(Ok(mut x) | Err(mut x)) = tri; + drop::(x); + + match tri { + Tri::A(Ok(mut x) | Err(mut x)) + | Tri::B(&Ok(mut x) | Err(mut x)) + | &Tri::C(Ok(mut x) | Err(mut x)) => drop::(x), + } +}