Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When encountering chained operators use heuristics to recover from bad turbofish #64909

Merged
merged 8 commits into from
Oct 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 68 additions & 37 deletions src/librustc_errors/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,31 @@ impl Diagnostic {
/// * may contain a name of a function, variable, or type, but not whole expressions
///
/// See `CodeSuggestion` for more information.
pub fn span_suggestion(&mut self, sp: Span, msg: &str,
suggestion: String,
applicability: Applicability) -> &mut Self {
pub fn span_suggestion(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::ShowCode,
);
self
}

pub fn span_suggestion_with_style(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
style: SuggestionStyle,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart {
Expand All @@ -309,16 +331,37 @@ impl Diagnostic {
}],
}],
msg: msg.to_owned(),
style: SuggestionStyle::ShowCode,
style,
applicability,
});
self
}

pub fn span_suggestion_verbose(
&mut self,
sp: Span,
msg: &str,
suggestion: String,
applicability: Applicability,
) -> &mut Self {
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
SuggestionStyle::ShowAlways,
);
self
}

/// Prints out a message with multiple suggested edits of the code.
pub fn span_suggestions(&mut self, sp: Span, msg: &str,
suggestions: impl Iterator<Item = String>, applicability: Applicability) -> &mut Self
{
pub fn span_suggestions(
&mut self,
sp: Span,
msg: &str,
suggestions: impl Iterator<Item = String>,
applicability: Applicability,
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: suggestions.map(|snippet| Substitution {
parts: vec![SubstitutionPart {
Expand All @@ -340,17 +383,13 @@ impl Diagnostic {
pub fn span_suggestion_short(
&mut self, sp: Span, msg: &str, suggestion: String, applicability: Applicability
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart {
snippet: suggestion,
span: sp,
}],
}],
msg: msg.to_owned(),
style: SuggestionStyle::HideCodeInline,
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
});
SuggestionStyle::HideCodeInline,
);
self
}

Expand All @@ -363,17 +402,13 @@ impl Diagnostic {
pub fn span_suggestion_hidden(
&mut self, sp: Span, msg: &str, suggestion: String, applicability: Applicability
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart {
snippet: suggestion,
span: sp,
}],
}],
msg: msg.to_owned(),
style: SuggestionStyle::HideCodeAlways,
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
});
SuggestionStyle::HideCodeAlways,
);
self
}

