Version 2.14.0-121.0.dev

Merge commit '8fd81f72281d9d3aa5ef3890c947cc7305c56a50' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index fa51e73..84a8767 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -65,11 +65,6 @@
   text ??= '';
   offsetLengthPairs ??= const [];
 
-  String escape(String input) => input.replaceAllMapped(
-        RegExp(r'[$}\\]'), // Replace any of $ } \
-        (c) => '\\${c[0]}', // Prefix with a backslash
-      );
-
   // Snippets syntax is documented in the LSP spec:
   // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax
   //
@@ -90,18 +85,18 @@
     final pairLength = offsetLengthPairs[i + 1];
 
     // Add any text that came before this tabstop to the result.
-    output.add(escape(text.substring(offset, pairOffset)));
+    output.add(escapeSnippetString(text.substring(offset, pairOffset)));
 
     // Add this tabstop
-    final tabStopText =
-        escape(text.substring(pairOffset, pairOffset + pairLength));
+    final tabStopText = escapeSnippetString(
+        text.substring(pairOffset, pairOffset + pairLength));
     output.add('\${${tabStopNumber++}:$tabStopText}');
 
     offset = pairOffset + pairLength;
   }
 
   // Add any remaining text that was after the last tabstop.
-  output.add(escape(text.substring(offset)));
+  output.add(escapeSnippetString(text.substring(offset)));
 
   return output.join('');
 }
@@ -528,6 +523,15 @@
       .firstWhere(isSupported, orElse: () => lsp.SymbolKind.Obj);
 }
 
+/// Escapes a string to be used in an LSP edit that uses Snippet mode.
+///
+/// Snippets can contain special markup like `${a:b}` so some characters need
+/// escaping (according to the LSP spec, those are `$`, `}` and `\`).
+String escapeSnippetString(String input) => input.replaceAllMapped(
+      RegExp(r'[$}\\]'), // Replace any of $ } \
+      (c) => '\\${c[0]}', // Prefix with a backslash
+    );
+
 String? getCompletionDetail(
   server.CompletionSuggestion suggestion,
   lsp.CompletionItemKind? completionKind,
@@ -1415,7 +1419,7 @@
               defaultArgumentListTextRanges,
             )
           : '\${0:}'; // No required params still gets a tabstop in the parens.
-      insertText += '($functionCallSuffix)';
+      insertText = '${escapeSnippetString(insertText)}($functionCallSuffix)';
     } else if (selectionOffset != 0 &&
         // We don't need a tabstop if the selection is the end of the string.
         selectionOffset != completion.length) {
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index f460280..f682c33 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -181,6 +181,32 @@
         insertText: r'Aaaaa(${0:a})',
       );
 
