diff options
| author | Laurențiu Nicola <lnicola@users.noreply.github.com> | 2024-11-28 06:54:16 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-11-28 06:54:16 +0000 |
| commit | 8cf30c235619f60c0a0f0a0779bd17e7e70bf0fb (patch) | |
| tree | 564950531f457ebbe2a114e486cb2c8c72bf9ed0 /compiler/rustc_parse/src/parser | |
| parent | 4e3354ef925d9711c125470097643236c3a983d0 (diff) | |
| parent | 1a435ed7edc19812c29006701b0106a0d0802542 (diff) | |
| download | rust-8cf30c235619f60c0a0f0a0779bd17e7e70bf0fb.tar.gz rust-8cf30c235619f60c0a0f0a0779bd17e7e70bf0fb.zip | |
Merge pull request #18566 from lnicola/sync-from-rust
minor: Sync from downstream
Diffstat (limited to 'compiler/rustc_parse/src/parser')
| -rw-r--r-- | compiler/rustc_parse/src/parser/attr_wrapper.rs | 32 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/expr.rs | 37 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/generics.rs | 62 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/item.rs | 55 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/mod.rs | 56 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/nonterminal.rs | 50 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/pat.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/stmt.rs | 109 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/tests.rs | 1231 |
9 files changed, 1524 insertions, 129 deletions
diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 8bd615e6d79..434f71beac2 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -136,8 +136,9 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { node_replacements.array_windows() { assert!( - node_range.0.end <= next_node_range.0.start, - "Node ranges should be disjoint: ({:?}, {:?}) ({:?}, {:?})", + node_range.0.end <= next_node_range.0.start + || node_range.0.end >= next_node_range.0.end, + "Node ranges should be disjoint or nested: ({:?}, {:?}) ({:?}, {:?})", node_range, tokens, next_node_range, @@ -145,8 +146,20 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { ); } - // Process the replace ranges. - for (node_range, target) in node_replacements.into_iter() { + // Process the replace ranges, starting from the highest start + // position and working our way back. If have tokens like: + // + // `#[cfg(FALSE)] struct Foo { #[cfg(FALSE)] field: bool }` + // + // Then we will generate replace ranges for both + // the `#[cfg(FALSE)] field: bool` and the entire + // `#[cfg(FALSE)] struct Foo { #[cfg(FALSE)] field: bool }` + // + // By starting processing from the replace range with the greatest + // start position, we ensure that any (outer) replace range which + // encloses another (inner) replace range will fully overwrite the + // inner range's replacement. + for (node_range, target) in node_replacements.into_iter().rev() { assert!( !node_range.0.is_empty(), "Cannot replace an empty node range: {:?}", @@ -383,9 +396,10 @@ impl<'a> Parser<'a> { // from `ParserRange` form to `NodeRange` form. We will perform the actual // replacement only when we convert the `LazyAttrTokenStream` to an // `AttrTokenStream`. - self.capture_state - .parser_replacements - .drain(parser_replacements_start..parser_replacements_end) + self.capture_state.parser_replacements + [parser_replacements_start..parser_replacements_end] + .iter() + .cloned() .chain(inner_attr_parser_replacements) .map(|(parser_range, data)| { (NodeRange::new(parser_range, collect_pos.start_pos), data) @@ -496,8 +510,8 @@ fn make_attr_token_stream( FlatToken::Token((Token { kind: TokenKind::CloseDelim(delim), span }, spacing)) => { let frame_data = mem::replace(&mut stack_top, stack_rest.pop().unwrap()); let (open_delim, open_sp, open_spacing) = frame_data.open_delim_sp.unwrap(); - assert_eq!( - open_delim, delim, + assert!( + open_delim.eq_ignoring_invisible_origin(&delim), "Mismatched open/close delims: open={open_delim:?} close={span:?}" ); let dspan = DelimSpan::from_pair(open_sp, span); diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 0ac6133e828..aa5e9586daf 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -49,7 +49,7 @@ pub(super) enum DestructuredFloat { /// 1.2 | 1.2e3 MiddleDot(Symbol, Span, Span, Symbol, Span), /// Invalid - Error(ErrorGuaranteed), + Error, } impl<'a> Parser<'a> { @@ -1005,7 +1005,7 @@ impl<'a> Parser<'a> { self.mk_expr_tuple_field_access(lo, ident1_span, base, sym1, None); self.mk_expr_tuple_field_access(lo, ident2_span, base1, sym2, suffix) } - DestructuredFloat::Error(_) => base, + DestructuredFloat::Error => base, }) } _ => { @@ -1015,7 +1015,7 @@ impl<'a> Parser<'a> { } } - fn error_unexpected_after_dot(&self) -> ErrorGuaranteed { + fn error_unexpected_after_dot(&self) { let actual = pprust::token_to_string(&self.token); let span = self.token.span; let sm = self.psess.source_map(); @@ -1025,7 +1025,7 @@ impl<'a> Parser<'a> { } _ => (span, actual), }; - self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual }) + self.dcx().emit_err(errors::UnexpectedTokenAfterDot { span, actual }); } /// We need an identifier or integer, but the next token is a float. @@ -1111,8 +1111,8 @@ impl<'a> Parser<'a> { // 1.2e+3 | 1.2e-3 [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => { // See the FIXME about `TokenCursor` above. - let guar = self.error_unexpected_after_dot(); - DestructuredFloat::Error(guar) + self.error_unexpected_after_dot(); + DestructuredFloat::Error } _ => panic!("unexpected components in a float token: {components:?}"), } @@ -1178,7 +1178,7 @@ impl<'a> Parser<'a> { fields.insert(start_idx, Ident::new(symbol2, span2)); fields.insert(start_idx, Ident::new(symbol1, span1)); } - DestructuredFloat::Error(_) => { + DestructuredFloat::Error => { trailing_dot = None; fields.insert(start_idx, Ident::new(symbol, self.prev_token.span)); } @@ -2683,6 +2683,13 @@ impl<'a> Parser<'a> { // ^^ // } // + // We account for macro calls that were meant as conditions as well. + // + // if ... { + // } else if macro! { foo bar } { + // ^^ + // } + // // If $cond is "statement-like" such as ExprKind::While then we // want to suggest wrapping in braces. // @@ -2693,7 +2700,9 @@ impl<'a> Parser<'a> { // } // ^ if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) - && classify::expr_requires_semi_to_be_stmt(&cond) => + && (classify::expr_requires_semi_to_be_stmt(&cond) + || matches!(cond.kind, ExprKind::MacCall(..))) + => { self.dcx().emit_err(errors::ExpectedElseBlock { first_tok_span, @@ -3582,11 +3591,19 @@ impl<'a> Parser<'a> { && !self.token.is_reserved_ident() && self.look_ahead(1, |t| { AssocOp::from_token(t).is_some() - || matches!(t.kind, token::OpenDelim(_)) + || matches!( + t.kind, + token::OpenDelim( + Delimiter::Parenthesis + | Delimiter::Bracket + | Delimiter::Brace + ) + ) || *t == token::Dot }) { - // Looks like they tried to write a shorthand, complex expression. + // Looks like they tried to write a shorthand, complex expression, + // E.g.: `n + m`, `f(a)`, `a[i]`, `S { x: 3 }`, or `x.y`. e.span_suggestion_verbose( self.token.span.shrink_to_lo(), "try naming a field", diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index 5aebe716b0a..76ecb77d750 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -1,6 +1,7 @@ use ast::token::Delimiter; use rustc_ast::{ - self as ast, AttrVec, GenericBounds, GenericParam, GenericParamKind, TyKind, WhereClause, token, + self as ast, AttrVec, DUMMY_NODE_ID, GenericBounds, GenericParam, GenericParamKind, TyKind, + WhereClause, token, }; use rustc_errors::{Applicability, PResult}; use rustc_span::Span; @@ -14,8 +15,8 @@ use crate::errors::{ WhereClauseBeforeTupleStructBodySugg, }; -enum PredicateOrStructBody { - Predicate(ast::WherePredicate), +enum PredicateKindOrStructBody { + PredicateKind(ast::WherePredicateKind), StructBody(ThinVec<ast::FieldDef>), } @@ -218,10 +219,11 @@ impl<'a> Parser<'a> { } else if this.token.can_begin_type() { // Trying to write an associated type bound? (#26271) let snapshot = this.create_snapshot_for_diagnostic(); - match this.parse_ty_where_predicate() { - Ok(where_predicate) => { + let lo = this.token.span; + match this.parse_ty_where_predicate_kind() { + Ok(_) => { this.dcx().emit_err(errors::BadAssocTypeBounds { - span: where_predicate.span(), + span: lo.to(this.prev_token.span), }); // FIXME - try to continue parsing other generics? } @@ -340,31 +342,33 @@ impl<'a> Parser<'a> { loop { let where_sp = where_lo.to(self.prev_token.span); let pred_lo = self.token.span; - if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) { + let kind = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) { let lifetime = self.expect_lifetime(); // Bounds starting with a colon are mandatory, but possibly empty. self.expect(&token::Colon)?; let bounds = self.parse_lt_param_bounds(); - where_clause.predicates.push(ast::WherePredicate::RegionPredicate( - ast::WhereRegionPredicate { - span: pred_lo.to(self.prev_token.span), - lifetime, - bounds, - }, - )); + ast::WherePredicateKind::RegionPredicate(ast::WhereRegionPredicate { + lifetime, + bounds, + }) } else if self.check_type() { - match self.parse_ty_where_predicate_or_recover_tuple_struct_body( + match self.parse_ty_where_predicate_kind_or_recover_tuple_struct_body( struct_, pred_lo, where_sp, )? { - PredicateOrStructBody::Predicate(pred) => where_clause.predicates.push(pred), - PredicateOrStructBody::StructBody(body) => { + PredicateKindOrStructBody::PredicateKind(kind) => kind, + PredicateKindOrStructBody::StructBody(body) => { tuple_struct_body = Some(body); break; } } } else { break; - } + }; + where_clause.predicates.push(ast::WherePredicate { + kind, + id: DUMMY_NODE_ID, + span: pred_lo.to(self.prev_token.span), + }); let prev_token = self.prev_token.span; let ate_comma = self.eat(&token::Comma); @@ -384,12 +388,12 @@ impl<'a> Parser<'a> { Ok((where_clause, tuple_struct_body)) } - fn parse_ty_where_predicate_or_recover_tuple_struct_body( + fn parse_ty_where_predicate_kind_or_recover_tuple_struct_body( &mut self, struct_: Option<(Ident, Span)>, pred_lo: Span, where_sp: Span, - ) -> PResult<'a, PredicateOrStructBody> { + ) -> PResult<'a, PredicateKindOrStructBody> { let mut snapshot = None; if let Some(struct_) = struct_ @@ -399,8 +403,8 @@ impl<'a> Parser<'a> { snapshot = Some((struct_, self.create_snapshot_for_diagnostic())); }; - match self.parse_ty_where_predicate() { - Ok(pred) => Ok(PredicateOrStructBody::Predicate(pred)), + match self.parse_ty_where_predicate_kind() { + Ok(pred) => Ok(PredicateKindOrStructBody::PredicateKind(pred)), Err(type_err) => { let Some(((struct_name, body_insertion_point), mut snapshot)) = snapshot else { return Err(type_err); @@ -436,7 +440,7 @@ impl<'a> Parser<'a> { }); self.restore_snapshot(snapshot); - Ok(PredicateOrStructBody::StructBody(body)) + Ok(PredicateKindOrStructBody::StructBody(body)) } Ok(_) => Err(type_err), Err(body_err) => { @@ -448,8 +452,7 @@ impl<'a> Parser<'a> { } } - fn parse_ty_where_predicate(&mut self) -> PResult<'a, ast::WherePredicate> { - let lo = self.token.span; + fn parse_ty_where_predicate_kind(&mut self) -> PResult<'a, ast::WherePredicateKind> { // Parse optional `for<'a, 'b>`. // This `for` is parsed greedily and applies to the whole predicate, // the bounded type can have its own `for` applying only to it. @@ -464,8 +467,7 @@ impl<'a> Parser<'a> { let ty = self.parse_ty_for_where_clause()?; if self.eat(&token::Colon) { let bounds = self.parse_generic_bounds()?; - Ok(ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { - span: lo.to(self.prev_token.span), + Ok(ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate { bound_generic_params: lifetime_defs, bounded_ty: ty, bounds, @@ -474,11 +476,7 @@ impl<'a> Parser<'a> { // FIXME: We are just dropping the binders in lifetime_defs on the floor here. } else if self.eat(&token::Eq) || self.eat(&token::EqEq) { let rhs_ty = self.parse_ty()?; - Ok(ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { - span: lo.to(self.prev_token.span), - lhs_ty: ty, - rhs_ty, - })) + Ok(ast::WherePredicateKind::EqPredicate(ast::WhereEqPredicate { lhs_ty: ty, rhs_ty })) } else { self.maybe_recover_bounds_doubled_colon(&ty)?; self.unexpected_any() diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 6b4e2d0f4e2..26e81b7676b 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -77,18 +77,35 @@ impl<'a> Parser<'a> { if !self.eat(term) { let token_str = super::token_descr(&self.token); if !self.maybe_consume_incorrect_semicolon(items.last().map(|x| &**x)) { + let is_let = self.token.is_keyword(kw::Let); + let is_let_mut = is_let && self.look_ahead(1, |t| t.is_keyword(kw::Mut)); + let let_has_ident = is_let && !is_let_mut && self.is_kw_followed_by_ident(kw::Let); + let msg = format!("expected item, found {token_str}"); let mut err = self.dcx().struct_span_err(self.token.span, msg); - let span = self.token.span; - if self.is_kw_followed_by_ident(kw::Let) { - err.span_label( - span, - "consider using `const` or `static` instead of `let` for global variables", - ); + + let label = if is_let { + "`let` cannot be used for global variables" } else { - err.span_label(span, "expected item") - .note("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>"); + "expected item" }; + err.span_label(self.token.span, label); + + if is_let { + if is_let_mut { + err.help("consider using `static` and a `Mutex` instead of `let mut`"); + } else if let_has_ident { + err.span_suggestion_short( + self.token.span, + "consider using `static` or `const` instead of `let`", + "static", + Applicability::MaybeIncorrect, + ); + } else { + err.help("consider using `static` or `const` instead of `let`"); + } + } + err.note("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>"); return Err(err); } } @@ -1774,6 +1791,17 @@ impl<'a> Parser<'a> { Ok((fields, recovered)) } + fn parse_unsafe_field(&mut self) -> Safety { + // not using parse_safety as that also accepts `safe`. + if self.eat_keyword(kw::Unsafe) { + let span = self.prev_token.span; + self.psess.gated_spans.gate(sym::unsafe_fields, span); + Safety::Unsafe(span) + } else { + Safety::Default + } + } + pub(super) fn parse_tuple_struct_body(&mut self) -> PResult<'a, ThinVec<FieldDef>> { // This is the case where we find `struct Foo<T>(T) where T: Copy;` // Unit like structs are handled in parse_item_struct function @@ -1797,6 +1825,8 @@ impl<'a> Parser<'a> { return Err(err); } }; + // Unsafe fields are not supported in tuple structs, as doing so would result in a + // parsing ambiguity for `struct X(unsafe fn())`. let ty = match p.parse_ty() { Ok(ty) => ty, Err(err) => { @@ -1811,6 +1841,7 @@ impl<'a> Parser<'a> { FieldDef { span: lo.to(ty.span), vis, + safety: Safety::Default, ident: None, id: DUMMY_NODE_ID, ty, @@ -1833,7 +1864,8 @@ impl<'a> Parser<'a> { self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; let vis = this.parse_visibility(FollowedByType::No)?; - this.parse_single_struct_field(adt_ty, lo, vis, attrs) + let safety = this.parse_unsafe_field(); + this.parse_single_struct_field(adt_ty, lo, vis, safety, attrs) .map(|field| (field, Trailing::No, UsePreAttrPos::No)) }) } @@ -1844,10 +1876,11 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, + safety: Safety, attrs: AttrVec, ) -> PResult<'a, FieldDef> { let mut seen_comma: bool = false; - let a_var = self.parse_name_and_ty(adt_ty, lo, vis, attrs)?; + let a_var = self.parse_name_and_ty(adt_ty, lo, vis, safety, attrs)?; if self.token == token::Comma { seen_comma = true; } @@ -1975,6 +2008,7 @@ impl<'a> Parser<'a> { adt_ty: &str, lo: Span, vis: Visibility, + safety: Safety, attrs: AttrVec, ) -> PResult<'a, FieldDef> { let name = self.parse_field_ident(adt_ty, lo)?; @@ -2000,6 +2034,7 @@ impl<'a> Parser<'a> { span: lo.to(self.prev_token.span), ident: Some(name), vis, + safety, id: DUMMY_NODE_ID, ty, attrs, diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 50a8b6542df..0ed8d152d2d 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -21,7 +21,9 @@ pub(crate) use item::FnParseMode; pub use pat::{CommaRecoveryMode, RecoverColon, RecoverComma}; use path::PathStyle; use rustc_ast::ptr::P; -use rustc_ast::token::{self, Delimiter, IdentIsRaw, Nonterminal, Token, TokenKind}; +use rustc_ast::token::{ + self, Delimiter, IdentIsRaw, InvisibleOrigin, MetaVarKind, Nonterminal, Token, TokenKind, +}; use rustc_ast::tokenstream::{ AttrsTarget, DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree, TokenTreeCursor, }; @@ -317,7 +319,7 @@ impl TokenCursor { spacing, delim, )); - if delim != Delimiter::Invisible { + if !delim.skip() { return (Token::new(token::OpenDelim(delim), sp.open), spacing.open); } // No open delimiter to return; continue on to the next iteration. @@ -326,7 +328,7 @@ impl TokenCursor { } else if let Some((tree_cursor, span, spacing, delim)) = self.stack.pop() { // We have exhausted this token stream. Move back to its parent token stream. self.tree_cursor = tree_cursor; - if delim != Delimiter::Invisible { + if !delim.skip() { return (Token::new(token::CloseDelim(delim), span.close), spacing.close); } // No close delimiter to return; continue on to the next iteration. @@ -410,6 +412,12 @@ pub(super) enum TokenDescription { Keyword, ReservedKeyword, DocComment, + + // Expanded metavariables are wrapped in invisible delimiters which aren't + // pretty-printed. In error messages we must handle these specially + // otherwise we get confusing things in messages like "expected `(`, found + // ``". It's better to say e.g. "expected `(`, found type metavariable". + MetaVar(MetaVarKind), } impl TokenDescription { @@ -419,26 +427,29 @@ impl TokenDescription { _ if token.is_used_keyword() => Some(TokenDescription::Keyword), _ if token.is_unused_keyword() => Some(TokenDescription::ReservedKeyword), token::DocComment(..) => Some(TokenDescription::DocComment), + token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(kind))) => { + Some(TokenDescription::MetaVar(kind)) + } _ => None, } } } pub fn token_descr(token: &Token) -> String { - let name = pprust::token_to_string(token).to_string(); - - let kind = match (TokenDescription::from_token(token), &token.kind) { - (Some(TokenDescription::ReservedIdentifier), _) => Some("reserved identifier"), - (Some(TokenDescription::Keyword), _) => Some("keyword"), - (Some(TokenDescription::ReservedKeyword), _) => Some("reserved keyword"), - (Some(TokenDescription::DocComment), _) => Some("doc comment"), - (None, TokenKind::NtIdent(..)) => Some("identifier"), - (None, TokenKind::NtLifetime(..)) => Some("lifetime"), - (None, TokenKind::Interpolated(node)) => Some(node.descr()), - (None, _) => None, - }; - - if let Some(kind) = kind { format!("{kind} `{name}`") } else { format!("`{name}`") } + let s = pprust::token_to_string(token).to_string(); + + match (TokenDescription::from_token(token), &token.kind) { + (Some(TokenDescription::ReservedIdentifier), _) => format!("reserved identifier `{s}`"), + (Some(TokenDescription::Keyword), _) => format!("keyword `{s}`"), + (Some(TokenDescription::ReservedKeyword), _) => format!("reserved keyword `{s}`"), + (Some(TokenDescription::DocComment), _) => format!("doc comment `{s}`"), + // Deliberately doesn't print `s`, which is empty. + (Some(TokenDescription::MetaVar(kind)), _) => format!("`{kind}` metavariable"), + (None, TokenKind::NtIdent(..)) => format!("identifier `{s}`"), + (None, TokenKind::NtLifetime(..)) => format!("lifetime `{s}`"), + (None, TokenKind::Interpolated(node)) => format!("{} `{s}`", node.descr()), + (None, _) => format!("`{s}`"), + } } impl<'a> Parser<'a> { @@ -641,9 +652,10 @@ impl<'a> Parser<'a> { return true; } + // Do an ASCII case-insensitive match, because all keywords are ASCII. if case == Case::Insensitive && let Some((ident, IdentIsRaw::No)) = self.token.ident() - && ident.as_str().to_lowercase() == kw.as_str().to_lowercase() + && ident.as_str().eq_ignore_ascii_case(kw.as_str()) { true } else { @@ -1162,7 +1174,7 @@ impl<'a> Parser<'a> { } debug_assert!(!matches!( next.0.kind, - token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) + token::OpenDelim(delim) | token::CloseDelim(delim) if delim.skip() )); self.inlined_bump_with(next) } @@ -1186,7 +1198,7 @@ impl<'a> Parser<'a> { match tree { TokenTree::Token(token, _) => return looker(token), &TokenTree::Delimited(dspan, _, delim, _) => { - if delim != Delimiter::Invisible { + if !delim.skip() { return looker(&Token::new(token::OpenDelim(delim), dspan.open)); } } @@ -1196,7 +1208,7 @@ impl<'a> Parser<'a> { // The tree cursor lookahead went (one) past the end of the // current token tree. Try to return a close delimiter. if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last() - && delim != Delimiter::Invisible + && !delim.skip() { // We are not in the outermost token stream, so we have // delimiters. Also, those delimiters are not skipped. @@ -1215,7 +1227,7 @@ impl<'a> Parser<'a> { token = cursor.next().0; if matches!( token.kind, - token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) + token::OpenDelim(delim) | token::CloseDelim(delim) if delim.skip() ) { continue; } diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index 43c3de90d9d..8fb6f85d0dd 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -3,7 +3,9 @@ use rustc_ast::ptr::P; use rustc_ast::token::Nonterminal::*; use rustc_ast::token::NtExprKind::*; use rustc_ast::token::NtPatKind::*; -use rustc_ast::token::{self, Delimiter, NonterminalKind, Token}; +use rustc_ast::token::{ + self, Delimiter, InvisibleOrigin, MetaVarKind, Nonterminal, NonterminalKind, Token, +}; use rustc_ast_pretty::pprust; use rustc_data_structures::sync::Lrc; use rustc_errors::PResult; @@ -22,7 +24,28 @@ impl<'a> Parser<'a> { #[inline] pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool { /// Checks whether the non-terminal may contain a single (non-keyword) identifier. - fn may_be_ident(nt: &token::Nonterminal) -> bool { + fn may_be_ident(kind: MetaVarKind) -> bool { + match kind { + MetaVarKind::Stmt + | MetaVarKind::Pat(_) + | MetaVarKind::Expr { .. } + | MetaVarKind::Ty + | MetaVarKind::Literal // `true`, `false` + | MetaVarKind::Meta + | MetaVarKind::Path => true, + + MetaVarKind::Item + | MetaVarKind::Block + | MetaVarKind::Vis => false, + + MetaVarKind::Ident + | MetaVarKind::Lifetime + | MetaVarKind::TT => unreachable!(), + } + } + + /// Old variant of `may_be_ident`. Being phased out. + fn nt_may_be_ident(nt: &Nonterminal) -> bool { match nt { NtStmt(_) | NtPat(_) @@ -69,7 +92,8 @@ impl<'a> Parser<'a> { | token::Ident(..) | token::NtIdent(..) | token::NtLifetime(..) - | token::Interpolated(_) => true, + | token::Interpolated(_) + | token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(_))) => true, _ => token.can_begin_type(), }, NonterminalKind::Block => match &token.kind { @@ -79,11 +103,29 @@ impl<'a> Parser<'a> { NtBlock(_) | NtStmt(_) | NtExpr(_) | NtLiteral(_) => true, NtItem(_) | NtPat(_) | NtTy(_) | NtMeta(_) | NtPath(_) | NtVis(_) => false, }, + token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(k))) => match k { + MetaVarKind::Block + | MetaVarKind::Stmt + | MetaVarKind::Expr { .. } + | MetaVarKind::Literal => true, + MetaVarKind::Item + | MetaVarKind::Pat(_) + | MetaVarKind::Ty + | MetaVarKind::Meta + | MetaVarKind::Path + | MetaVarKind::Vis => false, + MetaVarKind::Lifetime | MetaVarKind::Ident | MetaVarKind::TT => { + unreachable!() + } + }, _ => false, }, NonterminalKind::Path | NonterminalKind::Meta => match &token.kind { token::PathSep | token::Ident(..) | token::NtIdent(..) => true, - token::Interpolated(nt) => may_be_ident(nt), + token::Interpolated(nt) => nt_may_be_ident(nt), + token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(kind))) => { + may_be_ident(*kind) + } _ => false, }, NonterminalKind::Pat(pat_kind) => token.can_begin_pattern(pat_kind), diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 7f114013320..004b5b34813 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1,11 +1,12 @@ use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token}; +use rustc_ast::util::parser::AssocOp; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{ - self as ast, Arm, AttrVec, BinOpKind, BindingMode, ByRef, Expr, ExprKind, ExprPrecedence, - LocalKind, MacCall, Mutability, Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, - RangeSyntax, Stmt, StmtKind, + self as ast, Arm, AttrVec, BinOpKind, BindingMode, ByRef, Expr, ExprKind, LocalKind, MacCall, + Mutability, Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, Stmt, + StmtKind, }; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diag, DiagArgValue, PResult, StashKey}; @@ -458,7 +459,7 @@ impl<'a> Parser<'a> { .create_err(UnexpectedExpressionInPattern { span, is_bound, - expr_precedence: expr.precedence().order(), + expr_precedence: expr.precedence(), }) .stash(span, StashKey::ExprInPat) .unwrap(), @@ -545,7 +546,8 @@ impl<'a> Parser<'a> { let expr = match &err.args["expr_precedence"] { DiagArgValue::Number(expr_precedence) => { if *expr_precedence - <= ExprPrecedence::Binary(BinOpKind::Eq).order() as i32 + <= AssocOp::from_ast_binop(BinOpKind::Eq).precedence() + as i32 { format!("({expr})") } else { @@ -568,8 +570,9 @@ impl<'a> Parser<'a> { } Some(guard) => { // Are parentheses required around the old guard? - let wrap_guard = guard.precedence().order() - <= ExprPrecedence::Binary(BinOpKind::And).order(); + let wrap_guard = guard.precedence() + <= AssocOp::from_ast_binop(BinOpKind::And).precedence() + as i8; err.subdiagnostic( UnexpectedExpressionInPatternSugg::UpdateGuard { @@ -683,7 +686,9 @@ impl<'a> Parser<'a> { }) { self.bump(); - self.dcx().emit_err(RemoveLet { span: lo }); + // Trim extra space after the `let` + let span = lo.with_hi(self.token.span.lo()); + self.dcx().emit_err(RemoveLet { span: lo, suggestion: span }); lo = self.token.span; } diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index b7cdae3e3e1..190cd9ed061 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -475,6 +475,7 @@ impl<'a> Parser<'a> { } fn error_block_no_opening_brace_msg(&mut self, msg: Cow<'static, str>) -> Diag<'a> { + let prev = self.prev_token.span; let sp = self.token.span; let mut e = self.dcx().struct_span_err(sp, msg); let do_not_suggest_help = self.token.is_keyword(kw::In) || self.token == token::Colon; @@ -514,8 +515,97 @@ impl<'a> Parser<'a> { } else { stmt.span }; + self.suggest_fixes_misparsed_for_loop_head( + &mut e, + prev.between(sp), + stmt_span, + &stmt.kind, + ); + } + Err(e) => { + self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); + e.cancel(); + } + _ => {} + } + e.span_label(sp, "expected `{`"); + e + } + + fn suggest_fixes_misparsed_for_loop_head( + &self, + e: &mut Diag<'_>, + between: Span, + stmt_span: Span, + stmt_kind: &StmtKind, + ) { + match (&self.token.kind, &stmt_kind) { + (token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr)) + if let ExprKind::Call(..) = expr.kind => + { + // for _ in x y() {} + e.span_suggestion_verbose( + between, + "you might have meant to write a method call", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + (token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr)) + if let ExprKind::Field(..) = expr.kind => + { + // for _ in x y.z {} + e.span_suggestion_verbose( + between, + "you might have meant to write a field access", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + (token::CloseDelim(Delimiter::Brace), StmtKind::Expr(expr)) + if let ExprKind::Struct(expr) = &expr.kind + && let None = expr.qself + && expr.path.segments.len() == 1 => + { + // This is specific to "mistyped `if` condition followed by empty body" + // + // for _ in x y {} + e.span_suggestion_verbose( + between, + "you might have meant to write a field access", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + (token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr)) + if let ExprKind::Lit(lit) = expr.kind + && let None = lit.suffix + && let token::LitKind::Integer | token::LitKind::Float = lit.kind => + { + // for _ in x 0 {} + // for _ in x 0.0 {} + e.span_suggestion_verbose( + between, + format!("you might have meant to write a field access"), + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } + (token::OpenDelim(Delimiter::Brace), StmtKind::Expr(expr)) + if let ExprKind::Loop(..) + | ExprKind::If(..) + | ExprKind::While(..) + | ExprKind::Match(..) + | ExprKind::ForLoop { .. } + | ExprKind::TryBlock(..) + | ExprKind::Ret(..) + | ExprKind::Closure(..) + | ExprKind::Struct(..) + | ExprKind::Try(..) = expr.kind => + { + // These are more likely to have been meant as a block body. e.multipart_suggestion( - "try placing this code inside a block", + "you might have meant to write this as part of a block", vec![ (stmt_span.shrink_to_lo(), "{ ".to_string()), (stmt_span.shrink_to_hi(), " }".to_string()), @@ -524,14 +614,19 @@ impl<'a> Parser<'a> { Applicability::MaybeIncorrect, ); } - Err(e) => { - self.recover_stmt_(SemiColonMode::Break, BlockMode::Ignore); - e.cancel(); + (token::OpenDelim(Delimiter::Brace), _) => {} + (_, _) => { + e.multipart_suggestion( + "you might have meant to write this as part of a block", + vec![ + (stmt_span.shrink_to_lo(), "{ ".to_string()), + (stmt_span.shrink_to_hi(), " }".to_string()), + ], + // Speculative; has been misleading in the past (#46836). + Applicability::MaybeIncorrect, + ); } - _ => {} } - e.span_label(sp, "expected `{`"); - e } fn error_block_no_opening_brace<T>(&mut self) -> PResult<'a, T> { diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 92684505ab0..decaecd2682 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -12,7 +12,7 @@ use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, Toke use rustc_ast::{self as ast, PatKind, visit}; use rustc_ast_pretty::pprust::item_to_string; use rustc_data_structures::sync::Lrc; -use rustc_errors::emitter::HumanEmitter; +use rustc_errors::emitter::{HumanEmitter, OutputTheme}; use rustc_errors::{DiagCtxt, MultiSpan, PResult}; use rustc_session::parse::ParseSess; use rustc_span::source_map::{FilePathMapping, SourceMap}; @@ -36,16 +36,17 @@ fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> { )) } -fn create_test_handler() -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) { +fn create_test_handler(theme: OutputTheme) -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) { let output = Arc::new(Mutex::new(Vec::new())); let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); let fallback_bundle = rustc_errors::fallback_fluent_bundle( vec![crate::DEFAULT_LOCALE_RESOURCE, crate::DEFAULT_LOCALE_RESOURCE], false, ); - let emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) + let mut emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) .sm(Some(source_map.clone())) .diagnostic_width(Some(140)); + emitter = emitter.theme(theme); let dcx = DiagCtxt::new(Box::new(emitter)); (dcx, source_map, output) } @@ -69,7 +70,7 @@ fn with_expected_parse_error<T, F>(source_str: &str, expected_output: &str, f: F where F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>, { - let (handler, source_map, output) = create_test_handler(); + let (handler, source_map, output) = create_test_handler(OutputTheme::Ascii); let psess = ParseSess::with_dcx(handler, source_map); let mut p = string_to_parser(&psess, source_str.to_string()); let result = f(&mut p); @@ -189,34 +190,55 @@ impl<T: Write> Write for Shared<T> { } #[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests -fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) { +fn test_harness( + file_text: &str, + span_labels: Vec<SpanLabel>, + notes: Vec<(Option<(Position, Position)>, &'static str)>, + expected_output_ascii: &str, + expected_output_unicode: &str, +) { create_default_session_globals_then(|| { - let (dcx, source_map, output) = create_test_handler(); - source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); - - let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); - let mut msp = MultiSpan::from_span(primary_span); - for span_label in span_labels { - let span = make_span(&file_text, &span_label.start, &span_label.end); - msp.push_span_label(span, span_label.label); - println!("span: {:?} label: {:?}", span, span_label.label); - println!("text: {:?}", source_map.span_to_snippet(span)); - } + for (theme, expected_output) in [ + (OutputTheme::Ascii, expected_output_ascii), + (OutputTheme::Unicode, expected_output_unicode), + ] { + let (dcx, source_map, output) = create_test_handler(theme); + source_map + .new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); + + let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); + let mut msp = MultiSpan::from_span(primary_span); + for span_label in &span_labels { + let span = make_span(&file_text, &span_label.start, &span_label.end); + msp.push_span_label(span, span_label.label); + println!("span: {:?} label: {:?}", span, span_label.label); + println!("text: {:?}", source_map.span_to_snippet(span)); + } - dcx.handle().span_err(msp, "foo"); + let mut err = dcx.handle().struct_span_err(msp, "foo"); + for (position, note) in ¬es { + if let Some((start, end)) = position { + let span = make_span(&file_text, &start, &end); + err.span_note(span, *note); + } else { + err.note(*note); + } + } + err.emit(); - assert!( - expected_output.chars().next() == Some('\n'), - "expected output should begin with newline" - ); - let expected_output = &expected_output[1..]; + assert!( + expected_output.chars().next() == Some('\n'), + "expected output should begin with newline" + ); + let expected_output = &expected_output[1..]; - let bytes = output.lock().unwrap(); - let actual_output = str::from_utf8(&bytes).unwrap(); - println!("expected output:\n------\n{}------", expected_output); - println!("actual output:\n------\n{}------", actual_output); + let bytes = output.lock().unwrap(); + let actual_output = str::from_utf8(&bytes).unwrap(); + println!("expected output:\n------\n{}------", expected_output); + println!("actual output:\n------\n{}------", actual_output); - assert!(expected_output == actual_output) + assert!(expected_output == actual_output) + } }) } @@ -253,6 +275,7 @@ fn foo() { end: Position { string: "}", count: 1 }, label: "test", }], + vec![], r#" error: foo --> test.rs:2:10 @@ -263,6 +286,16 @@ error: foo | |_^ test "#, + r#" +error: foo + ╭▸ test.rs:2:10 + │ +2 │ fn foo() { + │ ┏━━━━━━━━━━┛ +3 │ ┃ } + ╰╴┗━┛ test + +"#, ); } @@ -280,6 +313,7 @@ fn foo() { end: Position { string: "}", count: 1 }, label: "test", }], + vec![], r#" error: foo --> test.rs:2:10 @@ -291,6 +325,17 @@ error: foo | |___^ test "#, + r#" +error: foo + ╭▸ test.rs:2:10 + │ +2 │ fn foo() { + │ ┏━━━━━━━━━━┛ + ‡ ┃ +5 │ ┃ } + ╰╴┗━━━┛ test + +"#, ); } #[test] @@ -315,6 +360,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -329,6 +375,20 @@ error: foo | `X` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 + │ ┏━━━━┛ │ + │ ┃┌──────┘ +4 │ ┃│ X1 Y1 +5 │ ┃│ X2 Y2 + │ ┃└────╿──┘ `Y` is a good letter too + │ ┗━━━━━┥ + ╰╴ `X` is a good letter + +"#, ); } @@ -353,6 +413,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -366,6 +427,72 @@ error: foo | `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 + │ ┏━━━━┛ │ + │ ┃┌──────┘ +4 │ ┃│ Y1 X1 + │ ┗│━━━━│━━┛ `X` is a good letter + │ └────┤ + ╰╴ `Y` is a good letter too + +"#, + ); +} + +#[test] +fn multiline_and_normal_overlap() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![ + SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "X2", count: 1 }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { string: "X0", count: 1 }, + end: Position { string: "Y0", count: 1 }, + label: "`Y` is a good letter too", + }, + ], + vec![], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ___---^- + | | | + | | `Y` is a good letter too +4 | | X1 Y1 Z1 +5 | | X2 Y2 Z2 + | |____^ `X` is a good letter + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━┬──┛─ + │ ┃ │ + │ ┃ `Y` is a good letter too +4 │ ┃ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 + ╰╴┗━━━━┛ `X` is a good letter + +"#, ); } @@ -392,6 +519,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -406,6 +534,789 @@ error: foo | |____- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌─────────┘ +5 │ ┃│ X2 Y2 Z2 + │ ┗│━━━━┛ `X` is a good letter +6 │ │ X3 Y3 Z3 + ╰╴ └────┘ `Y` is a good letter too + +"#, + ); +} + +#[test] +fn different_note_1() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar + +"#, + ); +} + +#[test] +fn different_note_2() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar"), (None, "qux")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_3() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar"), (None, "baz"), (None, "qux")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: baz + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ├ note: baz + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_1() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + )], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_2() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X2", count: 1 }, Position { string: "Y2", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:5:3 + | +5 | X2 Y2 Z2 + | ^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:5:3 + │ +5 │ X2 Y2 Z2 + ╰╴ ━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_3() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_4() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_5() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_6() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_7() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z3", count: 1 })), + "bar", + ), + (None, "baz"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | / X1 Y1 Z1 +5 | | X2 Y2 Z2 +6 | | X3 Y3 Z3 + | |__________^ + = note: baz +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ ┏ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 +6 │ ┃ X3 Y3 Z3 + │ ┗━━━━━━━━━━┛ + ╰ note: baz +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_8() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_9() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + (None, "baz"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: baz +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ╰ note: baz +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_10() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + (None, "baz"), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: baz + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ├ note: baz + ╰ note: qux + +"#, ); } @@ -436,6 +1347,7 @@ fn foo() { label: "`Z` label", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -452,6 +1364,22 @@ error: foo | `X` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━┛ │ │ + │ ┃┌───────┘ │ + │ ┃│┌─────────┘ +4 │ ┃││ X1 Y1 Z1 +5 │ ┃││ X2 Y2 Z2 + │ ┃│└────╿──│──┘ `Z` label + │ ┃└─────│──┤ + │ ┗━━━━━━┥ `Y` is a good letter too + ╰╴ `X` is a good letter + +"#, ); } @@ -482,6 +1410,7 @@ fn foo() { label: "`Z` label", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -496,6 +1425,20 @@ error: foo | `Z` label "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ ┏ X0 Y0 Z0 +4 │ ┃ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 + │ ┃ ╿ + │ ┃ │ + │ ┃ `X` is a good letter + │ ┗━━━━`Y` is a good letter too + ╰╴ `Z` label + +"#, ); } @@ -527,6 +1470,7 @@ fn foo() { label: "`Z`", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -545,6 +1489,24 @@ error: foo | |_______- `Z` "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿─┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ X2 Y2 Z2 + │ └───│──────┘ `Y` is a good letter too + │ ┌───┘ + │ │ +6 │ │ X3 Y3 Z3 + ╰╴ └───────┘ `Z` + +"#, ); } @@ -571,6 +1533,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -584,6 +1547,19 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ ┏ X0 Y0 Z0 +4 │ ┃ X1 Y1 Z1 + │ ┗━━━━┛ `X` is a good letter +5 │ X2 Y2 Z2 + │ ┌──────┘ +6 │ │ X3 Y3 Z3 + ╰╴└──────────┘ `Y` is a good letter too + +"#, ); } @@ -610,6 +1586,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -625,6 +1602,21 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿────┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ X2 Y2 Z2 +6 │ │ X3 Y3 Z3 + ╰╴ └──────────┘ `Y` is a good letter too + +"#, ); } @@ -653,6 +1645,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -661,6 +1654,57 @@ error: foo | ----^^^^-^^-- `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + ╰╴ ────━━━━─━━── `a` is a good letter + +"#, + ); +} + +#[test] +fn multiline_notes() { + test_harness( + r#" +fn foo() { + a { b { c } d } +} +"#, + vec![SpanLabel { + start: Position { string: "a", count: 1 }, + end: Position { string: "d", count: 1 }, + label: "`a` is a good letter", + }], + vec![(None, "foo\nbar"), (None, "foo\nbar")], + r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ `a` is a good letter + | + = note: foo + bar + = note: foo + bar + +"#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ━━━━━━━━━━━━━ `a` is a good letter + │ + ├ note: foo + │ bar + ╰ note: foo + bar + +"#, ); } @@ -684,6 +1728,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -692,6 +1737,14 @@ error: foo | ^^^^-------^^ `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━───────━━ `a` is a good letter + +"#, ); } @@ -720,6 +1773,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -730,6 +1784,16 @@ error: foo | `b` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + │ ────┯━━━─━━── + │ │ + ╰╴ `b` is a good letter + +"#, ); } @@ -753,6 +1817,7 @@ fn foo() { label: "`b` is a good letter", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -763,6 +1828,16 @@ error: foo | `b` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ━━━━┬──────━━ + │ │ + ╰╴ `b` is a good letter + +"#, ); } @@ -786,6 +1861,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -796,6 +1872,16 @@ error: foo | `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a bc d + │ ┯━━━──── + │ │ + ╰╴ `a` is a good letter + +"#, ); } @@ -819,6 +1905,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -827,6 +1914,14 @@ error: foo | ^^^^-------^^ "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━───────━━ + +"#, ); } @@ -855,6 +1950,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -863,6 +1959,14 @@ error: foo | ----^^^^-^^-- "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + ╰╴ ────━━━━─━━── + +"#, ); } @@ -886,6 +1990,7 @@ fn foo() { label: "`b` is a good letter", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -897,6 +2002,17 @@ error: foo | `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ┯━━━┬──────━━ + │ │ │ + │ │ `b` is a good letter + ╰╴ `a` is a good letter + +"#, ); } @@ -913,6 +2029,7 @@ fn foo() { end: Position { string: "d", count: 1 }, label: "`a` is a good letter", }], + vec![], r#" error: foo --> test.rs:3:3 @@ -921,6 +2038,14 @@ error: foo | ^^^^^^^^^^^^^ `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━━━━━━━━━━ `a` is a good letter + +"#, ); } @@ -937,6 +2062,7 @@ fn foo() { end: Position { string: "d", count: 1 }, label: "", }], + vec![], r#" error: foo --> test.rs:3:3 @@ -945,6 +2071,14 @@ error: foo | ^^^^^^^^^^^^^ "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━━━━━━━━━━ + +"#, ); } @@ -981,6 +2115,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -1000,6 +2135,25 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿────┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ 1 +6 │ │ 2 +7 │ │ 3 + ‡ │ +15 │ │ X2 Y2 Z2 +16 │ │ X3 Y3 Z3 + ╰╴ └──────────┘ `Y` is a good letter too + +"#, ); } @@ -1036,6 +2190,7 @@ fn foo() { label: "`Z` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -1058,6 +2213,28 @@ error: foo | |________^ `Y` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ 1 +5 │ ┃ 2 +6 │ ┃ 3 +7 │ ┃ X1 Y1 Z1 + │ ┃┌─────────┘ +8 │ ┃│ 4 +9 │ ┃│ 5 +10 │ ┃│ 6 +11 │ ┃│ X2 Y2 Z2 + │ ┃└──────────┘ `Z` is a good letter too + ‡ ┃ +15 │ ┃ 10 +16 │ ┃ X3 Y3 Z3 + ╰╴┗━━━━━━━━┛ `Y` is a good letter + +"#, ); } |
