From 5494eec981abc99997077f18800795a4d92f680e Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 3 May 2023 15:31:23 +1000 Subject: [PATCH 1/6] Work around `rust-analyzer` false-positive type errors --- compiler/rustc_mir_transform/src/coverage/debug.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/debug.rs b/compiler/rustc_mir_transform/src/coverage/debug.rs index e554c47064680..599b491009a8e 100644 --- a/compiler/rustc_mir_transform/src/coverage/debug.rs +++ b/compiler/rustc_mir_transform/src/coverage/debug.rs @@ -639,7 +639,7 @@ pub(super) fn dump_coverage_spanview<'tcx>( let def_id = mir_source.def_id(); let span_viewables = span_viewables(tcx, mir_body, basic_coverage_blocks, &coverage_spans); - let mut file = create_dump_file(tcx, "html", false, pass_name, &0, mir_body) + let mut file = create_dump_file(tcx, "html", false, pass_name, &0i32, mir_body) .expect("Unexpected error creating MIR spanview HTML file"); let crate_name = tcx.crate_name(def_id.krate); let item_name = tcx.def_path(def_id).to_filename_friendly_no_crate(); @@ -740,7 +740,7 @@ pub(super) fn dump_coverage_graphviz<'tcx>( .join("\n ") )); } - let mut file = create_dump_file(tcx, "dot", false, pass_name, &0, mir_body) + let mut file = create_dump_file(tcx, "dot", false, pass_name, &0i32, mir_body) .expect("Unexpected error creating BasicCoverageBlock graphviz DOT file"); graphviz_writer .write_graphviz(tcx, &mut file) From 340fc2d08a032e7cd5e9537b56ef54afb4d24d6a Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 19 May 2023 11:55:13 +0000 Subject: [PATCH 2/6] Leverage the interval property to precompute borrow kill points. --- compiler/rustc_borrowck/src/dataflow.rs | 95 +++++++++---------- .../rustc_borrowck/src/region_infer/mod.rs | 16 +++- .../rustc_borrowck/src/region_infer/values.rs | 16 ++++ compiler/rustc_index/src/interval.rs | 24 +++++ 4 files changed, 101 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 167f245361a22..68ef790ac26f8 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -156,10 +156,10 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { &mut self, borrow_index: BorrowIndex, borrow_region: RegionVid, - location: Location, + first_location: Location, ) { // We visit one BB at a time. The complication is that we may start in the - // middle of the first BB visited (the one containing `location`), in which + // middle of the first BB visited (the one containing `first_location`), in which // case we may have to later on process the first part of that BB if there // is a path back to its start. @@ -168,61 +168,58 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { // `visited` once they are added to `stack`, before they are actually // processed, because this avoids the need to look them up again on // completion. - self.visited.insert(location.block); + self.visited.insert(first_location.block); - let mut first_lo = location.statement_index; - let first_hi = self.body[location.block].statements.len(); + let first_block = first_location.block; + let mut first_lo = first_location.statement_index; + let first_hi = self.body[first_block].statements.len(); - self.visit_stack.push(StackEntry { bb: location.block, lo: first_lo, hi: first_hi }); + self.visit_stack.push(StackEntry { bb: first_block, lo: first_lo, hi: first_hi }); - while let Some(StackEntry { bb, lo, hi }) = self.visit_stack.pop() { - // If we process the first part of the first basic block (i.e. we encounter that block - // for the second time), we no longer have to visit its successors again. - let mut finished_early = bb == location.block && hi != first_hi; - for i in lo..=hi { - let location = Location { block: bb, statement_index: i }; + 'preorder: while let Some(StackEntry { bb, lo, hi }) = self.visit_stack.pop() { + if let Some(kill_stmt) = + self.regioncx.first_non_contained_inclusive(borrow_region, bb, lo, hi) + { + let kill_location = Location { block: bb, statement_index: kill_stmt }; // If region does not contain a point at the location, then add to list and skip // successor locations. - if !self.regioncx.region_contains(borrow_region, location) { - debug!("borrow {:?} gets killed at {:?}", borrow_index, location); - self.borrows_out_of_scope_at_location - .entry(location) - .or_default() - .push(borrow_index); - finished_early = true; - break; - } + debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location); + self.borrows_out_of_scope_at_location + .entry(kill_location) + .or_default() + .push(borrow_index); + continue 'preorder; } - if !finished_early { - // Add successor BBs to the work list, if necessary. - let bb_data = &self.body[bb]; - debug_assert!(hi == bb_data.statements.len()); - for succ_bb in bb_data.terminator().successors() { - if !self.visited.insert(succ_bb) { - if succ_bb == location.block && first_lo > 0 { - // `succ_bb` has been seen before. If it wasn't - // fully processed, add its first part to `stack` - // for processing. - self.visit_stack.push(StackEntry { - bb: succ_bb, - lo: 0, - hi: first_lo - 1, - }); - - // And update this entry with 0, to represent the - // whole BB being processed. - first_lo = 0; - } - } else { - // succ_bb hasn't been seen before. Add it to - // `stack` for processing. - self.visit_stack.push(StackEntry { - bb: succ_bb, - lo: 0, - hi: self.body[succ_bb].statements.len(), - }); + // If we process the first part of the first basic block (i.e. we encounter that block + // for the second time), we no longer have to visit its successors again. + if bb == first_block && hi != first_hi { + continue; + } + + // Add successor BBs to the work list, if necessary. + let bb_data = &self.body[bb]; + debug_assert!(hi == bb_data.statements.len()); + for succ_bb in bb_data.terminator().successors() { + if !self.visited.insert(succ_bb) { + if succ_bb == first_block && first_lo > 0 { + // `succ_bb` has been seen before. If it wasn't + // fully processed, add its first part to `stack` + // for processing. + self.visit_stack.push(StackEntry { bb: succ_bb, lo: 0, hi: first_lo - 1 }); + + // And update this entry with 0, to represent the + // whole BB being processed. + first_lo = 0; } + } else { + // succ_bb hasn't been seen before. Add it to + // `stack` for processing. + self.visit_stack.push(StackEntry { + bb: succ_bb, + lo: 0, + hi: self.body[succ_bb].statements.len(), + }); } } } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 8fbe814c85607..3be06a94bc0b8 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -12,7 +12,7 @@ use rustc_infer::infer::outlives::test_type_match; use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq}; use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin}; use rustc_middle::mir::{ - Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureOutlivesSubjectTy, + BasicBlock, Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureOutlivesSubjectTy, ClosureRegionRequirements, ConstraintCategory, Local, Location, ReturnConstraint, TerminatorKind, }; @@ -598,6 +598,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.contains(scc, p) } + /// Returns the lowest statement index in `start..=end` which is not contained by `r`. + /// + /// Panics if called before `solve()` executes. + pub(crate) fn first_non_contained_inclusive( + &self, + r: RegionVid, + block: BasicBlock, + start: usize, + end: usize, + ) -> Option { + let scc = self.constraint_sccs.scc(r); + self.scc_values.first_non_contained_inclusive(scc, block, start, end) + } + /// Returns access to the value of `r` for debugging purposes. pub(crate) fn region_value_str(&self, r: RegionVid) -> String { let scc = self.constraint_sccs.scc(r); diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 193e20601152b..60ddf9ebaea96 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -283,6 +283,22 @@ impl RegionValues { elem.contained_in_row(self, r) } + /// Returns the lowest statement index in `start..=end` which is not contained by `r`. + pub(crate) fn first_non_contained_inclusive( + &self, + r: N, + block: BasicBlock, + start: usize, + end: usize, + ) -> Option { + let row = self.points.row(r)?; + let block = self.elements.entry_point(block); + let start = block.plus(start); + let end = block.plus(end); + let first_unset = row.first_unset_in(start..=end)?; + Some(first_unset.index() - block.index()) + } + /// `self[to] |= values[from]`, essentially: that is, take all the /// elements for the region `from` from `values` and add them to /// the region `to` in `self`. diff --git a/compiler/rustc_index/src/interval.rs b/compiler/rustc_index/src/interval.rs index 7ed4860c274df..9199a78c326a1 100644 --- a/compiler/rustc_index/src/interval.rs +++ b/compiler/rustc_index/src/interval.rs @@ -181,6 +181,30 @@ impl IntervalSet { self.map.is_empty() } + /// Equivalent to `range.iter().find(|i| !self.contains(i))`. + pub fn first_unset_in(&self, range: impl RangeBounds + Clone) -> Option { + let start = inclusive_start(range.clone()); + let Some(end) = inclusive_end(self.domain, range) else { + // empty range + return None; + }; + if start > end { + return None; + } + let Some(last) = self.map.partition_point(|r| r.0 <= start).checked_sub(1) else { + // All ranges in the map start after the new range's end + return Some(I::new(start as usize)); + }; + let (_, prev_end) = self.map[last]; + if start > prev_end { + Some(I::new(start as usize)) + } else if prev_end < end { + Some(I::new(prev_end as usize + 1)) + } else { + None + } + } + /// Returns the maximum (last) element present in the set from `range`. pub fn last_set_in(&self, range: impl RangeBounds + Clone) -> Option { let start = inclusive_start(range.clone()); From 72d41f3bd36d3c18c31f719a7f8e2a0ca372c332 Mon Sep 17 00:00:00 2001 From: Matthew Jasper Date: Mon, 22 May 2023 14:52:52 +0100 Subject: [PATCH 3/6] Run AST validation on match guards correctly --- .../rustc_ast_passes/src/ast_validation.rs | 5 +- .../src/build/expr/as_operand.rs | 8 +- .../ui/rfc-2294-if-let-guard/feature-gate.rs | 12 + .../rfc-2294-if-let-guard/feature-gate.stderr | 209 +++++++++++++++--- .../ast-validate-guards.rs | 23 ++ .../ast-validate-guards.stderr | 21 ++ 6 files changed, 240 insertions(+), 38 deletions(-) create mode 100644 tests/ui/rfc-2497-if-let-chains/ast-validate-guards.rs create mode 100644 tests/ui/rfc-2497-if-let-chains/ast-validate-guards.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index bf43bbdbbeeba..3a210dc34f60a 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -736,11 +736,10 @@ impl<'a> Visitor<'a> for AstValidator<'a> { this.visit_expr(&arm.body); this.visit_pat(&arm.pat); walk_list!(this, visit_attribute, &arm.attrs); - if let Some(guard) = &arm.guard && let ExprKind::Let(_, guard_expr, _) = &guard.kind { + if let Some(guard) = &arm.guard { this.with_let_management(None, |this, _| { - this.visit_expr(guard_expr) + this.visit_expr(guard) }); - return; } } } diff --git a/compiler/rustc_mir_build/src/build/expr/as_operand.rs b/compiler/rustc_mir_build/src/build/expr/as_operand.rs index 6941da331fc58..744111edb84e4 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_operand.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_operand.rs @@ -118,7 +118,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let category = Category::of(&expr.kind).unwrap(); debug!(?category, ?expr.kind); match category { - Category::Constant if let NeedsTemporary::No = needs_temporary || !expr.ty.needs_drop(this.tcx, this.param_env) => { + Category::Constant + if matches!(needs_temporary, NeedsTemporary::No) + || !expr.ty.needs_drop(this.tcx, this.param_env) => + { let constant = this.as_constant(expr); block.and(Operand::Constant(Box::new(constant))) } @@ -126,7 +129,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let operand = unpack!(block = this.as_temp(block, scope, expr, Mutability::Mut)); // Overwrite temp local info if we have something more interesting to record. if !matches!(local_info, LocalInfo::Boring) { - let decl_info = this.local_decls[operand].local_info.as_mut().assert_crate_local(); + let decl_info = + this.local_decls[operand].local_info.as_mut().assert_crate_local(); if let LocalInfo::Boring | LocalInfo::BlockTailTemp(_) = **decl_info { **decl_info = local_info; } diff --git a/tests/ui/rfc-2294-if-let-guard/feature-gate.rs b/tests/ui/rfc-2294-if-let-guard/feature-gate.rs index f0105e08e27c3..3beb20f0a376b 100644 --- a/tests/ui/rfc-2294-if-let-guard/feature-gate.rs +++ b/tests/ui/rfc-2294-if-let-guard/feature-gate.rs @@ -10,10 +10,12 @@ fn _if_let_guard() { () if (let 0 = 1) => {} //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here () if (((let 0 = 1))) => {} //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here () if true && let 0 = 1 => {} //~^ ERROR `if let` guards are experimental @@ -26,16 +28,20 @@ fn _if_let_guard() { () if (let 0 = 1) && true => {} //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here () if true && (let 0 = 1) => {} //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here () if (let 0 = 1) && (let 0 = 1) => {} //~^ ERROR `let` expressions in this position are unstable //~| ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here + //~| ERROR `let` expressions are not supported here () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} //~^ ERROR `if let` guards are experimental @@ -47,6 +53,10 @@ fn _if_let_guard() { //~| ERROR expected expression, found `let` statement //~| ERROR expected expression, found `let` statement //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here + //~| ERROR `let` expressions are not supported here + //~| ERROR `let` expressions are not supported here + () if let Range { start: _, end: _ } = (true..true) && false => {} //~^ ERROR `if let` guards are experimental @@ -68,9 +78,11 @@ fn _macros() { use_expr!((let 0 = 1 && 0 == 0)); //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here use_expr!((let 0 = 1)); //~^ ERROR `let` expressions in this position are unstable //~| ERROR expected expression, found `let` statement + //~| ERROR `let` expressions are not supported here match () { #[cfg(FALSE)] () if let 0 = 1 => {} diff --git a/tests/ui/rfc-2294-if-let-guard/feature-gate.stderr b/tests/ui/rfc-2294-if-let-guard/feature-gate.stderr index 96fe11911b7a0..dc182ce464a09 100644 --- a/tests/ui/rfc-2294-if-let-guard/feature-gate.stderr +++ b/tests/ui/rfc-2294-if-let-guard/feature-gate.stderr @@ -5,67 +5,67 @@ LL | () if (let 0 = 1) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:14:18 + --> $DIR/feature-gate.rs:15:18 | LL | () if (((let 0 = 1))) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:26:16 + --> $DIR/feature-gate.rs:28:16 | LL | () if (let 0 = 1) && true => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:30:24 + --> $DIR/feature-gate.rs:33:24 | LL | () if true && (let 0 = 1) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:34:16 + --> $DIR/feature-gate.rs:38:16 | LL | () if (let 0 = 1) && (let 0 = 1) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:34:31 + --> $DIR/feature-gate.rs:38:31 | LL | () if (let 0 = 1) && (let 0 = 1) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:40:42 + --> $DIR/feature-gate.rs:46:42 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:40:55 + --> $DIR/feature-gate.rs:46:55 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:40:68 + --> $DIR/feature-gate.rs:46:68 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:68:16 + --> $DIR/feature-gate.rs:78:16 | LL | use_expr!((let 0 = 1 && 0 == 0)); | ^^^ error: expected expression, found `let` statement - --> $DIR/feature-gate.rs:71:16 + --> $DIR/feature-gate.rs:82:16 | LL | use_expr!((let 0 = 1)); | ^^^ error: no rules expected the token `let` - --> $DIR/feature-gate.rs:80:15 + --> $DIR/feature-gate.rs:92:15 | LL | macro_rules! use_expr { | --------------------- when calling this macro @@ -74,11 +74,154 @@ LL | use_expr!(let 0 = 1); | ^^^ no rules expected this token in macro call | note: while trying to match meta-variable `$e:expr` - --> $DIR/feature-gate.rs:61:10 + --> $DIR/feature-gate.rs:71:10 | LL | ($e:expr) => { | ^^^^^^^ +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:10:16 + | +LL | () if (let 0 = 1) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:10:16 + | +LL | () if (let 0 = 1) => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:15:18 + | +LL | () if (((let 0 = 1))) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:15:18 + | +LL | () if (((let 0 = 1))) => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:28:16 + | +LL | () if (let 0 = 1) && true => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:28:16 + | +LL | () if (let 0 = 1) && true => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:33:24 + | +LL | () if true && (let 0 = 1) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:33:24 + | +LL | () if true && (let 0 = 1) => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:38:16 + | +LL | () if (let 0 = 1) && (let 0 = 1) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:38:16 + | +LL | () if (let 0 = 1) && (let 0 = 1) => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:38:31 + | +LL | () if (let 0 = 1) && (let 0 = 1) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:38:31 + | +LL | () if (let 0 = 1) && (let 0 = 1) => {} + | ^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:46:42 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:46:42 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:46:55 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:46:42 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:46:68 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:46:42 + | +LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:78:16 + | +LL | use_expr!((let 0 = 1 && 0 == 0)); + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:78:16 + | +LL | use_expr!((let 0 = 1 && 0 == 0)); + | ^^^^^^^^^^^^^^^^^^^ + +error: `let` expressions are not supported here + --> $DIR/feature-gate.rs:82:16 + | +LL | use_expr!((let 0 = 1)); + | ^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `let`s wrapped in parentheses are not supported in a context with let chains + --> $DIR/feature-gate.rs:82:16 + | +LL | use_expr!((let 0 = 1)); + | ^^^^^^^^^ + error[E0658]: `if let` guards are experimental --> $DIR/feature-gate.rs:7:12 | @@ -90,7 +233,7 @@ LL | () if let 0 = 1 => {} = help: you can write `if matches!(, )` instead of `if let = ` error[E0658]: `if let` guards are experimental - --> $DIR/feature-gate.rs:18:12 + --> $DIR/feature-gate.rs:20:12 | LL | () if true && let 0 = 1 => {} | ^^^^^^^^^^^^^^^^^^^^ @@ -100,7 +243,7 @@ LL | () if true && let 0 = 1 => {} = help: you can write `if matches!(, )` instead of `if let = ` error[E0658]: `if let` guards are experimental - --> $DIR/feature-gate.rs:22:12 + --> $DIR/feature-gate.rs:24:12 | LL | () if let 0 = 1 && true => {} | ^^^^^^^^^^^^^^^^^^^^ @@ -110,7 +253,7 @@ LL | () if let 0 = 1 && true => {} = help: you can write `if matches!(, )` instead of `if let = ` error[E0658]: `if let` guards are experimental - --> $DIR/feature-gate.rs:40:12 + --> $DIR/feature-gate.rs:46:12 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -120,7 +263,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: you can write `if matches!(, )` instead of `if let = ` error[E0658]: `if let` guards are experimental - --> $DIR/feature-gate.rs:51:12 + --> $DIR/feature-gate.rs:61:12 | LL | () if let Range { start: _, end: _ } = (true..true) && false => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -130,7 +273,7 @@ LL | () if let Range { start: _, end: _ } = (true..true) && false => {} = help: you can write `if matches!(, )` instead of `if let = ` error[E0658]: `if let` guards are experimental - --> $DIR/feature-gate.rs:76:12 + --> $DIR/feature-gate.rs:88:12 | LL | () if let 0 = 1 => {} | ^^^^^^^^^^^^ @@ -149,7 +292,7 @@ LL | () if (let 0 = 1) => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:14:18 + --> $DIR/feature-gate.rs:15:18 | LL | () if (((let 0 = 1))) => {} | ^^^^^^^^^ @@ -158,7 +301,7 @@ LL | () if (((let 0 = 1))) => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:18:23 + --> $DIR/feature-gate.rs:20:23 | LL | () if true && let 0 = 1 => {} | ^^^^^^^^^ @@ -167,7 +310,7 @@ LL | () if true && let 0 = 1 => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:22:15 + --> $DIR/feature-gate.rs:24:15 | LL | () if let 0 = 1 && true => {} | ^^^^^^^^^ @@ -176,7 +319,7 @@ LL | () if let 0 = 1 && true => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:26:16 + --> $DIR/feature-gate.rs:28:16 | LL | () if (let 0 = 1) && true => {} | ^^^^^^^^^ @@ -185,7 +328,7 @@ LL | () if (let 0 = 1) && true => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:30:24 + --> $DIR/feature-gate.rs:33:24 | LL | () if true && (let 0 = 1) => {} | ^^^^^^^^^ @@ -194,7 +337,7 @@ LL | () if true && (let 0 = 1) => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:34:16 + --> $DIR/feature-gate.rs:38:16 | LL | () if (let 0 = 1) && (let 0 = 1) => {} | ^^^^^^^^^ @@ -203,7 +346,7 @@ LL | () if (let 0 = 1) && (let 0 = 1) => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:34:31 + --> $DIR/feature-gate.rs:38:31 | LL | () if (let 0 = 1) && (let 0 = 1) => {} | ^^^^^^^^^ @@ -212,7 +355,7 @@ LL | () if (let 0 = 1) && (let 0 = 1) => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:40:15 + --> $DIR/feature-gate.rs:46:15 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^ @@ -221,7 +364,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:40:28 + --> $DIR/feature-gate.rs:46:28 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^ @@ -230,7 +373,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:40:42 + --> $DIR/feature-gate.rs:46:42 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^ @@ -239,7 +382,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:40:55 + --> $DIR/feature-gate.rs:46:55 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^ @@ -248,7 +391,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:40:68 + --> $DIR/feature-gate.rs:46:68 | LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = 5) => {} | ^^^^^^^^^ @@ -257,7 +400,7 @@ LL | () if let 0 = 1 && let 1 = 2 && (let 2 = 3 && let 3 = 4 && let 4 = = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:51:15 + --> $DIR/feature-gate.rs:61:15 | LL | () if let Range { start: _, end: _ } = (true..true) && false => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -266,7 +409,7 @@ LL | () if let Range { start: _, end: _ } = (true..true) && false => {} = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:68:16 + --> $DIR/feature-gate.rs:78:16 | LL | use_expr!((let 0 = 1 && 0 == 0)); | ^^^^^^^^^ @@ -275,7 +418,7 @@ LL | use_expr!((let 0 = 1 && 0 == 0)); = help: add `#![feature(let_chains)]` to the crate attributes to enable error[E0658]: `let` expressions in this position are unstable - --> $DIR/feature-gate.rs:71:16 + --> $DIR/feature-gate.rs:82:16 | LL | use_expr!((let 0 = 1)); | ^^^^^^^^^ @@ -283,6 +426,6 @@ LL | use_expr!((let 0 = 1)); = note: see issue #53667 for more information = help: add `#![feature(let_chains)]` to the crate attributes to enable -error: aborting due to 34 previous errors +error: aborting due to 45 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.rs b/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.rs new file mode 100644 index 0000000000000..e6dee2a1d0613 --- /dev/null +++ b/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.rs @@ -0,0 +1,23 @@ +#![feature(let_chains)] + +fn let_or_guard(x: Result, ()>) { + match x { + Ok(opt) if let Some(4) = opt || false => {} + //~^ ERROR `let` expressions are not supported here + _ => {} + } +} + +fn hiding_unsafe_mod(x: Result, ()>) { + match x { + Ok(opt) + if { + unsafe mod a {}; + //~^ ERROR module cannot be declared unsafe + false + } => {} + _ => {} + } +} + +fn main() {} diff --git a/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.stderr b/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.stderr new file mode 100644 index 0000000000000..26850998cc4ef --- /dev/null +++ b/tests/ui/rfc-2497-if-let-chains/ast-validate-guards.stderr @@ -0,0 +1,21 @@ +error: `let` expressions are not supported here + --> $DIR/ast-validate-guards.rs:5:20 + | +LL | Ok(opt) if let Some(4) = opt || false => {} + | ^^^^^^^^^^^^^^^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +note: `||` operators are not supported in let chain expressions + --> $DIR/ast-validate-guards.rs:5:38 + | +LL | Ok(opt) if let Some(4) = opt || false => {} + | ^^ + +error: module cannot be declared unsafe + --> $DIR/ast-validate-guards.rs:15:17 + | +LL | unsafe mod a {}; + | ^^^^^^ + +error: aborting due to 2 previous errors + From 0a7293ff00c98b37be4ca257170d7ead5cf838d3 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 24 May 2023 14:41:18 +0200 Subject: [PATCH 4/6] Migrate GUI colors test to original CSS color format --- tests/rustdoc-gui/stab-badge.goml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/rustdoc-gui/stab-badge.goml b/tests/rustdoc-gui/stab-badge.goml index e23404188714b..bb3d2aaa3dca0 100644 --- a/tests/rustdoc-gui/stab-badge.goml +++ b/tests/rustdoc-gui/stab-badge.goml @@ -26,16 +26,16 @@ define-function: ( call-function: ("check-badge", { "theme": "ayu", - "color": "rgb(197, 197, 197)", - "background": "rgb(49, 69, 89)", + "color": "#c5c5c5", + "background": "#314559", }) call-function: ("check-badge", { "theme": "dark", - "color": "rgb(221, 221, 221)", - "background": "rgb(49, 69, 89)", + "color": "#ddd", + "background": "#314559", }) call-function: ("check-badge", { "theme": "light", - "color": "rgb(0, 0, 0)", - "background": "rgb(255, 245, 214)", + "color": "black", + "background": "#fff5d6", }) From b1387e776c011766e12f6d3ae72e4fa3f59f0fe9 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 23 May 2023 04:18:02 +0000 Subject: [PATCH 5/6] Don't skip mir typeck if body has errors --- compiler/rustc_borrowck/src/type_check/mod.rs | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index fa4bc926f2739..b42d5c3d18c97 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -183,17 +183,10 @@ pub(crate) fn type_check<'mir, 'tcx>( &mut borrowck_context, ); - let errors_reported = { - let mut verifier = TypeVerifier::new(&mut checker, promoted); - verifier.visit_body(&body); - verifier.errors_reported - }; - - if !errors_reported { - // if verifier failed, don't do further checks to avoid ICEs - checker.typeck_mir(body); - } + let mut verifier = TypeVerifier::new(&mut checker, promoted); + verifier.visit_body(&body); + checker.typeck_mir(body); checker.equate_inputs_and_outputs(&body, universal_regions, &normalized_inputs_and_output); checker.check_signature_annotation(&body); @@ -294,7 +287,6 @@ struct TypeVerifier<'a, 'b, 'tcx> { cx: &'a mut TypeChecker<'b, 'tcx>, promoted: &'b IndexSlice>, last_span: Span, - errors_reported: bool, } impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { @@ -383,13 +375,11 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { }; }; - if !self.errors_reported { - let promoted_body = &self.promoted[promoted]; - self.sanitize_promoted(promoted_body, location); + let promoted_body = &self.promoted[promoted]; + self.sanitize_promoted(promoted_body, location); - let promoted_ty = promoted_body.return_ty(); - check_err(self, promoted_body, ty, promoted_ty); - } + let promoted_ty = promoted_body.return_ty(); + check_err(self, promoted_body, ty, promoted_ty); } else { self.cx.ascribe_user_type( constant.literal.ty(), @@ -483,9 +473,6 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { for local_decl in &body.local_decls { self.sanitize_type(local_decl, local_decl.ty); } - if self.errors_reported { - return; - } self.super_body(body); } } @@ -495,7 +482,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { cx: &'a mut TypeChecker<'b, 'tcx>, promoted: &'b IndexSlice>, ) -> Self { - TypeVerifier { promoted, last_span: cx.body.span, cx, errors_reported: false } + TypeVerifier { promoted, last_span: cx.body.span, cx } } fn body(&self) -> &Body<'tcx> { @@ -529,7 +516,6 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { for elem in place.projection.iter() { if place_ty.variant_index.is_none() { if let Err(guar) = place_ty.ty.error_reported() { - assert!(self.errors_reported); return PlaceTy::from_ty(self.tcx().ty_error(guar)); } } @@ -593,10 +579,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { self.visit_body(&promoted_body); - if !self.errors_reported { - // if verifier failed, don't do further checks to avoid ICEs - self.cx.typeck_mir(promoted_body); - } + self.cx.typeck_mir(promoted_body); self.cx.body = parent_body; // Merge the outlives constraints back in, at the given location. @@ -762,7 +745,6 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { } fn error(&mut self) -> Ty<'tcx> { - self.errors_reported = true; self.tcx().ty_error_misc() } From d7ccbdd69627dc574f569aef51fa979157cd25d8 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 23 May 2023 03:55:46 +0000 Subject: [PATCH 6/6] Split out opaque from type_of --- .../rustc_hir_analysis/src/collect/type_of.rs | 310 +----------------- .../src/collect/type_of/opaque.rs | 298 +++++++++++++++++ 2 files changed, 303 insertions(+), 305 deletions(-) create mode 100644 compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 97c6cb491d1d6..8e082d3c5328b 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -1,10 +1,7 @@ use rustc_errors::{Applicability, StashKey}; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit; -use rustc_hir::intravisit::Visitor; -use rustc_hir::{HirId, Node}; -use rustc_middle::hir::nested_filter; +use rustc_hir::HirId; use rustc_middle::ty::print::with_forced_trimmed_paths; use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::util::IntTypeExt; @@ -14,7 +11,8 @@ use rustc_span::{Span, DUMMY_SP}; use super::ItemCtxt; use super::{bad_placeholder, is_suggestable_infer_ty}; -use crate::errors::UnconstrainedOpaqueType; + +mod opaque; fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { use hir::*; @@ -429,7 +427,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder find_opaque_ty_constraints_for_tait(tcx, def_id), + }) => opaque::find_opaque_ty_constraints_for_tait(tcx, def_id), // Opaque types desugared from `impl Trait`. ItemKind::OpaqueTy(OpaqueTy { origin: @@ -443,7 +441,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder, def_id: LocalDefId) -> ty::EarlyBinder = impl Bar; -/// -/// // Okay -- `Foo` is applied to two distinct, generic types. -/// fn a() -> Foo { .. } -/// -/// // Not okay -- `Foo` is applied to `T` twice. -/// fn b() -> Foo { .. } -/// -/// // Not okay -- `Foo` is applied to a non-generic type. -/// fn b() -> Foo { .. } -/// ``` -/// -fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Ty<'_> { - use rustc_hir::{Expr, ImplItem, Item, TraitItem}; - - struct ConstraintLocator<'tcx> { - tcx: TyCtxt<'tcx>, - - /// def_id of the opaque type whose defining uses are being checked - def_id: LocalDefId, - - /// as we walk the defining uses, we are checking that all of them - /// define the same hidden type. This variable is set to `Some` - /// with the first type that we find, and then later types are - /// checked against it (we also carry the span of that first - /// type). - found: Option>, - - /// In the presence of dead code, typeck may figure out a hidden type - /// while borrowck will not. We collect these cases here and check at - /// the end that we actually found a type that matches (modulo regions). - typeck_types: Vec>, - } - - impl ConstraintLocator<'_> { - #[instrument(skip(self), level = "debug")] - fn check(&mut self, item_def_id: LocalDefId) { - // Don't try to check items that cannot possibly constrain the type. - if !self.tcx.has_typeck_results(item_def_id) { - debug!("no constraint: no typeck results"); - return; - } - // Calling `mir_borrowck` can lead to cycle errors through - // const-checking, avoid calling it if we don't have to. - // ```rust - // type Foo = impl Fn() -> usize; // when computing type for this - // const fn bar() -> Foo { - // || 0usize - // } - // const BAZR: Foo = bar(); // we would mir-borrowck this, causing cycles - // // because we again need to reveal `Foo` so we can check whether the - // // constant does not contain interior mutability. - // ``` - let tables = self.tcx.typeck(item_def_id); - if let Some(guar) = tables.tainted_by_errors { - self.found = - Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: self.tcx.ty_error(guar) }); - return; - } - let Some(&typeck_hidden_ty) = tables.concrete_opaque_types.get(&self.def_id) else { - debug!("no constraints in typeck results"); - return; - }; - if self.typeck_types.iter().all(|prev| prev.ty != typeck_hidden_ty.ty) { - self.typeck_types.push(typeck_hidden_ty); - } - - // Use borrowck to get the type with unerased regions. - let concrete_opaque_types = &self.tcx.mir_borrowck(item_def_id).concrete_opaque_types; - debug!(?concrete_opaque_types); - if let Some(&concrete_type) = concrete_opaque_types.get(&self.def_id) { - debug!(?concrete_type, "found constraint"); - if let Some(prev) = &mut self.found { - if concrete_type.ty != prev.ty && !(concrete_type, prev.ty).references_error() { - let guar = - prev.report_mismatch(&concrete_type, self.def_id, self.tcx).emit(); - prev.ty = self.tcx.ty_error(guar); - } - } else { - self.found = Some(concrete_type); - } - } - } - } - - impl<'tcx> intravisit::Visitor<'tcx> for ConstraintLocator<'tcx> { - type NestedFilter = nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.tcx.hir() - } - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if let hir::ExprKind::Closure(closure) = ex.kind { - self.check(closure.def_id); - } - intravisit::walk_expr(self, ex); - } - fn visit_item(&mut self, it: &'tcx Item<'tcx>) { - trace!(?it.owner_id); - // The opaque type itself or its children are not within its reveal scope. - if it.owner_id.def_id != self.def_id { - self.check(it.owner_id.def_id); - intravisit::walk_item(self, it); - } - } - fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) { - trace!(?it.owner_id); - // The opaque type itself or its children are not within its reveal scope. - if it.owner_id.def_id != self.def_id { - self.check(it.owner_id.def_id); - intravisit::walk_impl_item(self, it); - } - } - fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) { - trace!(?it.owner_id); - self.check(it.owner_id.def_id); - intravisit::walk_trait_item(self, it); - } - } - - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); - let scope = tcx.hir().get_defining_scope(hir_id); - let mut locator = ConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] }; - - debug!(?scope); - - if scope == hir::CRATE_HIR_ID { - tcx.hir().walk_toplevel_module(&mut locator); - } else { - trace!("scope={:#?}", tcx.hir().get(scope)); - match tcx.hir().get(scope) { - // We explicitly call `visit_*` methods, instead of using `intravisit::walk_*` methods - // This allows our visitor to process the defining item itself, causing - // it to pick up any 'sibling' defining uses. - // - // For example, this code: - // ``` - // fn foo() { - // type Blah = impl Debug; - // let my_closure = || -> Blah { true }; - // } - // ``` - // - // requires us to explicitly process `foo()` in order - // to notice the defining usage of `Blah`. - Node::Item(it) => locator.visit_item(it), - Node::ImplItem(it) => locator.visit_impl_item(it), - Node::TraitItem(it) => locator.visit_trait_item(it), - other => bug!("{:?} is not a valid scope for an opaque type item", other), - } - } - - let Some(hidden) = locator.found else { - let reported = tcx.sess.emit_err(UnconstrainedOpaqueType { - span: tcx.def_span(def_id), - name: tcx.item_name(tcx.local_parent(def_id).to_def_id()), - what: match tcx.hir().get(scope) { - _ if scope == hir::CRATE_HIR_ID => "module", - Node::Item(hir::Item { kind: hir::ItemKind::Mod(_), .. }) => "module", - Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }) => "impl", - _ => "item", - }, - }); - return tcx.ty_error(reported); - }; - - // Only check against typeck if we didn't already error - if !hidden.ty.references_error() { - for concrete_type in locator.typeck_types { - if concrete_type.ty != tcx.erase_regions(hidden.ty) - && !(concrete_type, hidden).references_error() - { - hidden.report_mismatch(&concrete_type, def_id, tcx).emit(); - } - } - } - - hidden.ty -} - -fn find_opaque_ty_constraints_for_rpit( - tcx: TyCtxt<'_>, - def_id: LocalDefId, - owner_def_id: LocalDefId, -) -> Ty<'_> { - use rustc_hir::{Expr, ImplItem, Item, TraitItem}; - - struct ConstraintChecker<'tcx> { - tcx: TyCtxt<'tcx>, - - /// def_id of the opaque type whose defining uses are being checked - def_id: LocalDefId, - - found: ty::OpaqueHiddenType<'tcx>, - } - - impl ConstraintChecker<'_> { - #[instrument(skip(self), level = "debug")] - fn check(&self, def_id: LocalDefId) { - // Use borrowck to get the type with unerased regions. - let concrete_opaque_types = &self.tcx.mir_borrowck(def_id).concrete_opaque_types; - debug!(?concrete_opaque_types); - for (&def_id, &concrete_type) in concrete_opaque_types { - if def_id != self.def_id { - // Ignore constraints for other opaque types. - continue; - } - - debug!(?concrete_type, "found constraint"); - - if concrete_type.ty != self.found.ty - && !(concrete_type, self.found).references_error() - { - self.found.report_mismatch(&concrete_type, self.def_id, self.tcx).emit(); - } - } - } - } - - impl<'tcx> intravisit::Visitor<'tcx> for ConstraintChecker<'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - - fn nested_visit_map(&mut self) -> Self::Map { - self.tcx.hir() - } - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if let hir::ExprKind::Closure(closure) = ex.kind { - self.check(closure.def_id); - } - intravisit::walk_expr(self, ex); - } - fn visit_item(&mut self, it: &'tcx Item<'tcx>) { - trace!(?it.owner_id); - // The opaque type itself or its children are not within its reveal scope. - if it.owner_id.def_id != self.def_id { - self.check(it.owner_id.def_id); - intravisit::walk_item(self, it); - } - } - fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) { - trace!(?it.owner_id); - // The opaque type itself or its children are not within its reveal scope. - if it.owner_id.def_id != self.def_id { - self.check(it.owner_id.def_id); - intravisit::walk_impl_item(self, it); - } - } - fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) { - trace!(?it.owner_id); - self.check(it.owner_id.def_id); - intravisit::walk_trait_item(self, it); - } - } - - let concrete = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied(); - - if let Some(concrete) = concrete { - let scope = tcx.hir().local_def_id_to_hir_id(owner_def_id); - debug!(?scope); - let mut locator = ConstraintChecker { def_id, tcx, found: concrete }; - - match tcx.hir().get(scope) { - Node::Item(it) => intravisit::walk_item(&mut locator, it), - Node::ImplItem(it) => intravisit::walk_impl_item(&mut locator, it), - Node::TraitItem(it) => intravisit::walk_trait_item(&mut locator, it), - other => bug!("{:?} is not a valid scope for an opaque type item", other), - } - } - - concrete.map(|concrete| concrete.ty).unwrap_or_else(|| { - let table = tcx.typeck(owner_def_id); - if let Some(guar) = table.tainted_by_errors { - // Some error in the - // owner fn prevented us from populating - // the `concrete_opaque_types` table. - tcx.ty_error(guar) - } else { - table.concrete_opaque_types.get(&def_id).map(|ty| ty.ty).unwrap_or_else(|| { - // We failed to resolve the opaque type or it - // resolves to itself. We interpret this as the - // no values of the hidden type ever being constructed, - // so we can just make the hidden type be `!`. - // For backwards compatibility reasons, we fall back to - // `()` until we the diverging default is changed. - tcx.mk_diverging_default() - }) - } - }) -} - fn infer_placeholder_type<'a>( tcx: TyCtxt<'a>, def_id: LocalDefId, diff --git a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs new file mode 100644 index 0000000000000..f7c5b44678f72 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs @@ -0,0 +1,298 @@ +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self as hir, Expr, ImplItem, Item, Node, TraitItem}; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_span::DUMMY_SP; + +use crate::errors::UnconstrainedOpaqueType; + +/// Checks "defining uses" of opaque `impl Trait` types to ensure that they meet the restrictions +/// laid for "higher-order pattern unification". +/// This ensures that inference is tractable. +/// In particular, definitions of opaque types can only use other generics as arguments, +/// and they cannot repeat an argument. Example: +/// +/// ```ignore (illustrative) +/// type Foo = impl Bar; +/// +/// // Okay -- `Foo` is applied to two distinct, generic types. +/// fn a() -> Foo { .. } +/// +/// // Not okay -- `Foo` is applied to `T` twice. +/// fn b() -> Foo { .. } +/// +/// // Not okay -- `Foo` is applied to a non-generic type. +/// fn b() -> Foo { .. } +/// ``` +#[instrument(skip(tcx), level = "debug")] +pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Ty<'_> { + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); + let scope = tcx.hir().get_defining_scope(hir_id); + let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] }; + + debug!(?scope); + + if scope == hir::CRATE_HIR_ID { + tcx.hir().walk_toplevel_module(&mut locator); + } else { + trace!("scope={:#?}", tcx.hir().get(scope)); + match tcx.hir().get(scope) { + // We explicitly call `visit_*` methods, instead of using `intravisit::walk_*` methods + // This allows our visitor to process the defining item itself, causing + // it to pick up any 'sibling' defining uses. + // + // For example, this code: + // ``` + // fn foo() { + // type Blah = impl Debug; + // let my_closure = || -> Blah { true }; + // } + // ``` + // + // requires us to explicitly process `foo()` in order + // to notice the defining usage of `Blah`. + Node::Item(it) => locator.visit_item(it), + Node::ImplItem(it) => locator.visit_impl_item(it), + Node::TraitItem(it) => locator.visit_trait_item(it), + other => bug!("{:?} is not a valid scope for an opaque type item", other), + } + } + + let Some(hidden) = locator.found else { + let reported = tcx.sess.emit_err(UnconstrainedOpaqueType { + span: tcx.def_span(def_id), + name: tcx.item_name(tcx.local_parent(def_id).to_def_id()), + what: match tcx.hir().get(scope) { + _ if scope == hir::CRATE_HIR_ID => "module", + Node::Item(hir::Item { kind: hir::ItemKind::Mod(_), .. }) => "module", + Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }) => "impl", + _ => "item", + }, + }); + return tcx.ty_error(reported); + }; + + // Only check against typeck if we didn't already error + if !hidden.ty.references_error() { + for concrete_type in locator.typeck_types { + if concrete_type.ty != tcx.erase_regions(hidden.ty) + && !(concrete_type, hidden).references_error() + { + hidden.report_mismatch(&concrete_type, def_id, tcx).emit(); + } + } + } + + hidden.ty +} + +struct TaitConstraintLocator<'tcx> { + tcx: TyCtxt<'tcx>, + + /// def_id of the opaque type whose defining uses are being checked + def_id: LocalDefId, + + /// as we walk the defining uses, we are checking that all of them + /// define the same hidden type. This variable is set to `Some` + /// with the first type that we find, and then later types are + /// checked against it (we also carry the span of that first + /// type). + found: Option>, + + /// In the presence of dead code, typeck may figure out a hidden type + /// while borrowck will not. We collect these cases here and check at + /// the end that we actually found a type that matches (modulo regions). + typeck_types: Vec>, +} + +impl TaitConstraintLocator<'_> { + #[instrument(skip(self), level = "debug")] + fn check(&mut self, item_def_id: LocalDefId) { + // Don't try to check items that cannot possibly constrain the type. + if !self.tcx.has_typeck_results(item_def_id) { + debug!("no constraint: no typeck results"); + return; + } + // Calling `mir_borrowck` can lead to cycle errors through + // const-checking, avoid calling it if we don't have to. + // ```rust + // type Foo = impl Fn() -> usize; // when computing type for this + // const fn bar() -> Foo { + // || 0usize + // } + // const BAZR: Foo = bar(); // we would mir-borrowck this, causing cycles + // // because we again need to reveal `Foo` so we can check whether the + // // constant does not contain interior mutability. + // ``` + let tables = self.tcx.typeck(item_def_id); + if let Some(guar) = tables.tainted_by_errors { + self.found = Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: self.tcx.ty_error(guar) }); + return; + } + let Some(&typeck_hidden_ty) = tables.concrete_opaque_types.get(&self.def_id) else { + debug!("no constraints in typeck results"); + return; + }; + if self.typeck_types.iter().all(|prev| prev.ty != typeck_hidden_ty.ty) { + self.typeck_types.push(typeck_hidden_ty); + } + + // Use borrowck to get the type with unerased regions. + let concrete_opaque_types = &self.tcx.mir_borrowck(item_def_id).concrete_opaque_types; + debug!(?concrete_opaque_types); + if let Some(&concrete_type) = concrete_opaque_types.get(&self.def_id) { + debug!(?concrete_type, "found constraint"); + if let Some(prev) = &mut self.found { + if concrete_type.ty != prev.ty && !(concrete_type, prev.ty).references_error() { + let guar = prev.report_mismatch(&concrete_type, self.def_id, self.tcx).emit(); + prev.ty = self.tcx.ty_error(guar); + } + } else { + self.found = Some(concrete_type); + } + } + } +} + +impl<'tcx> intravisit::Visitor<'tcx> for TaitConstraintLocator<'tcx> { + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + if let hir::ExprKind::Closure(closure) = ex.kind { + self.check(closure.def_id); + } + intravisit::walk_expr(self, ex); + } + fn visit_item(&mut self, it: &'tcx Item<'tcx>) { + trace!(?it.owner_id); + // The opaque type itself or its children are not within its reveal scope. + if it.owner_id.def_id != self.def_id { + self.check(it.owner_id.def_id); + intravisit::walk_item(self, it); + } + } + fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) { + trace!(?it.owner_id); + // The opaque type itself or its children are not within its reveal scope. + if it.owner_id.def_id != self.def_id { + self.check(it.owner_id.def_id); + intravisit::walk_impl_item(self, it); + } + } + fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) { + trace!(?it.owner_id); + self.check(it.owner_id.def_id); + intravisit::walk_trait_item(self, it); + } +} + +pub(super) fn find_opaque_ty_constraints_for_rpit( + tcx: TyCtxt<'_>, + def_id: LocalDefId, + owner_def_id: LocalDefId, +) -> Ty<'_> { + let concrete = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied(); + + if let Some(concrete) = concrete { + let scope = tcx.hir().local_def_id_to_hir_id(owner_def_id); + debug!(?scope); + let mut locator = RpitConstraintChecker { def_id, tcx, found: concrete }; + + match tcx.hir().get(scope) { + Node::Item(it) => intravisit::walk_item(&mut locator, it), + Node::ImplItem(it) => intravisit::walk_impl_item(&mut locator, it), + Node::TraitItem(it) => intravisit::walk_trait_item(&mut locator, it), + other => bug!("{:?} is not a valid scope for an opaque type item", other), + } + } + + concrete.map(|concrete| concrete.ty).unwrap_or_else(|| { + let table = tcx.typeck(owner_def_id); + if let Some(guar) = table.tainted_by_errors { + // Some error in the + // owner fn prevented us from populating + // the `concrete_opaque_types` table. + tcx.ty_error(guar) + } else { + table.concrete_opaque_types.get(&def_id).map(|ty| ty.ty).unwrap_or_else(|| { + // We failed to resolve the opaque type or it + // resolves to itself. We interpret this as the + // no values of the hidden type ever being constructed, + // so we can just make the hidden type be `!`. + // For backwards compatibility reasons, we fall back to + // `()` until we the diverging default is changed. + tcx.mk_diverging_default() + }) + } + }) +} + +struct RpitConstraintChecker<'tcx> { + tcx: TyCtxt<'tcx>, + + /// def_id of the opaque type whose defining uses are being checked + def_id: LocalDefId, + + found: ty::OpaqueHiddenType<'tcx>, +} + +impl RpitConstraintChecker<'_> { + #[instrument(skip(self), level = "debug")] + fn check(&self, def_id: LocalDefId) { + // Use borrowck to get the type with unerased regions. + let concrete_opaque_types = &self.tcx.mir_borrowck(def_id).concrete_opaque_types; + debug!(?concrete_opaque_types); + for (&def_id, &concrete_type) in concrete_opaque_types { + if def_id != self.def_id { + // Ignore constraints for other opaque types. + continue; + } + + debug!(?concrete_type, "found constraint"); + + if concrete_type.ty != self.found.ty && !(concrete_type, self.found).references_error() + { + self.found.report_mismatch(&concrete_type, self.def_id, self.tcx).emit(); + } + } + } +} + +impl<'tcx> intravisit::Visitor<'tcx> for RpitConstraintChecker<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + if let hir::ExprKind::Closure(closure) = ex.kind { + self.check(closure.def_id); + } + intravisit::walk_expr(self, ex); + } + fn visit_item(&mut self, it: &'tcx Item<'tcx>) { + trace!(?it.owner_id); + // The opaque type itself or its children are not within its reveal scope. + if it.owner_id.def_id != self.def_id { + self.check(it.owner_id.def_id); + intravisit::walk_item(self, it); + } + } + fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) { + trace!(?it.owner_id); + // The opaque type itself or its children are not within its reveal scope. + if it.owner_id.def_id != self.def_id { + self.check(it.owner_id.def_id); + intravisit::walk_impl_item(self, it); + } + } + fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) { + trace!(?it.owner_id); + self.check(it.owner_id.def_id); + intravisit::walk_trait_item(self, it); + } +}