+  Future<void> test_completeFunctionCalls_escapesDollarArgs() =>
+      checkCompleteFunctionCallInsertText(
+        r'''
+        int myFunction(String a$a, int b, {String c}) {
+          var a = [[myFu^]]
+        }
+        ''',
+        'myFunction(…)',
+        insertTextFormat: InsertTextFormat.Snippet,
+        // The dollar should have been escaped.
+        insertText: r'myFunction(${1:a\$a}, ${2:b})',
+      );
+
+  Future<void> test_completeFunctionCalls_escapesDollarName() =>
+      checkCompleteFunctionCallInsertText(
+        r'''
+        int myFunc$tion(String a, int b, {String c}) {
+          var a = [[myFu^]]
+        }
+        ''',
+        r'myFunc$tion(…)',
+        insertTextFormat: InsertTextFormat.Snippet,
+        // The dollar should have been escaped.
+        insertText: r'myFunc\$tion(${1:a}, ${2:b})',
+      );
+
   Future<void> test_completeFunctionCalls_existingArgList_constructor() =>
       checkCompleteFunctionCallInsertText(
         '''
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 6e3eab5..bd7f1e8 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,6 +1,9 @@
 ## 1.6.0-dev
 * Deprecated `AnalysisDriver` default constructor.  Added `tmp1`. The goal
   is to allow deprecating and removing unused  parameters.
+* Added AST structures and visit methods to support the upcoming "constructor
+  tearoffs" feature: `ConstructorReference`, `FunctionReference`, and
+  `TypeLiteral`.
 
 ## 1.5.0
 * Support for the language version `2.14`.
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index a28b0ba..aafc82f 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -508,6 +508,8 @@
 
   R? visitConstructorName(ConstructorName node);
 
+  R? visitConstructorReference(ConstructorReference node);
+
   R? visitContinueStatement(ContinueStatement node);
 
   R? visitDeclaredIdentifier(DeclaredIdentifier node);
@@ -566,6 +568,8 @@
 
   R? visitFunctionExpressionInvocation(FunctionExpressionInvocation node);
 
+  R? visitFunctionReference(FunctionReference node);
+
   R? visitFunctionTypeAlias(FunctionTypeAlias functionTypeAlias);
 
   R? visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node);
@@ -683,6 +687,8 @@
 
   R? visitTypeArgumentList(TypeArgumentList node);
 
+  R? visitTypeLiteral(TypeLiteral node);
+
   R? visitTypeName(TypeName node);
 
   R? visitTypeParameter(TypeParameter node);
diff --git a/pkg/analyzer/lib/dart/ast/visitor.dart b/pkg/analyzer/lib/dart/ast/visitor.dart
index 1bbc19e..39e66dc 100644
--- a/pkg/analyzer/lib/dart/ast/visitor.dart
+++ b/pkg/analyzer/lib/dart/ast/visitor.dart
@@ -226,6 +226,10 @@
   R? visitConstructorName(ConstructorName node) => visitNode(node);
 
   @override
+  R? visitConstructorReference(ConstructorReference node) =>
+      visitExpression(node);
+
+  @override
   R? visitContinueStatement(ContinueStatement node) => visitStatement(node);
 
   R? visitDeclaration(Declaration node) => visitAnnotatedNode(node);
@@ -341,6 +345,9 @@
       visitInvocationExpression(node);
 
   @override
+  R? visitFunctionReference(FunctionReference node) => visitExpression(node);
+
+  @override
   R? visitFunctionTypeAlias(FunctionTypeAlias node) => visitTypeAlias(node);
 
   @override
@@ -570,6 +577,9 @@
   R? visitTypedLiteral(TypedLiteral node) => visitLiteral(node);
 
   @override
+  R? visitTypeLiteral(TypeLiteral node) => visitExpression(node);
+
+  @override
   R? visitTypeName(TypeName node) => visitNode(node);
 
   @override
@@ -767,6 +777,12 @@
   }
 
   @override
+  R? visitConstructorReference(ConstructorReference node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  @override
   R? visitContinueStatement(ContinueStatement node) {
     node.visitChildren(this);
     return null;
@@ -941,6 +957,12 @@
   }
 
   @override
+  R? visitFunctionReference(FunctionReference node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  @override
   R? visitFunctionTypeAlias(FunctionTypeAlias node) {
     node.visitChildren(this);
     return null;
@@ -1290,6 +1312,12 @@
   }
 
   @override
+  R? visitTypeLiteral(TypeLiteral node) {
+    node.visitChildren(this);
+    return null;
+  }
+
+  @override
   R? visitTypeName(TypeName node) {
     node.visitChildren(this);
     return null;
@@ -1430,6 +1458,9 @@
   R? visitConstructorName(ConstructorName node) => null;
 
   @override
+  R? visitConstructorReference(ConstructorReference node) => null;
+
+  @override
   R? visitContinueStatement(ContinueStatement node) => null;
 
   @override
@@ -1519,6 +1550,9 @@
       null;
 
   @override
+  R? visitFunctionReference(FunctionReference node) => null;
+
+  @override
   R? visitFunctionTypeAlias(FunctionTypeAlias node) => null;
 
   @override
@@ -1696,6 +1730,9 @@
   R? visitTypeArgumentList(TypeArgumentList node) => null;
 
   @override
+  R? visitTypeLiteral(TypeLiteral node) => null;
+
+  @override
   R? visitTypeName(TypeName node) => null;
 
   @override
@@ -1812,6 +1849,9 @@
   R? visitConstructorName(ConstructorName node) => _throw(node);
 
   @override
+  R? visitConstructorReference(ConstructorReference node) => _throw(node);
+
+  @override
   R? visitContinueStatement(ContinueStatement node) => _throw(node);
 
   @override
@@ -1904,6 +1944,9 @@
       _throw(node);
 
   @override
+  R? visitFunctionReference(FunctionReference node) => _throw(node);
+
+  @override
   R? visitFunctionTypeAlias(FunctionTypeAlias node) => _throw(node);
 
   @override
@@ -2084,6 +2127,9 @@
   R? visitTypeArgumentList(TypeArgumentList node) => _throw(node);
 
   @override
+  R? visitTypeLiteral(TypeLiteral node) => _throw(node);
+
+  @override
   R? visitTypeName(TypeName node) => _throw(node);
 
   @override
@@ -2332,6 +2378,14 @@
   }
 
   @override
+  T? visitConstructorReference(ConstructorReference node) {
+    stopwatch.start();
+    T? result = _baseVisitor.visitConstructorReference(node);
+    stopwatch.stop();
+    return result;
+  }
+
+  @override
   T? visitContinueStatement(ContinueStatement node) {
     stopwatch.start();
     T? result = _baseVisitor.visitContinueStatement(node);
@@ -2564,6 +2618,14 @@
   }
 
   @override
+  T? visitFunctionReference(FunctionReference node) {
+    stopwatch.start();
+    T? result = _baseVisitor.visitFunctionReference(node);
+    stopwatch.stop();
+    return result;
+  }
+
+  @override
   T? visitFunctionTypeAlias(FunctionTypeAlias node) {
     stopwatch.start();
     T? result = _baseVisitor.visitFunctionTypeAlias(node);
@@ -3029,6 +3091,14 @@
   }
 
   @override
+  T? visitTypeLiteral(TypeLiteral node) {
+    stopwatch.start();
+    T? result = _baseVisitor.visitTypeLiteral(node);
+    stopwatch.stop();
+    return result;
+  }
+
+  @override
   T? visitTypeName(TypeName node) {
     stopwatch.start();
     T? result = _baseVisitor.visitTypeName(node);
@@ -3194,6 +3264,9 @@
   R? visitConstructorName(ConstructorName node) => visitNode(node);
 
   @override
+  R? visitConstructorReference(ConstructorReference node) => visitNode(node);
+
+  @override
   R? visitContinueStatement(ContinueStatement node) => visitNode(node);
 
   @override
@@ -3290,6 +3363,9 @@
       visitNode(node);
 
   @override
+  R? visitFunctionReference(FunctionReference node) => visitNode(node);
+
+  @override
   R? visitFunctionTypeAlias(FunctionTypeAlias node) => visitNode(node);
 
   @override
@@ -3477,6 +3553,9 @@
   R? visitTypeArgumentList(TypeArgumentList node) => visitNode(node);
 
   @override
+  R? visitTypeLiteral(TypeLiteral node) => visitNode(node);
+
+  @override
   R? visitTypeName(TypeName node) => visitNode(node);
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart
index 060ada1..e02e41b 100644
--- a/pkg/analyzer/lib/src/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/src/dart/ast/ast.dart
@@ -2715,10 +2715,8 @@
   Precedence get precedence => Precedence.postfix;
 
   @override
-  E? accept<E>(AstVisitor<E> visitor) {
-    throw UnimplementedError(
-        'Visitor support for ConstructorReference is not yet implemented');
-  }
+  E? accept<E>(AstVisitor<E> visitor) =>
+      visitor.visitConstructorReference(this);
 
   @override
   void visitChildren(AstVisitor visitor) {
@@ -5064,10 +5062,7 @@
   }
 
   @override
-  E? accept<E>(AstVisitor<E> visitor) {
-    throw UnimplementedError(
-        'Visitor support for FunctionReference is not yet implemented');
-  }
+  E? accept<E>(AstVisitor<E> visitor) => visitor.visitFunctionReference(this);
 
   @override
   void visitChildren(AstVisitor visitor) {
@@ -10106,10 +10101,7 @@
   }
 
   @override
-  E? accept<E>(AstVisitor<E> visitor) {
-    throw UnimplementedError(
-        'Visitor support for TypeLiteral is not yet implemented');
-  }
+  E? accept<E>(AstVisitor<E> visitor) => visitor.visitTypeLiteral(this);
 
   @override
   void visitChildren(AstVisitor visitor) {
diff --git a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
index 0f5f371..a9d231b 100644
--- a/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/ast/to_source_visitor.dart
@@ -351,6 +351,11 @@
   }
 
   @override
+  void visitConstructorReference(ConstructorReference node) {
+    safelyVisitNode(node.constructorName);
+  }
+
+  @override
   void visitContinueStatement(ContinueStatement node) {
     sink.write("continue");
     safelyVisitNodeWithPrefix(" ", node.label);
@@ -614,6 +619,12 @@
   }
 
   @override
+  void visitFunctionReference(FunctionReference node) {
+    safelyVisitNode(node.function);
+    safelyVisitNode(node.typeArguments);
+  }
+
+  @override
   void visitFunctionTypeAlias(FunctionTypeAlias node) {
     safelyVisitNodeListWithSeparatorAndSuffix(node.metadata, " ", " ");
     sink.write("typedef ");
@@ -1084,6 +1095,11 @@
   }
 
   @override
+  void visitTypeLiteral(TypeLiteral node) {
+    safelyVisitNode(node.typeName);
+  }
+
+  @override
   void visitTypeName(TypeName node) {
     safelyVisitNode(node.name);
     safelyVisitNode(node.typeArguments);
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index 8989422..a2a8475 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -339,6 +339,10 @@
           cloneNullableToken(node.period), cloneNullableNode(node.name));
 
   @override
+  AstNode visitConstructorReference(ConstructorReference node) => astFactory
+      .constructorReference(constructorName: cloneNode(node.constructorName));
+
+  @override
   ContinueStatement visitContinueStatement(ContinueStatement node) =>
       astFactory.continueStatement(cloneToken(node.continueKeyword),
           cloneNullableNode(node.label), cloneToken(node.semicolon));
@@ -581,6 +585,12 @@
           cloneNullableNode(node.typeArguments), cloneNode(node.argumentList));
 
   @override
+  AstNode visitFunctionReference(FunctionReference node) =>
+      astFactory.functionReference(
+          function: cloneNode(node.function),
+          typeArguments: cloneNullableNode(node.typeArguments));
+
+  @override
   FunctionTypeAlias visitFunctionTypeAlias(FunctionTypeAlias node) =>
       astFactory.functionTypeAlias(
           cloneNullableNode(node.documentationComment),
@@ -1012,6 +1022,10 @@
           cloneNodeList(node.arguments), cloneToken(node.rightBracket));
 
   @override
+  TypeLiteral visitTypeLiteral(TypeLiteral node) =>
+      astFactory.typeLiteral(typeName: cloneNode(node.typeName));
+
+  @override
   TypeNameImpl visitTypeName(TypeName node) => astFactory.typeName(
       cloneNode(node.name), cloneNullableNode(node.typeArguments),
       question: cloneNullableToken(node.question));
@@ -1450,6 +1464,12 @@
   }
 
   @override
+  bool visitConstructorReference(ConstructorReference node) {
+    ConstructorReference other = _other as ConstructorReference;
+    return isEqualNodes(node.constructorName, other.constructorName);
+  }
+
+  @override
   bool visitContinueStatement(ContinueStatement node) {
     ContinueStatement other = _other as ContinueStatement;
     return isEqualTokens(node.continueKeyword, other.continueKeyword) &&
@@ -1722,6 +1742,13 @@
   }
 
   @override
+  bool visitFunctionReference(FunctionReference node) {
+    FunctionReference other = _other as FunctionReference;
+    return isEqualNodes(node.function, other.function) &&
+        isEqualNodes(node.typeArguments, other.typeArguments);
+  }
+
+  @override
   bool visitFunctionTypeAlias(FunctionTypeAlias node) {
     FunctionTypeAlias other = _other as FunctionTypeAlias;
     return isEqualNodes(
@@ -2239,6 +2266,12 @@
   }
 
   @override
+  bool visitTypeLiteral(TypeLiteral node) {
+    TypeLiteral other = _other as TypeLiteral;
+    return isEqualNodes(node.typeName, other.typeName);
+  }
+
+  @override
   bool visitTypeName(TypeName node) {
     TypeName other = _other as TypeName;
     return isEqualNodes(node.name, other.name) &&
@@ -2961,6 +2994,15 @@
   }
 
   @override
+  bool visitConstructorReference(covariant ConstructorReferenceImpl node) {
+    if (identical(node.constructorName, _oldNode)) {
+      node.constructorName = _newNode as ConstructorNameImpl;
+      return true;
+    }
+    return visitNode(node);
+  }
+
+  @override
   bool visitContinueStatement(covariant ContinueStatementImpl node) {
     if (identical(node.label, _oldNode)) {
       node.label = _newNode as SimpleIdentifier;
@@ -3273,6 +3315,18 @@
   }
 
   @override
+  bool visitFunctionReference(covariant FunctionReferenceImpl node) {
+    if (identical(node.function, _oldNode)) {
+      node.function = _newNode as ExpressionImpl;
+      return true;
+    } else if (identical(node.typeArguments, _oldNode)) {
+      node.typeArguments = _newNode as TypeArgumentListImpl;
+      return true;
+    }
+    return visitNode(node);
+  }
+
+  @override
   bool visitFunctionTypeAlias(covariant FunctionTypeAliasImpl node) {
     if (identical(node.returnType, _oldNode)) {
       node.returnType = _newNode as TypeAnnotation;
@@ -3871,6 +3925,15 @@
   }
 
   @override
+  bool visitTypeLiteral(covariant TypeLiteralImpl node) {
+    if (identical(node.typeName, _oldNode)) {
+      node.typeName = _newNode as TypeNameImpl;
+      return true;
+    }
+    return visitNode(node);
+  }
+
+  @override
   bool visitTypeName(covariant TypeNameImpl node) {
     if (identical(node.name, _oldNode)) {
       node.name = _newNode as Identifier;
diff --git a/pkg/dartdev/lib/src/templates/console_full.dart b/pkg/dartdev/lib/src/templates/console_full.dart
index c610331..f17c05c 100644
--- a/pkg/dartdev/lib/src/templates/console_full.dart
+++ b/pkg/dartdev/lib/src/templates/console_full.dart
@@ -41,7 +41,7 @@
 #   path: ^1.8.0
 
 dev_dependencies:
-  lints: ^1.0.0-0
+  lints: ^1.0.0
   test: ^1.16.0
 ''';
 
diff --git a/pkg/dartdev/lib/src/templates/console_simple.dart b/pkg/dartdev/lib/src/templates/console_simple.dart
index 991e3f6..22f9d9a 100644
--- a/pkg/dartdev/lib/src/templates/console_simple.dart
+++ b/pkg/dartdev/lib/src/templates/console_simple.dart
@@ -39,7 +39,7 @@
 #   path: ^1.8.0
 
 dev_dependencies:
-  lints: ^1.0.0-0
+  lints: ^1.0.0
 ''';
 
 final String _readme = '''
diff --git a/pkg/dartdev/lib/src/templates/package_simple.dart b/pkg/dartdev/lib/src/templates/package_simple.dart
index ca4b820..586c799 100644
--- a/pkg/dartdev/lib/src/templates/package_simple.dart
+++ b/pkg/dartdev/lib/src/templates/package_simple.dart
@@ -55,7 +55,7 @@
 #   path: ^1.8.0
 
 dev_dependencies:
-  lints: ^1.0.0-0
+  lints: ^1.0.0
   test: ^1.16.0
 ''';
 
diff --git a/pkg/dartdev/lib/src/templates/server_shelf.dart b/pkg/dartdev/lib/src/templates/server_shelf.dart
index 391a241..15f5898 100644
--- a/pkg/dartdev/lib/src/templates/server_shelf.dart
+++ b/pkg/dartdev/lib/src/templates/server_shelf.dart
@@ -36,7 +36,7 @@
 # homepage: https://www.example.com
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: '>=2.12.0 <3.0.0'
 
 dependencies:
   args: ^2.0.0
@@ -45,7 +45,7 @@
 
 dev_dependencies:
   http: ^0.13.0
-  lints: ^1.0.0-0
+  lints: ^1.0.0
   test_process: ^2.0.0
   test: ^1.15.0
 ''';
diff --git a/pkg/dartdev/lib/src/templates/web_simple.dart b/pkg/dartdev/lib/src/templates/web_simple.dart
index a42dbb2..836ec57 100644
--- a/pkg/dartdev/lib/src/templates/web_simple.dart
+++ b/pkg/dartdev/lib/src/templates/web_simple.dart
@@ -39,7 +39,7 @@
 dev_dependencies:
   build_runner: ^1.10.0
   build_web_compilers: ^2.11.0
-  lints: ^1.0.0-0
+  lints: ^1.0.0
 ''';
 
 final String _readme = '''
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 2c45796c..635cdd2 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -21,8 +21,6 @@
 // that would replace api used below. This api was made private in
 // an effort to discourage further use.
 // ignore_for_file: implementation_imports
-import 'package:front_end/src/api_prototype/compiler_options.dart'
-    show CompilerOptions, parseExperimentalFlags;
 import 'package:front_end/src/api_unstable/vm.dart';
 import 'package:front_end/widget_cache.dart';
 import 'package:kernel/ast.dart' show Library, Procedure, LibraryDependency;
diff --git a/pkg/frontend_server/lib/src/to_string_transformer.dart b/pkg/frontend_server/lib/src/to_string_transformer.dart
index 9942dd4..c8c2ae2 100644
--- a/pkg/frontend_server/lib/src/to_string_transformer.dart
+++ b/pkg/frontend_server/lib/src/to_string_transformer.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:kernel/ast.dart';
-import 'package:kernel/visitor.dart';
 import '../frontend_server.dart';
 
 // Transformer/visitor for toString
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 88c10b3..583a636 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -884,6 +884,7 @@
     case MethodRecognizer::kFfiAbi:
     case MethodRecognizer::kReachabilityFence:
     case MethodRecognizer::kUtf8DecoderScan:
+    case MethodRecognizer::kHas63BitSmis:
       return true;
     default:
       return false;
@@ -1480,6 +1481,13 @@
       body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
       body += Box(kUnboxedFfiIntPtr);
     } break;
