Initial infrasturucte for sharing type analysis logic.

This change introduces the mixin TypeAnalyzer, which is intended to be
mixed into analyzer and front end classes to provide shared logic for
type analysis of Dart code.  This work is currently experimental, and
not hooked up to the analyzer or front end, but the eventual hope is
that it can replace the logic that's currently duplicated between the
analyzer's ResolverVisitor and the front end's InferenceVisitorImpl.
A secondary goal of introducing this code is to allow some of the
static consequences of the patterns proposal to be explored now, even
before parser support is finished.

To avoid introducing additional code duplication while the project is
in this experimental phase, I'm going to attempt to restrict myself as
much as possible to prototyping functionality that is not yet
implemented in the analyzer or front end.  This initial CL is an
exception; it introduces the method `analyzeIntLiteral`, which
duplicates logic that already exists in both the front end and
analyzer that analyzes integer literals.  I'm doing this because it's
just complex enough to serve as a validation of the basic approach,
and because integer literals will be handy in writing test cases for
the expanded switch functionality in the patterns proposal, which I
plan to work on next.

Although the code is not used in the analyzer or front end yet, it is
unit tested in isolation, and it's integrated into the existing flow
analysis unit tests.

Change-Id: I07c7cd709eec9e8492669f2dc8db57fb7c10798f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255081
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
index 2ab7b57..116c859 100644
--- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analysis_result.dart
@@ -24,6 +24,15 @@
   Type resolveShorting();
 }
 
+/// Container for the result of running type analysis on an integer literal.
+class IntTypeAnalysisResult<Type extends Object>
+    extends SimpleTypeAnalysisResult<Type> {
+  /// Whether the integer literal was converted to a double.
+  final bool convertedToDouble;
+
+  IntTypeAnalysisResult({required super.type, required this.convertedToDouble});
+}
+
 /// Container for the result of running type analysis on an expression that does
 /// not contain any null shorting.
 class SimpleTypeAnalysisResult<Type extends Object>
diff --git a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
new file mode 100644
index 0000000..2447096
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, 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 '../flow_analysis/flow_analysis.dart';
+import 'type_analysis_result.dart';
+import 'type_operations.dart';
+
+/// Type analysis logic to be shared between the analyzer and front end.  The
+/// intention is that the client's main type inference visitor class can include
+/// this mix-in and call shared analysis logic as needed.
+///
+/// Concrete methods in this mixin, typically named `analyzeX` for some `X`,
+/// are intended to be called by the client in order to analyze an AST node (or
+/// equivalent) of type `X`; a client's `visit` method shouldn't have to do much
+/// than call the corresponding `analyze` method, passing in AST node's children
+/// and other properties, possibly take some client-specific actions with the
+/// returned value (such as storing intermediate inference results), and then
+/// return the returned value up the call stack.
+///
+/// Abstract methods in this mixin are intended to be implemented by the client;
+/// these are called by the `analyzeX` methods to report analysis results, to
+/// query the client-specific information (e.g. to obtain the client's
+/// representation of core types), and to trigger recursive analysis of child
+/// AST nodes.
+mixin TypeAnalyzer<Node extends Object, Statement extends Node,
+    Expression extends Node, Variable extends Object, Type extends Object> {
+  /// Returns the type `double`.
+  Type get doubleType;
+
+  /// Returns the type `dynamic`.
+  Type get dynamicType;
+
+  /// Returns the client's [FlowAnalysis] object.
+  ///
+  /// May be `null`, because the analyzer doesn't have a flow analysis object
+  /// in play when analyzing top level initializers (see
+  /// https://github.com/dart-lang/sdk/issues/49701).
+  FlowAnalysis<Node, Statement, Expression, Variable, Type>? get flow;
+
+  /// Returns the type `int`.
+  Type get intType;
+
+  /// Returns the client's implementation of the [TypeOperations] class.
+  TypeOperations<Type> get typeOperations;
+
+  /// Returns the unknown type context (`?`) used in type inference.
+  Type get unknownType;
+
+  /// Analyzes an integer literal, given the type context [context].
+  IntTypeAnalysisResult<Type> analyzeIntLiteral(Type context) {
+    bool convertToDouble = !typeOperations.isSubtypeOf(intType, context) &&
+        typeOperations.isSubtypeOf(doubleType, context);
+    Type type = convertToDouble ? doubleType : intType;
+    return new IntTypeAnalysisResult<Type>(
+        type: type, convertedToDouble: convertToDouble);
+  }
+
+  /// Calls the appropriate `analyze` method according to the form of
+  /// [expression].
+  ///
+  /// For example, if [node] is a binary expression (`a + b`), calls
+  /// [analyzeBinaryExpression].
+  ExpressionTypeAnalysisResult<Type> dispatchExpression(
+      Expression expression, Type context);
+
+  /// Calls the appropriate `analyze` method according to the form of
+  /// [statement].
+  ///
+  /// For example, if [statement] is a `while` loop, calls [analyzeWhileLoop].
+  void dispatchStatement(Statement statement);
+}
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 47e5c94..2455fe9 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -10,6 +10,7 @@
     show EqualityInfo, FlowAnalysis, Operations;
 import 'package:_fe_analyzer_shared/src/type_inference/assigned_variables.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
