From 90afc0765e5e536af6307b63e1655a38df06e235 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Wed, 22 Jan 2020 19:23:37 -0500 Subject: [PATCH 01/12] Use a `ParamEnvAnd` for caching in `ObligationForest` Previously, we used a plain `Predicate` to cache results (e.g. successes and failures) in ObligationForest. However, fulfillment depends on the precise `ParamEnv` used, so this is unsound in general. This commit changes the impl of `ForestObligation` for `PendingPredicateObligation` to use `ParamEnvAnd` instead of `Predicate` for the associated type. The associated type and method are renamed from 'predicate' to 'cache_key' to reflect the fact that type is no longer just a predicate. --- src/librustc/traits/fulfill.rs | 9 ++++-- .../obligation_forest/graphviz.rs | 2 +- .../obligation_forest/mod.rs | 29 +++++++++++-------- .../obligation_forest/tests.rs | 4 +-- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/librustc/traits/fulfill.rs b/src/librustc/traits/fulfill.rs index 0aac6fb81e4..07352a3f947 100644 --- a/src/librustc/traits/fulfill.rs +++ b/src/librustc/traits/fulfill.rs @@ -18,10 +18,13 @@ use super::{FulfillmentError, FulfillmentErrorCode}; use super::{ObligationCause, PredicateObligation}; impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> { - type Predicate = ty::Predicate<'tcx>; + /// Note that we include both the `ParamEnv` and the `Predicate`, + /// as the `ParamEnv` can influence whether fulfillment succeeds + /// or fails. + type CacheKey = ty::ParamEnvAnd<'tcx, ty::Predicate<'tcx>>; - fn as_predicate(&self) -> &Self::Predicate { - &self.obligation.predicate + fn as_cache_key(&self) -> Self::CacheKey { + self.obligation.param_env.and(self.obligation.predicate) } } diff --git a/src/librustc_data_structures/obligation_forest/graphviz.rs b/src/librustc_data_structures/obligation_forest/graphviz.rs index ddf89d99621..96ee72d187b 100644 --- a/src/librustc_data_structures/obligation_forest/graphviz.rs +++ b/src/librustc_data_structures/obligation_forest/graphviz.rs @@ -51,7 +51,7 @@ impl<'a, O: ForestObligation + 'a> dot::Labeller<'a> for &'a ObligationForest fn node_label(&self, index: &Self::Node) -> dot::LabelText<'_> { let node = &self.nodes[*index]; - let label = format!("{:?} ({:?})", node.obligation.as_predicate(), node.state.get()); + let label = format!("{:?} ({:?})", node.obligation.as_cache_key(), node.state.get()); dot::LabelText::LabelStr(label.into()) } diff --git a/src/librustc_data_structures/obligation_forest/mod.rs b/src/librustc_data_structures/obligation_forest/mod.rs index 974d9dcfae4..500ce5c71f3 100644 --- a/src/librustc_data_structures/obligation_forest/mod.rs +++ b/src/librustc_data_structures/obligation_forest/mod.rs @@ -86,9 +86,13 @@ mod graphviz; mod tests; pub trait ForestObligation: Clone + Debug { - type Predicate: Clone + hash::Hash + Eq + Debug; + type CacheKey: Clone + hash::Hash + Eq + Debug; - fn as_predicate(&self) -> &Self::Predicate; + /// Converts this `ForestObligation` suitable for use as a cache key. + /// If two distinct `ForestObligations`s return the same cache key, + /// then it must be sound to use the result of processing one obligation + /// (e.g. success for error) for the other obligation + fn as_cache_key(&self) -> Self::CacheKey; } pub trait ObligationProcessor { @@ -138,12 +142,12 @@ pub struct ObligationForest { nodes: Vec>, /// A cache of predicates that have been successfully completed. - done_cache: FxHashSet, + done_cache: FxHashSet, /// A cache of the nodes in `nodes`, indexed by predicate. Unfortunately, /// its contents are not guaranteed to match those of `nodes`. See the /// comments in `process_obligation` for details. - active_cache: FxHashMap, + active_cache: FxHashMap, /// A vector reused in compress(), to avoid allocating new vectors. node_rewrites: RefCell>, @@ -157,7 +161,7 @@ pub struct ObligationForest { /// See [this][details] for details. /// /// [details]: https://github.com/rust-lang/rust/pull/53255#issuecomment-421184780 - error_cache: FxHashMap>, + error_cache: FxHashMap>, } #[derive(Debug)] @@ -305,11 +309,12 @@ impl ObligationForest { // Returns Err(()) if we already know this obligation failed. fn register_obligation_at(&mut self, obligation: O, parent: Option) -> Result<(), ()> { - if self.done_cache.contains(obligation.as_predicate()) { + if self.done_cache.contains(&obligation.as_cache_key()) { + debug!("register_obligation_at: ignoring already done obligation: {:?}", obligation); return Ok(()); } - match self.active_cache.entry(obligation.as_predicate().clone()) { + match self.active_cache.entry(obligation.as_cache_key().clone()) { Entry::Occupied(o) => { let node = &mut self.nodes[*o.get()]; if let Some(parent_index) = parent { @@ -333,7 +338,7 @@ impl ObligationForest { && self .error_cache .get(&obligation_tree_id) - .map(|errors| errors.contains(obligation.as_predicate())) + .map(|errors| errors.contains(&obligation.as_cache_key())) .unwrap_or(false); if already_failed { @@ -380,7 +385,7 @@ impl ObligationForest { self.error_cache .entry(node.obligation_tree_id) .or_default() - .insert(node.obligation.as_predicate().clone()); + .insert(node.obligation.as_cache_key().clone()); } /// Performs a pass through the obligation list. This must @@ -618,11 +623,11 @@ impl ObligationForest { // `self.nodes`. See the comment in `process_obligation` // for more details. if let Some((predicate, _)) = - self.active_cache.remove_entry(node.obligation.as_predicate()) + self.active_cache.remove_entry(&node.obligation.as_cache_key()) { self.done_cache.insert(predicate); } else { - self.done_cache.insert(node.obligation.as_predicate().clone()); + self.done_cache.insert(node.obligation.as_cache_key().clone()); } if do_completed == DoCompleted::Yes { // Extract the success stories. @@ -635,7 +640,7 @@ impl ObligationForest { // We *intentionally* remove the node from the cache at this point. Otherwise // tests must come up with a different type on every type error they // check against. - self.active_cache.remove(node.obligation.as_predicate()); + self.active_cache.remove(&node.obligation.as_cache_key()); self.insert_into_error_cache(index); node_rewrites[index] = orig_nodes_len; dead_nodes += 1; diff --git a/src/librustc_data_structures/obligation_forest/tests.rs b/src/librustc_data_structures/obligation_forest/tests.rs index e29335aab28..01652465eea 100644 --- a/src/librustc_data_structures/obligation_forest/tests.rs +++ b/src/librustc_data_structures/obligation_forest/tests.rs @@ -4,9 +4,9 @@ use std::fmt; use std::marker::PhantomData; impl<'a> super::ForestObligation for &'a str { - type Predicate = &'a str; + type CacheKey = &'a str; - fn as_predicate(&self) -> &Self::Predicate { + fn as_cache_key(&self) -> Self::CacheKey { self } } From 6509db844315882db7ec0b624ca1e7b04d72568d Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 5 Feb 2020 05:03:37 +0100 Subject: [PATCH 02/12] or_patterns: harden bindings test --- .../ui/or-patterns/or-pattern-mismatch.rs | 70 ++++++- .../ui/or-patterns/or-pattern-mismatch.stderr | 182 +++++++++++++++++- 2 files changed, 245 insertions(+), 7 deletions(-) diff --git a/src/test/ui/or-patterns/or-pattern-mismatch.rs b/src/test/ui/or-patterns/or-pattern-mismatch.rs index 973954bca5a..5ec7dc6962c 100644 --- a/src/test/ui/or-patterns/or-pattern-mismatch.rs +++ b/src/test/ui/or-patterns/or-pattern-mismatch.rs @@ -1,4 +1,68 @@ -enum Blah { A(isize, isize, usize), B(isize, isize) } +// 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. -fn main() { match Blah::A(1, 1, 2) { Blah::A(_, x, y) | Blah::B(x, y) => { } } } -//~^ ERROR mismatched types +#![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-pattern-mismatch.stderr b/src/test/ui/or-patterns/or-pattern-mismatch.stderr index bc288e06250..84c38e3be90 100644 --- a/src/test/ui/or-patterns/or-pattern-mismatch.stderr +++ b/src/test/ui/or-patterns/or-pattern-mismatch.stderr @@ -1,9 +1,183 @@ error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:3:68 + --> $DIR/or-pattern-mismatch.rs:13:39 | -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` +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: aborting due to previous error +error[E0308]: mismatched types + --> $DIR/or-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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-pattern-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`. From 29437e55a56c1c1251ae5f7276f3e95dac4b609a Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 5 Feb 2020 05:05:29 +0100 Subject: [PATCH 03/12] or_patterns: rename previous test --- ...s => or-patterns-binding-type-mismatch.rs} | 0 ... or-patterns-binding-type-mismatch.stderr} | 44 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) rename src/test/ui/or-patterns/{or-pattern-mismatch.rs => or-patterns-binding-type-mismatch.rs} (100%) rename src/test/ui/or-patterns/{or-pattern-mismatch.stderr => or-patterns-binding-type-mismatch.stderr} (85%) diff --git a/src/test/ui/or-patterns/or-pattern-mismatch.rs b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.rs similarity index 100% rename from src/test/ui/or-patterns/or-pattern-mismatch.rs rename to src/test/ui/or-patterns/or-patterns-binding-type-mismatch.rs diff --git a/src/test/ui/or-patterns/or-pattern-mismatch.stderr b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr similarity index 85% rename from src/test/ui/or-patterns/or-pattern-mismatch.stderr rename to src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr index 84c38e3be90..5094f04b920 100644 --- a/src/test/ui/or-patterns/or-pattern-mismatch.stderr +++ b/src/test/ui/or-patterns/or-patterns-binding-type-mismatch.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:13:39 + --> $DIR/or-patterns-binding-type-mismatch.rs:13:39 | LL | match Blah::A(1, 1, 2) { | ---------------- this expression has type `main::Blah` @@ -7,7 +7,7 @@ LL | Blah::A(_, x, y) | Blah::B(x, y) => {} | ^ expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:17:44 + --> $DIR/or-patterns-binding-type-mismatch.rs:17:44 | LL | match Some(Blah::A(1, 1, 2)) { | ---------------------- this expression has type `std::option::Option` @@ -15,7 +15,7 @@ LL | Some(Blah::A(_, x, y) | Blah::B(x, y)) => {} | ^ expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:21:19 + --> $DIR/or-patterns-binding-type-mismatch.rs:21:19 | LL | match (0u8, 1u16) { | ----------- this expression has type `(u8, u16)` @@ -23,7 +23,7 @@ LL | (x, y) | (y, x) => {} | ^ expected `u16`, found `u8` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:21:22 + --> $DIR/or-patterns-binding-type-mismatch.rs:21:22 | LL | match (0u8, 1u16) { | ----------- this expression has type `(u8, u16)` @@ -31,7 +31,7 @@ LL | (x, y) | (y, x) => {} | ^ expected `u8`, found `u16` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:26:41 + --> $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)>)>` @@ -39,7 +39,7 @@ LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} | ^ expected `u16`, found `u8` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:26:50 + --> $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)>)>` @@ -47,7 +47,7 @@ LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} | ^ expected `u8`, found `u16` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:26:59 + --> $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)>)>` @@ -55,7 +55,7 @@ LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} | ^ expected `u32`, found `u16` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:26:62 + --> $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)>)>` @@ -63,7 +63,7 @@ LL | Some((x, Some((y, z)))) | Some((y, Some((x, z) | (z, x)))) => {} | ^ expected `u8`, found `u32` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:34:42 + --> $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` @@ -71,7 +71,7 @@ LL | if let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2) { | expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:38:47 + --> $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` @@ -79,7 +79,7 @@ LL | if let Some(Blah::A(_, x, y) | Blah::B(x, y)) = Some(Blah::A(1, 1, 2)) | expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:42:22 + --> $DIR/or-patterns-binding-type-mismatch.rs:42:22 | LL | if let (x, y) | (y, x) = (0u8, 1u16) { | ^ ----------- this expression has type `(u8, u16)` @@ -87,7 +87,7 @@ LL | if let (x, y) | (y, x) = (0u8, 1u16) { | expected `u16`, found `u8` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:42:25 + --> $DIR/or-patterns-binding-type-mismatch.rs:42:25 | LL | if let (x, y) | (y, x) = (0u8, 1u16) { | ^ ----------- this expression has type `(u8, u16)` @@ -95,7 +95,7 @@ LL | if let (x, y) | (y, x) = (0u8, 1u16) { | expected `u8`, found `u16` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:47:44 + --> $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` @@ -104,7 +104,7 @@ 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-pattern-mismatch.rs:47:53 + --> $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` @@ -113,7 +113,7 @@ 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-pattern-mismatch.rs:47:62 + --> $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` @@ -122,7 +122,7 @@ 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-pattern-mismatch.rs:47:65 + --> $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` @@ -131,7 +131,7 @@ 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-pattern-mismatch.rs:55:39 + --> $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` @@ -139,7 +139,7 @@ LL | let Blah::A(_, x, y) | Blah::B(x, y) = Blah::A(1, 1, 2); | expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:58:19 + --> $DIR/or-patterns-binding-type-mismatch.rs:58:19 | LL | let (x, y) | (y, x) = (0u8, 1u16); | ^ ----------- this expression has type `(u8, u16)` @@ -147,7 +147,7 @@ LL | let (x, y) | (y, x) = (0u8, 1u16); | expected `u16`, found `u8` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:58:22 + --> $DIR/or-patterns-binding-type-mismatch.rs:58:22 | LL | let (x, y) | (y, x) = (0u8, 1u16); | ^ ----------- this expression has type `(u8, u16)` @@ -155,7 +155,7 @@ LL | let (x, y) | (y, x) = (0u8, 1u16); | expected `u8`, found `u16` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:62:42 + --> $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 @@ -163,7 +163,7 @@ LL | fn f1((Blah::A(_, x, y) | Blah::B(x, y)): Blah) {} | expected `usize`, found `isize` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:65:22 + --> $DIR/or-patterns-binding-type-mismatch.rs:65:22 | LL | fn f2(((x, y) | (y, x)): (u8, u16)) {} | ^ --------- expected due to this @@ -171,7 +171,7 @@ LL | fn f2(((x, y) | (y, x)): (u8, u16)) {} | expected `u16`, found `u8` error[E0308]: mismatched types - --> $DIR/or-pattern-mismatch.rs:65:25 + --> $DIR/or-patterns-binding-type-mismatch.rs:65:25 | LL | fn f2(((x, y) | (y, x)): (u8, u16)) {} | ^ --------- expected due to this From 17e632d382dfae46e9dfa684db9bddec3e8951a7 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 5 Feb 2020 10:32:54 +0100 Subject: [PATCH 04/12] or_patterns: test default binding modes --- .../or-patterns-default-binding-modes.rs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/test/ui/or-patterns/or-patterns-default-binding-modes.rs 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 00000000000..3b6047c7be4 --- /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), + } +} From b5aca3128d5c0ee2441ec9ca9a9c3ae4f391ef16 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Wed, 5 Feb 2020 04:45:13 +0100 Subject: [PATCH 05/12] typeck: refactor default binding mode logic & improve docs --- src/librustc_typeck/check/pat.rs | 127 +++++++++++++++++-------------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/src/librustc_typeck/check/pat.rs b/src/librustc_typeck/check/pat.rs index f9dee0e477f..81e10e7a540 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,15 +234,48 @@ 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 adjust_mode { + AdjustMode::Pass => (expected, def_bm), + AdjustMode::Reset => (expected, INITIAL_BM), + AdjustMode::Peel => self.peel_off_references(pat, expected, def_bm), + } + } + + /// 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 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(..) => 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() { + // 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, + }, + // 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 &(&22, &44) { @@ -240,47 +284,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // ``` // // See issue #46688. - let def_bm = match pat.kind { - PatKind::Ref(..) => ty::BindByValue(hir::Mutability::Not), - _ => def_bm, - }; - (expected, def_bm) - } - } - - /// Is the pattern a "non reference pattern"? - /// 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 { - 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::Path(_) => match opt_path_res.unwrap() { - Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => false, - _ => true, - }, - // 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: - // ``` - // match &1 { - // e @ &(1...2) | e @ &(3...4) => {} - // _ => {} - // } - // ``` - // - // We should consider whether we should do something special in nested or-patterns. - PatKind::Or(_) | PatKind::Wild | PatKind::Binding(..) | PatKind::Ref(..) => false, + 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. // From 38060567e89bb142e8a060d91bf53f7e82eaaae6 Mon Sep 17 00:00:00 2001 From: varkor Date: Sat, 11 Jan 2020 14:30:02 +0000 Subject: [PATCH 06/12] Correct inference of primitive operand type behind binary operation --- src/librustc_typeck/check/op.rs | 41 ++++++++++++++----- .../infer-binary-operand-behind-reference.rs | 13 ++++++ 2 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 src/test/ui/inference/infer-binary-operand-behind-reference.rs diff --git a/src/librustc_typeck/check/op.rs b/src/librustc_typeck/check/op.rs index 028c39b80e4..0c1557a59c2 100644 --- a/src/librustc_typeck/check/op.rs +++ b/src/librustc_typeck/check/op.rs @@ -25,7 +25,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) { - self.enforce_builtin_binop_types(lhs, lhs_ty, rhs, rhs_ty, op); + self.enforce_builtin_binop_types(&lhs.span, lhs_ty, &rhs.span, rhs_ty, op); self.tcx.mk_unit() } else { return_ty @@ -86,8 +86,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) { - let builtin_return_ty = - self.enforce_builtin_binop_types(lhs_expr, lhs_ty, rhs_expr, rhs_ty, op); + let builtin_return_ty = self.enforce_builtin_binop_types( + &lhs_expr.span, + lhs_ty, + &rhs_expr.span, + rhs_ty, + op, + ); self.demand_suptype(expr.span, builtin_return_ty, return_ty); } @@ -98,19 +103,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn enforce_builtin_binop_types( &self, - lhs_expr: &'tcx hir::Expr<'tcx>, + lhs_span: &Span, lhs_ty: Ty<'tcx>, - rhs_expr: &'tcx hir::Expr<'tcx>, + rhs_span: &Span, rhs_ty: Ty<'tcx>, op: hir::BinOp, ) -> Ty<'tcx> { debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op)); + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. + // (See https://github.com/rust-lang/rust/issues/57447.) + let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty)); + let tcx = self.tcx; match BinOpCategory::from(op) { BinOpCategory::Shortcircuit => { - self.demand_suptype(lhs_expr.span, tcx.mk_bool(), lhs_ty); - self.demand_suptype(rhs_expr.span, tcx.mk_bool(), rhs_ty); + self.demand_suptype(*lhs_span, tcx.mk_bool(), lhs_ty); + self.demand_suptype(*rhs_span, tcx.mk_bool(), rhs_ty); tcx.mk_bool() } @@ -121,13 +130,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { BinOpCategory::Math | BinOpCategory::Bitwise => { // both LHS and RHS and result will have the same type - self.demand_suptype(rhs_expr.span, lhs_ty, rhs_ty); + self.demand_suptype(*rhs_span, lhs_ty, rhs_ty); lhs_ty } BinOpCategory::Comparison => { // both LHS and RHS and result will have the same type - self.demand_suptype(rhs_expr.span, lhs_ty, rhs_ty); + self.demand_suptype(*rhs_span, lhs_ty, rhs_ty); tcx.mk_bool() } } @@ -862,6 +871,14 @@ enum Op { Unary(hir::UnOp, Span), } +/// Dereferences a single level of immutable referencing. +fn deref_ty_if_possible<'tcx>(ty: Ty<'tcx>) -> Ty<'tcx> { + match ty.kind { + ty::Ref(_, ty, hir::Mutability::Not) => ty, + _ => ty, + } +} + /// Returns `true` if this is a built-in arithmetic operation (e.g., u32 /// + u32, i16x4 == i16x4) and false if these types would have to be /// overloaded to be legal. There are two reasons that we distinguish @@ -878,7 +895,11 @@ enum Op { /// Reason #2 is the killer. I tried for a while to always use /// overloaded logic and just check the types in constants/codegen after /// the fact, and it worked fine, except for SIMD types. -nmatsakis -fn is_builtin_binop(lhs: Ty<'_>, rhs: Ty<'_>, op: hir::BinOp) -> bool { +fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool { + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. + // (See https://github.com/rust-lang/rust/issues/57447.) + let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs)); + match BinOpCategory::from(op) { BinOpCategory::Shortcircuit => true, diff --git a/src/test/ui/inference/infer-binary-operand-behind-reference.rs b/src/test/ui/inference/infer-binary-operand-behind-reference.rs new file mode 100644 index 00000000000..4e41077ba34 --- /dev/null +++ b/src/test/ui/inference/infer-binary-operand-behind-reference.rs @@ -0,0 +1,13 @@ +// check-pass + +fn main() { + let _: u8 = 0 + 0; + let _: u8 = 0 + &0; + let _: u8 = &0 + 0; + let _: u8 = &0 + &0; + + let _: f32 = 0.0 + 0.0; + let _: f32 = 0.0 + &0.0; + let _: f32 = &0.0 + 0.0; + let _: f32 = &0.0 + &0.0; +} From 0276d7a32e1c83abc3106f7b36b711faf1f74dff Mon Sep 17 00:00:00 2001 From: varkor Date: Sun, 9 Feb 2020 01:51:13 +0000 Subject: [PATCH 07/12] Add more tests --- .../infer-binary-operand-behind-reference.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/ui/inference/infer-binary-operand-behind-reference.rs b/src/test/ui/inference/infer-binary-operand-behind-reference.rs index 4e41077ba34..0c0a3171ee9 100644 --- a/src/test/ui/inference/infer-binary-operand-behind-reference.rs +++ b/src/test/ui/inference/infer-binary-operand-behind-reference.rs @@ -1,6 +1,8 @@ // check-pass fn main() { + // Test that we can infer the type of binary operands when + // references are involved, on various types and operators. let _: u8 = 0 + 0; let _: u8 = 0 + &0; let _: u8 = &0 + 0; @@ -10,4 +12,19 @@ fn main() { let _: f32 = 0.0 + &0.0; let _: f32 = &0.0 + 0.0; let _: f32 = &0.0 + &0.0; + + let _: u8 = 0 << 0; + let _: u8 = 0 << &0; + let _: u8 = &0 << 0; + let _: u8 = &0 << &0; + + // Test type inference when variable types are indirectly inferred. + let a = 22; + let _: usize = a + &44; + + // When we have no expected type, the types of the operands is the default type. + let _ = 0 + 0; + let _ = 0 + &0; + let _ = &0 + 0; + let _ = &0 + &0; } From ebbaf4611a9605412d2aa31c8ebaf0745557fff0 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Tue, 11 Feb 2020 10:14:50 +0100 Subject: [PATCH 08/12] simplify_try: address some of eddyb's comments --- src/librustc_mir/transform/simplify_try.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/librustc_mir/transform/simplify_try.rs b/src/librustc_mir/transform/simplify_try.rs index bd661195a48..3f28f033047 100644 --- a/src/librustc_mir/transform/simplify_try.rs +++ b/src/librustc_mir/transform/simplify_try.rs @@ -52,6 +52,8 @@ impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { Some(x) => x, }; if local_tmp_s0 != local_tmp_s1 + // Avoid moving into ourselves. + || local_0 == local_1 // The field-and-variant information match up. || vf_s0 != vf_s1 // Source and target locals have the same type. @@ -64,6 +66,7 @@ impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { } // Right shape; transform! + s0.source_info = s2.source_info; match &mut s0.kind { StatementKind::Assign(box (place, rvalue)) => { *place = local_0.into(); From f5bd9646be31d865a083193c21c7448d546ce07c Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 13 Feb 2020 12:19:36 +0100 Subject: [PATCH 09/12] fix extra subslice lowering --- src/librustc_ast_lowering/pat.rs | 17 +++++++----- .../issue-69103-extra-binding-subslice.rs | 18 +++++++++++++ .../issue-69103-extra-binding-subslice.stderr | 26 +++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.rs create mode 100644 src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.stderr diff --git a/src/librustc_ast_lowering/pat.rs b/src/librustc_ast_lowering/pat.rs index 4c3c4ddac78..b42b12c4dd8 100644 --- a/src/librustc_ast_lowering/pat.rs +++ b/src/librustc_ast_lowering/pat.rs @@ -128,6 +128,13 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let mut slice = None; let mut prev_rest_span = None; + // Lowers `$bm $ident @ ..` to `$bm $ident @ _`. + let lower_rest_sub = |this: &mut Self, pat, bm, ident, sub| { + let lower_sub = |this: &mut Self| Some(this.pat_wild_with_node_id_of(sub)); + let node = this.lower_pat_ident(pat, bm, ident, lower_sub); + this.pat_with_node_id_of(pat, node) + }; + let mut iter = pats.iter(); // Lower all the patterns until the first occurrence of a sub-slice pattern. for pat in iter.by_ref() { @@ -142,9 +149,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // Record, lower it to `$binding_mode $ident @ _`, and stop here. PatKind::Ident(ref bm, ident, Some(ref sub)) if sub.is_rest() => { prev_rest_span = Some(sub.span); - let lower_sub = |this: &mut Self| Some(this.pat_wild_with_node_id_of(sub)); - let node = self.lower_pat_ident(pat, bm, ident, lower_sub); - slice = Some(self.pat_with_node_id_of(pat, node)); + slice = Some(lower_rest_sub(self, pat, bm, ident, sub)); break; } // It was not a subslice pattern so lower it normally. @@ -157,9 +162,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // There was a previous subslice pattern; make sure we don't allow more. let rest_span = match pat.kind { PatKind::Rest => Some(pat.span), - PatKind::Ident(.., Some(ref sub)) if sub.is_rest() => { - // The `HirValidator` is merciless; add a `_` pattern to avoid ICEs. - after.push(self.pat_wild_with_node_id_of(pat)); + PatKind::Ident(ref bm, ident, Some(ref sub)) if sub.is_rest() => { + // #69103: Lower into `binding @ _` as above to avoid ICEs. + after.push(lower_rest_sub(self, pat, bm, ident, sub)); Some(sub.span) } _ => None, diff --git a/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.rs b/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.rs new file mode 100644 index 00000000000..061b0d675b3 --- /dev/null +++ b/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.rs @@ -0,0 +1,18 @@ +// We used to not lower the extra `b @ ..` into `b @ _` which meant that no type +// was registered for the binding `b` although it passed through resolve. +// This resulted in an ICE (#69103). + +fn main() { + let [a @ .., b @ ..] = &mut [1, 2]; + //~^ ERROR `..` can only be used once per slice pattern + b; + + let [.., c @ ..] = [1, 2]; + //~^ ERROR `..` can only be used once per slice pattern + c; + + // This never ICEd, but let's make sure it won't regress either. + let (.., d @ ..) = (1, 2); + //~^ ERROR `..` patterns are not allowed here + d; +} diff --git a/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.stderr b/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.stderr new file mode 100644 index 00000000000..9432e2f0c9d --- /dev/null +++ b/src/test/ui/array-slice-vec/issue-69103-extra-binding-subslice.stderr @@ -0,0 +1,26 @@ +error: `..` can only be used once per slice pattern + --> $DIR/issue-69103-extra-binding-subslice.rs:6:22 + | +LL | let [a @ .., b @ ..] = &mut [1, 2]; + | -- ^^ can only be used once per slice pattern + | | + | previously used here + +error: `..` can only be used once per slice pattern + --> $DIR/issue-69103-extra-binding-subslice.rs:10:18 + | +LL | let [.., c @ ..] = [1, 2]; + | -- ^^ can only be used once per slice pattern + | | + | previously used here + +error: `..` patterns are not allowed here + --> $DIR/issue-69103-extra-binding-subslice.rs:15:18 + | +LL | let (.., d @ ..) = (1, 2); + | ^^ + | + = note: only allowed in tuple, tuple struct, and slice patterns + +error: aborting due to 3 previous errors + From dbd8220891d229f9092e623b8a1dcadbddeb12fc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Feb 2020 09:44:03 +1100 Subject: [PATCH 10/12] Avoid `base_parser`, it's not needed. --- src/librustc_expand/mbe/macro_rules.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/librustc_expand/mbe/macro_rules.rs b/src/librustc_expand/mbe/macro_rules.rs index 9e6edee265c..da4309c3b3d 100644 --- a/src/librustc_expand/mbe/macro_rules.rs +++ b/src/librustc_expand/mbe/macro_rules.rs @@ -191,9 +191,9 @@ fn generic_extension<'cx>( let mut best_failure: Option<(Token, &str)> = None; // We create a base parser that can be used for the "black box" parts. - // Every iteration needs a fresh copy of that base parser. However, the - // parser is not mutated on many of the iterations, particularly when - // dealing with macros like this: + // Every iteration needs a fresh copy of that parser. However, the parser + // is not mutated on many of the iterations, particularly when dealing with + // macros like this: // // macro_rules! foo { // ("a") => (A); @@ -209,11 +209,9 @@ fn generic_extension<'cx>( // hacky, but speeds up the `html5ever` benchmark significantly. (Issue // 68836 suggests a more comprehensive but more complex change to deal with // this situation.) - let base_parser = base_parser_from_cx(&cx.current_expansion, &cx.parse_sess, arg.clone()); + let parser = parser_from_cx(&cx.current_expansion, &cx.parse_sess, arg.clone()); for (i, lhs) in lhses.iter().enumerate() { - let mut parser = Cow::Borrowed(&base_parser); - // try each arm's matchers let lhs_tt = match *lhs { mbe::TokenTree::Delimited(_, ref delim) => &delim.tts[..], @@ -226,7 +224,7 @@ fn generic_extension<'cx>( // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. let mut gated_spans_snaphot = mem::take(&mut *cx.parse_sess.gated_spans.spans.borrow_mut()); - match parse_tt(&mut parser, lhs_tt) { + match parse_tt(&mut Cow::Borrowed(&parser), lhs_tt) { Success(named_matches) => { // The matcher was `Success(..)`ful. // Merge the gated spans from parsing the matcher with the pre-existing ones. @@ -293,7 +291,7 @@ fn generic_extension<'cx>( // Restore to the state before snapshotting and maybe try again. mem::swap(&mut gated_spans_snaphot, &mut cx.parse_sess.gated_spans.spans.borrow_mut()); } - drop(base_parser); + drop(parser); let (token, label) = best_failure.expect("ran no matchers"); let span = token.span.substitute_dummy(sp); @@ -311,9 +309,8 @@ fn generic_extension<'cx>( mbe::TokenTree::Delimited(_, ref delim) => &delim.tts[..], _ => continue, }; - let base_parser = - base_parser_from_cx(&cx.current_expansion, &cx.parse_sess, arg.clone()); - match parse_tt(&mut Cow::Borrowed(&base_parser), lhs_tt) { + let parser = parser_from_cx(&cx.current_expansion, &cx.parse_sess, arg.clone()); + match parse_tt(&mut Cow::Borrowed(&parser), lhs_tt) { Success(_) => { if comma_span.is_dummy() { err.note("you might be missing a comma"); @@ -395,8 +392,8 @@ pub fn compile_declarative_macro( ), ]; - let base_parser = Parser::new(sess, body, None, true, true, rustc_parse::MACRO_ARGUMENTS); - let argument_map = match parse_tt(&mut Cow::Borrowed(&base_parser), &argument_gram) { + let parser = Parser::new(sess, body, None, true, true, rustc_parse::MACRO_ARGUMENTS); + let argument_map = match parse_tt(&mut Cow::Borrowed(&parser), &argument_gram) { Success(m) => m, Failure(token, msg) => { let s = parse_failure_msg(&token); @@ -1212,7 +1209,7 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String { } } -fn base_parser_from_cx<'cx>( +fn parser_from_cx<'cx>( current_expansion: &'cx ExpansionData, sess: &'cx ParseSess, tts: TokenStream, From 88b0912d27afa0fa2f9593c3c7545207127ff91c Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 11 Feb 2020 08:44:55 +1100 Subject: [PATCH 11/12] Fix a typo in a variable name. --- src/librustc_expand/mbe/macro_rules.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/librustc_expand/mbe/macro_rules.rs b/src/librustc_expand/mbe/macro_rules.rs index da4309c3b3d..f6abcce4d3f 100644 --- a/src/librustc_expand/mbe/macro_rules.rs +++ b/src/librustc_expand/mbe/macro_rules.rs @@ -222,13 +222,14 @@ fn generic_extension<'cx>( // This is used so that if a matcher is not `Success(..)`ful, // then the spans which became gated when parsing the unsuccessful matcher // are not recorded. On the first `Success(..)`ful matcher, the spans are merged. - let mut gated_spans_snaphot = mem::take(&mut *cx.parse_sess.gated_spans.spans.borrow_mut()); + let mut gated_spans_snapshot = + mem::take(&mut *cx.parse_sess.gated_spans.spans.borrow_mut()); match parse_tt(&mut Cow::Borrowed(&parser), lhs_tt) { Success(named_matches) => { // The matcher was `Success(..)`ful. // Merge the gated spans from parsing the matcher with the pre-existing ones. - cx.parse_sess.gated_spans.merge(gated_spans_snaphot); + cx.parse_sess.gated_spans.merge(gated_spans_snapshot); let rhs = match rhses[i] { // ignore delimiters @@ -289,7 +290,7 @@ fn generic_extension<'cx>( // The matcher was not `Success(..)`ful. // Restore to the state before snapshotting and maybe try again. - mem::swap(&mut gated_spans_snaphot, &mut cx.parse_sess.gated_spans.spans.borrow_mut()); + mem::swap(&mut gated_spans_snapshot, &mut cx.parse_sess.gated_spans.spans.borrow_mut()); } drop(parser); From d8589de1f05fdf84016ad8b6fa1d14a076385b90 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 13 Feb 2020 18:39:40 +0100 Subject: [PATCH 12/12] Update pulldown-cmark dependency --- Cargo.lock | 42 ++++---- src/librustdoc/Cargo.toml | 2 +- src/librustdoc/html/markdown.rs | 170 ++++++++++++++++++-------------- 3 files changed, 114 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 149f02b4b8c..f3b2f41a09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1564,7 +1564,7 @@ dependencies = [ "rand_xoshiro", "sized-chunks", "typenum", - "version_check 0.9.1", + "version_check", ] [[package]] @@ -2014,9 +2014,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.2.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +checksum = "53445de381a1f436797497c61d851644d0e8e88e6140f22872ad33a704933978" [[package]] name = "memmap" @@ -2602,17 +2602,6 @@ dependencies = [ "url 2.1.0", ] -[[package]] -name = "pulldown-cmark" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77043da1282374688ee212dc44b3f37ff929431de9c9adc3053bd3cee5630357" -dependencies = [ - "bitflags", - "memchr", - "unicase", -] - [[package]] name = "pulldown-cmark" version = "0.6.1" @@ -2625,6 +2614,17 @@ dependencies = [ "unicase", ] +[[package]] +name = "pulldown-cmark" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c2d7fd131800e0d63df52aff46201acaab70b431a4a1ec6f0343fe8e64f35a4" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "punycode" version = "0.4.0" @@ -4158,7 +4158,7 @@ version = "0.0.0" dependencies = [ "itertools 0.8.0", "minifier", - "pulldown-cmark 0.5.3", + "pulldown-cmark 0.7.0", "rustc-rayon", "serde", "serde_json", @@ -5158,11 +5158,11 @@ checksum = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" [[package]] name = "unicase" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.1.5", + "version_check", ] [[package]] @@ -5332,12 +5332,6 @@ dependencies = [ "failure", ] -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.1" diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index 74e46b1eae3..4af13e4cd58 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -9,7 +9,7 @@ name = "rustdoc" path = "lib.rs" [dependencies] -pulldown-cmark = { version = "0.5.3", default-features = false } +pulldown-cmark = { version = "0.7", default-features = false } minifier = "0.0.33" rayon = { version = "0.3.0", package = "rustc-rayon" } serde = { version = "1.0", features = ["derive"] } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index c87964af020..07c092a511c 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -33,7 +33,7 @@ use crate::html::highlight; use crate::html::toc::TocBuilder; use crate::test; -use pulldown_cmark::{html, CowStr, Event, Options, Parser, Tag}; +use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag}; #[cfg(test)] mod tests; @@ -189,10 +189,15 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { let compile_fail; let ignore; let edition; - if let Some(Event::Start(Tag::CodeBlock(lang))) = event { - let parse_result = LangString::parse(&lang, self.check_error_codes, false); + if let Some(Event::Start(Tag::CodeBlock(kind))) = event { + let parse_result = match kind { + CodeBlockKind::Fenced(ref lang) => { + LangString::parse(&lang, self.check_error_codes, false) + } + CodeBlockKind::Indented => LangString::all_false(), + }; if !parse_result.rust { - return Some(Event::Start(Tag::CodeBlock(lang))); + return Some(Event::Start(Tag::CodeBlock(kind))); } compile_fail = parse_result.compile_fail; ignore = parse_result.ignore; @@ -370,11 +375,11 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator for HeadingLinks<'a, } let event = self.inner.next(); - if let Some(Event::Start(Tag::Header(level))) = event { + if let Some(Event::Start(Tag::Heading(level))) = event { let mut id = String::new(); for event in &mut self.inner { match &event { - Event::End(Tag::Header(..)) => break, + Event::End(Tag::Heading(..)) => break, Event::Text(text) | Event::Code(text) => { id.extend(text.chars().filter_map(slugify)); } @@ -391,10 +396,10 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator for HeadingLinks<'a, let mut html_header = String::new(); html::push_html(&mut html_header, self.buf.iter().cloned()); let sec = builder.push(level as u32, html_header, id.clone()); - self.buf.push_front(Event::InlineHtml(format!("{} ", sec).into())); + self.buf.push_front(Event::Html(format!("{} ", sec).into())); } - self.buf.push_back(Event::InlineHtml(format!("", level).into())); + self.buf.push_back(Event::Html(format!("", level).into())); let start_tags = format!( "\ @@ -402,7 +407,7 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator for HeadingLinks<'a, id = id, level = level ); - return Some(Event::InlineHtml(start_tags.into())); + return Some(Event::Html(start_tags.into())); } event } @@ -556,40 +561,44 @@ pub fn find_testable_code( error_codes: ErrorCodes, enable_per_target_ignores: bool, ) { - let mut parser = Parser::new(doc); + let mut parser = Parser::new(doc).into_offset_iter(); let mut prev_offset = 0; let mut nb_lines = 0; let mut register_header = None; - while let Some(event) = parser.next() { + while let Some((event, offset)) = parser.next() { match event { - Event::Start(Tag::CodeBlock(s)) => { - let offset = parser.get_offset(); - - let block_info = if s.is_empty() { - LangString::all_false() - } else { - LangString::parse(&*s, error_codes, enable_per_target_ignores) + Event::Start(Tag::CodeBlock(kind)) => { + let block_info = match kind { + CodeBlockKind::Fenced(ref lang) => { + if lang.is_empty() { + LangString::all_false() + } else { + LangString::parse(lang, error_codes, enable_per_target_ignores) + } + } + CodeBlockKind::Indented => LangString::all_false(), }; if !block_info.rust { continue; } + let mut test_s = String::new(); - while let Some(Event::Text(s)) = parser.next() { + while let Some((Event::Text(s), _)) = parser.next() { test_s.push_str(&s); } - let text = test_s .lines() .map(|l| map_line(l).for_code()) .collect::>>() .join("\n"); - nb_lines += doc[prev_offset..offset].lines().count(); - let line = tests.get_line() + nb_lines; + + nb_lines += doc[prev_offset..offset.start].lines().count(); + let line = tests.get_line() + nb_lines + 1; tests.add_test(text, block_info, line); - prev_offset = offset; + prev_offset = offset.start; } - Event::Start(Tag::Header(level)) => { + Event::Start(Tag::Heading(level)) => { register_header = Some(level as u32); } Event::Text(ref s) if register_header.is_some() => { @@ -783,7 +792,7 @@ impl MarkdownHtml<'_> { // Treat inline HTML as plain text. let p = p.map(|event| match event { - Event::Html(text) | Event::InlineHtml(text) => Event::Text(text), + Event::Html(text) => Event::Text(text), _ => event, }); @@ -842,10 +851,10 @@ pub fn plain_summary_line(md: &str) -> String { let next_event = next_event.unwrap(); let (ret, is_in) = match next_event { Event::Start(Tag::Paragraph) => (None, 1), - Event::Start(Tag::Header(_)) => (None, 1), + Event::Start(Tag::Heading(_)) => (None, 1), Event::Code(code) => (Some(format!("`{}`", code)), 0), Event::Text(ref s) if self.is_in > 0 => (Some(s.as_ref().to_owned()), 0), - Event::End(Tag::Paragraph) | Event::End(Tag::Header(_)) => (None, -1), + Event::End(Tag::Paragraph) | Event::End(Tag::Heading(_)) => (None, -1), _ => (None, 0), }; if is_in > 0 || (is_in < 0 && self.is_in > 0) { @@ -940,68 +949,79 @@ crate fn rust_code_blocks(md: &str) -> Vec { return code_blocks; } - let mut p = Parser::new_ext(md, opts()); - - let mut code_block_start = 0; - let mut code_start = 0; - let mut is_fenced = false; - let mut previous_offset = 0; - let mut in_rust_code_block = false; - while let Some(event) = p.next() { - let offset = p.get_offset(); + let mut p = Parser::new_ext(md, opts()).into_offset_iter(); + while let Some((event, offset)) = p.next() { match event { Event::Start(Tag::CodeBlock(syntax)) => { - let lang_string = if syntax.is_empty() { - LangString::all_false() - } else { - LangString::parse(&*syntax, ErrorCodes::Yes, false) - }; - - if lang_string.rust { - in_rust_code_block = true; - - code_start = offset; - code_block_start = match md[previous_offset..offset].find("```") { - Some(fence_idx) => { - is_fenced = true; - previous_offset + fence_idx + let (syntax, code_start, code_end, range, is_fenced) = match syntax { + CodeBlockKind::Fenced(syntax) => { + let syntax = syntax.as_ref(); + let lang_string = if syntax.is_empty() { + LangString::all_false() + } else { + LangString::parse(&*syntax, ErrorCodes::Yes, false) + }; + if !lang_string.rust { + continue; } - None => { - is_fenced = false; - offset + let syntax = if syntax.is_empty() { None } else { Some(syntax.to_owned()) }; + let (code_start, mut code_end) = match p.next() { + Some((Event::Text(_), offset)) => (offset.start, offset.end), + Some((_, sub_offset)) => { + let code = Range { start: sub_offset.start, end: sub_offset.start }; + code_blocks.push(RustCodeBlock { + is_fenced: true, + range: offset, + code, + syntax, + }); + continue; + } + None => { + let code = Range { start: offset.end, end: offset.end }; + code_blocks.push(RustCodeBlock { + is_fenced: true, + range: offset, + code, + syntax, + }); + continue; + } + }; + while let Some((Event::Text(_), offset)) = p.next() { + code_end = offset.end; } - }; - } - } - Event::End(Tag::CodeBlock(syntax)) if in_rust_code_block => { - in_rust_code_block = false; - - let code_block_end = if is_fenced { - let fence_str = &md[previous_offset..offset].chars().rev().collect::(); - fence_str - .find("```") - .map(|fence_idx| offset - fence_idx) - .unwrap_or_else(|| offset) - } else if md.as_bytes().get(offset).map(|b| *b == b'\n').unwrap_or_default() { - offset - 1 - } else { - offset + (syntax, code_start, code_end, offset, true) + } + CodeBlockKind::Indented => { + // The ending of the offset goes too far sometime so we reduce it by one in + // these cases. + if offset.end > offset.start + && md.get(offset.end..=offset.end) == Some(&"\n") + { + ( + None, + offset.start, + offset.end, + Range { start: offset.start, end: offset.end - 1 }, + false, + ) + } else { + (None, offset.start, offset.end, offset, false) + } + } }; - let code_end = if is_fenced { previous_offset } else { code_block_end }; - code_blocks.push(RustCodeBlock { is_fenced, - range: Range { start: code_block_start, end: code_block_end }, + range, code: Range { start: code_start, end: code_end }, - syntax: if !syntax.is_empty() { Some(syntax.into_string()) } else { None }, + syntax, }); } _ => (), } - - previous_offset = offset; } code_blocks