+    case MethodRecognizer::kHas63BitSmis: {
+#if defined(TARGET_ARCH_IS_64_BIT) && !defined(DART_COMPRESSED_POINTERS)
+      body += Constant(Bool::True());
+#else
+      body += Constant(Bool::False());
+#endif  // defined(ARCH_IS_64_BIT)
+    } break;
     default: {
       UNREACHABLE();
       break;
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index 7e54439..228018f 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -203,6 +203,7 @@
   V(Future, wait, FutureWait, 0xc71e731d)                                      \
   V(_RootZone, runUnary, RootZoneRunUnary, 0x966a802c)                         \
   V(_FutureListener, handleValue, FutureListenerHandleValue, 0x165b47c4)       \
+  V(::, has63BitSmis, Has63BitSmis, 0xf61b5ab2)                                \
 
 // List of intrinsics:
 // (class-name, function-name, intrinsification method, fingerprint).
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 2e26b48..f26d236 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1000,6 +1000,10 @@
   // (no implementations or indirect subclasses are allowed).
   const auto& klass = Class::Handle(Z, obj.clazz());
   const auto& super_klass = Class::Handle(Z, klass.SuperClass());
+  if (super_klass.IsNull()) {
+    // This means klass is Object.
+    return false;
+  }
   if (super_klass.Name() != Symbols::Struct().ptr() &&
       super_klass.Name() != Symbols::Union().ptr()) {
     return false;
diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart
index 7ee1e6b..6cc2388 100644
--- a/sdk/lib/_internal/vm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/vm/lib/internal_patch.dart
@@ -102,9 +102,9 @@
   }
 }
 
-final bool has63BitSmis = _has63BitSmis();
-
-bool _has63BitSmis() native "Internal_has63BitSmis";
+@pragma("vm:recognized", "other")
+@pragma('vm:prefer-inline')
+bool get has63BitSmis native "Internal_has63BitSmis";
 
 @pragma("vm:recognized", "other")
 @pragma("vm:entry-point", "call")