+import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
 import 'package:_fe_analyzer_shared/src/type_inference/type_operations.dart';
 import 'package:test/test.dart';
 
@@ -132,6 +133,9 @@
         [List<Statement>? ifFalse]) =>
     new _If(condition, block(ifTrue), ifFalse == null ? null : block(ifFalse));
 
+Expression intLiteral(int value, {bool? expectConversionToDouble}) =>
+    new _IntLiteral(value, expectConversionToDouble: expectConversionToDouble);
+
 Statement labeled(Statement Function(LabeledStatement) callback) {
   var labeledStatement = LabeledStatement._();
   labeledStatement._body = callback(labeledStatement);
@@ -181,6 +185,16 @@
   /// If `this` is an expression `x`, creates the expression `x as typeStr`.
   Expression as_(String typeStr) => new _As(this, Type(typeStr));
 
+  /// Wraps `this` in such a way that, when the test is run, it will verify that
+  /// the context provided when analyzing the expression matches
+  /// [expectedContext].
+  Expression checkContext(String expectedContext) =>
+      _CheckExpressionContext(this, expectedContext);
+
+  /// Wraps `this` in such a way that, when the test is run, it will verify that
+  /// the IR produced matches [expectedIr].
+  Expression checkIr(String expectedIr) => _CheckExpressionIr(this, expectedIr);
+
   /// Creates an [Expression] that, when analyzed, will behave the same as
   /// `this`, but after visiting it, will verify that the type of the expression
   /// was [expectedType].
@@ -198,6 +212,11 @@
   /// If `this` is an expression `x`, creates the expression `x ?? other`.
   Expression ifNull(Expression other) => new _IfNull(this, other);
 
+  /// Creates a [Statement] that, when analyzed, will analyze `this`, supplying
+  /// a context type of [context].
+  Statement inContext(String context) =>
+      _ExpressionInContext(this, Type(context));
+
   /// If `this` is an expression `x`, creates the expression `x is typeStr`.
   ///
   /// With [isInverted] set to `true`, creates the expression `x is! typeStr`.
@@ -240,13 +259,16 @@
   static const Map<String, bool> _coreSubtypes = const {
     'bool <: int': false,
     'bool <: Object': true,
+    'double <: double?': true,
     'double <: Object': true,
     'double <: Object?': true,
     'double <: num': true,
     'double <: num?': true,
     'double <: int': false,
     'double <: int?': false,
+    'double <: String': false,
     'int <: double': false,
+    'int <: double?': false,
     'int <: int?': true,
     'int <: Iterable': false,
     'int <: List': false,
@@ -258,6 +280,7 @@
     'int <: Object': true,
     'int <: Object?': true,
     'int <: String': false,
+    'int <: ?': true,
     'int? <: int': false,
     'int? <: Null': false,
     'int? <: num': false,
@@ -631,6 +654,10 @@
 abstract class Statement extends Node {
   Statement() : super._();
 
+  /// Wraps `this` in such a way that, when the test is run, it will verify that
+  /// the IR produced matches [expectedIr].
+  Statement checkIr(String expectedIr) => _CheckStatementIr(this, expectedIr);
+
   void preVisit(AssignedVariables<Node, Var> assignedVariables);
 
   /// If `this` is a statement `x`, creates a pseudo-expression that models
@@ -873,6 +900,54 @@
   }
 }
 
+class _CheckExpressionContext extends Expression {
+  final Expression inner;
+
+  final String expectedContext;
+
+  _CheckExpressionContext(this.inner, this.expectedContext);
+
+  @override
+  void preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    inner.preVisit(assignedVariables);
+  }
+
+  @override
+  String toString() => '$inner (should be in context $expectedContext)';
+
+  @override
+  ExpressionTypeAnalysisResult<Type> visit(Harness h, Type context) {
+    expect(context.type, expectedContext);
+    var result =
+        h.typeAnalyzer.analyzeParenthesizedExpression(this, inner, context);
+    return result;
+  }
+}
+
+class _CheckExpressionIr extends Expression {
+  final Expression inner;
+
+  final String expectedIr;
+
+  _CheckExpressionIr(this.inner, this.expectedIr);
+
+  @override
+  void preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    inner.preVisit(assignedVariables);
+  }
+
+  @override
+  String toString() => '$inner (should produce IR $expectedIr)';
+
+  @override
+  ExpressionTypeAnalysisResult<Type> visit(Harness h, Type context) {
+    var result =
+        h.typeAnalyzer.analyzeParenthesizedExpression(this, inner, context);
+    h.irBuilder.check(expectedIr);
+    return result;
+  }
+}
+
 class _CheckExpressionType extends Expression {
   final Expression target;
   final String expectedType;
@@ -943,6 +1018,28 @@
   }
 }
 