Expand All @@ -384,17 +419,13 @@ impl Diagnostic {
pub fn tool_only_span_suggestion(
&mut self, sp: Span, msg: &str, suggestion: String, applicability: Applicability
) -> &mut Self {
self.suggestions.push(CodeSuggestion {
substitutions: vec![Substitution {
parts: vec![SubstitutionPart {
snippet: suggestion,
span: sp,
}],
}],
msg: msg.to_owned(),
style: SuggestionStyle::CompletelyHidden,
self.span_suggestion_with_style(
sp,
msg,
suggestion,
applicability,
});
SuggestionStyle::CompletelyHidden,
);
self
}

Expand Down
12 changes: 8 additions & 4 deletions src/librustc_errors/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,14 @@ pub trait Emitter {
sugg.msg.split_whitespace().count() < 10 &&
// don't display multiline suggestions as labels
!sugg.substitutions[0].parts[0].snippet.contains('\n') &&
// when this style is set we want the suggestion to be a message, not inline
sugg.style != SuggestionStyle::HideCodeAlways &&
// trivial suggestion for tooling's sake, never shown
sugg.style != SuggestionStyle::CompletelyHidden
![
// when this style is set we want the suggestion to be a message, not inline
SuggestionStyle::HideCodeAlways,
// trivial suggestion for tooling's sake, never shown
SuggestionStyle::CompletelyHidden,
// subtle suggestion, never shown inline
SuggestionStyle::ShowAlways,
].contains(&sugg.style)
{
let substitution = &sugg.substitutions[0].parts[0].snippet.trim();
let msg = if substitution.len() == 0 || sugg.style.hide_inline() {
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_errors/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub enum SuggestionStyle {
/// This will *not* show the code if the suggestion is inline *and* the suggested code is
/// empty.
ShowCode,
/// Always show the suggested code independently.
ShowAlways,
}

impl SuggestionStyle {
Expand Down
163 changes: 150 additions & 13 deletions src/libsyntax/parse/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use syntax_pos::{Span, DUMMY_SP, MultiSpan, SpanSnippetError};
use log::{debug, trace};
use std::mem;

const TURBOFISH: &'static str = "use `::<...>` instead of `<...>` to specify type arguments";
/// Creates a placeholder argument.
crate fn dummy_arg(ident: Ident) -> Param {
let pat = P(Pat {
Expand Down Expand Up @@ -543,35 +544,154 @@ impl<'a> Parser<'a> {
}

/// Produces an error if comparison operators are chained (RFC #558).
/// We only need to check the LHS, not the RHS, because all comparison ops
/// have same precedence and are left-associative.
crate fn check_no_chained_comparison(&self, lhs: &Expr, outer_op: &AssocOp) -> PResult<'a, ()> {
debug_assert!(outer_op.is_comparison(),
"check_no_chained_comparison: {:?} is not comparison",
outer_op);
/// We only need to check the LHS, not the RHS, because all comparison ops have same
/// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
///
/// This can also be hit if someone incorrectly writes `foo<bar>()` when they should have used
/// the turbofish (`foo::<bar>()`) syntax. We attempt some heuristic recovery if that is the
/// case.
///
/// Keep in mind that given that `outer_op.is_comparison()` holds and comparison ops are left
/// associative we can infer that we have:
///
/// outer_op
/// / \
/// inner_op r2
/// / \
/// l1 r1
crate fn check_no_chained_comparison(
&mut self,
lhs: &Expr,
outer_op: &AssocOp,
estebank marked this conversation as resolved.
Show resolved Hide resolved
) -> PResult<'a, Option<P<Expr>>> {
debug_assert!(
outer_op.is_comparison(),
"check_no_chained_comparison: {:?} is not comparison",
outer_op,
);

let mk_err_expr = |this: &Self, span| {
Ok(Some(this.mk_expr(span, ExprKind::Err, ThinVec::new())))
};

match lhs.kind {
ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
// Respan to include both operators.
let op_span = op.span.to(self.token.span);
let op_span = op.span.to(self.prev_span);
let mut err = self.struct_span_err(
op_span,
"chained comparison operators require parentheses",
);

let suggest = |err: &mut DiagnosticBuilder<'_>| {
err.span_suggestion_verbose(
op_span.shrink_to_lo(),
TURBOFISH,
"::".to_string(),
Applicability::MaybeIncorrect,
);
};

if op.node == BinOpKind::Lt &&
*outer_op == AssocOp::Less || // Include `<` to provide this recommendation
*outer_op == AssocOp::Greater // even in a case like the following:
{ // Foo<Bar<Baz<Qux, ()>>>
err.help(
"use `::<...>` instead of `<...>` if you meant to specify type arguments");
err.help("or use `(...)` if you meant to specify fn arguments");
// These cases cause too many knock-down errors, bail out (#61329).
return Err(err);
if *outer_op == AssocOp::Less {
let snapshot = self.clone();
self.bump();
Centril marked this conversation as resolved.
Show resolved Hide resolved
// So far we have parsed `foo<bar<`, consume the rest of the type args.
let modifiers = [
(token::Lt, 1),
(token::Gt, -1),
(token::BinOp(token::Shr), -2),
];
self.consume_tts(1, &modifiers[..]);

if !&[
token::OpenDelim(token::Paren),
token::ModSep,
].contains(&self.token.kind) {
// We don't have `foo< bar >(` or `foo< bar >::`, so we rewind the
// parser and bail out.
mem::replace(self, snapshot.clone());
}
estebank marked this conversation as resolved.
Show resolved Hide resolved
}
return if token::ModSep == self.token.kind {
// We have some certainty that this was a bad turbofish at this point.
// `foo< bar >::`
Centril marked this conversation as resolved.
Show resolved Hide resolved
suggest(&mut err);

let snapshot = self.clone();
self.bump(); // `::`

// Consume the rest of the likely `foo<bar>::new()` or return at `foo<bar>`.
match self.parse_expr() {
Ok(_) => {
// 99% certain that the suggestion is correct, continue parsing.
err.emit();
// FIXME: actually check that the two expressions in the binop are
// paths and resynthesize new fn call expression instead of using
// `ExprKind::Err` placeholder.
mk_err_expr(self, lhs.span.to(self.prev_span))
}
Err(mut expr_err) => {
expr_err.cancel();
// Not entirely sure now, but we bubble the error up with the
// suggestion.
mem::replace(self, snapshot);
Err(err)
}
}
} else if token::OpenDelim(token::Paren) == self.token.kind {
// We have high certainty that this was a bad turbofish at this point.
// `foo< bar >(`
suggest(&mut err);
// Consume the fn call arguments.
match self.consume_fn_args() {
Err(()) => Err(err),
Ok(()) => {
err.emit();
// FIXME: actually check that the two expressions in the binop are
// paths and resynthesize new fn call expression instead of using
// `ExprKind::Err` placeholder.
mk_err_expr(self, lhs.span.to(self.prev_span))
}
}
} else {
// All we know is that this is `foo < bar >` and *nothing* else. Try to
// be helpful, but don't attempt to recover.
Centril marked this conversation as resolved.
Show resolved Hide resolved
err.help(TURBOFISH);
err.help("or use `(...)` if you meant to specify fn arguments");
// These cases cause too many knock-down errors, bail out (#61329).
Err(err)
};
}
err.emit();
}
_ => {}
}
Ok(())
Ok(None)
}

fn consume_fn_args(&mut self) -> Result<(), ()> {
let snapshot = self.clone();
self.bump(); // `(`

// Consume the fn call arguments.
let modifiers = [
(token::OpenDelim(token::Paren), 1),
(token::CloseDelim(token::Paren), -1),
];
self.consume_tts(1, &modifiers[..]);

if self.token.kind == token::Eof {
// Not entirely sure that what we consumed were fn arguments, rollback.
mem::replace(self, snapshot);
Err(())
} else {
// 99% certain that the suggestion is correct, continue parsing.
Ok(())
}
}

crate fn maybe_report_ambiguous_plus(
Expand Down Expand Up @@ -1348,6 +1468,23 @@ impl<'a> Parser<'a> {
err
}

fn consume_tts(
Centril marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
mut acc: i64, // `i64` because malformed code can have more closing delims than opening.
// Not using `FxHashMap` due to `token::TokenKind: !Eq + !Hash`.
modifier: &[(token::TokenKind, i64)],
) {
while acc > 0 {
if let Some((_, val)) = modifier.iter().find(|(t, _)| *t == self.token.kind) {
acc += *val;
}
if self.token.kind == token::Eof {
break;
}
self.bump();
}
}

/// Replace duplicated recovered parameters with `_` pattern to avoid unecessary errors.
///
/// This is necessary because at this point we don't know whether we parsed a function with
Expand Down
4 changes: 3 additions & 1 deletion src/libsyntax/parse/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ impl<'a> Parser<'a> {

self.bump();
if op.is_comparison() {
self.check_no_chained_comparison(&lhs, &op)?;
if let Some(expr) = self.check_no_chained_comparison(&lhs, &op)? {
return Ok(expr);
}
}
// Special cases:
if op == AssocOp::As {
Expand Down
Loading