Prototype integration of shared type analysis logic with CFE.
This change replaces the InferenceVisitorImpl.visitSwitchStatement with a call to the new shared type analyzer, and implements the necessary callbacks to allow analysis to work end-to-end. Change-Id: I73a9514428b4ce092762d3ea450545986d0e1ea9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/257491 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.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:_fe_analyzer_shared/src/util/link.dart';
|
||||
import 'package:front_end/src/api_prototype/lowering_predicates.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
@@ -42,6 +45,7 @@ import 'inference_helper.dart';
|
||||
import 'inference_results.dart';
|
||||
import 'inference_visitor_base.dart';
|
||||
import 'object_access_target.dart';
|
||||
import 'shared_type_analyzer.dart';
|
||||
import 'type_constraint_gatherer.dart';
|
||||
import 'type_inference_engine.dart';
|
||||
import 'type_inferrer.dart' show TypeInferrerImpl;
|
||||
@@ -74,19 +78,62 @@ abstract class InferenceVisitor {
|
||||
}
|
||||
|
||||
class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
with
|
||||
TypeAnalyzer<Node, Statement, Expression, VariableDeclaration, DartType>
|
||||
implements
|
||||
ExpressionVisitor1<ExpressionInferenceResult, DartType>,
|
||||
StatementVisitor<StatementInferenceResult>,
|
||||
InitializerVisitor<InitializerInferenceResult>,
|
||||
InferenceVisitor {
|
||||
/// Debug-only: if `true`, manipulations of [_rewriteStack] performed by
|
||||
/// [popRewrite] and [pushRewrite] will be printed.
|
||||
static const bool _debugRewriteStack = false;
|
||||
|
||||
Class? mapEntryClass;
|
||||
|
||||
@override
|
||||
final TypeOperations2<DartType> typeOperations;
|
||||
|
||||
/// Context information for the current closure, or `null` if we are not
|
||||
/// inside a closure.
|
||||
ClosureContext? _closureContext;
|
||||
|
||||
InferenceVisitorImpl(TypeInferrerImpl inferrer, InferenceHelper? helper)
|
||||
: super(inferrer, helper);
|
||||
/// If a switch statement is being visited and the type being switched on is a
|
||||
/// (possibly nullable) enumerated type, the set of enum values for which no
|
||||
/// case head has been seen yet; otherwise `null`.
|
||||
///
|
||||
/// Enum values are represented by the [Field] object they are desugared into.
|
||||
/// If the type being switched on is nullable, then this set also includes a
|
||||
/// value of `null` if no case head has been seen yet that handles `null`.
|
||||
Set<Field?>? _enumFields;
|
||||
|
||||
/// Stack for obtaining rewritten expressions and statements. After
|
||||
/// [dispatchExpression] or [dispatchStatement] visits a node for type
|
||||
/// inference, the visited node (which may have been changed by the inference
|
||||
/// process) is pushed onto this stack. Later, during the processing of the
|
||||
/// enclosing node, the visited node is popped off the stack again, and the
|
||||
/// enclosing node is updated to point to the new, rewritten node.
|
||||
///
|
||||
/// The stack sometimes contains `null`s. These account for situations where
|
||||
/// it's necessary to push a value onto the stack to balance a later pop, but
|
||||
/// there is no suitable expression or statement to push.
|
||||
final List<Node?> _rewriteStack = [];
|
||||
|
||||
@override
|
||||
final TypeAnalyzerOptions options;
|
||||
|
||||
@override
|
||||
late final SharedTypeAnalyzerErrors? errors = isTopLevel
|
||||
? null
|
||||
: new SharedTypeAnalyzerErrors(
|
||||
helper: helper, uriForInstrumentation: uriForInstrumentation);
|
||||
|
||||
InferenceVisitorImpl(
|
||||
TypeInferrerImpl inferrer, InferenceHelper? helper, this.typeOperations)
|
||||
: options = new TypeAnalyzerOptions(
|
||||
nullSafetyEnabled: inferrer.libraryBuilder.isNonNullableByDefault,
|
||||
patternsEnabled: false),
|
||||
super(inferrer, helper);
|
||||
|
||||
ClosureContext get closureContext => _closureContext!;
|
||||
|
||||
@@ -6580,111 +6627,32 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
|
||||
@override
|
||||
StatementInferenceResult visitSwitchStatement(SwitchStatement node) {
|
||||
ExpressionInferenceResult expressionResult = inferExpression(
|
||||
node.expression, const UnknownType(), true,
|
||||
isVoidAllowed: false);
|
||||
node.expression = expressionResult.expression..parent = node;
|
||||
DartType expressionType = expressionResult.inferredType;
|
||||
|
||||
Set<Field?>? enumFields;
|
||||
if (expressionType is InterfaceType && expressionType.classNode.isEnum) {
|
||||
enumFields = <Field?>{
|
||||
...expressionType.classNode.fields
|
||||
.where((Field field) => field.isEnumElement)
|
||||
};
|
||||
if (expressionType.isPotentiallyNullable) {
|
||||
enumFields.add(null);
|
||||
}
|
||||
// Stack: ()
|
||||
Set<Field?>? previousEnumFields = _enumFields;
|
||||
Expression expression = node.expression;
|
||||
SwitchStatementTypeAnalysisResult<DartType> analysisResult =
|
||||
analyzeSwitchStatement(node, expression, node.cases.length);
|
||||
// Note that a switch statement with a `default` clause is always considered
|
||||
// exhaustive, but the kernel format also keeps track of whether the switch
|
||||
// statement is "explicitly exhaustive", meaning that it has a `case` clause
|
||||
// for every possible enum value. So if there's a `default` clause we need
|
||||
// to call `isSwitchExhaustive` to figure out whether the switch is
|
||||
// *explicitly* exhaustive.
|
||||
node.isExplicitlyExhaustive = analysisResult.hasDefault
|
||||
? isSwitchExhaustive(node, analysisResult.scrutineeType)
|
||||
: analysisResult.isExhaustive;
|
||||
_enumFields = previousEnumFields;
|
||||
// Stack: (Expression)
|
||||
Node? rewrite = popRewrite();
|
||||
if (!identical(expression, rewrite)) {
|
||||
expression = rewrite as Expression;
|
||||
node.expression = expression..parent = node;
|
||||
}
|
||||
|
||||
flowAnalysis.switchStatement_expressionEnd(node);
|
||||
|
||||
bool hasDefault = false;
|
||||
bool lastCaseTerminates = true;
|
||||
for (int caseIndex = 0; caseIndex < node.cases.length; ++caseIndex) {
|
||||
SwitchCaseImpl switchCase = node.cases[caseIndex] as SwitchCaseImpl;
|
||||
hasDefault = hasDefault || switchCase.isDefault;
|
||||
flowAnalysis.switchStatement_beginCase();
|
||||
flowAnalysis.switchStatement_beginAlternatives();
|
||||
flowAnalysis.switchStatement_endAlternative();
|
||||
flowAnalysis.switchStatement_endAlternatives(node,
|
||||
hasLabels: switchCase.hasLabel);
|
||||
for (int index = 0; index < switchCase.expressions.length; index++) {
|
||||
ExpressionInferenceResult caseExpressionResult = inferExpression(
|
||||
switchCase.expressions[index], expressionType, true,
|
||||
isVoidAllowed: false);
|
||||
Expression caseExpression = caseExpressionResult.expression;
|
||||
switchCase.expressions[index] = caseExpression..parent = switchCase;
|
||||
DartType caseExpressionType = caseExpressionResult.inferredType;
|
||||
if (enumFields != null) {
|
||||
if (caseExpression is StaticGet) {
|
||||
enumFields.remove(caseExpression.target);
|
||||
} else if (caseExpression is NullLiteral) {
|
||||
enumFields.remove(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isTopLevel) {
|
||||
if (libraryBuilder.isNonNullableByDefault) {
|
||||
if (!typeSchemaEnvironment.isSubtypeOf(caseExpressionType,
|
||||
expressionType, SubtypeCheckMode.withNullabilities)) {
|
||||
helper.addProblem(
|
||||
templateSwitchExpressionNotSubtype.withArguments(
|
||||
caseExpressionType,
|
||||
expressionType,
|
||||
isNonNullableByDefault),
|
||||
caseExpression.fileOffset,
|
||||
noLength,
|
||||
context: [
|
||||
messageSwitchExpressionNotAssignableCause.withLocation(
|
||||
uriForInstrumentation,
|
||||
node.expression.fileOffset,
|
||||
noLength)
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Check whether the expression type is assignable to the case
|
||||
// expression type.
|
||||
if (!isAssignable(expressionType, caseExpressionType)) {
|
||||
helper.addProblem(
|
||||
templateSwitchExpressionNotAssignable.withArguments(
|
||||
expressionType,
|
||||
caseExpressionType,
|
||||
isNonNullableByDefault),
|
||||
caseExpression.fileOffset,
|
||||
noLength,
|
||||
context: [
|
||||
messageSwitchExpressionNotAssignableCause.withLocation(
|
||||
uriForInstrumentation,
|
||||
node.expression.fileOffset,
|
||||
noLength)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementInferenceResult bodyResult = inferStatement(switchCase.body);
|
||||
if (bodyResult.hasChanged) {
|
||||
switchCase.body = bodyResult.statement..parent = switchCase;
|
||||
}
|
||||
|
||||
if (isNonNullableByDefault) {
|
||||
lastCaseTerminates = !flowAnalysis.isReachable;
|
||||
if (!isTopLevel) {
|
||||
// The last case block is allowed to complete normally.
|
||||
if (caseIndex < node.cases.length - 1 && flowAnalysis.isReachable) {
|
||||
libraryBuilder.addProblem(messageSwitchCaseFallThrough,
|
||||
switchCase.fileOffset, noLength, helper.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.isExplicitlyExhaustive = enumFields != null && enumFields.isEmpty;
|
||||
bool isExhaustive = node.isExplicitlyExhaustive || hasDefault;
|
||||
flowAnalysis.switchStatement_end(isExhaustive);
|
||||
Statement? replacement;
|
||||
if (isExhaustive && !hasDefault && shouldThrowUnsoundnessException) {
|
||||
if (!lastCaseTerminates) {
|
||||
if (analysisResult.isExhaustive &&
|
||||
!analysisResult.hasDefault &&
|
||||
shouldThrowUnsoundnessException) {
|
||||
if (!analysisResult.lastCaseTerminates) {
|
||||
LabeledStatement breakTarget;
|
||||
if (node.parent is LabeledStatement) {
|
||||
breakTarget = node.parent as LabeledStatement;
|
||||
@@ -7569,6 +7537,219 @@ class InferenceVisitorImpl extends InferenceVisitorBase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pops the top entry off of [_rewriteStack].
|
||||
Node? popRewrite() {
|
||||
Node? expression = _rewriteStack.removeLast();
|
||||
if (_debugRewriteStack) {
|
||||
assert(_debugPrint('POP ${expression.runtimeType} $expression'));
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// Pushes an entry onto [_rewriteStack].
|
||||
void pushRewrite(Node? node) {
|
||||
if (_debugRewriteStack) {
|
||||
assert(_debugPrint('PUSH ${node.runtimeType} $node'));
|
||||
}
|
||||
_rewriteStack.add(node);
|
||||
}
|
||||
|
||||
/// Helper function used to print information to the console in debug mode.
|
||||
/// This method returns `true` so that it can be conveniently called inside of
|
||||
/// an `assert` statement.
|
||||
bool _debugPrint(String s) {
|
||||
print(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
DartType get boolType => throw new UnimplementedError('TODO(paulberry)');
|
||||
|
||||
@override
|
||||
ExpressionTypeAnalysisResult<DartType> dispatchExpression(
|
||||
Expression node, DartType context) {
|
||||
ExpressionInferenceResult expressionResult =
|
||||
inferExpression(node, context, true).stopShorting();
|
||||
pushRewrite(expressionResult.expression);
|
||||
return new SimpleTypeAnalysisResult(type: expressionResult.inferredType);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispatchPattern(
|
||||
DartType matchedType,
|
||||
Map<VariableDeclaration, VariableTypeInfo<Node, DartType>> typeInfos,
|
||||
MatchContext<Node, Expression> context,
|
||||
Node node) {
|
||||
// The front end's representation of a switch cases currently doesn't have
|
||||
// any support for patterns; each case is represented as an expression. So
|
||||
// analyze it as a constant pattern.
|
||||
return analyzeConstantPattern(
|
||||
matchedType, typeInfos, context, node, node as Expression);
|
||||
}
|
||||
|
||||
@override
|
||||
DartType dispatchPatternSchema(Node node) {
|
||||
// The front end's representation of a switch cases currently doesn't have
|
||||
// any support for patterns; each case is represented as an expression. So
|
||||
// analyze it as a constant pattern.
|
||||
return analyzeConstantPatternSchema();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispatchStatement(Statement statement) {
|
||||
StatementInferenceResult result = inferStatement(statement);
|
||||
pushRewrite(result.hasChanged ? result.statement : statement);
|
||||
}
|
||||
|
||||
@override
|
||||
DartType get doubleType => throw new UnimplementedError('TODO(paulberry)');
|
||||
|
||||
@override
|
||||
DartType get dynamicType => throw new UnimplementedError('TODO(paulberry)');
|
||||
|
||||
@override
|
||||
void finishExpressionCase(Expression node, int caseIndex) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void handleMergedStatementCase(covariant SwitchStatement node,
|
||||
{required int caseIndex,
|
||||
required int executionPathIndex,
|
||||
required int numStatements}) {
|
||||
assert(numStatements == 1 || numStatements == 0);
|
||||
if (numStatements == 1) {
|
||||
// Stack: (Statement)
|
||||
SwitchCase case_ = node.cases[executionPathIndex];
|
||||
Statement body = case_.body;
|
||||
Node? rewrite = popRewrite();
|
||||
// Stack: ()
|
||||
if (!identical(body, rewrite)) {
|
||||
body = rewrite as Statement;
|
||||
case_.body = body..parent = case_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FlowAnalysis<Node, Statement, Expression, VariableDeclaration, DartType>?
|
||||
get flow => flowAnalysis;
|
||||
|
||||
@override
|
||||
SwitchExpressionMemberInfo<Node, Expression> getSwitchExpressionMemberInfo(
|
||||
Expression node, int index) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
SwitchStatementMemberInfo<Node, Statement, Expression>
|
||||
getSwitchStatementMemberInfo(
|
||||
covariant SwitchStatement node, int caseIndex) {
|
||||
SwitchCaseImpl case_ = node.cases[caseIndex] as SwitchCaseImpl;
|
||||
return new SwitchStatementMemberInfo([
|
||||
for (Expression expression in case_.expressions)
|
||||
new CaseHeadOrDefaultInfo(pattern: expression),
|
||||
if (case_.isDefault) new CaseHeadOrDefaultInfo(pattern: null)
|
||||
], [
|
||||
case_.body
|
||||
], labels: case_.hasLabel ? [case_] : const []);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleCaseHead(covariant SwitchStatement node,
|
||||
{required int caseIndex, required int subIndex}) {
|
||||
// Stack: (Pattern, Expression)
|
||||
popRewrite(); // "when" expression
|
||||
// Stack: (Pattern)
|
||||
SwitchCaseImpl case_ = node.cases[caseIndex] as SwitchCaseImpl;
|
||||
Expression expression = case_.expressions[subIndex];
|
||||
Node? rewrite = popRewrite();
|
||||
// Stack: ()
|
||||
if (!identical(expression, rewrite)) {
|
||||
expression = rewrite as Expression;
|
||||
case_.expressions[subIndex] = expression..parent = case_;
|
||||
}
|
||||
Set<Field?>? enumFields = _enumFields;
|
||||
if (enumFields != null) {
|
||||
if (expression is StaticGet) {
|
||||
enumFields.remove(expression.target);
|
||||
} else if (expression is NullLiteral) {
|
||||
enumFields.remove(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void handleCase_afterCaseHeads(Statement node, int caseIndex, int numHeads) {}
|
||||
|
||||
@override
|
||||
void handleDefault(Node node, int caseIndex) {}
|
||||
|
||||
@override
|
||||
void handleNoStatement(Statement node) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void handleNoGuard(Node node, int caseIndex) {
|
||||
// Stack: ()
|
||||
pushRewrite(null);
|
||||
// Stack: (Expression)
|
||||
}
|
||||
|
||||
@override
|
||||
void handleSwitchScrutinee(DartType type) {
|
||||
if (type is InterfaceType && type.classNode.isEnum) {
|
||||
_enumFields = <Field?>{
|
||||
...type.classNode.fields.where((Field field) => field.isEnumElement),
|
||||
if (type.isPotentiallyNullable) null
|
||||
};
|
||||
} else {
|
||||
_enumFields = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
DartType get intType => throw new UnimplementedError('TODO(paulberry)');
|
||||
|
||||
@override
|
||||
bool isSwitchExhaustive(Node node, DartType expressionType) {
|
||||
Set<Field?>? enumFields = _enumFields;
|
||||
return enumFields != null && enumFields.isEmpty;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isVariablePattern(Node node) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
DartType listType(DartType elementType) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
DartType get objectQuestionType =>
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
|
||||
@override
|
||||
void setVariableType(VariableDeclaration variable, DartType type) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
DartType get unknownType => const UnknownType();
|
||||
|
||||
@override
|
||||
DartType variableTypeFromInitializerType(DartType type) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void checkCleanState() {
|
||||
assert(_rewriteStack.isEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
/// Offset and type information collection in [InferenceVisitor.inferMapEntry].
|
||||
|
||||
@@ -179,6 +179,10 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
|
||||
FlowAnalysis<TreeNode, Statement, Expression, VariableDeclaration, DartType>
|
||||
get flowAnalysis => _inferrer.flowAnalysis;
|
||||
|
||||
/// Provides access to the [OperationsCfe] object. This is needed by
|
||||
/// [isAssignable].
|
||||
OperationsCfe get operations => _inferrer.operations;
|
||||
|
||||
TypeSchemaEnvironment get typeSchemaEnvironment =>
|
||||
_inferrer.typeSchemaEnvironment;
|
||||
|
||||
@@ -386,18 +390,8 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
|
||||
typeContext.classNode == coreTypes.doubleClass;
|
||||
}
|
||||
|
||||
bool isAssignable(DartType contextType, DartType expressionType) {
|
||||
if (isNonNullableByDefault) {
|
||||
if (expressionType is DynamicType) return true;
|
||||
return typeSchemaEnvironment
|
||||
.performNullabilityAwareSubtypeCheck(expressionType, contextType)
|
||||
.isSubtypeWhenUsingNullabilities();
|
||||
}
|
||||
return typeSchemaEnvironment
|
||||
.performNullabilityAwareSubtypeCheck(expressionType, contextType)
|
||||
.orSubtypeCheckFor(contextType, expressionType, typeSchemaEnvironment)
|
||||
.isSubtypeWhenIgnoringNullabilities();
|
||||
}
|
||||
bool isAssignable(DartType contextType, DartType expressionType) =>
|
||||
operations.isAssignableTo(expressionType, contextType);
|
||||
|
||||
/// Ensures that [expressionType] is assignable to [contextType].
|
||||
///
|
||||
@@ -4098,6 +4092,11 @@ abstract class InferenceVisitorBase implements InferenceVisitor {
|
||||
charOffset,
|
||||
length);
|
||||
}
|
||||
|
||||
/// The client of type inference should call this method after asking
|
||||
/// inference to visit a node. This performs assertions to make sure that
|
||||
/// temporary type inference state has been properly cleaned up.
|
||||
void checkCleanState();
|
||||
}
|
||||
|
||||
/// Describes assignability kind of one type to another.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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.md file.
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
|
||||
import '../fasta_codes.dart';
|
||||
import 'inference_helper.dart';
|
||||
|
||||
/// Implementation of [TypeAnalyzerErrors] that reports errors using the
|
||||
/// front end's [InferenceHelper] class.
|
||||
class SharedTypeAnalyzerErrors
|
||||
implements
|
||||
TypeAnalyzerErrors<Node, Statement, Expression, VariableDeclaration,
|
||||
DartType> {
|
||||
final InferenceHelper helper;
|
||||
|
||||
final Uri uriForInstrumentation;
|
||||
|
||||
SharedTypeAnalyzerErrors(
|
||||
{required this.helper, required this.uriForInstrumentation});
|
||||
|
||||
@override
|
||||
void assertInErrorRecovery() {
|
||||
// TODO(paulberry): figure out how to do this.
|
||||
}
|
||||
|
||||
@override
|
||||
void caseExpressionTypeMismatch(
|
||||
{required Expression scrutinee,
|
||||
required Expression caseExpression,
|
||||
required caseExpressionType,
|
||||
required scrutineeType,
|
||||
required bool nullSafetyEnabled}) {
|
||||
helper.addProblem(
|
||||
nullSafetyEnabled
|
||||
? templateSwitchExpressionNotSubtype.withArguments(
|
||||
caseExpressionType, scrutineeType, nullSafetyEnabled)
|
||||
: templateSwitchExpressionNotAssignable.withArguments(
|
||||
scrutineeType, caseExpressionType, nullSafetyEnabled),
|
||||
caseExpression.fileOffset,
|
||||
noLength,
|
||||
context: [
|
||||
messageSwitchExpressionNotAssignableCause.withLocation(
|
||||
uriForInstrumentation, scrutinee.fileOffset, noLength)
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
void inconsistentMatchVar(
|
||||
{required Node pattern,
|
||||
required DartType type,
|
||||
required Node previousPattern,
|
||||
required DartType previousType}) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void inconsistentMatchVarExplicitness(
|
||||
{required Node pattern, required Node previousPattern}) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void nonBooleanCondition(Expression node) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void patternDoesNotAllowLate(Node pattern) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void patternTypeMismatchInIrrefutableContext(
|
||||
{required Node pattern,
|
||||
required Node context,
|
||||
required DartType matchedType,
|
||||
required DartType requiredType}) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void refutablePatternInIrrefutableContext(Node pattern, Node context) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
void switchCaseCompletesNormally(
|
||||
covariant SwitchStatement node, int caseIndex, int numMergedCases) {
|
||||
helper.addProblem(messageSwitchCaseFallThrough,
|
||||
node.cases[caseIndex].fileOffset, noLength);
|
||||
}
|
||||
}
|
||||
@@ -300,11 +300,13 @@ class FlowAnalysisResult {
|
||||
|
||||
/// CFE-specific implementation of [TypeOperations].
|
||||
class OperationsCfe
|
||||
with TypeOperations<DartType>
|
||||
with TypeOperations<DartType>, TypeOperations2<DartType>
|
||||
implements Operations<VariableDeclaration, DartType> {
|
||||
final TypeEnvironment typeEnvironment;
|
||||
|
||||
OperationsCfe(this.typeEnvironment);
|
||||
final bool isNonNullableByDefault;
|
||||
|
||||
OperationsCfe(this.typeEnvironment, {required this.isNonNullableByDefault});
|
||||
|
||||
@override
|
||||
TypeClassification classifyType(DartType? type) {
|
||||
@@ -382,6 +384,44 @@ class OperationsCfe
|
||||
}
|
||||
return from;
|
||||
}
|
||||
|
||||
@override
|
||||
DartType glb(DartType type1, DartType type2) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(DartType fromType, DartType toType) {
|
||||
if (isNonNullableByDefault) {
|
||||
if (fromType is DynamicType) return true;
|
||||
return typeEnvironment
|
||||
.performNullabilityAwareSubtypeCheck(fromType, toType)
|
||||
.isSubtypeWhenUsingNullabilities();
|
||||
} else {
|
||||
return typeEnvironment
|
||||
.performNullabilityAwareSubtypeCheck(fromType, toType)
|
||||
.orSubtypeCheckFor(toType, fromType, typeEnvironment)
|
||||
.isSubtypeWhenIgnoringNullabilities();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isDynamic(DartType type) => type is DynamicType;
|
||||
|
||||
@override
|
||||
DartType lub(DartType type1, DartType type2) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
DartType makeNullable(DartType type) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
|
||||
@override
|
||||
DartType? matchListType(DartType type) {
|
||||
throw new UnimplementedError('TODO(paulberry)');
|
||||
}
|
||||
}
|
||||
|
||||
/// Type inference results used for testing.
|
||||
|
||||
@@ -94,16 +94,16 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
|
||||
final TypeInferenceEngine engine;
|
||||
|
||||
final OperationsCfe operations;
|
||||
|
||||
@override
|
||||
late final FlowAnalysis<TreeNode, Statement, Expression, VariableDeclaration,
|
||||
DartType> flowAnalysis = libraryBuilder
|
||||
.isNonNullableByDefault
|
||||
? new FlowAnalysis(
|
||||
new OperationsCfe(engine.typeSchemaEnvironment), assignedVariables,
|
||||
respectImplicitlyTypedVarInitializers:
|
||||
libraryBuilder.libraryFeatures.constructorTearoffs.isEnabled)
|
||||
: new FlowAnalysis.legacy(
|
||||
new OperationsCfe(engine.typeSchemaEnvironment), assignedVariables);
|
||||
DartType> flowAnalysis =
|
||||
libraryBuilder.isNonNullableByDefault
|
||||
? new FlowAnalysis(operations, assignedVariables,
|
||||
respectImplicitlyTypedVarInitializers:
|
||||
libraryBuilder.libraryFeatures.constructorTearoffs.isEnabled)
|
||||
: new FlowAnalysis.legacy(operations, assignedVariables);
|
||||
|
||||
@override
|
||||
final AssignedVariables<TreeNode, VariableDeclaration> assignedVariables;
|
||||
@@ -142,12 +142,14 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
const [], const DynamicType(), libraryBuilder.nonNullable),
|
||||
classHierarchy = engine.classHierarchy,
|
||||
instrumentation = isTopLevel ? null : engine.instrumentation,
|
||||
typeSchemaEnvironment = engine.typeSchemaEnvironment {}
|
||||
typeSchemaEnvironment = engine.typeSchemaEnvironment,
|
||||
operations = new OperationsCfe(engine.typeSchemaEnvironment,
|
||||
isNonNullableByDefault: libraryBuilder.isNonNullableByDefault);
|
||||
|
||||
InferenceVisitorBase _createInferenceVisitor(InferenceHelper? helper) {
|
||||
// For full (non-top level) inference, we need access to the
|
||||
// InferenceHelper so that we can perform error reporting.
|
||||
return new InferenceVisitorImpl(this, helper);
|
||||
return new InferenceVisitorImpl(this, helper, operations);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -157,7 +159,9 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
ExpressionInferenceResult result = visitor.inferExpression(
|
||||
initializer, const UnknownType(), true,
|
||||
isVoidAllowed: true);
|
||||
return visitor.inferDeclarationType(result.inferredType);
|
||||
DartType type = visitor.inferDeclarationType(result.inferredType);
|
||||
visitor.checkCleanState();
|
||||
return type;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -170,6 +174,7 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
initializerResult = visitor.ensureAssignableResult(
|
||||
declaredType, initializerResult,
|
||||
isVoidAllowed: declaredType is VoidType);
|
||||
visitor.checkCleanState();
|
||||
return initializerResult;
|
||||
}
|
||||
|
||||
@@ -191,6 +196,7 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
}
|
||||
result =
|
||||
closureContext.handleImplicitReturn(visitor, body, result, fileOffset);
|
||||
visitor.checkCleanState();
|
||||
DartType? futureValueType = closureContext.futureValueType;
|
||||
assert(!(asyncMarker == AsyncMarker.Async && futureValueType == null),
|
||||
"No future value type computed.");
|
||||
@@ -231,6 +237,7 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
InvocationInferenceResult result = visitor.inferInvocation(
|
||||
visitor, typeContext, fileOffset, targetType, targetInvocationArguments,
|
||||
staticTarget: target);
|
||||
visitor.checkCleanState();
|
||||
DartType resultType = result.inferredType;
|
||||
if (resultType is InterfaceType) {
|
||||
return resultType.typeArguments;
|
||||
@@ -247,8 +254,10 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
// TODO(paulberry): experiment to see if dynamic dispatch would be better,
|
||||
// so that the type hierarchy will be simpler (which may speed up "is"
|
||||
// checks).
|
||||
InferenceVisitor visitor = _createInferenceVisitor(helper);
|
||||
return visitor.inferInitializer(initializer);
|
||||
InferenceVisitorBase visitor = _createInferenceVisitor(helper);
|
||||
InitializerInferenceResult result = visitor.inferInitializer(initializer);
|
||||
visitor.checkCleanState();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -260,6 +269,7 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
// because inference on metadata requires the helper.
|
||||
InferenceVisitorBase visitor = _createInferenceVisitor(helper);
|
||||
visitor.inferMetadata(visitor, parent, annotations);
|
||||
visitor.checkCleanState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +288,7 @@ class TypeInferrerImpl implements TypeInferrer {
|
||||
initializer =
|
||||
visitor.ensureAssignableResult(declaredType, result).expression;
|
||||
}
|
||||
visitor.checkCleanState();
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ artifacts
|
||||
artificial
|
||||
asgerf
|
||||
askesc
|
||||
asking
|
||||
aspect
|
||||
aspx
|
||||
asserting
|
||||
@@ -101,6 +102,7 @@ backping
|
||||
backstop
|
||||
badly
|
||||
bail
|
||||
balance
|
||||
bang
|
||||
bar
|
||||
basically
|
||||
@@ -274,6 +276,7 @@ containers
|
||||
continuations
|
||||
contra
|
||||
contribute
|
||||
conveniently
|
||||
convention
|
||||
conversely
|
||||
coordinated
|
||||
@@ -437,6 +440,7 @@ emitting
|
||||
en
|
||||
encapsulation
|
||||
end'ed
|
||||
end's
|
||||
enforce
|
||||
enforced
|
||||
enforces
|
||||
@@ -634,6 +638,7 @@ ideal
|
||||
identifies
|
||||
identifying
|
||||
ideographic
|
||||
idle
|
||||
idn
|
||||
ids
|
||||
idx
|
||||
@@ -826,6 +831,7 @@ malbounded
|
||||
manage
|
||||
mangled
|
||||
manipulation
|
||||
manipulations
|
||||
manner
|
||||
markdown
|
||||
masking
|
||||
@@ -918,6 +924,7 @@ obligated
|
||||
observatory
|
||||
observe
|
||||
obstruct
|
||||
obtaining
|
||||
occasionally
|
||||
occupied
|
||||
offending
|
||||
@@ -1402,6 +1409,7 @@ svg
|
||||
sw
|
||||
swapped
|
||||
sweep
|
||||
switched
|
||||
symbolic
|
||||
synchronously
|
||||
syncs
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
class C<X, Y> {}
|
||||
|
||||
void main() {
|
||||
switch (1) {
|
||||
C<int, int> case 1: break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class C<X, Y> {}
|
||||
|
||||
void main() {}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class C<X, Y> {}
|
||||
|
||||
void main() {}
|
||||
@@ -0,0 +1,22 @@
|
||||
library /*isNonNullableByDefault*/;
|
||||
//
|
||||
// Problems in library:
|
||||
//
|
||||
// pkg/front_end/testcases/general/switch_error_recovery.dart:9:5: Error: Expected to find 'case'.
|
||||
// C<int, int> case 1: break;
|
||||
// ^
|
||||
//
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class C<X extends core::Object? = dynamic, Y extends core::Object? = dynamic> extends core::Object {
|
||||
synthetic constructor •() → self::C<self::C::X%, self::C::Y%>
|
||||
: super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
switch(1) {
|
||||
#L1:
|
||||
{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
library /*isNonNullableByDefault*/;
|
||||
//
|
||||
// Problems in library:
|
||||
//
|
||||
// pkg/front_end/testcases/general/switch_error_recovery.dart:9:5: Error: Expected to find 'case'.
|
||||
// C<int, int> case 1: break;
|
||||
// ^
|
||||
//
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class C<X extends core::Object? = dynamic, Y extends core::Object? = dynamic> extends core::Object {
|
||||
synthetic constructor •() → self::C<self::C::X%, self::C::Y%>
|
||||
: super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
switch(1) {
|
||||
#L1:
|
||||
{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
library /*isNonNullableByDefault*/;
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class C<X extends core::Object? = dynamic, Y extends core::Object? = dynamic> extends core::Object {
|
||||
synthetic constructor •() → self::C<self::C::X%, self::C::Y%>
|
||||
;
|
||||
}
|
||||
static method main() → void
|
||||
;
|
||||
@@ -0,0 +1,22 @@
|
||||
library /*isNonNullableByDefault*/;
|
||||
//
|
||||
// Problems in library:
|
||||
//
|
||||
// pkg/front_end/testcases/general/switch_error_recovery.dart:9:5: Error: Expected to find 'case'.
|
||||
// C<int, int> case 1: break;
|
||||
// ^
|
||||
//
|
||||
import self as self;
|
||||
import "dart:core" as core;
|
||||
|
||||
class C<X extends core::Object? = dynamic, Y extends core::Object? = dynamic> extends core::Object {
|
||||
synthetic constructor •() → self::C<self::C::X%, self::C::Y%>
|
||||
: super core::Object::•()
|
||||
;
|
||||
}
|
||||
static method main() → void {
|
||||
switch(1) {
|
||||
#L1:
|
||||
{}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user