+class _CheckStatementIr extends Statement {
+  final Statement inner;
+
+  final String expectedIr;
+
+  _CheckStatementIr(this.inner, this.expectedIr);
+
+  @override
+  void preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    inner.preVisit(assignedVariables);
+  }
+
+  @override
+  String toString() => '$inner (should produce IR $expectedIr)';
+
+  @override
+  void visit(Harness h) {
+    h.typeAnalyzer.dispatchStatement(inner);
+    h.irBuilder.check(expectedIr);
+  }
+}
+
 class _CheckUnassigned extends Statement {
   final Var variable;
   final bool expectedUnassignedState;
@@ -1106,6 +1203,27 @@
   }
 }
 
+class _ExpressionInContext extends Statement {
+  final Expression expr;
+
+  final Type context;
+
+  _ExpressionInContext(this.expr, this.context);
+
+  @override
+  void preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    expr.preVisit(assignedVariables);
+  }
+
+  @override
+  String toString() => '$expr (in context $context);';
+
+  @override
+  void visit(Harness h) {
+    h.typeAnalyzer.analyzeExpression(expr, context);
+  }
+}
+
 class _ExpressionStatement extends Statement {
   final Expression expr;
 
@@ -1292,6 +1410,33 @@
   }
 }
 
+class _IntLiteral extends Expression {
+  final int value;
+
+  /// `true` or `false` if we should assert that int->double conversion either
+  /// does, or does not, happen.  `null` if no assertion should be done.
+  final bool? expectConversionToDouble;
+
+  _IntLiteral(this.value, {this.expectConversionToDouble});
+
+  @override
+  void preVisit(AssignedVariables<Node, Var> assignedVariables) {}
+
+  @override
+  String toString() => '$value';
+
+  @override
+  ExpressionTypeAnalysisResult<Type> visit(Harness h, Type context) {
+    var result = h.typeAnalyzer.analyzeIntLiteral(context);
+    if (expectConversionToDouble != null) {
+      expect(result.convertedToDouble, expectConversionToDouble);
+    }
+    h.irBuilder
+        .atom(result.convertedToDouble ? '${value.toDouble()}f' : '$value');
+    return result;
+  }
+}
+
 class _Is extends Expression {
   final Expression target;
   final Type type;
@@ -1381,7 +1526,8 @@
   readWrite,
 }
 