diff --git a/sdk/lib/core/double.dart b/sdk/lib/core/double.dart
index 337d5f9..29d36db 100644
--- a/sdk/lib/core/double.dart
+++ b/sdk/lib/core/double.dart
@@ -41,7 +41,7 @@
 
   double abs();
 
-  /// Returns the sign of the double's numerical value.
+  /// The sign of the double's numerical value.
   ///
   /// Returns -1.0 if the value is less than zero,
   /// +1.0 if the value is greater than zero,
diff --git a/sdk/lib/core/int.dart b/sdk/lib/core/int.dart
index a799e46..3e5bb4b 100644
--- a/sdk/lib/core/int.dart
+++ b/sdk/lib/core/int.dart
@@ -227,7 +227,10 @@
 
   /// Returns the absolute value of this integer.
   ///
-  /// For any integer `x`, the result is the same as `x < 0 ? -x : x`.
+  /// For any integer `value`,
+  /// the result is the same as `value < 0 ? -value : value`.
+  ///
+  /// Integer overflow may cause the result of `-value` to stay negative.
   int abs();
 
   /// Returns the sign of this integer.
diff --git a/sdk/lib/core/num.dart b/sdk/lib/core/num.dart
index b560025..21d1348 100644
--- a/sdk/lib/core/num.dart
+++ b/sdk/lib/core/num.dart
@@ -178,60 +178,89 @@
   /// otherwise the result is a [double].
   num remainder(num other);
 
-  /// Whether [other] is numerically smaller than this number.
+  /// Whether this number is numerically smaller than [other].
   ///
-  /// If either operand is the [double] NaN, the result is always false.
+  /// Returns `true` if this number is smaller than [other],
+  /// and returns `false` if this number is greater than or equal to [other]
+  /// or if either value is a NaN value like [double.nan].
   bool operator <(num other);
 
-  /// Whether [other] is numerically smaller than or equal to this number.
+  /// Whether this number is numerically smaller than or equal to [other].
   ///
-  /// If either operand is the [double] NaN, the result is always false.
+  /// Returns `true` if this number is smaller than or equal to [other],
+  /// and returns `false` if this number is greater than [other]
+  /// or if either value is a NaN value like [double.nan].
   bool operator <=(num other);
 
-  /// Whether [other] is numerically greater than this number.
+  /// Whether this number is numerically greater than [other].
   ///
-  /// If either operand is the [double] NaN, the result is always false.
+  /// Returns `true` if this number is greater than [other],
+  /// and returns `false` if this number is smaller than or equal to [other]
+  /// or if either value is a NaN value like [double.nan].
   bool operator >(num other);
 
-  /// Whether [other] is numerically greater than or equal to this number.
+  /// Whether this number is numerically greater than or equal to [other].
   ///
-  /// If either operand is the [double] NaN, the result is always false.
+  /// Returns `true` if this number is greater than or equal to [other],
+  /// and returns `false` if this number is smaller than [other]
+  /// or if either value is a NaN value like [double.nan].
   bool operator >=(num other);
 
-  /// Whether the number is the double Not-a-Number value.
+  /// Whether the number is a Not-a-Number value.
+  ///
+  /// Is `true` if this number is the [double.nan] value
+  /// or any other of the possible [double] NaN values.
+  /// Is `false` if this number is an integer,
+  /// a finite double or an infinite double ([double.infinity]
+  /// or [double.negativeInfinity]).
+  ///
+  /// All numbers satisfy exacly one of of [isInfinite], [isFinite]
+  /// and `isNaN`.
   bool get isNaN;
 
-  /// Whether if the number is negative.
+  /// Whether this number is negative.
   ///
-  /// Negative numbers are those less than zero, and the double `-0.0`.
+  /// A number is negative if it's smaller than zero,
+  /// or if it is the double `-0.0`.
+  /// This precludes a NaN value like [double.nan] from being negative.
   bool get isNegative;
 
   /// Whether the number is positive infinity or negative infinity.
+  ///
+  /// Only satisfied by [double.infinity] and [double.negativeInfinity].
+  ///
+  /// All numbers satisfy exacly one of of `isInfinite`, [isFinite]
+  /// and [isNaN].
   bool get isInfinite;
 
   /// Whether the number is finite.
   ///
-  /// The only non-finite numbers are NaN, positive infinity, and
+  /// The only non-finite numbers are NaN values, positive infinity, and
   /// negative infinity. All integers are finite.
+  ///
+  /// All numbers satisfy exacly one of of [isInfinite], `isFinite`
+  /// and [isNaN].
   bool get isFinite;
 
   /// The absolute value of this number.
   ///
   /// The absolute value is the value itself, if the value is non-negative,
   /// and `-value` if the value is negative.
+  ///
+  /// Integer overflow may cause the result of `-value` to stay negative.
   num abs();
 
   /// Negative one, zero or positive one depending on the sign and
-  /// numerical value of the number.
+  /// numerical value of this number.
   ///
-  /// Returns minus one if the number is less than zero,
-  /// plus one if the number is greater than zero,
-  /// and zero if the number is equal to zero.
+  /// The value minus one if this number is less than zero,
+  /// plus one if this number is greater than zero,
+  /// and zero if this number is equal to zero.
   ///
-  /// Returns NaN if the number is the [double] NaN value.
+  /// Returns NaN if the number is a [double] NaN value.
   ///
   /// Returns a number of the same type as this number.
-  /// For doubles, `-0.0.sign == -0.0`.
+  /// For doubles, `(-0.0).sign` is `-0.0`.
   ///
   /// The result satisfies:
   /// ```dart
