add scanner/parser support for >>>=

Change-Id: I8bbf19e70f35aba600cb768fc3c7537f2aa604e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97602
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/test/generated/parser_fasta_test.dart b/pkg/analyzer/test/generated/parser_fasta_test.dart
index f90130b..14663b1 100644
--- a/pkg/analyzer/test/generated/parser_fasta_test.dart
+++ b/pkg/analyzer/test/generated/parser_fasta_test.dart
@@ -56,42 +56,12 @@
 class ClassMemberParserTest_Fasta extends FastaParserTestCase
     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();
-
-    // ---------------------------------------------------
-
+    CompilationUnitImpl unit = parseCompilationUnit(
+        'class C { bool operator >>>(other) => false; }',
+        enableGtGtGt: true);
     ClassDeclaration declaration = unit.declarations[0];
-    ClassMember member = declaration.members[0];
-    expect(member, isNotNull);
-    expect(member, new TypeMatcher<MethodDeclaration>());
-    MethodDeclaration method = member;
+    MethodDeclaration method = declaration.members[0];
+
     expect(method.documentationComment, isNull);
     expect(method.externalKeyword, isNull);
     expect(method.modifierKeyword, isNull);
@@ -103,6 +73,24 @@
     expect(method.parameters, isNotNull);
     expect(method.body, isNotNull);
   }
