Add scanner and parser support for new >>> operator

This adds support for the new >>> operator as detailed in
https://github.com/dart-lang/language/blob/master/accepted/future-releases/constant-update-2018/feature-specification.md#new-operators

Tracking issue: https://github.com/dart-lang/language/issues/60

Enable support by setting AbstractScanner.enableGtGtGt = true

Change-Id: I8e7d2fb341971452d8e05d734a1de612880fef5e
Reviewed-on: https://dart-review.googlesource.com/c/85421
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index 1bcac17..2e121cc 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -48,7 +48,56 @@
 
 @reflectiveTest
 class ClassMemberParserTest_Fasta extends FastaParserTestCase
-    with ClassMemberParserTestMixin {}
+    with ClassMemberParserTestMixin {
+  void test_parseClassMember_operator_gtgtgt() {
+    final sourceText = 'class C { bool operator >>>(other) => false; }';
+
+    // ---------------------------------------------------
+    // TODO(danrubel): Replace this section with a call to parseCompilationUnit
+    // once '>>>' token support is enabled permanently.
+
+    var source = new StringSource(sourceText, 'parser_test_StringSource.dart');
+    GatheringErrorListener errorListener =
+        new GatheringErrorListener(checkRanges: true);
+
+    // Scan tokens
+    StringScanner scanner = new StringScanner(sourceText, includeComments: true)
+      ..enableGtGtGt = true;
+    Token tokens = scanner.tokenize();
+    expect(scanner.hasErrors, isFalse);
+
+    // Run parser
+    ErrorReporter errorReporter = new ErrorReporter(errorListener, source);
+    fasta.Parser parser = new fasta.Parser(null);
+    AstBuilder astBuilder = new AstBuilder(errorReporter, source.uri, true);
+    parser.listener = astBuilder;
+    astBuilder.parser = parser;
+    parser.parseUnit(tokens);
+
+    CompilationUnitImpl unit = astBuilder.pop();
+    expect(unit, isNotNull);
+    unit.localDeclarations = astBuilder.localDeclarations;
+    errorListener.assertNoErrors();
+
+    // ---------------------------------------------------
+
+    ClassDeclaration declaration = unit.declarations[0];
+    ClassMember member = declaration.members[0];
+    expect(member, isNotNull);
+    expect(member, new TypeMatcher<MethodDeclaration>());
+    MethodDeclaration method = member;
+    expect(method.documentationComment, isNull);
+    expect(method.externalKeyword, isNull);
+    expect(method.modifierKeyword, isNull);
+    expect(method.propertyKeyword, isNull);
+    expect(method.returnType, isNotNull);
+    expect(method.name.name, '>>>');
+    expect(method.operatorKeyword, isNotNull);
+    expect(method.typeParameters, isNull);
+    expect(method.parameters, isNotNull);
+    expect(method.body, isNotNull);
+  }
+}
 
 /**
  * Tests of the fasta parser based on [ComplexParserTestMixin].
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart
index edfb5c7..bc96678 100644
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart
@@ -263,7 +263,15 @@
 
   /// Experimental flag for enabling set literal support.
   /// See https://github.com/dart-lang/sdk/issues/35121
-  bool parseSetLiterals = false;
+  bool enableSetLiterals = false;
+
+  /// Obsolete experimental flag for enabling set literal support.
+  /// Use enableSetLiterals instead.
+  /// TODO(danrubel): Remove this once this has been merged into the analyzer
+  /// branch and the references to this have been cleaned up.
+  set parseSetLiterals(bool value) {
+    enableSetLiterals = value;
+  }
 
   /// Represents parser state: what asynchronous syntax is allowed in the
   /// function being currently parsed. In rare situations, this can be set by
@@ -4200,7 +4208,7 @@
   /// This method parses the portion of a set or map literal that starts with
   /// the left curly brace when there are no leading type arguments.
   Token parseLiteralSetOrMapSuffix(final Token start, Token constKeyword) {
-    if (!parseSetLiterals) {
+    if (!enableSetLiterals) {
       // TODO(danrubel): remove this once set literals are permanent
       return parseLiteralMapSuffix(start, constKeyword);
     }
@@ -4323,7 +4331,7 @@
   /// if not. This is a suffix parser because it is assumed that type arguments
   /// have been parsed, or `listener.handleNoTypeArguments` has been executed.
   Token parseLiteralSetSuffix(Token token, Token constKeyword) {
-    if (!parseSetLiterals) {
+    if (!enableSetLiterals) {
       // TODO(danrubel): remove this once set literals are permanent
       return parseLiteralMapSuffix(token, constKeyword);
     }
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info.dart b/pkg/front_end/lib/src/fasta/parser/type_info.dart
index 9e43321..585fedf3 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -26,27 +26,27 @@
   /// Call this function when the token after [token] must be a type (not void).
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type, inserting a synthetic type reference if
-  /// necessary. This may modify the token stream when parsing `>>` in valid
-  /// code or during recovery.
+  /// necessary. This may modify the token stream when parsing `>>` or `>>>`
+  /// in valid code or during recovery.
   Token ensureTypeNotVoid(Token token, Parser parser);
 
   /// Call this function when the token after [token] must be a type or void.
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type, inserting a synthetic type reference if
-  /// necessary. This may modify the token stream when parsing `>>` in valid
-  /// code or during recovery.
+  /// necessary. This may modify the token stream when parsing `>>` or `>>>`
+  /// in valid code or during recovery.
   Token ensureTypeOrVoid(Token token, Parser parser);
 
   /// Call this function to parse an optional type (not void) after [token].
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type. This may modify the token stream
-  /// when parsing `>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` in valid code or during recovery.
   Token parseTypeNotVoid(Token token, Parser parser);
 
   /// Call this function to parse an optional type or void after [token].
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the type. This may modify the token stream
-  /// when parsing `>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` in valid code or during recovery.
   Token parseType(Token token, Parser parser);
 
   /// Call this function with the [token] before the type to obtain
@@ -76,14 +76,14 @@
   /// Call this function to parse optional type arguments after [token].
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the arguments. This may modify the token stream
-  /// when parsing `>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` in valid code or during recovery.
   Token parseArguments(Token token, Parser parser);
 
   /// Call this function to parse optional type parameters
   /// (also known as type variables) after [token].
   /// This function will call the appropriate event methods on the [Parser]'s
   /// listener to handle the parameters. This may modify the token stream
-  /// when parsing `>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` in valid code or during recovery.
   Token parseVariables(Token token, Parser parser);
 
   /// Call this function with the [token] before the type var to obtain
diff --git a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
index 7f959e9..f46d819 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info_impl.dart
@@ -30,6 +30,7 @@
         skipMetadata,
         splitGtEq,
         splitGtFromGtGtEq,
+        splitGtFromGtGtGt,
         splitGtGt,
         syntheticGt;
 
@@ -990,16 +991,17 @@
   }
 }
 
-/// Return `true` if [token] is one of `>`, `>>`, `>=', or `>>=`.
+/// Return `true` if [token] is one of `>`, `>>`, `>=`, `>>>`, or `>>=`.
 bool isCloser(Token token) {
   final value = token.stringValue;
   return identical(value, '>') ||
       identical(value, '>>') ||
       identical(value, '>=') ||
+      identical(value, '>>>') ||
       identical(value, '>>=');
 }
 
-/// If [beforeCloser].next is one of `>`, `>>`, `>=', or `>>=`,
+/// If [beforeCloser].next is one of `>`, `>>`, `>=`, `>>>`, or `>>=`,
 /// then update the token stream and return `true`.
 bool parseCloser(Token beforeCloser) {
   Token unsplit = beforeCloser.next;
@@ -1015,7 +1017,7 @@
 }
 
 /// If [closer] is `>` then return it.
-/// If [closer] is one of `>>`, `>=', or `>>=` then split then token
+/// If [closer] is one of `>>`, `>=`, `>>>`, or `>>=` then split then token
 /// and return the leading `>` without updating the token stream.
 /// If [closer] is none of the above, then return null;
 Token splitCloser(Token closer) {
@@ -1026,6 +1028,8 @@
     return splitGtGt(closer);
   } else if (identical(value, '>=')) {
     return splitGtEq(closer);
+  } else if (identical(value, '>>>')) {
+    return splitGtFromGtGtGt(closer);
   } else if (identical(value, '>>=')) {
     return splitGtFromGtGtEq(closer);
   }
diff --git a/pkg/front_end/lib/src/fasta/parser/util.dart b/pkg/front_end/lib/src/fasta/parser/util.dart
index 50937cb6..e02385f 100644
--- a/pkg/front_end/lib/src/fasta/parser/util.dart
+++ b/pkg/front_end/lib/src/fasta/parser/util.dart
@@ -178,6 +178,18 @@
       ..next = token.next);
 }
 
+/// Split `>>>` into two separate tokens... `>` followed by `>>`.
+/// Call [Token.setNext] to add the token to the stream.
+Token splitGtFromGtGtGt(Token token) {
+  assert(optional('>>>', token));
+  return new SimpleToken(
+      TokenType.GT, token.charOffset, token.precedingComments)
+    ..setNext(new SimpleToken(TokenType.GT_GT, token.charOffset + 1)
+      // Set next rather than calling Token.setNext
+      // so that the previous token is not set.
+      ..next = token.next);
+}
+
 /// Return a synthetic `<` followed by [next].
 /// Call [Token.setNext] to add the token to the stream.
 Token syntheticGt(Token next) {
diff --git a/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart b/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
index 55266e8..6ac7640 100644
--- a/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
+++ b/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
@@ -44,6 +44,11 @@
 
   final bool includeComments;
 
+  /// Experimental flag for enabling parsing of `>>>`.
+  /// See https://github.com/dart-lang/language/issues/61
+  /// and https://github.com/dart-lang/language/issues/60
+  bool enableGtGtGt = false;
+
   /**
    * The string offset for the next token that will be created.
    *
@@ -638,7 +643,7 @@
   }
 
   int tokenizeGreaterThan(int next) {
-    // > >= >> >>=
+    // > >= >> >>= >>>
     next = advance();
     if (identical($EQ, next)) {
       appendPrecedenceToken(TokenType.GT_EQ);
@@ -648,6 +653,9 @@
       if (identical($EQ, next)) {
         appendPrecedenceToken(TokenType.GT_GT_EQ);
         return advance();
+      } else if (enableGtGtGt && identical($GT, next)) {
+        appendPrecedenceToken(TokenType.GT_GT_GT);
+        return advance();
       } else {
         appendGtGt(TokenType.GT_GT);
         return next;
diff --git a/pkg/front_end/test/fasta/parser/type_info_test.dart b/pkg/front_end/test/fasta/parser/type_info_test.dart
index f3f7c2c..975eda8 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -2,11 +2,15 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 import 'package:front_end/src/fasta/messages.dart';
 import 'package:front_end/src/fasta/parser.dart';
 import 'package:front_end/src/fasta/parser/type_info.dart';
 import 'package:front_end/src/fasta/parser/type_info_impl.dart';
-import 'package:front_end/src/fasta/scanner.dart';
+import 'package:front_end/src/fasta/scanner.dart' hide scanString;
+import 'package:front_end/src/fasta/scanner/recover.dart'
+    show defaultRecoveryStrategy;
 import 'package:front_end/src/scanner/token.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -26,6 +30,35 @@
   });
 }
 
+/// TODO(danrubel): Remove this and use scanner.dart scanString
+/// once support for `>>>` is permanently enabled.
+///
+/// Scan/tokenize the given [source].
+/// If [recover] is null, then the [defaultRecoveryStrategy] is used.
+ScannerResult scanString(String source,
+    {bool includeComments: false,
+    bool scanLazyAssignmentOperators: false,
+    Recover recover}) {
+  assert(source != null, 'source must not be null');
+  StringScanner scanner =
+      new StringScanner(source, includeComments: includeComments)
+        ..enableGtGtGt = true;
+  return _tokenizeAndRecover(scanner, recover, source: source);
+}
+
+/// TODO(danrubel): Remove this once support for `>>>` is permanently enabled.
+ScannerResult _tokenizeAndRecover(Scanner scanner, Recover recover,
+    {List<int> bytes, String source}) {
+  Token tokens = scanner.tokenize();
+  if (scanner.hasErrors) {
+    if (bytes == null) bytes = utf8.encode(source);
+    recover ??= defaultRecoveryStrategy;
+    tokens = recover(bytes, tokens, scanner.lineStarts);
+  }
+  return new ScannerResult(
+      tokens, scanner.lineStarts, scanner.hasErrors, scanner.errors);
+}
+
 @reflectiveTest
 class NoTypeInfoTest {
   void test_basic() {
@@ -1250,6 +1283,39 @@
           'handleType S',
           'endTypeArguments 1 < >'
         ]);
+    expectComplexTypeArg('<S<T<U>>>', typeArgumentCount: 1, expectedCalls: [
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier U typeReference',
+      'handleNoTypeArguments >>>',
+      'handleType U',
+      'endTypeArguments 1 < >',
+      'handleType T',
+      'endTypeArguments 1 < >',
+      'handleType S',
+      'endTypeArguments 1 < >'
+    ]);
+    expectComplexTypeArg('<S<T<U,V>>>', typeArgumentCount: 1, expectedCalls: [
+      'beginTypeArguments <',
+      'handleIdentifier S typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier T typeReference',
+      'beginTypeArguments <',
+      'handleIdentifier U typeReference',
+      'handleNoTypeArguments ,',
+      'handleType U',
+      'handleIdentifier V typeReference',
+      'handleNoTypeArguments >>>',
+      'handleType V',
+      'endTypeArguments 2 < >',
+      'handleType T',
+      'endTypeArguments 1 < >',
+      'handleType S',
+      'endTypeArguments 1 < >'
+    ]);
     expectComplexTypeArg('<S<Function()>>',
         typeArgumentCount: 1,
         expectedCalls: [
@@ -1299,6 +1365,26 @@
           'handleType S',
           'endTypeArguments 1 < >'
         ]);
+    expectComplexTypeArg('<S<T<void Function()>>>',
+        typeArgumentCount: 1,
+        expectedCalls: [
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier T typeReference',
+          'beginTypeArguments <',
+          'handleNoTypeVariables (',
+          'beginFunctionType void', // was 'beginFunctionType Function'
+          'handleVoidKeyword void', // was 'handleNoType <'
+          'beginFormalParameters ( MemberKind.GeneralizedFunctionType',
+          'endFormalParameters 0 ( ) MemberKind.GeneralizedFunctionType',
+          'endFunctionType Function',
+          'endTypeArguments 1 < >',
+          'handleType T',
+          'endTypeArguments 1 < >',
+          'handleType S',
+          'endTypeArguments 1 < >'
+        ]);
   }
 
   void test_computeTypeArg_complex_recovery() {
@@ -1751,7 +1837,7 @@
           'handleIdentifier List typeReference',
           'beginTypeArguments <',
           'handleIdentifier T typeReference',
-          'handleNoTypeArguments >',
+          'handleNoTypeArguments >>>',
           'handleType T',
           'endTypeArguments 1 < >',
           'handleType List',
@@ -1760,6 +1846,32 @@
           'endTypeVariable > 0 extends',
           'endTypeVariables < >'
         ]);
+    expectComplexTypeParam('<T extends List<Map<S, T>>>',
+        typeArgumentCount: 1,
+        expectedCalls: [
+          'beginTypeVariables <',
+          'beginMetadataStar T',
+          'endMetadataStar 0',
+          'handleIdentifier T typeVariableDeclaration',
+          'beginTypeVariable T',
+          'handleTypeVariablesDefined > 1',
+          'handleIdentifier List typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier Map typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'handleNoTypeArguments ,',
+          'handleType S',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments >>>',
+          'handleType T',
+          'endTypeArguments 2 < >',
+          'handleType Map',
+          'endTypeArguments 1 < >',
+          'handleType List',
+          'endTypeVariable > 0 extends',
+          'endTypeVariables < >'
+        ]);
   }
 
   void test_computeTypeParam_34850() {