-class _MiniAstTypeAnalyzer {
+class _MiniAstTypeAnalyzer
+    with TypeAnalyzer<Node, Statement, Expression, Var, Type> {
   final Harness _harness;
 
   Statement? _currentBreakTarget;
@@ -1392,21 +1538,33 @@
 
   late final Type boolType = Type('bool');
 
+  @override
+  late final Type doubleType = Type('double');
+
+  @override
   late final Type dynamicType = Type('dynamic');
 
+  @override
+  late final Type intType = Type('int');
+
   late final Type neverType = Type('Never');
 
   late final Type nullType = Type('Null');
 
+  @override
   late final Type unknownType = Type('?');
 
   _MiniAstTypeAnalyzer(this._harness);
 
+  @override
   FlowAnalysis<Node, Statement, Expression, Var, Type> get flow =>
       _harness.flow;
 
   Type get thisType => _harness._thisType!;
 
+  @override
+  TypeOperations<Type> get typeOperations => _harness;
+
   void analyzeAssertStatement(Expression condition, Expression? message) {
     flow.assert_begin();
     analyzeExpression(condition);
@@ -1513,7 +1671,11 @@
   Type analyzeExpression(Expression expression, [Type? context]) {
     // TODO(paulberry): make the [context] argument required.
     context ??= unknownType;
-    return dispatchExpression(expression, context).resolveShorting();
+    var result = dispatchExpression(expression, context);
+    if (flow.operations.isNever(result.provisionalType)) {
+      flow.handleExit();
+    }
+    return result.resolveShorting();
   }
 
   void analyzeExpressionStatement(Expression expression) {
@@ -1710,16 +1872,12 @@
     flow.whileStatement_end();
   }
 
+  @override
   ExpressionTypeAnalysisResult<Type> dispatchExpression(
           Expression expression, Type context) =>
-      _irBuilder.guard(expression, () {
-        var result = expression.visit(_harness, context);
-        if (flow.operations.isNever(result.provisionalType)) {
-          flow.handleExit();
-        }
-        return result;
-      });
+      _irBuilder.guard(expression, () => expression.visit(_harness, context));
 
+  @override
   void dispatchStatement(Statement statement) =>
       _irBuilder.guard(statement, () => statement.visit(_harness));
 
@@ -1754,6 +1912,9 @@
     return _harness.getMember(receiverType, memberName);
   }
 
+  @override
+  String toString() => _irBuilder.toString();
+
   /// Computes the type that should be inferred for an implicitly typed variable
   /// whose initializer expression has static type [type].
   Type variableTypeFromInitializerType(Type type) {
diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart
new file mode 100644
index 0000000..ee058e6
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, 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 'package:test/test.dart';
+
+import '../mini_ast.dart';
+
+main() {
+  late Harness h;
+
+  setUp(() {
+    h = Harness();
+  });
+
+  group('Expressions:', () {
+    group('integer literal', () {
+      test('double context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: true)
+              .checkType('double')
+              .checkIr('1.0f')
+              .inContext('double'),
+        ]);
+      });
+
+      test('int context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: false)
+              .checkType('int')
+              .checkIr('1')
+              .inContext('int'),
+        ]);
+      });
+
+      test('num context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: false)
+              .checkType('int')
+              .checkIr('1')
+              .inContext('num'),
+        ]);
+      });
+
+      test('double? context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: true)
+              .checkType('double')
+              .checkIr('1.0f')
+              .inContext('double?'),
+        ]);
+      });
+
+      test('int? context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: false)
+              .checkType('int')
+              .checkIr('1')
+              .inContext('int?'),
+        ]);
+      });
+
+      test('unknown context', () {
+        h.run([
+          intLiteral(1, expectConversionToDouble: false)
+              .checkType('int')
+              .checkIr('1')
+              .inContext('?'),
+        ]);
+      });
+
+      test('unrelated context', () {
+        // Note: an unrelated context can arise in the case of assigning to a
+        // promoted variable, e.g.:
+        //
+        //   Object x;
+        //   if (x is String) {
+        //     x = 1;
+        //   }
+        h.run([
+          intLiteral(1, expectConversionToDouble: false)
+              .checkType('int')
+              .checkIr('1')
+              .inContext('String'),
+        ]);
+      });
+    });
+  });
+}
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index bbc6fe4..50ead00 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -45,6 +45,7 @@
 altered
 analogous
 analogy
+analyzes
 annotate
 ansi
 answering
@@ -664,6 +665,7 @@
 instantiator
 integrate
 intends
+intention
 intentionally
 interested
 interfere