@@ -17,6 +17,7 @@ use syntax_pos::{Span, DUMMY_SP, MultiSpan, SpanSnippetError};
1717use log:: { debug, trace} ;
1818use std:: mem;
1919
20+ const TURBOFISH : & ' static str = "use `::<...>` instead of `<...>` to specify type arguments" ;
2021/// Creates a placeholder argument.
2122crate fn dummy_arg ( ident : Ident ) -> Param {
2223 let pat = P ( Pat {
@@ -543,35 +544,154 @@ impl<'a> Parser<'a> {
543544 }
544545
545546 /// Produces an error if comparison operators are chained (RFC #558).
546- /// We only need to check the LHS, not the RHS, because all comparison ops
547- /// have same precedence and are left-associative.
548- crate fn check_no_chained_comparison ( & self , lhs : & Expr , outer_op : & AssocOp ) -> PResult < ' a , ( ) > {
549- debug_assert ! ( outer_op. is_comparison( ) ,
550- "check_no_chained_comparison: {:?} is not comparison" ,
551- outer_op) ;
547+ /// We only need to check the LHS, not the RHS, because all comparison ops have same
548+ /// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
549+ ///
550+ /// This can also be hit if someone incorrectly writes `foo<bar>()` when they should have used
551+ /// the turbofish (`foo::<bar>()`) syntax. We attempt some heuristic recovery if that is the
552+ /// case.
553+ ///
554+ /// Keep in mind that given that `outer_op.is_comparison()` holds and comparison ops are left
555+ /// associative we can infer that we have:
556+ ///
557+ /// outer_op
558+ /// / \
559+ /// inner_op r2
560+ /// / \
561+ /// l1 r1
562+ crate fn check_no_chained_comparison (
563+ & mut self ,
564+ lhs : & Expr ,
565+ outer_op : & AssocOp ,
566+ ) -> PResult < ' a , Option < P < Expr > > > {
567+ debug_assert ! (
568+ outer_op. is_comparison( ) ,
569+ "check_no_chained_comparison: {:?} is not comparison" ,
570+ outer_op,
571+ ) ;
572+
573+ let mk_err_expr = |this : & Self , span| {
574+ Ok ( Some ( this. mk_expr ( span, ExprKind :: Err , ThinVec :: new ( ) ) ) )
575+ } ;
576+
552577 match lhs. kind {
553578 ExprKind :: Binary ( op, _, _) if op. node . is_comparison ( ) => {
554579 // Respan to include both operators.
555- let op_span = op. span . to ( self . token . span ) ;
580+ let op_span = op. span . to ( self . prev_span ) ;
556581 let mut err = self . struct_span_err (
557582 op_span,
558583 "chained comparison operators require parentheses" ,
559584 ) ;
585+
586+ let suggest = |err : & mut DiagnosticBuilder < ' _ > | {
587+ err. span_suggestion_verbose (
588+ op_span. shrink_to_lo ( ) ,
589+ TURBOFISH ,
590+ "::" . to_string ( ) ,
591+ Applicability :: MaybeIncorrect ,
592+ ) ;
593+ } ;
594+
560595 if op. node == BinOpKind :: Lt &&
561596 * outer_op == AssocOp :: Less || // Include `<` to provide this recommendation
562597 * outer_op == AssocOp :: Greater // even in a case like the following:
563598 { // Foo<Bar<Baz<Qux, ()>>>
564- err. help (
565- "use `::<...>` instead of `<...>` if you meant to specify type arguments" ) ;
566- err. help ( "or use `(...)` if you meant to specify fn arguments" ) ;
567- // These cases cause too many knock-down errors, bail out (#61329).
568- return Err ( err) ;
599+ if * outer_op == AssocOp :: Less {
600+ let snapshot = self . clone ( ) ;
601+ self . bump ( ) ;
602+ // So far we have parsed `foo<bar<`, consume the rest of the type args.
603+ let modifiers = [
604+ ( token:: Lt , 1 ) ,
605+ ( token:: Gt , -1 ) ,
606+ ( token:: BinOp ( token:: Shr ) , -2 ) ,
607+ ] ;
608+ self . consume_tts ( 1 , & modifiers[ ..] ) ;
609+
610+ if !& [
611+ token:: OpenDelim ( token:: Paren ) ,
612+ token:: ModSep ,
613+ ] . contains ( & self . token . kind ) {
614+ // We don't have `foo< bar >(` or `foo< bar >::`, so we rewind the
615+ // parser and bail out.
616+ mem:: replace ( self , snapshot. clone ( ) ) ;
617+ }
618+ }
619+ return if token:: ModSep == self . token . kind {
620+ // We have some certainty that this was a bad turbofish at this point.
621+ // `foo< bar >::`
622+ suggest ( & mut err) ;
623+
624+ let snapshot = self . clone ( ) ;
625+ self . bump ( ) ; // `::`
626+
627+ // Consume the rest of the likely `foo<bar>::new()` or return at `foo<bar>`.
628+ match self . parse_expr ( ) {
629+ Ok ( _) => {
630+ // 99% certain that the suggestion is correct, continue parsing.
631+ err. emit ( ) ;
632+ // FIXME: actually check that the two expressions in the binop are
633+ // paths and resynthesize new fn call expression instead of using
634+ // `ExprKind::Err` placeholder.
635+ mk_err_expr ( self , lhs. span . to ( self . prev_span ) )
636+ }
637+ Err ( mut expr_err) => {
638+ expr_err. cancel ( ) ;
639+ // Not entirely sure now, but we bubble the error up with the
640+ // suggestion.
641+ mem:: replace ( self , snapshot) ;
642+ Err ( err)
643+ }
644+ }
645+ } else if token:: OpenDelim ( token:: Paren ) == self . token . kind {
646+ // We have high certainty that this was a bad turbofish at this point.
647+ // `foo< bar >(`
648+ suggest ( & mut err) ;
649+ // Consume the fn call arguments.
650+ match self . consume_fn_args ( ) {
651+ Err ( ( ) ) => Err ( err) ,
652+ Ok ( ( ) ) => {
653+ err. emit ( ) ;
654+ // FIXME: actually check that the two expressions in the binop are
655+ // paths and resynthesize new fn call expression instead of using
656+ // `ExprKind::Err` placeholder.
657+ mk_err_expr ( self , lhs. span . to ( self . prev_span ) )
658+ }
659+ }
660+ } else {
661+ // All we know is that this is `foo < bar >` and *nothing* else. Try to
662+ // be helpful, but don't attempt to recover.
663+ err. help ( TURBOFISH ) ;
664+ err. help ( "or use `(...)` if you meant to specify fn arguments" ) ;
665+ // These cases cause too many knock-down errors, bail out (#61329).
666+ Err ( err)
667+ } ;
569668 }
570669 err. emit ( ) ;
571670 }
572671 _ => { }
573672 }
574- Ok ( ( ) )
673+ Ok ( None )
674+ }
675+
676+ fn consume_fn_args ( & mut self ) -> Result < ( ) , ( ) > {
677+ let snapshot = self . clone ( ) ;
678+ self . bump ( ) ; // `(`
679+
680+ // Consume the fn call arguments.
681+ let modifiers = [
682+ ( token:: OpenDelim ( token:: Paren ) , 1 ) ,
683+ ( token:: CloseDelim ( token:: Paren ) , -1 ) ,
684+ ] ;
685+ self . consume_tts ( 1 , & modifiers[ ..] ) ;
686+
687+ if self . token . kind == token:: Eof {
688+ // Not entirely sure that what we consumed were fn arguments, rollback.
689+ mem:: replace ( self , snapshot) ;
690+ Err ( ( ) )
691+ } else {
692+ // 99% certain that the suggestion is correct, continue parsing.
693+ Ok ( ( ) )
694+ }
575695 }
576696
577697 crate fn maybe_report_ambiguous_plus (
@@ -1364,6 +1484,23 @@ impl<'a> Parser<'a> {
13641484 err
13651485 }
13661486
1487+ fn consume_tts (
1488+ & mut self ,
1489+ mut acc : i64 , // `i64` because malformed code can have more closing delims than opening.
1490+ // Not using `FxHashMap` due to `token::TokenKind: !Eq + !Hash`.
1491+ modifier : & [ ( token:: TokenKind , i64 ) ] ,
1492+ ) {
1493+ while acc > 0 {
1494+ if let Some ( ( _, val) ) = modifier. iter ( ) . find ( |( t, _) | * t == self . token . kind ) {
1495+ acc += * val;
1496+ }
1497+ if self . token . kind == token:: Eof {
1498+ break ;
1499+ }
1500+ self . bump ( ) ;
1501+ }
1502+ }
1503+
13671504 /// Replace duplicated recovered parameters with `_` pattern to avoid unecessary errors.
13681505 ///
13691506 /// This is necessary because at this point we don't know whether we parsed a function with
0 commit comments