+
+  void test_parseClassMember_operator_gtgtgteq() {
+    CompilationUnitImpl unit = parseCompilationUnit(
+        'class C { foo(int value) { x >>>= value; } }',
+        enableGtGtGtEq: true);
+    ClassDeclaration declaration = unit.declarations[0];
+    MethodDeclaration method = declaration.members[0];
+    BlockFunctionBody blockFunctionBody = method.body;
+    NodeList<Statement> statements = blockFunctionBody.block.statements;
+    expect(statements, hasLength(1));
+    ExpressionStatement statement = statements[0];
+    AssignmentExpression assignment = statement.expression;
+    SimpleIdentifier leftHandSide = assignment.leftHandSide;
+    expect(leftHandSide.name, 'x');
+    expect(assignment.operator.lexeme, '>>>=');
+    SimpleIdentifier rightHandSide = assignment.rightHandSide;
+    expect(rightHandSide.name, 'value');
+  }
 }
 
 /**
@@ -1406,12 +1394,16 @@
   CompilationUnit parseCompilationUnit(String content,
       {List<ErrorCode> codes,
       List<ExpectedError> errors,
-      bool enableControlFlowCollections}) {
+      bool enableControlFlowCollections,
+      bool enableGtGtGt,
+      bool enableGtGtGtEq}) {
     GatheringErrorListener listener =
         new GatheringErrorListener(checkRanges: true);
 
     CompilationUnit unit = parseCompilationUnit2(content, listener,
-        enableControlFlowCollections: enableControlFlowCollections);
+        enableControlFlowCollections: enableControlFlowCollections,
+        enableGtGtGt: enableGtGtGt,
+        enableGtGtGtEq: enableGtGtGtEq);
 
     // Assert and return result
     if (codes != null) {
@@ -1427,7 +1419,9 @@
 
   CompilationUnit parseCompilationUnit2(
       String content, GatheringErrorListener listener,
-      {bool enableControlFlowCollections}) {
+      {bool enableControlFlowCollections,
+      bool enableGtGtGt,
+      bool enableGtGtGtEq}) {
     var source = new StringSource(content, 'parser_test_StringSource.dart');
 
     void reportError(
@@ -1437,7 +1431,10 @@
     }
 
     // Scan tokens
-    ScannerResult result = scanString(content, includeComments: true);
+    ScannerResult result = scanString(content,
+        includeComments: true,
+        enableGtGtGt: enableGtGtGt ?? enableGtGtGtEq ?? false,
+        enableGtGtGtEq: enableGtGtGtEq ?? false);
     Token token = result.tokens;
     if (result.hasErrors) {
       // The default recovery strategy used by scanString
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 8214825..cf59846 100644
--- a/pkg/front_end/lib/src/fasta/parser/type_info.dart
+++ b/pkg/front_end/lib/src/fasta/parser/type_info.dart
@@ -34,26 +34,26 @@
   /// 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 `>>` or `>>>`
-  /// in valid code or during recovery.
+  /// 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 `>>` or `>>>`
-  /// in valid code or during recovery.
+  /// 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 `>>` or `>>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` 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 `>>` or `>>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` or `>>>=` in valid code or during recovery.
   Token parseType(Token token, Parser parser);
 
   /// Call this function with the [token] before the type to obtain
@@ -83,14 +83,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 `>>` or `>>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` 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 `>>` or `>>>` in valid code or during recovery.
+  /// when parsing `>>` or `>>>` 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 6d670a1..b6d698e 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
@@ -31,6 +31,7 @@
         splitGtEq,
         splitGtFromGtGtEq,
         splitGtFromGtGtGt,
+        splitGtFromGtGtGtEq,
         splitGtGt,
         syntheticGt;
 
@@ -1163,17 +1164,18 @@
   }
 }
 
-/// 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, '>>=');
+      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;
@@ -1189,8 +1191,8 @@
 }
 
 /// If [closer] is `>` then return it.
-/// If [closer] is one of `>>`, `>=`, `>>>`, or `>>=` then split then token
-/// and return the leading `>` without updating the token stream.
+/// If [closer] is one of `>>`, `>>>`, `>=`, `>>=`,  or `>>>=` then split
+/// the token and return the leading `>` without updating the token stream.
 /// If [closer] is none of the above, then return null;
 Token splitCloser(Token closer) {
   String value = closer.stringValue;
@@ -1204,6 +1206,8 @@
     return splitGtFromGtGtGt(closer);
   } else if (identical(value, '>>=')) {
     return splitGtFromGtGtEq(closer);
+  } else if (identical(value, '>>>=')) {
+    return splitGtFromGtGtGtEq(closer);
   }
   return null;
 }
diff --git a/pkg/front_end/lib/src/fasta/parser/util.dart b/pkg/front_end/lib/src/fasta/parser/util.dart
index e02385f..2e7b441 100644
--- a/pkg/front_end/lib/src/fasta/parser/util.dart
+++ b/pkg/front_end/lib/src/fasta/parser/util.dart
@@ -190,6 +190,18 @@
       ..next = token.next);
 }
 
+/// Split `>>>=` into two separate tokens... `>` followed by `>>=`.
+/// Call [Token.setNext] to add the token to the stream.
+Token splitGtFromGtGtGtEq(Token token) {
+  assert(optional('>>>=', token));
+  return new SimpleToken(
+      TokenType.GT, token.charOffset, token.precedingComments)
+    ..setNext(new SimpleToken(TokenType.GT_GT_EQ, 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.dart b/pkg/front_end/lib/src/fasta/scanner.dart
index bcd395a..8f8e495 100644
--- a/pkg/front_end/lib/src/fasta/scanner.dart
+++ b/pkg/front_end/lib/src/fasta/scanner.dart
@@ -71,6 +71,7 @@
 /// If [recover] is null, then the [defaultRecoveryStrategy] is used.
 ScannerResult scanString(String source,
     {bool enableGtGtGt: false,
+    bool enableGtGtGtEq: false,
     bool includeComments: false,
     bool scanLazyAssignmentOperators: false,
     Recover recover}) {
@@ -80,6 +81,7 @@
   StringScanner scanner =
       new StringScanner(source, includeComments: includeComments);
   scanner.enableGtGtGt = enableGtGtGt;
+  scanner.enableGtGtGtEq = enableGtGtGtEq;
   return _tokenizeAndRecover(scanner, recover, source: source);
 }
 
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 bbda63f..ddbf88a 100644
--- a/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
+++ b/pkg/front_end/lib/src/fasta/scanner/abstract_scanner.dart
@@ -43,11 +43,16 @@
 
   final bool includeComments;
 
-  /// Experimental flag for enabling parsing of `>>>`.
+  /// Experimental flag for enabling scanning of `>>>`.
   /// See https://github.com/dart-lang/language/issues/61
   /// and https://github.com/dart-lang/language/issues/60
   bool enableGtGtGt = false;
 
+  /// Experimental flag for enabling scanning of `>>>=`.
+  /// See https://github.com/dart-lang/language/issues/61
+  /// and https://github.com/dart-lang/language/issues/60
+  bool enableGtGtGtEq = false;
+
   /**
    * The string offset for the next token that will be created.
    *
@@ -642,7 +647,7 @@
   }
 
   int tokenizeGreaterThan(int next) {
-    // > >= >> >>= >>>
+    // > >= >> >>= >>> >>>=
     next = advance();
     if (identical($EQ, next)) {
       appendPrecedenceToken(TokenType.GT_EQ);
@@ -653,8 +658,13 @@
         appendPrecedenceToken(TokenType.GT_GT_EQ);
         return advance();
       } else if (enableGtGtGt && identical($GT, next)) {
+        next = advance();
+        if (enableGtGtGtEq && identical($EQ, next)) {
+          appendPrecedenceToken(TokenType.GT_GT_GT_EQ);
+          return advance();
+        }
         appendPrecedenceToken(TokenType.GT_GT_GT);
-        return advance();
+        return next;
       } else {
         appendGtGt(TokenType.GT_GT);
         return next;
diff --git a/pkg/front_end/lib/src/fasta/scanner/token_constants.dart b/pkg/front_end/lib/src/fasta/scanner/token_constants.dart
index f29765c..fff742e 100644
--- a/pkg/front_end/lib/src/fasta/scanner/token_constants.dart
+++ b/pkg/front_end/lib/src/fasta/scanner/token_constants.dart
@@ -88,3 +88,4 @@
 const int GENERIC_METHOD_TYPE_LIST_TOKEN = GENERIC_METHOD_TYPE_ASSIGN_TOKEN + 1;
 const int GT_GT_GT_TOKEN = GENERIC_METHOD_TYPE_LIST_TOKEN + 1;
 const int PERIOD_PERIOD_PERIOD_QUESTION_TOKEN = GT_GT_GT_TOKEN + 1;
+const int GT_GT_GT_EQ_TOKEN = PERIOD_PERIOD_PERIOD_QUESTION_TOKEN + 1;
diff --git a/pkg/front_end/lib/src/scanner/token.dart b/pkg/front_end/lib/src/scanner/token.dart
index 5a78cd2..62718b0 100644
--- a/pkg/front_end/lib/src/scanner/token.dart
+++ b/pkg/front_end/lib/src/scanner/token.dart
@@ -1279,6 +1279,10 @@
       '>>>', 'GT_GT_GT', SHIFT_PRECEDENCE, GT_GT_GT_TOKEN,
       isOperator: true, isUserDefinableOperator: true);
 
+  static const TokenType GT_GT_GT_EQ = const TokenType(
+      '>>>=', 'GT_GT_GT_EQ', ASSIGNMENT_PRECEDENCE, GT_GT_GT_EQ_TOKEN,
+      isOperator: true);
+
   static const TokenType HASH =
       const TokenType('#', 'HASH', NO_PRECEDENCE, HASH_TOKEN);
 
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 531df46..5c37a69 100644
--- a/pkg/front_end/test/fasta/parser/type_info_test.dart
+++ b/pkg/front_end/test/fasta/parser/type_info_test.dart
@@ -45,7 +45,8 @@
   assert(source != null, 'source must not be null');
   StringScanner scanner =
       new StringScanner(source, includeComments: includeComments)
-        ..enableGtGtGt = true;
+        ..enableGtGtGt = true
+        ..enableGtGtGtEq = true;
   return _tokenizeAndRecover(scanner, recover, source: source);
 }
 
@@ -1806,6 +1807,45 @@
       'handleType S null',
       'endTypeArguments 1 < >'
     ]);
+    expectComplexTypeArg('<S<T<U>>>=',
+        typeArgumentCount: 1,
+        expectedAfter: '=',
+        expectedCalls: [
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier T typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier U typeReference',
+          'handleNoTypeArguments >>>=',
+          'handleType U null',
+          'endTypeArguments 1 < >',
+          'handleType T null',
+          'endTypeArguments 1 < >',
+          'handleType S null',
+          'endTypeArguments 1 < >'
+        ]);
+    expectComplexTypeArg('<S<T<U,V>>>=',
+        typeArgumentCount: 1,
+        expectedAfter: '=',
+        expectedCalls: [
+          'beginTypeArguments <',
+          'handleIdentifier S typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier T typeReference',
+          'beginTypeArguments <',
+          'handleIdentifier U typeReference',
+          'handleNoTypeArguments ,',
+          'handleType U null',
+          'handleIdentifier V typeReference',
+          'handleNoTypeArguments >>>=',
+          'handleType V null',
+          'endTypeArguments 2 < >',
+          'handleType T null',
+          'endTypeArguments 1 < >',
+          'handleType S null',
+          'endTypeArguments 1 < >'
+        ]);
     expectComplexTypeArg('<S<Function()>>',
         typeArgumentCount: 1,
         expectedCalls: [
@@ -1875,6 +1915,27 @@
           'handleType S null',
           'endTypeArguments 1 < >'
         ]);
+    expectComplexTypeArg('<S<T<void Function()>>>=',
+        typeArgumentCount: 1,
+        expectedAfter: '=',
+        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 null',
+          'endTypeArguments 1 < >',
+          'handleType T null',
+          'endTypeArguments 1 < >',
+          'handleType S null',
+          'endTypeArguments 1 < >'
+        ]);
   }
 
   void test_computeTypeArg_complex_recovery() {
@@ -2362,6 +2423,33 @@
           'endTypeVariable > 0 extends',
           'endTypeVariables < >'
         ]);
+    expectComplexTypeParam('<T extends List<Map<S, T>>>=',
+        typeArgumentCount: 1,
+        expectedAfter: '=',
+        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 null',
+          'handleIdentifier T typeReference',
+          'handleNoTypeArguments >>>=',
+          'handleType T null',
+          'endTypeArguments 2 < >',
+          'handleType Map null',
+          'endTypeArguments 1 < >',
+          'handleType List null',
+          'endTypeVariable >= 0 extends',
+          'endTypeVariables < >'
+        ]);
   }
 
   void test_computeTypeParam_34850() {