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>
diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart index acdc16e..a348e48 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
@@ -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_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 @@ } 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 @@ @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 @@ } } } + + /// 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].
diff --git a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart index eab3dd4..af056bf 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/inference_visitor_base.dart
@@ -179,6 +179,10 @@ 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 @@ 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 @@ 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.
diff --git a/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart b/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart new file mode 100644 index 0000000..e6ebd79 --- /dev/null +++ b/pkg/front_end/lib/src/fasta/type_inference/shared_type_analyzer.dart
@@ -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); + } +}
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart index 266c506..380608f 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart
@@ -300,11 +300,13 @@ /// 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 @@ } 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.
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart index 798d641..1110cc9 100644 --- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart +++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -94,16 +94,16 @@ 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 @@ 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 @@ 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 @@ initializerResult = visitor.ensureAssignableResult( declaredType, initializerResult, isVoidAllowed: declaredType is VoidType); + visitor.checkCleanState(); return initializerResult; } @@ -191,6 +196,7 @@ } 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 @@ 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 @@ // 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 @@ // because inference on metadata requires the helper. InferenceVisitorBase visitor = _createInferenceVisitor(helper); visitor.inferMetadata(visitor, parent, annotations); + visitor.checkCleanState(); } } @@ -278,6 +288,7 @@ initializer = visitor.ensureAssignableResult(declaredType, result).expression; } + visitor.checkCleanState(); return initializer; } }
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index efe056a..476e815 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -71,6 +71,7 @@ artificial asgerf askesc +asking aspect aspx asserting @@ -101,6 +102,7 @@ backstop badly bail +balance bang bar basically @@ -274,6 +276,7 @@ continuations contra contribute +conveniently convention conversely coordinated @@ -437,6 +440,7 @@ en encapsulation end'ed +end's enforce enforced enforces @@ -634,6 +638,7 @@ identifies identifying ideographic +idle idn ids idx @@ -826,6 +831,7 @@ manage mangled manipulation +manipulations manner markdown masking @@ -918,6 +924,7 @@ observatory observe obstruct +obtaining occasionally occupied offending @@ -1402,6 +1409,7 @@ sw swapped sweep +switched symbolic synchronously syncs
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart b/pkg/front_end/testcases/general/switch_error_recovery.dart new file mode 100644 index 0000000..5888e37 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart
@@ -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; + } +}
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline.expect new file mode 100644 index 0000000..0f2e897 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline.expect
@@ -0,0 +1,3 @@ +class C<X, Y> {} + +void main() {}
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline_modelled.expect new file mode 100644 index 0000000..0f2e897 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@ +class C<X, Y> {} + +void main() {}
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.expect new file mode 100644 index 0000000..688fb59 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.expect
@@ -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: + {} + } +}
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.modular.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.modular.expect new file mode 100644 index 0000000..688fb59 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.modular.expect
@@ -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: + {} + } +}
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.outline.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.outline.expect new file mode 100644 index 0000000..6fd9e64 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.outline.expect
@@ -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 + ;
diff --git a/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.transformed.expect b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.transformed.expect new file mode 100644 index 0000000..688fb59 --- /dev/null +++ b/pkg/front_end/testcases/general/switch_error_recovery.dart.weak.transformed.expect
@@ -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: + {} + } +}