diff --git a/tests/language/generic_methods/generic_invocation_all_subexpression_types_test.dart b/tests/language/generic_methods/generic_invocation_all_subexpression_types_test.dart
new file mode 100644
index 0000000..fc34619
--- /dev/null
+++ b/tests/language/generic_methods/generic_invocation_all_subexpression_types_test.dart
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `EXPR<typeArguments>(arguments)` is properly parsed
+// as a generic invocation, for all types of expressions that may appear as
+// EXPR.  We try to pay extra attention to ambiguous expressions (that is, where
+// interpreting the `<` and `>` as operators would also have led to a valid
+// parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object? x = absent, Object? y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(this<C, C>(0)), 'f(this<C, C>(0))');
+    // Note: SyntaxTracker can't see the parens around `this` in the line below
+    checkSyntax(f((this)<C, C>(0)), 'f(this<C, C>(0))');
+  }
+}
+
+main() {
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  checkSyntax(f(f<C, C>(0)), 'f(f<C, C>(0))');
+  checkSyntax(f(x.method<C, C>(0)), 'f(x.method<C, C>(0))');
+  checkSyntax(f(C()<C, C>(0)), 'f(new C()<C, C>(0))');
+  checkSyntax(f(new C()<C, C>(0)), 'f(new C()<C, C>(0))');
+  checkSyntax(f(f()<C, C>(0)), 'f(f()<C, C>(0))');
+  checkSyntax(f(x.method()<C, C>(0)), 'f(x.method()<C, C>(0))');
+  checkSyntax(f(x[0]()<C, C>(0)), 'f(x[0]()<C, C>(0))');
+  checkSyntax(f(#x<C, C>(0)), 'f(#x<C, C>(0))');
+  checkSyntax(f(null<C, C>(0)), 'f(null<C, C>(0))');
+  checkSyntax(f(0<C, C>(0)), 'f(0<C, C>(0))');
+  checkSyntax(f(0.5<C, C>(0)), 'f(0.5<C, C>(0))');
+  checkSyntax(f([]<C, C>(0)), 'f([]<C, C>(0))');
+  checkSyntax(f([0]<C, C>(0)), 'f([0]<C, C>(0))');
+  checkSyntax(f({}<C, C>(0)), 'f({}<C, C>(0))');
+  checkSyntax(f({0}<C, C>(0)), 'f({0}<C, C>(0))');
+  checkSyntax(f({0: 0}<C, C>(0)), 'f({ 0: 0 }<C, C>(0))');
+  checkSyntax(f(true<C, C>(0)), 'f(true<C, C>(0))');
+  checkSyntax(f("s"<C, C>(0)), 'f("s"<C, C>(0))');
+  checkSyntax(f(r"s"<C, C>(0)), 'f("s"<C, C>(0))');
+  checkSyntax(f(x[0]<C, C>(0)), 'f(x[0]<C, C>(0))');
+  // Note: SyntaxTracker can't see the `!` in the line below
+  checkSyntax(f(x!<C, C>(0)), 'f(x<C, C>(0))');
+  // Note: SyntaxTracker can't see the parens around `x` in the line below
+  checkSyntax(f((x)<C, C>(0)), 'f(x<C, C>(0))');
+  ThisTest().test();
+}
diff --git a/tests/language/generic_methods/non_generic_invocation_all_first_expression_types_test.dart b/tests/language/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
new file mode 100644
index 0000000..35fde2d
--- /dev/null
+++ b/tests/language/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `EXPR<a,b>-x` is properly parsed as a pair of
+// expressions separated by a `,`, for all types of expressions that may appear
+// as EXPR.  We try to pay extra attention to expressions that will become
+// ambiguous when the "constructor tearoffs" feature is enabled (that is, where
+// interpreting the `<` and `>` as delimiting a list of type arguments would
+// also have led to a valid parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object? x = absent, Object? y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+
+  Object? operator <(Object? other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(this < C, C > -x), 'f((this < C), (C > (-x)))');
+    // Note: SyntaxTracker can't see the parens around `this` in the line below
+    checkSyntax(f((this) < C, C > -x), 'f((this < C), (C > (-x)))');
+  }
+}
+
+class SuperTest extends C {
+  SuperTest() : super.syntax('super');
+
+  void test() {
+    checkSyntax(f(super < C, C > -x), 'f((super < C), (C > (-x)))');
+  }
+}
+
+main() {
+  const y = 123;
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  SyntaxTracker.known[y] = 'y';
+  checkSyntax(f(x < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(x.getter < C, C > -x), 'f((x.getter < C), (C > (-x)))');
+  checkSyntax(f(C() < C, C > -x), 'f((new C() < C), (C > (-x)))');
+  checkSyntax(f(new C() < C, C > -x), 'f((new C() < C), (C > (-x)))');
+  checkSyntax(f(f() < C, C > -x), 'f((f() < C), (C > (-x)))');
+  checkSyntax(f(x.method() < C, C > -x), 'f((x.method() < C), (C > (-x)))');
+  checkSyntax(f(x[0]() < C, C > -x), 'f((x[0]() < C), (C > (-x)))');
+  checkSyntax(f(#x < C, C > -x), 'f((#x < C), (C > (-x)))');
+  checkSyntax(f(null < C, C > -x), 'f((null < C), (C > (-x)))');
+  checkSyntax(f(0 < y, C > -x), 'f(true, (C > (-x)))');
+  checkSyntax(f(0.5 < y, C > -x), 'f(true, (C > (-x)))');
+  checkSyntax(f([] < C, C > -x), 'f(([] < C), (C > (-x)))');
+  checkSyntax(f([0] < C, C > -x), 'f(([0] < C), (C > (-x)))');
+  checkSyntax(f({} < C, C > -x), 'f(({} < C), (C > (-x)))');
+  checkSyntax(f({0} < C, C > -x), 'f(({0} < C), (C > (-x)))');
+  checkSyntax(f({0: 0} < C, C > -x), 'f(({ 0: 0 } < C), (C > (-x)))');
+  checkSyntax(f(true < C, C > -x), 'f((true < C), (C > (-x)))');
+  checkSyntax(f("s" < C, C > -x), 'f(("s" < C), (C > (-x)))');
+  checkSyntax(f(r"s" < C, C > -x), 'f(("s" < C), (C > (-x)))');
+  checkSyntax(f(x[0] < C, C > -x), 'f((x[0] < C), (C > (-x)))');
+  // Note: SyntaxTracker can't see the `!` in the line below
+  checkSyntax(f(x! < C, C > -x), 'f((x < C), (C > (-x)))');
+  // Note: SyntaxTracker can't see the parens around `x` in the line below
+  checkSyntax(f((x) < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(-x < C, C > -x), 'f(((-x) < C), (C > (-x)))');
+  checkSyntax(f(!true < C, C > -x), 'f((false < C), (C > (-x)))');
+  checkSyntax(f(!(true) < C, C > -x), 'f((false < C), (C > (-x)))');
+  checkSyntax(f(~x < C, C > -x), 'f(((~x) < C), (C > (-x)))');
+  checkSyntax(f(x * x < C, C > -x), 'f(((x * x) < C), (C > (-x)))');
+  checkSyntax(f(x / x < C, C > -x), 'f(((x / x) < C), (C > (-x)))');
+  checkSyntax(f(x ~/ x < C, C > -x), 'f(((x ~/ x) < C), (C > (-x)))');
+  checkSyntax(f(x % x < C, C > -x), 'f(((x % x) < C), (C > (-x)))');
+  checkSyntax(f(x + x < C, C > -x), 'f(((x + x) < C), (C > (-x)))');
+  checkSyntax(f(x - x < C, C > -x), 'f(((x - x) < C), (C > (-x)))');
+  checkSyntax(f(x << x < C, C > -x), 'f(((x << x) < C), (C > (-x)))');
+  checkSyntax(f(x >> x < C, C > -x), 'f(((x >> x) < C), (C > (-x)))');
+  checkSyntax(f(x & x < C, C > -x), 'f(((x & x) < C), (C > (-x)))');
+  checkSyntax(f(x ^ x < C, C > -x), 'f(((x ^ x) < C), (C > (-x)))');
+  checkSyntax(f(x | x < C, C > -x), 'f(((x | x) < C), (C > (-x)))');
+  ThisTest().test();
+  SuperTest().test();
+}
diff --git a/tests/language/generic_methods/non_generic_invocation_all_second_expression_types_test.dart b/tests/language/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
new file mode 100644
index 0000000..3e698e4
--- /dev/null
+++ b/tests/language/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `f<a,b>EXPR` is properly parsed as a pair of
+// expressions separated by a `,`, for all types of expressions that may appear
+// as EXPR.  We try to pay extra attention to expressions that will become
+// ambiguous when the "constructor tearoffs" feature is enabled (that is, where
+// interpreting the `<` and `>` as delimiting a list of type arguments would
+// also have led to a valid parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object? x = absent, Object? y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+
+  Object? operator <(Object? other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(x < C, C > this), 'f((x < C), (C > this))');
+  }
+}
+
+main() {
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  checkSyntax(f(x < C, C > x), 'f((x < C), (C > x))');
+  checkSyntax(f(x < C, C > x.getter), 'f((x < C), (C > x.getter))');
+  checkSyntax(f(x < C, C > C()), 'f((x < C), (C > new C()))');
+  checkSyntax(f(x < C, C > new C()), 'f((x < C), (C > new C()))');
+  checkSyntax(f(x < C, C > f()), 'f((x < C), (C > f()))');
+  checkSyntax(f(x < C, C > x.method()), 'f((x < C), (C > x.method()))');
+  checkSyntax(f(x < C, C > x[0]()), 'f((x < C), (C > x[0]()))');
+  checkSyntax(f(x < C, C > #x), 'f((x < C), (C > #x))');
+  checkSyntax(f(x < C, C > null), 'f((x < C), (C > null))');
+  checkSyntax(f(x < C, C > 0), 'f((x < C), (C > 0))');
+  checkSyntax(f(x < C, C > 0.5), 'f((x < C), (C > 0.5))');
+  checkSyntax(f(x < C, C > []), 'f((x < C), (C > []))');
+  checkSyntax(f(x < C, C > [0]), 'f((x < C), (C > [0]))');
+  checkSyntax(f(x < C, C > {}), 'f((x < C), (C > {}))');
+  checkSyntax(f(x < C, C > {0}), 'f((x < C), (C > {0}))');
+  checkSyntax(f(x < C, C > {0: 0}), 'f((x < C), (C > { 0: 0 }))');
+  checkSyntax(f(x < C, C > true), 'f((x < C), (C > true))');
+  checkSyntax(f(x < C, C > "s"), 'f((x < C), (C > "s"))');
+  checkSyntax(f(x < C, C > r"s"), 'f((x < C), (C > "s"))');
+  checkSyntax(f(x < C, C > x[0]), 'f((x < C), (C > x[0]))');
+  // Note: SyntaxTracker can't see the `!` in the line below
+  checkSyntax(f(x < C, C > x!), 'f((x < C), (C > x))');
+  checkSyntax(f(x < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(x < C, C > !true), 'f((x < C), (C > false))');
+  checkSyntax(f(x < C, C > !(true)), 'f((x < C), (C > false))');
+  checkSyntax(f(x < C, C > ~x), 'f((x < C), (C > (~x)))');
+  checkSyntax(f(x < C, C > x * x), 'f((x < C), (C > (x * x)))');
+  checkSyntax(f(x < C, C > x / x), 'f((x < C), (C > (x / x)))');
+  checkSyntax(f(x < C, C > x ~/ x), 'f((x < C), (C > (x ~/ x)))');
+  checkSyntax(f(x < C, C > x % x), 'f((x < C), (C > (x % x)))');
+  checkSyntax(f(x < C, C > x + x), 'f((x < C), (C > (x + x)))');
+  checkSyntax(f(x < C, C > x - x), 'f((x < C), (C > (x - x)))');
+  checkSyntax(f(x < C, C > x << x), 'f((x < C), (C > (x << x)))');
+  checkSyntax(f(x < C, C > x >> x), 'f((x < C), (C > (x >> x)))');
+  checkSyntax(f(x < C, C > x & x), 'f((x < C), (C > (x & x)))');
+  checkSyntax(f(x < C, C > x ^ x), 'f((x < C), (C > (x ^ x)))');
+  checkSyntax(f(x < C, C > x | x), 'f((x < C), (C > (x | x)))');
+  ThisTest().test();
+}
diff --git a/tests/language/syntax_helper.dart b/tests/language/syntax_helper.dart
new file mode 100644
index 0000000..7cf4147
--- /dev/null
+++ b/tests/language/syntax_helper.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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:expect/expect.dart";
+
+/// Helper class for determining when no argument is passed to a function.
+class Absent {
+  const Absent();
+}
+
+const absent = Absent();
+
+/// Returns an approximate representation of the syntax that was used to
+/// construct [x].  Extra parentheses are used around unary and binary
+/// expressions to disambiguate precedence.
+String syntax(Object? x) {
+  var knownSyntax = SyntaxTracker.known[x];
+  if (knownSyntax != null) return knownSyntax;
+  if (x is SyntaxTracker || x is num) {
+    return x.toString();
+  } else if (x is List) {
+    return '[${x.map(syntax).join(', ')}]';
+  } else if (x is Set) {
+    if (x.isEmpty) return 'Set()';
+    return '{${x.map(syntax).join(', ')}}';
+  } else if (x is Map) {
+    if (x.isEmpty) return '{}';
+    var entries = x.entries
+        .map((entry) => '${syntax(entry.key)}: ${syntax(entry.value)}');
+    return '{ ${entries.join(', ')} }';
+  } else if (x is String) {
+    return json.encode(x);
+  } else {
+    throw UnimplementedError('Unknown syntax for $x.  '
+        'Consider adding to `SyntaxTracker.known`.');
+  }
+}
+
+/// Instances of this class record the syntax of operations performed on them.
+class SyntaxTracker {
+  final String _syntax;
+
+  SyntaxTracker(this._syntax);
+
+  String toString() => _syntax;
+
+  static String args([Object? x = absent, Object? y = absent]) =>
+      '(${[x, y].where((v) => v is! Absent).join(', ')})';
+
+  static String typeArgs(Type T, Type U) =>
+      T.toString() == 'dynamic' ? '' : '<${syntax(T)}, ${syntax(U)}>';
+
+  /// Simple objects with known syntactic representations.  Tests can add to
+  /// this map.
+  static Map<Object?, String> known = {
+    true: 'true',
+    false: 'false',
+    null: 'null'
+  };
+}
+
+/// Extension allowing us to detect the syntax of most operations performed on
+/// arbitrary types.
+extension SyntaxTrackingExtension on Object? {
+  Object? method<T, U>([Object? x = absent, Object? y = absent]) => SyntaxTracker(
+      '${syntax(this)}.method${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+  Object? call<T, U>([Object? x = absent, Object? y = absent]) => SyntaxTracker(
+      '${syntax(this)}${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+  Object? get getter => SyntaxTracker('${syntax(this)}.getter');
+
+  Object? operator [](Object? index) =>
+      SyntaxTracker('${syntax(this)}[${syntax(index)}]');
+
+  Object? operator -() => SyntaxTracker('(-${syntax(this)})');
+
+  Object? operator ~() => SyntaxTracker('(~${syntax(this)})');
+
+  Object? operator *(Object? other) =>
+      SyntaxTracker('(${syntax(this)} * ${syntax(other)})');
+
+  Object? operator /(Object? other) =>
+      SyntaxTracker('(${syntax(this)} / ${syntax(other)})');
+
+  Object? operator ~/(Object? other) =>
+      SyntaxTracker('(${syntax(this)} ~/ ${syntax(other)})');
+
+  Object? operator %(Object? other) =>
+      SyntaxTracker('(${syntax(this)} % ${syntax(other)})');
+
+  Object? operator +(Object? other) =>
+      SyntaxTracker('(${syntax(this)} + ${syntax(other)})');
+
+  Object? operator -(Object? other) =>
+      SyntaxTracker('(${syntax(this)} - ${syntax(other)})');
+
+  Object? operator <<(Object? other) =>
+      SyntaxTracker('(${syntax(this)} << ${syntax(other)})');
+
+  Object? operator >>(Object? other) =>
+      SyntaxTracker('(${syntax(this)} >> ${syntax(other)})');
+
+  Object? operator &(Object? other) =>
+      SyntaxTracker('(${syntax(this)} & ${syntax(other)})');
+
+  Object? operator ^(Object? other) =>
+      SyntaxTracker('(${syntax(this)} ^ ${syntax(other)})');
+
+  Object? operator |(Object? other) =>
+      SyntaxTracker('(${syntax(this)} | ${syntax(other)})');
+
+  Object? operator <(Object? other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+
+  Object? operator >(Object? other) =>
+      SyntaxTracker('(${syntax(this)} > ${syntax(other)})');
+
+  Object? operator <=(Object? other) =>
+      SyntaxTracker('(${syntax(this)} <= ${syntax(other)})');
+
+  Object? operator >=(Object? other) =>
+      SyntaxTracker('(${syntax(this)} >= ${syntax(other)})');
+}
+
+void checkSyntax(Object? x, String expectedSyntax) {
+  Expect.equals(expectedSyntax, syntax(x));
+}
+
+Object? f<T, U>([Object? x = absent, Object? y = absent]) => SyntaxTracker(
+    'f${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+Object? x = SyntaxTracker('x');
diff --git a/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart b/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart
new file mode 100644
index 0000000..c1e64c5
--- /dev/null
+++ b/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `EXPR<typeArguments>(arguments)` is properly parsed
+// as a generic invocation, for all types of expressions that may appear as
+// EXPR.  We try to pay extra attention to ambiguous expressions (that is, where
+// interpreting the `<` and `>` as operators would also have led to a valid
+// parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object x = absent, Object y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(this<C, C>(0)), 'f(this<C, C>(0))');
+    // Note: SyntaxTracker can't see the parens around `this` in the line below
+    checkSyntax(f((this)<C, C>(0)), 'f(this<C, C>(0))');
+  }
+}
+
+main() {
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  checkSyntax(f(f<C, C>(0)), 'f(f<C, C>(0))');
+  checkSyntax(f(x.method<C, C>(0)), 'f(x.method<C, C>(0))');
+  checkSyntax(f(C()<C, C>(0)), 'f(new C()<C, C>(0))');
+  checkSyntax(f(new C()<C, C>(0)), 'f(new C()<C, C>(0))');
+  checkSyntax(f(f()<C, C>(0)), 'f(f()<C, C>(0))');
+  checkSyntax(f(x.method()<C, C>(0)), 'f(x.method()<C, C>(0))');
+  checkSyntax(f(x[0]()<C, C>(0)), 'f(x[0]()<C, C>(0))');
+  checkSyntax(f(#x<C, C>(0)), 'f(#x<C, C>(0))');
+  checkSyntax(f(null<C, C>(0)), 'f(null<C, C>(0))');
+  checkSyntax(f(0<C, C>(0)), 'f(0<C, C>(0))');
+  checkSyntax(f(0.5<C, C>(0)), 'f(0.5<C, C>(0))');
+  checkSyntax(f([]<C, C>(0)), 'f([]<C, C>(0))');
+  checkSyntax(f([0]<C, C>(0)), 'f([0]<C, C>(0))');
+  checkSyntax(f({}<C, C>(0)), 'f({}<C, C>(0))');
+  checkSyntax(f({0}<C, C>(0)), 'f({0}<C, C>(0))');
+  checkSyntax(f({0: 0}<C, C>(0)), 'f({ 0: 0 }<C, C>(0))');
+  checkSyntax(f(true<C, C>(0)), 'f(true<C, C>(0))');
+  checkSyntax(f("s"<C, C>(0)), 'f("s"<C, C>(0))');
+  checkSyntax(f(r"s"<C, C>(0)), 'f("s"<C, C>(0))');
+  checkSyntax(f(x[0]<C, C>(0)), 'f(x[0]<C, C>(0))');
+  // Note: SyntaxTracker can't see the parens around `x` in the line below
+  checkSyntax(f((x)<C, C>(0)), 'f(x<C, C>(0))');
+  ThisTest().test();
+}
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
new file mode 100644
index 0000000..1b56800
--- /dev/null
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `EXPR<a,b>-x` is properly parsed as a pair of
+// expressions separated by a `,`, for all types of expressions that may appear
+// as EXPR.  We try to pay extra attention to expressions that will become
+// ambiguous when the "constructor tearoffs" feature is enabled (that is, where
+// interpreting the `<` and `>` as delimiting a list of type arguments would
+// also have led to a valid parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object x = absent, Object y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+
+  Object operator <(Object other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(this < C, C > -x), 'f((this < C), (C > (-x)))');
+    // Note: SyntaxTracker can't see the parens around `this` in the line below
+    checkSyntax(f((this) < C, C > -x), 'f((this < C), (C > (-x)))');
+  }
+}
+
+class SuperTest extends C {
+  SuperTest() : super.syntax('super');
+
+  void test() {
+    checkSyntax(f(super < C, C > -x), 'f((super < C), (C > (-x)))');
+  }
+}
+
+main() {
+  const y = 123;
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  SyntaxTracker.known[y] = 'y';
+  checkSyntax(f(x < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(x.getter < C, C > -x), 'f((x.getter < C), (C > (-x)))');
+  checkSyntax(f(C() < C, C > -x), 'f((new C() < C), (C > (-x)))');
+  checkSyntax(f(new C() < C, C > -x), 'f((new C() < C), (C > (-x)))');
+  checkSyntax(f(f() < C, C > -x), 'f((f() < C), (C > (-x)))');
+  checkSyntax(f(x.method() < C, C > -x), 'f((x.method() < C), (C > (-x)))');
+  checkSyntax(f(x[0]() < C, C > -x), 'f((x[0]() < C), (C > (-x)))');
+  checkSyntax(f(#x < C, C > -x), 'f((#x < C), (C > (-x)))');
+  checkSyntax(f(null < C, C > -x), 'f((null < C), (C > (-x)))');
+  checkSyntax(f(0 < y, C > -x), 'f(true, (C > (-x)))');
+  checkSyntax(f(0.5 < y, C > -x), 'f(true, (C > (-x)))');
+  checkSyntax(f([] < C, C > -x), 'f(([] < C), (C > (-x)))');
+  checkSyntax(f([0] < C, C > -x), 'f(([0] < C), (C > (-x)))');
+  checkSyntax(f({} < C, C > -x), 'f(({} < C), (C > (-x)))');
+  checkSyntax(f({0} < C, C > -x), 'f(({0} < C), (C > (-x)))');
+  checkSyntax(f({0: 0} < C, C > -x), 'f(({ 0: 0 } < C), (C > (-x)))');
+  checkSyntax(f(true < C, C > -x), 'f((true < C), (C > (-x)))');
+  checkSyntax(f("s" < C, C > -x), 'f(("s" < C), (C > (-x)))');
+  checkSyntax(f(r"s" < C, C > -x), 'f(("s" < C), (C > (-x)))');
+  checkSyntax(f(x[0] < C, C > -x), 'f((x[0] < C), (C > (-x)))');
+  // Note: SyntaxTracker can't see the parens around `x` in the line below
+  checkSyntax(f((x) < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(-x < C, C > -x), 'f(((-x) < C), (C > (-x)))');
+  checkSyntax(f(!true < C, C > -x), 'f((false < C), (C > (-x)))');
+  checkSyntax(f(!(true) < C, C > -x), 'f((false < C), (C > (-x)))');
+  checkSyntax(f(~x < C, C > -x), 'f(((~x) < C), (C > (-x)))');
+  checkSyntax(f(x * x < C, C > -x), 'f(((x * x) < C), (C > (-x)))');
+  checkSyntax(f(x / x < C, C > -x), 'f(((x / x) < C), (C > (-x)))');
+  checkSyntax(f(x ~/ x < C, C > -x), 'f(((x ~/ x) < C), (C > (-x)))');
+  checkSyntax(f(x % x < C, C > -x), 'f(((x % x) < C), (C > (-x)))');
+  checkSyntax(f(x + x < C, C > -x), 'f(((x + x) < C), (C > (-x)))');
+  checkSyntax(f(x - x < C, C > -x), 'f(((x - x) < C), (C > (-x)))');
+  checkSyntax(f(x << x < C, C > -x), 'f(((x << x) < C), (C > (-x)))');
+  checkSyntax(f(x >> x < C, C > -x), 'f(((x >> x) < C), (C > (-x)))');
+  checkSyntax(f(x & x < C, C > -x), 'f(((x & x) < C), (C > (-x)))');
+  checkSyntax(f(x ^ x < C, C > -x), 'f(((x ^ x) < C), (C > (-x)))');
+  checkSyntax(f(x | x < C, C > -x), 'f(((x | x) < C), (C > (-x)))');
+  ThisTest().test();
+  SuperTest().test();
+}
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
new file mode 100644
index 0000000..daead6d
--- /dev/null
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+// This test verifies that `f<a,b>EXPR` is properly parsed as a pair of
+// expressions separated by a `,`, for all types of expressions that may appear
+// as EXPR.  We try to pay extra attention to expressions that will become
+// ambiguous when the "constructor tearoffs" feature is enabled (that is, where
+// interpreting the `<` and `>` as delimiting a list of type arguments would
+// also have led to a valid parse).
+
+import '../syntax_helper.dart';
+
+class C extends SyntaxTracker {
+  C([Object x = absent, Object y = absent])
+      : super('new C${SyntaxTracker.args(x, y)}');
+
+  C.syntax(String s) : super(s);
+
+  Object operator <(Object other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+}
+
+class ThisTest extends C {
+  ThisTest() : super.syntax('this');
+
+  void test() {
+    checkSyntax(f(x < C, C > this), 'f((x < C), (C > this))');
+  }
+}
+
+main() {
+  SyntaxTracker.known[C] = 'C';
+  SyntaxTracker.known[#x] = '#x';
+  checkSyntax(f(x < C, C > x), 'f((x < C), (C > x))');
+  checkSyntax(f(x < C, C > x.getter), 'f((x < C), (C > x.getter))');
+  checkSyntax(f(x < C, C > C()), 'f((x < C), (C > new C()))');
+  checkSyntax(f(x < C, C > new C()), 'f((x < C), (C > new C()))');
+  checkSyntax(f(x < C, C > f()), 'f((x < C), (C > f()))');
+  checkSyntax(f(x < C, C > x.method()), 'f((x < C), (C > x.method()))');
+  checkSyntax(f(x < C, C > x[0]()), 'f((x < C), (C > x[0]()))');
+  checkSyntax(f(x < C, C > #x), 'f((x < C), (C > #x))');
+  checkSyntax(f(x < C, C > null), 'f((x < C), (C > null))');
+  checkSyntax(f(x < C, C > 0), 'f((x < C), (C > 0))');
+  checkSyntax(f(x < C, C > 0.5), 'f((x < C), (C > 0.5))');
+  checkSyntax(f(x < C, C > []), 'f((x < C), (C > []))');
+  checkSyntax(f(x < C, C > [0]), 'f((x < C), (C > [0]))');
+  checkSyntax(f(x < C, C > {}), 'f((x < C), (C > {}))');
+  checkSyntax(f(x < C, C > {0}), 'f((x < C), (C > {0}))');
+  checkSyntax(f(x < C, C > {0: 0}), 'f((x < C), (C > { 0: 0 }))');
+  checkSyntax(f(x < C, C > true), 'f((x < C), (C > true))');
+  checkSyntax(f(x < C, C > "s"), 'f((x < C), (C > "s"))');
+  checkSyntax(f(x < C, C > r"s"), 'f((x < C), (C > "s"))');
+  checkSyntax(f(x < C, C > x[0]), 'f((x < C), (C > x[0]))');
+  checkSyntax(f(x < C, C > -x), 'f((x < C), (C > (-x)))');
+  checkSyntax(f(x < C, C > !true), 'f((x < C), (C > false))');
+  checkSyntax(f(x < C, C > !(true)), 'f((x < C), (C > false))');
+  checkSyntax(f(x < C, C > ~x), 'f((x < C), (C > (~x)))');
+  checkSyntax(f(x < C, C > x * x), 'f((x < C), (C > (x * x)))');
+  checkSyntax(f(x < C, C > x / x), 'f((x < C), (C > (x / x)))');
+  checkSyntax(f(x < C, C > x ~/ x), 'f((x < C), (C > (x ~/ x)))');
+  checkSyntax(f(x < C, C > x % x), 'f((x < C), (C > (x % x)))');
+  checkSyntax(f(x < C, C > x + x), 'f((x < C), (C > (x + x)))');
+  checkSyntax(f(x < C, C > x - x), 'f((x < C), (C > (x - x)))');
+  checkSyntax(f(x < C, C > x << x), 'f((x < C), (C > (x << x)))');
+  checkSyntax(f(x < C, C > x >> x), 'f((x < C), (C > (x >> x)))');
+  checkSyntax(f(x < C, C > x & x), 'f((x < C), (C > (x & x)))');
+  checkSyntax(f(x < C, C > x ^ x), 'f((x < C), (C > (x ^ x)))');
+  checkSyntax(f(x < C, C > x | x), 'f((x < C), (C > (x | x)))');
+  ThisTest().test();
+}
diff --git a/tests/language_2/syntax_helper.dart b/tests/language_2/syntax_helper.dart
new file mode 100644
index 0000000..1c65097
--- /dev/null
+++ b/tests/language_2/syntax_helper.dart
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// 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:expect/expect.dart";
+
+/// Helper class for determining when no argument is passed to a function.
+class Absent {
+  const Absent();
+}
+
+const absent = Absent();
+
+/// Returns an approximate representation of the syntax that was used to
+/// construct [x].  Extra parentheses are used around unary and binary
+/// expressions to disambiguate precedence.
+String syntax(Object x) {
+  var knownSyntax = SyntaxTracker.known[x];
+  if (knownSyntax != null) return knownSyntax;
+  if (x is SyntaxTracker || x is num) {
+    return x.toString();
+  } else if (x is List) {
+    return '[${x.map(syntax).join(', ')}]';
+  } else if (x is Set) {
+    if (x.isEmpty) return 'Set()';
+    return '{${x.map(syntax).join(', ')}}';
+  } else if (x is Map) {
+    if (x.isEmpty) return '{}';
+    var entries = x.entries
+        .map((entry) => '${syntax(entry.key)}: ${syntax(entry.value)}');
+    return '{ ${entries.join(', ')} }';
+  } else if (x is String) {
+    return json.encode(x);
+  } else {
+    throw UnimplementedError('Unknown syntax for $x.  '
+        'Consider adding to `SyntaxTracker.known`.');
+  }
+}
+
+/// Instances of this class record the syntax of operations performed on them.
+class SyntaxTracker {
+  final String _syntax;
+
+  SyntaxTracker(this._syntax);
+
+  String toString() => _syntax;
+
+  static String args([Object x = absent, Object y = absent]) =>
+      '(${[x, y].where((v) => v is! Absent).join(', ')})';
+
+  static String typeArgs(Type T, Type U) =>
+      T.toString() == 'dynamic' ? '' : '<${syntax(T)}, ${syntax(U)}>';
+
+  /// Simple objects with known syntactic representations.  Tests can add to
+  /// this map.
+  static Map<Object, String> known = {
+    true: 'true',
+    false: 'false',
+    null: 'null'
+  };
+}
+
+/// Extension allowing us to detect the syntax of most operations performed on
+/// arbitrary types.
+extension SyntaxTrackingExtension on Object {
+  Object method<T, U>([Object x = absent, Object y = absent]) => SyntaxTracker(
+      '${syntax(this)}.method${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+  Object call<T, U>([Object x = absent, Object y = absent]) => SyntaxTracker(
+      '${syntax(this)}${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+  Object get getter => SyntaxTracker('${syntax(this)}.getter');
+
+  Object operator [](Object index) =>
+      SyntaxTracker('${syntax(this)}[${syntax(index)}]');
+
+  Object operator -() => SyntaxTracker('(-${syntax(this)})');
+
+  Object operator ~() => SyntaxTracker('(~${syntax(this)})');
+
+  Object operator *(Object other) =>
+      SyntaxTracker('(${syntax(this)} * ${syntax(other)})');
+
+  Object operator /(Object other) =>
+      SyntaxTracker('(${syntax(this)} / ${syntax(other)})');
+
+  Object operator ~/(Object other) =>
+      SyntaxTracker('(${syntax(this)} ~/ ${syntax(other)})');
+
+  Object operator %(Object other) =>
+      SyntaxTracker('(${syntax(this)} % ${syntax(other)})');
+
+  Object operator +(Object other) =>
+      SyntaxTracker('(${syntax(this)} + ${syntax(other)})');
+
+  Object operator -(Object other) =>
+      SyntaxTracker('(${syntax(this)} - ${syntax(other)})');
+
+  Object operator <<(Object other) =>
+      SyntaxTracker('(${syntax(this)} << ${syntax(other)})');
+
+  Object operator >>(Object other) =>
+      SyntaxTracker('(${syntax(this)} >> ${syntax(other)})');
+
+  Object operator &(Object other) =>
+      SyntaxTracker('(${syntax(this)} & ${syntax(other)})');
+
+  Object operator ^(Object other) =>
+      SyntaxTracker('(${syntax(this)} ^ ${syntax(other)})');
+
+  Object operator |(Object other) =>
+      SyntaxTracker('(${syntax(this)} | ${syntax(other)})');
+
+  Object operator <(Object other) =>
+      SyntaxTracker('(${syntax(this)} < ${syntax(other)})');
+
+  Object operator >(Object other) =>
+      SyntaxTracker('(${syntax(this)} > ${syntax(other)})');
+
+  Object operator <=(Object other) =>
+      SyntaxTracker('(${syntax(this)} <= ${syntax(other)})');
+
+  Object operator >=(Object other) =>
+      SyntaxTracker('(${syntax(this)} >= ${syntax(other)})');
+}
+
+void checkSyntax(Object x, String expectedSyntax) {
+  Expect.equals(expectedSyntax, syntax(x));
+}
+
+Object f<T, U>([Object x = absent, Object y = absent]) => SyntaxTracker(
+    'f${SyntaxTracker.typeArgs(T, U)}${SyntaxTracker.args(x, y)}');
+
+Object x = SyntaxTracker('x');
diff --git a/tools/VERSION b/tools/VERSION
index c64b96a..317622d 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 120
+PRERELEASE 121
 PRERELEASE_PATCH 0
\ No newline at end of file