From 8da75ede060ec3ef9035a4c0153f8ee29bdd8faa Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Thu, 22 Sep 2022 22:41:32 +0000 Subject: [PATCH] Shared type analysis: simplify handling of patterns. Patterns may need to be visited twice during analysis: once to determine a type schema, and a second type to resolve the pattern match. Previously, the shared TypeAnalyzer had just a single `dispatchPattern` method, so it had to create temporary objects to record the structure of the patterns between the two visits. Now, there are two dispatch methods: `dispatchPatternSchema` and `dispatchPattern`. This avoids the creation of a bunch of temporary objects and makes the design much simpler. (Based on an idea from Brian Wilkerson) Change-Id: If10b6b7fb578594c3f660baa55d7e28123652638 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260282 Reviewed-by: Brian Wilkerson Commit-Queue: Paul Berry --- .../type_inference/type_analysis_result.dart | 34 - .../lib/src/type_inference/type_analyzer.dart | 724 +++++++----------- pkg/_fe_analyzer_shared/test/mini_ast.dart | 180 +++-- pkg/analyzer/lib/src/dart/ast/ast.dart | 153 ++++ pkg/analyzer/lib/src/generated/resolver.dart | 66 +- .../test/spell_checking_list_code.txt | 1 + 6 files changed, 574 insertions(+), 584 deletions(-) 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 cc70e0d0662..e6c03d33cf8 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 @@ -106,40 +106,6 @@ class MatchContext { topPattern: topPattern); } -/// Data structure returned by the [TypeAnalyzer] `analyze` methods for -/// patterns. -abstract class PatternDispatchResult { - /// The AST node for this pattern. - Node get node; - - /// The type schema for this pattern. - Type get typeSchema; - - /// Called by the [TypeAnalyzer] when an attempt is made to match this - /// pattern. - /// - /// [matchedType] is the type of the thing being matched (for a variable - /// declaration, this is the type of the initializer or substructure thereof; - /// for a switch statement this is the type of the scrutinee or substructure - /// thereof). - /// - /// [typeInfos] is a data structure keeping track of the variable patterns - /// seen so far and their type information. - /// - /// [context] keeps track of other contextual information pertinent to the - /// match, such as whether it is late and/or final, whether there is an - /// initializer expression (and if so, what it is), and whether the match is - /// happening in an irrefutable context (and if so, what surrounding construct - /// causes it to be irrefutable). - /// - /// Stack effect (see [TypeAnalyzer] for explanation): pushes (Pattern). - void match( - Type matchedType, - Map> typeInfos, - MatchContext context); -} - /// Container for the result of running type analysis on an expression that does /// not contain any null shorting. class SimpleTypeAnalysisResult 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 index 7bfe7c861a3..4ca5963e4a6 100644 --- a/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart +++ b/pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart @@ -162,25 +162,79 @@ mixin TypeAnalyzer> typeInfos, + MatchContext context, + Node innerPattern, + Type type) { + dispatchPattern(type, typeInfos, context, innerPattern); + // Stack: (Pattern) + } + + /// Computes the type schema for a cast pattern. /// /// Stack effect: none. - PatternDispatchResult analyzeCastPattern( - Node node, Node innerPattern, Type type) { - return new _CastPatternDispatchResult( - this, node, dispatchPattern(innerPattern), type); - } + Type analyzeCastPatternSchema() => objectQuestionType; /// Analyzes a constant pattern. [node] is the pattern itself, and /// [expression] is the constant expression. Depending on the client's /// representation, [node] and [expression] might or might not be identical. /// + /// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and + /// [context]. + /// + /// Stack effect: pushes (Expression). + void analyzeConstantPattern( + Type matchedType, + Map> typeInfos, + MatchContext context, + Node node, + Expression expression) { + // Stack: () + TypeAnalyzerErrors? errors = + this.errors; + Node? irrefutableContext = context.irrefutableContext; + if (irrefutableContext != null) { + errors?.refutablePatternInIrrefutableContext(node, irrefutableContext); + } + Type staticType = analyzeExpression(expression, matchedType); + // Stack: (Expression) + if (errors != null && !options.patternsEnabled) { + Expression? switchScrutinee = context.getSwitchScrutinee(node); + if (switchScrutinee != null) { + bool nullSafetyEnabled = options.nullSafetyEnabled; + bool matches = nullSafetyEnabled + ? typeOperations.isSubtypeOf(staticType, matchedType) + : typeOperations.isAssignableTo(staticType, matchedType); + if (!matches) { + errors.caseExpressionTypeMismatch( + caseExpression: expression, + scrutinee: switchScrutinee, + caseExpressionType: staticType, + scrutineeType: matchedType, + nullSafetyEnabled: nullSafetyEnabled); + } + } + } + } + + /// Computes the type schema for a constant pattern. + /// /// Stack effect: none. - PatternDispatchResult - analyzeConstantPattern(Node node, Expression expression) { - return new _ConstantPatternDispatchResult( - this, node, expression); + Type analyzeConstantPatternSchema() { + // Constant patterns are only allowed in refutable contexts, and refutable + // contexts don't propagate a type schema into the scrutinee. So this + // code path is only reachable if the user's code contains errors. + errors?.assertInErrorRecovery(); + return unknownType; } /// Analyzes an expression. [node] is the expression to analyze, and @@ -219,12 +273,10 @@ mixin TypeAnalyzer - patternDispatchResult = dispatchPattern(pattern); Map> typeInfos = {}; // TODO(paulberry): rework handling of isFinal - patternDispatchResult.match(initializerType, typeInfos, - new MatchContext(isFinal: false, topPattern: pattern)); + dispatchPattern(initializerType, typeInfos, + new MatchContext(isFinal: false, topPattern: pattern), pattern); // Stack: (Expression, Pattern) if (guard != null) { _checkGuardType(guard, analyzeExpression(guard, boolType)); @@ -273,24 +325,20 @@ mixin TypeAnalyzer - patternDispatchResult = dispatchPattern(pattern); - if (isLate && - patternDispatchResult is! _VariablePatternDispatchResult) { + if (isLate && !isVariablePattern(pattern)) { errors?.patternDoesNotAllowLate(pattern); } if (isLate) { flow?.lateInitializer_begin(node); } Type initializerType = - analyzeExpression(initializer, patternDispatchResult.typeSchema); + analyzeExpression(initializer, dispatchPatternSchema(pattern)); // Stack: (Expression) if (isLate) { flow?.lateInitializer_end(); } Map> typeInfos = {}; - patternDispatchResult.match( + dispatchPattern( initializerType, typeInfos, new MatchContext( @@ -298,7 +346,8 @@ mixin TypeAnalyzer analyzeListPattern( + /// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and + /// [context]. + /// + /// Stack effect: pushes (n * Pattern) where n = elements.length. + Type analyzeListPattern( + Type matchedType, + Map> typeInfos, + MatchContext context, Node node, {Type? elementType, required List elements}) { - return new _ListPatternDispatchResult( - this, - node, - elementType, - [for (Node element in elements) dispatchPattern(element)]); + // Stack: () + Type? matchedElementType = typeOperations.matchListType(matchedType); + if (matchedElementType == null) { + if (typeOperations.isDynamic(matchedType)) { + matchedElementType = dynamicType; + } else { + matchedElementType = objectQuestionType; + } + } + for (Node element in elements) { + dispatchPattern(matchedElementType, typeInfos, context, element); + } + // Stack: (n * Pattern) where n = elements.length + Type requiredType = listType(elementType ?? matchedElementType); + Node? irrefutableContext = context.irrefutableContext; + if (irrefutableContext != null && + !typeOperations.isAssignableTo(matchedType, requiredType)) { + errors?.patternTypeMismatchInIrrefutableContext( + pattern: node, + context: irrefutableContext, + matchedType: matchedType, + requiredType: requiredType); + } + return requiredType; + } + + /// Computes the type schema for a list pattern. [elementType] is the list + /// element type (if explicitly supplied), and [elements] is the list of + /// subpatterns. + /// + /// Stack effect: none. + Type analyzeListPatternSchema( + {Type? elementType, required List elements}) { + if (elementType == null) { + if (elements.isEmpty) { + return objectQuestionType; + } + elementType = dispatchPatternSchema(elements[0]); + for (int i = 1; i < elements.length; i++) { + elementType = typeOperations.glb( + elementType!, dispatchPatternSchema(elements[i])); + } + } + return listType(elementType!); } /// Analyzes a logical-or or logical-and pattern. [node] is the pattern @@ -334,24 +428,94 @@ mixin TypeAnalyzer analyzeLogicalPattern( - Node node, Node lhs, Node rhs, + /// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and + /// [context]. + /// + /// Stack effect: pushes (Pattern left, Pattern right) + void analyzeLogicalPattern( + Type matchedType, + Map> typeInfos, + MatchContext context, + Node node, + Node lhs, + Node rhs, {required bool isAnd}) { - return new _LogicalPatternDispatchResult( - this, node, dispatchPattern(lhs), dispatchPattern(rhs), isAnd); + // Stack: () + if (!isAnd) { + Node? irrefutableContext = context.irrefutableContext; + if (irrefutableContext != null) { + errors?.refutablePatternInIrrefutableContext(node, irrefutableContext); + // Avoid cascading errors + context = context.makeRefutable(); + } + } + dispatchPattern(matchedType, typeInfos, context, lhs); + // Stack: (Pattern left) + dispatchPattern(matchedType, typeInfos, context, rhs); + // Stack: (Pattern left, Pattern right) + } + + /// Computes the type schema for a logical-or or logical-and pattern. [lhs] + /// and [rhs] are the left and right sides of the `|` or `&` operator. + /// [isAnd] indicates whether [node] is a logical-or or a logical-and. + /// + /// Stack effect: none. + Type analyzeLogicalPatternSchema(Node lhs, Node rhs, {required bool isAnd}) { + if (isAnd) { + return typeOperations.glb( + dispatchPatternSchema(lhs), dispatchPatternSchema(rhs)); + } else { + // Logical-or patterns are only allowed in refutable contexts, and + // refutable contexts don't propagate a type schema into the scrutinee. + // So this code path is only reachable if the user's code contains errors. + errors?.assertInErrorRecovery(); + return unknownType; + } } /// Analyzes a null-check or null-assert pattern. [node] is the pattern /// itself, [innerPattern] is the sub-pattern, and [isAssert] indicates /// whether this is a null-check or a null-assert pattern. /// + /// See [dispatchPattern] for the meanings of [matchedType], [typeInfos], and + /// [context]. + /// + /// Stack effect: pushes (Pattern innerPattern). + void analyzeNullCheckOrAssertPattern( + Type matchedType, + Map> typeInfos, + MatchContext context, + Node node, + Node innerPattern, + {required bool isAssert}) { + // Stack: () + Type innerMatchedType = typeOperations.promoteToNonNull(matchedType); + Node? irrefutableContext = context.irrefutableContext; + if (irrefutableContext != null && !isAssert) { + errors?.refutablePatternInIrrefutableContext(node, irrefutableContext); + // Avoid cascading errors + context = context.makeRefutable(); + } + dispatchPattern(innerMatchedType, typeInfos, context, innerPattern); + // Stack: (Pattern) + } + + /// Computes the type schema for a null-check or null-assert pattern. + /// [innerPattern] is the sub-pattern and [isAssert] indicates whether this is + /// a null-check or a null-assert pattern. + /// /// Stack effect: none. - PatternDispatchResult - analyzeNullCheckOrAssertPattern(Node node, Node innerPattern, - {required bool isAssert}) { - return new _NullCheckOrAssertPatternDispatchResult( - this, node, dispatchPattern(innerPattern), isAssert); + Type analyzeNullCheckOrAssertPatternSchema(Node innerPattern, + {required bool isAssert}) { + if (isAssert) { + return typeOperations.makeNullable(dispatchPatternSchema(innerPattern)); + } else { + // Null-check patterns are only allowed in refutable contexts, and + // refutable contexts don't propagate a type schema into the scrutinee. + // So this code path is only reachable if the user's code contains errors. + errors?.assertInErrorRecovery(); + return unknownType; + } } /// Analyzes an expression of the form `switch (expression) { cases }`. @@ -374,13 +538,14 @@ mixin TypeAnalyzer> typeInfos = {}; Node? pattern = memberInfo.head.pattern; if (pattern != null) { - dispatchPattern(pattern).match( + dispatchPattern( expressionType, typeInfos, new MatchContext( isFinal: false, switchScrutinee: scrutinee, - topPattern: pattern)); + topPattern: pattern), + pattern); // Stack: (Expression, i * ExpressionCase, Pattern) Expression? guard = memberInfo.head.guard; bool hasGuard = guard != null; @@ -449,13 +614,14 @@ mixin TypeAnalyzer head = heads[j]; Node? pattern = head.pattern; if (pattern != null) { - dispatchPattern(pattern).match( + dispatchPattern( scrutineeType, typeInfos, new MatchContext( isFinal: false, switchScrutinee: scrutinee, - topPattern: pattern)); + topPattern: pattern), + pattern); // Stack: (Expression, numExecutionPaths * StatementCase, // numHeads * CaseHead, Pattern), Expression? guard = head.guard; @@ -543,17 +709,61 @@ mixin TypeAnalyzer - analyzeVariablePattern(Node node, Variable? variable, Type? declaredType, - {required bool isFinal}) { - return new _VariablePatternDispatchResult( - this, node, variable, declaredType, isFinal); + Type analyzeVariablePattern( + Type matchedType, + Map> typeInfos, + MatchContext context, + Node node, + Variable? variable, + Type? declaredType, + {required bool isFinal}) { + Type staticType = + declaredType ?? variableTypeFromInitializerType(matchedType); + Node? irrefutableContext = context.irrefutableContext; + if (irrefutableContext != null && + !typeOperations.isAssignableTo(matchedType, staticType)) { + errors?.patternTypeMismatchInIrrefutableContext( + pattern: node, + context: irrefutableContext, + matchedType: matchedType, + requiredType: staticType); + } + bool isImplicitlyTyped = declaredType == null; + if (variable != null) { + bool isFirstMatch = _recordTypeInfo(typeInfos, + pattern: node, + variable: variable, + staticType: staticType, + isImplicitlyTyped: isImplicitlyTyped); + if (isFirstMatch) { + flow?.declare(variable, false); + setVariableType(variable, staticType); + // TODO(paulberry): are we handling _isFinal correctly? + // TODO(paulberry): do we need to verify that all instances of a + // variable are final or all are not final? + flow?.initialize(variable, matchedType, context.getInitializer(node), + isFinal: context.isFinal || isFinal, + isLate: context.isLate, + isImplicitlyTyped: isImplicitlyTyped); + } + } + return staticType; } + /// Computes the type schema for a variable pattern. [declaredType] is the + /// explicitly declared type (if present). + /// + /// Stack effect: none. + Type analyzeVariablePatternSchema(Type? declaredType) => + declaredType ?? unknownType; + /// Calls the appropriate `analyze` method according to the form of /// [expression], and then adjusts the stack as needed to combine any /// sub-structures into a single expression. @@ -567,9 +777,32 @@ mixin TypeAnalyzer> typeInfos, + MatchContext context, + Node node); + + /// Calls the appropriate `analyze...Schema` method according to the form of + /// [pattern]. + /// /// Stack effect: none. - PatternDispatchResult dispatchPattern( - Node pattern); + Type dispatchPatternSchema(Node pattern); /// Calls the appropriate `analyze` method according to the form of /// [statement], and then adjusts the stack as needed to combine any @@ -629,22 +862,6 @@ mixin TypeAnalyzer { /// The static type of this variable. Type get staticType => _latestStaticType; } - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeCastPattern] -class _CastPatternDispatchResult - extends _PatternDispatchResultImpl { - final PatternDispatchResult _innerPattern; - - final Type _type; - - _CastPatternDispatchResult( - super._typeAnalyzer, super.node, this._innerPattern, this._type); - - @override - Type get typeSchema => _typeAnalyzer.objectQuestionType; - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - _innerPattern.match(_type, typeInfos, context); - // Stack: (Pattern) - _typeAnalyzer.handleCastPattern(node, matchedType: matchedType); - // Stack: (Pattern) - } -} - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeConstantPattern] -class _ConstantPatternDispatchResult - extends _PatternDispatchResultImpl { - /// The constant or literal expression. - /// - /// Depending on the client's representation, this might or might not be - /// identical to [node]. - final Expression _expression; - - _ConstantPatternDispatchResult( - super.typeAnalyzer, super.node, this._expression); - - @override - Type get typeSchema { - // Constant patterns are only allowed in refutable contexts, and refutable - // contexts don't propagate a type schema into the scrutinee. So this - // code path is only reachable if the user's code contains errors. - _typeAnalyzer.errors?.assertInErrorRecovery(); - return _typeAnalyzer.unknownType; - } - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - // Stack: () - Node? irrefutableContext = context.irrefutableContext; - if (irrefutableContext != null) { - _typeAnalyzer.errors - ?.refutablePatternInIrrefutableContext(node, irrefutableContext); - } - Type staticType = _typeAnalyzer.analyzeExpression(_expression, matchedType); - // Stack: (Expression) - TypeAnalyzerErrors? errors = - _typeAnalyzer.errors; - TypeAnalyzerOptions options = _typeAnalyzer.options; - if (errors != null && !options.patternsEnabled) { - Expression? switchScrutinee = context.getSwitchScrutinee(node); - if (switchScrutinee != null) { - TypeOperations2 typeOperations = _typeAnalyzer.typeOperations; - bool nullSafetyEnabled = options.nullSafetyEnabled; - bool matches = nullSafetyEnabled - ? typeOperations.isSubtypeOf(staticType, matchedType) - : typeOperations.isAssignableTo(staticType, matchedType); - if (!matches) { - errors.caseExpressionTypeMismatch( - caseExpression: _expression, - scrutinee: switchScrutinee, - caseExpressionType: staticType, - scrutineeType: matchedType, - nullSafetyEnabled: nullSafetyEnabled); - } - } - } - _typeAnalyzer.handleConstantPattern(node, matchedType: matchedType); - // Stack: (Pattern) - } -} - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeListPattern] -class _ListPatternDispatchResult - extends _PatternDispatchResultImpl { - final Type? _elementType; - - final List> _elements; - - _ListPatternDispatchResult( - super.typeAnalyzer, super._node, this._elementType, this._elements); - - @override - Type get typeSchema { - Type? elementType = _elementType; - if (elementType == null) { - if (_elements.isEmpty) { - return _typeAnalyzer.objectQuestionType; - } - elementType = _elements[0].typeSchema; - for (int i = 1; i < _elements.length; i++) { - elementType = _typeAnalyzer.typeOperations - .glb(elementType!, _elements[i].typeSchema); - } - } - return _typeAnalyzer.listType(elementType!); - } - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - // Stack: () - Type? elementType = _typeAnalyzer.typeOperations.matchListType(matchedType); - if (elementType == null) { - if (_typeAnalyzer.typeOperations.isDynamic(matchedType)) { - elementType = _typeAnalyzer.dynamicType; - } else { - elementType = _typeAnalyzer.objectQuestionType; - } - } - for (PatternDispatchResult element - in _elements) { - element.match(elementType, typeInfos, context); - } - // Stack: (n * Pattern) where n = _elements.length - Type? requiredType = _typeAnalyzer.listType(_elementType ?? elementType); - Node? irrefutableContext = context.irrefutableContext; - if (irrefutableContext != null && - !_typeAnalyzer.typeOperations - .isAssignableTo(matchedType, requiredType)) { - _typeAnalyzer.errors?.patternTypeMismatchInIrrefutableContext( - pattern: node, - context: irrefutableContext, - matchedType: matchedType, - requiredType: requiredType); - } - _typeAnalyzer.handleListPattern(node, _elements.length, - matchedType: matchedType, requiredType: requiredType); - // Stack: (Pattern) - } -} - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeLogicalPattern] -class _LogicalPatternDispatchResult - extends _PatternDispatchResultImpl { - final PatternDispatchResult _lhs; - - final PatternDispatchResult _rhs; - - final bool _isAnd; - - _LogicalPatternDispatchResult( - super._typeAnalyzer, super.node, this._lhs, this._rhs, this._isAnd); - - @override - Type get typeSchema { - if (_isAnd) { - return _typeAnalyzer.typeOperations.glb(_lhs.typeSchema, _rhs.typeSchema); - } else { - // Logical-or patterns are only allowed in refutable contexts, and - // refutable contexts don't propagate a type schema into the scrutinee. - // So this code path is only reachable if the user's code contains errors. - _typeAnalyzer.errors?.assertInErrorRecovery(); - return _typeAnalyzer.unknownType; - } - } - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - // Stack: () - if (!_isAnd) { - Node? irrefutableContext = context.irrefutableContext; - if (irrefutableContext != null) { - _typeAnalyzer.errors - ?.refutablePatternInIrrefutableContext(node, irrefutableContext); - // Avoid cascading errors - context = context.makeRefutable(); - } - } - _lhs.match(matchedType, typeInfos, context); - // Stack: (Pattern left) - _rhs.match(matchedType, typeInfos, context); - // Stack: (Pattern left, Pattern right) - _typeAnalyzer.handleLogicalPattern(node, - isAnd: _isAnd, matchedType: matchedType); - // Stack: (Pattern) - } -} - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeNullCheckOrAssertPattern] -class _NullCheckOrAssertPatternDispatchResult - extends _PatternDispatchResultImpl { - final PatternDispatchResult _innerPattern; - - final bool _isAssert; - - _NullCheckOrAssertPatternDispatchResult( - super._typeAnalyzer, super.node, this._innerPattern, this._isAssert); - - @override - Type get typeSchema { - if (_isAssert) { - return _typeAnalyzer.typeOperations - .makeNullable(_innerPattern.typeSchema); - } else { - // Null-check patterns are only allowed in refutable contexts, and - // refutable contexts don't propagate a type schema into the scrutinee. - // So this code path is only reachable if the user's code contains errors. - _typeAnalyzer.errors?.assertInErrorRecovery(); - return _typeAnalyzer.unknownType; - } - } - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - // Stack: () - Type innerMatchedType = - _typeAnalyzer.typeOperations.promoteToNonNull(matchedType); - Node? irrefutableContext = context.irrefutableContext; - if (irrefutableContext != null && !_isAssert) { - _typeAnalyzer.errors - ?.refutablePatternInIrrefutableContext(node, irrefutableContext); - // Avoid cascading errors - context = context.makeRefutable(); - } - _innerPattern.match(innerMatchedType, typeInfos, context); - // Stack: (Pattern) - _typeAnalyzer.handleNullCheckOrAssertPattern(node, - matchedType: matchedType, isAssert: _isAssert); - // Stack: (Pattern) - } -} - -/// Common base class for all specializations of [PatternDispatchResult] -/// returned by methods in [TypeAnalyzer]. -abstract class _PatternDispatchResultImpl - implements PatternDispatchResult { - /// Pointer back to the [TypeAnalyzer]. - final TypeAnalyzer _typeAnalyzer; - - @override - final Node node; - - _PatternDispatchResultImpl(this._typeAnalyzer, this.node); -} - -/// Specialization of [PatternDispatchResult] returned by -/// [TypeAnalyzer.analyzeVariablePattern] -class _VariablePatternDispatchResult - extends _PatternDispatchResultImpl { - final Variable? _variable; - - final Type? _declaredType; - - final bool _isFinal; - - _VariablePatternDispatchResult(super._typeAnalyzer, super.node, - this._variable, this._declaredType, this._isFinal); - - @override - Type get typeSchema => _declaredType ?? _typeAnalyzer.unknownType; - - @override - void match( - Type matchedType, - Map> typeInfos, - MatchContext context) { - // Stack: () - Type staticType = _declaredType ?? - _typeAnalyzer.variableTypeFromInitializerType(matchedType); - Node? irrefutableContext = context.irrefutableContext; - if (irrefutableContext != null && - !_typeAnalyzer.typeOperations.isAssignableTo(matchedType, staticType)) { - _typeAnalyzer.errors?.patternTypeMismatchInIrrefutableContext( - pattern: node, - context: irrefutableContext, - matchedType: matchedType, - requiredType: staticType); - } - bool isImplicitlyTyped = _declaredType == null; - Variable? variable = _variable; - if (variable != null) { - bool isFirstMatch = _typeAnalyzer._recordTypeInfo(typeInfos, - pattern: node, - variable: variable, - staticType: staticType, - isImplicitlyTyped: isImplicitlyTyped); - if (isFirstMatch) { - _typeAnalyzer.flow?.declare(variable, false); - _typeAnalyzer.setVariableType(variable, staticType); - // TODO(paulberry): are we handling _isFinal correctly? - // TODO(paulberry): do we need to verify that all instances of a - // variable are final or all are not final? - _typeAnalyzer.flow?.initialize( - variable, matchedType, context.getInitializer(node), - isFinal: context.isFinal || _isFinal, - isLate: context.isLate, - isImplicitlyTyped: isImplicitlyTyped); - } - } - _typeAnalyzer.handleVariablePattern(node, - matchedType: matchedType, staticType: staticType); - // Stack: (Pattern) - } -} diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index 3db55157dc0..60004b9c12d 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -968,6 +968,8 @@ abstract class Pattern extends Node with CaseHead, CaseHeads { Pattern as_(String type) => new _CastPattern(this, Type(type), location: computeLocation()); + Type computeSchema(Harness h); + Pattern or(Pattern other) => _LogicalPattern(this, other, isAnd: false, location: computeLocation()); @@ -977,7 +979,11 @@ abstract class Pattern extends Node with CaseHead, CaseHeads { @override String toString() => _debugString(needsKeywordOrType: true); - PatternDispatchResult visit(Harness h); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context); CaseHead when(Expression guard) => _GuardedCaseHead(this, guard, location: location); @@ -1245,6 +1251,8 @@ class _CastPattern extends Pattern { _CastPattern(this._inner, this._type, {required super.location}) : super._(); + Type computeSchema(Harness h) => h.typeAnalyzer.analyzeCastPatternSchema(); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { @@ -1252,8 +1260,18 @@ class _CastPattern extends Pattern { } @override - PatternDispatchResult visit(Harness h) { - return h.typeAnalyzer.analyzeCastPattern(this, _inner, _type); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + h.typeAnalyzer + .analyzeCastPattern(matchedType, typeInfos, context, _inner, _type); + h.irBuilder.atom(_type.type, Kind.type, location: location); + h.irBuilder.atom(matchedType.type, Kind.type, location: location); + h.irBuilder.apply( + 'castPattern', [Kind.pattern, Kind.type, Kind.type], Kind.pattern, + names: ['matchedType'], location: location); } @override @@ -1513,15 +1531,26 @@ class _ConstantPattern extends Pattern { _ConstantPattern(this.constant, {required super.location}) : super._(); + Type computeSchema(Harness h) => + h.typeAnalyzer.analyzeConstantPatternSchema(); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { constant.preVisit(visitor); } - @override - PatternDispatchResult visit(Harness h) => - h.typeAnalyzer.analyzeConstantPattern(this, constant); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + h.typeAnalyzer.analyzeConstantPattern( + matchedType, typeInfos, context, this, constant); + h.irBuilder.atom(matchedType.type, Kind.type, location: location); + h.irBuilder.apply('const', [Kind.expression, Kind.type], Kind.pattern, + names: ['matchedType'], location: location); + } @override _debugString({required bool needsKeywordOrType}) => constant.toString(); @@ -2040,6 +2069,9 @@ class _ListPattern extends Pattern { _ListPattern(this._elementType, this._elements, {required super.location}) : super._(); + Type computeSchema(Harness h) => h.typeAnalyzer + .analyzeListPatternSchema(elementType: _elementType, elements: _elements); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { @@ -2048,10 +2080,22 @@ class _ListPattern extends Pattern { } } - @override - PatternDispatchResult visit(Harness h) { - return h.typeAnalyzer.analyzeListPattern(this, + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + var requiredType = h.typeAnalyzer.analyzeListPattern( + matchedType, typeInfos, context, this, elementType: _elementType, elements: _elements); + h.irBuilder.atom(matchedType.type, Kind.type, location: location); + h.irBuilder.atom(requiredType.type, Kind.type, location: location); + h.irBuilder.apply( + 'listPattern', + [...List.filled(_elements.length, Kind.pattern), Kind.type, Kind.type], + Kind.pattern, + names: ['matchedType', 'requiredType'], + location: location); } @override @@ -2129,6 +2173,9 @@ class _LogicalPattern extends Pattern { {required this.isAnd, required super.location}) : super._(); + Type computeSchema(Harness h) => + h.typeAnalyzer.analyzeLogicalPatternSchema(_lhs, _rhs, isAnd: isAnd); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { @@ -2148,9 +2195,18 @@ class _LogicalPattern extends Pattern { } } - @override - PatternDispatchResult visit(Harness h) { - return h.typeAnalyzer.analyzeLogicalPattern(this, _lhs, _rhs, isAnd: isAnd); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + h.typeAnalyzer.analyzeLogicalPattern( + matchedType, typeInfos, context, this, _lhs, _rhs, + isAnd: isAnd); + h.irBuilder.atom(matchedType.type, Kind.type, location: location); + h.irBuilder.apply(isAnd ? 'logicalAndPattern' : 'logicalOrPattern', + [Kind.pattern, Kind.pattern, Kind.type], Kind.pattern, + names: ['matchedType'], location: location); } @override @@ -2588,9 +2644,17 @@ class _MiniAstTypeAnalyzer _irBuilder.guard(expression, () => expression.visit(_harness, context)); @override - PatternDispatchResult dispatchPattern( + void dispatchPattern( + Type matchedType, + Map> typeInfos, + MatchContext context, covariant Pattern node) { - return node.visit(_harness); + return node.visit(_harness, matchedType, typeInfos, context); + } + + @override + Type dispatchPatternSchema(covariant Pattern node) { + return node.computeSchema(_harness); } @override @@ -2644,50 +2708,11 @@ class _MiniAstTypeAnalyzer location: node.location); } - @override - void handleCastPattern(covariant _CastPattern node, - {required Type matchedType}) { - _irBuilder.atom(node._type.type, Kind.type, location: node.location); - _irBuilder.atom(matchedType.type, Kind.type, location: node.location); - _irBuilder.apply( - 'castPattern', [Kind.pattern, Kind.type, Kind.type], Kind.pattern, - names: ['matchedType'], location: node.location); - } - - @override - void handleConstantPattern(Node node, {required Type matchedType}) { - _irBuilder.atom(matchedType.type, Kind.type, location: node.location); - _irBuilder.apply('const', [Kind.expression, Kind.type], Kind.pattern, - names: ['matchedType'], location: node.location); - } - @override void handleDefault(Node node, int caseIndex) { _irBuilder.atom('default', Kind.caseHead, location: node.location); } - @override - void handleListPattern(Node node, int numElements, - {required Type matchedType, required Type requiredType}) { - _irBuilder.atom(matchedType.type, Kind.type, location: node.location); - _irBuilder.atom(requiredType.type, Kind.type, location: node.location); - _irBuilder.apply( - 'listPattern', - [...List.filled(numElements, Kind.pattern), Kind.type, Kind.type], - Kind.pattern, - names: ['matchedType', 'requiredType'], - location: node.location); - } - - @override - void handleLogicalPattern(Node node, - {required bool isAnd, required Type matchedType}) { - _irBuilder.atom(matchedType.type, Kind.type, location: node.location); - _irBuilder.apply(isAnd ? 'logicalAndPattern' : 'logicalOrPattern', - [Kind.pattern, Kind.pattern, Kind.type], Kind.pattern, - names: ['matchedType'], location: node.location); - } - @override void handleMergedStatementCase(Statement node, {required int caseIndex, @@ -2723,19 +2748,9 @@ class _MiniAstTypeAnalyzer _irBuilder.atom('noop', Kind.statement, location: node.location); } - @override - void handleNullCheckOrAssertPattern(Node node, - {required Type matchedType, required bool isAssert}) { - _irBuilder.atom(matchedType.type, Kind.type, location: node.location); - _irBuilder.apply(isAssert ? 'nullAssertPattern' : 'nullCheckPattern', - [Kind.pattern, Kind.type], Kind.pattern, - names: ['matchedType'], location: node.location); - } - @override void handleSwitchScrutinee(Type type) {} - @override void handleVariablePattern(covariant _VariablePattern node, {required Type matchedType, required Type staticType}) { _irBuilder.atom(node.variable?.name ?? '_', Kind.variable, @@ -2757,6 +2772,9 @@ class _MiniAstTypeAnalyzer return node.isExhaustive; } + @override + bool isVariablePattern(Node pattern) => pattern is _VariablePattern; + Type leastUpperBound(Type t1, Type t2) => _harness._lub(t1, t2); @override @@ -2889,16 +2907,27 @@ class _NullCheckOrAssertPattern extends Pattern { {required super.location}) : super._(); + Type computeSchema(Harness h) => h.typeAnalyzer + .analyzeNullCheckOrAssertPatternSchema(_inner, isAssert: _isAssert); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { _inner.preVisit(visitor, variableBinder); } - @override - PatternDispatchResult visit(Harness h) { - return h.typeAnalyzer - .analyzeNullCheckOrAssertPattern(this, _inner, isAssert: _isAssert); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + h.typeAnalyzer.analyzeNullCheckOrAssertPattern( + matchedType, typeInfos, context, this, _inner, + isAssert: _isAssert); + h.irBuilder.atom(matchedType.type, Kind.type, location: location); + h.irBuilder.apply(_isAssert ? 'nullAssertPattern' : 'nullCheckPattern', + [Kind.pattern, Kind.type], Kind.pattern, + names: ['matchedType'], location: location); } @override @@ -3301,6 +3330,9 @@ class _VariablePattern extends Pattern { {this.isFinal = false, required super.location}) : super._(); + Type computeSchema(Harness h) => + h.typeAnalyzer.analyzeVariablePatternSchema(declaredType); + @override void preVisit( PreVisitor visitor, VariableBinder variableBinder) { @@ -3310,10 +3342,16 @@ class _VariablePattern extends Pattern { } } - @override - PatternDispatchResult visit(Harness h) { - return h.typeAnalyzer - .analyzeVariablePattern(this, variable, declaredType, isFinal: isFinal); + void visit( + Harness h, + Type matchedType, + Map> typeInfos, + MatchContext context) { + var staticType = h.typeAnalyzer.analyzeVariablePattern( + matchedType, typeInfos, context, this, variable, declaredType, + isFinal: isFinal); + h.typeAnalyzer.handleVariablePattern(this, + matchedType: matchedType, staticType: staticType); } @override diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index 65306dbb963..6aa163ef8d0 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart @@ -5,6 +5,8 @@ import 'dart:collection'; import 'dart:math' as math; +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:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/precedence.dart'; @@ -1111,6 +1113,19 @@ class BinaryPatternImpl extends DartPatternImpl implements BinaryPattern { @override E? accept(AstVisitor visitor) => visitor.visitBinaryPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { leftOperand.accept(visitor); @@ -1518,6 +1533,19 @@ class CastPatternImpl extends DartPatternImpl implements CastPattern { @override E? accept(AstVisitor visitor) => visitor.visitCastPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { type.accept(visitor); @@ -2730,6 +2758,19 @@ class ConstantPatternImpl extends DartPatternImpl implements ConstantPattern { @override E? accept(AstVisitor visitor) => visitor.visitConstantPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { expression.accept(visitor); @@ -3269,6 +3310,14 @@ abstract class DartPatternImpl extends AstNodeImpl implements DartPattern { // TODO(brianwilkerson) Remove this and implement it in subclasses when we // have constants for pattern-related precedence values. Precedence get precedence => throw UnimplementedError(); + + DartType computePatternSchema(ResolverVisitor resolverVisitor); + + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context); } /// A node that represents the declaration of one or more names. Each declared @@ -4636,6 +4685,19 @@ class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern { @override E? accept(AstVisitor visitor) => visitor.visitExtractorPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { typeName.accept(visitor); @@ -7971,6 +8033,19 @@ class ListPatternImpl extends DartPatternImpl implements ListPattern { @override E? accept(AstVisitor visitor) => visitor.visitListPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { typeArguments?.accept(visitor); @@ -8165,6 +8240,19 @@ class MapPatternImpl extends DartPatternImpl implements MapPattern { @override E? accept(AstVisitor visitor) => visitor.visitMapPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { typeArguments?.accept(visitor); @@ -9383,6 +9471,19 @@ class ParenthesizedPatternImpl extends DartPatternImpl E? accept(AstVisitor visitor) => visitor.visitParenthesizedPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { pattern.accept(visitor); @@ -9814,6 +9915,19 @@ class PostfixPatternImpl extends DartPatternImpl implements PostfixPattern { @override E? accept(AstVisitor visitor) => visitor.visitPostfixPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { operand.accept(visitor); @@ -10279,6 +10393,19 @@ class RecordPatternImpl extends DartPatternImpl implements RecordPattern { @override E? accept(AstVisitor visitor) => visitor.visitRecordPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { fields.accept(visitor); @@ -10562,6 +10689,19 @@ class RelationalPatternImpl extends DartPatternImpl @override E? accept(AstVisitor visitor) => visitor.visitRelationalPattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { operand.accept(visitor); @@ -13237,6 +13377,19 @@ class VariablePatternImpl extends DartPatternImpl implements VariablePattern { @override E? accept(AstVisitor visitor) => visitor.visitVariablePattern(this); + @override + DartType computePatternSchema(ResolverVisitor resolverVisitor) => + throw UnimplementedError('TODO(paulberry)'); + + @override + void resolvePattern( + ResolverVisitor resolverVisitor, + DartType matchedType, + Map> typeInfos, + MatchContext context) { + throw UnimplementedError('TODO(paulberry)'); + } + @override void visitChildren(AstVisitor visitor) { type?.accept(visitor); diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 3a8cc21096d..1279d7dfd27 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -707,15 +707,31 @@ class ResolverVisitor extends ThrowingAstVisitor } @override - PatternDispatchResult - dispatchPattern(AstNode pattern) { - if (pattern is Expression) { - return analyzeConstantPattern(pattern, pattern); + void dispatchPattern( + DartType matchedType, + Map> typeInfos, + MatchContext context, + AstNode node) { + if (node is DartPatternImpl) { + node.resolvePattern(this, matchedType, typeInfos, context); } else { - throw UnimplementedError('TODO(paulberry): ${pattern.runtimeType}'); + // This can occur inside conventional switch statements, since + // [SwitchCase] points directly to an [Expression] rather than to a + // [ConstantPattern]. So we mimic what + // [ConstantPatternImpl.resolvePattern] would do. + analyzeConstantPattern( + matchedType, typeInfos, context, node, node as Expression); + // Stack: (Expression) + popRewrite(); + // Stack: () } } + @override + DartType dispatchPatternSchema(covariant DartPatternImpl node) { + return node.computePatternSchema(this); + } + @override void dispatchStatement(Statement statement) { statement.accept(this); @@ -782,36 +798,11 @@ class ResolverVisitor extends ThrowingAstVisitor switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]); } - @override - void handleCastPattern(AstNode node, - {required DartType matchedType, DartType? staticType}) { - throw UnimplementedError('TODO(paulberry)'); - } - - @override - void handleConstantPattern(AstNode node, {required DartType matchedType}) { - // Stack: (Expression) - popRewrite(); - // Stack: () - } - @override void handleDefault(covariant SwitchStatement node, int caseIndex) { switchExhaustiveness!.visitSwitchMember(node.members[caseIndex]); } - @override - void handleListPattern(AstNode node, int numElements, - {required DartType matchedType, required DartType requiredType}) { - throw UnimplementedError('TODO(paulberry)'); - } - - @override - void handleLogicalPattern(AstNode node, - {required bool isAnd, required DartType matchedType}) { - throw UnimplementedError('TODO(paulberry)'); - } - @override void handleMergedStatementCase(covariant SwitchStatement node, {required int caseIndex, @@ -834,23 +825,11 @@ class ResolverVisitor extends ThrowingAstVisitor throw UnimplementedError('TODO(paulberry)'); } - @override - void handleNullCheckOrAssertPattern(AstNode node, - {required DartType matchedType, required bool isAssert}) { - throw UnimplementedError('TODO(paulberry)'); - } - @override void handleSwitchScrutinee(DartType type) { switchExhaustiveness = SwitchExhaustiveness(type); } - @override - void handleVariablePattern(AstNode node, - {required DartType matchedType, DartType? staticType}) { - throw UnimplementedError('TODO(paulberry)'); - } - /// If generic function instantiation should be performed on `expression`, /// inserts a [FunctionReference] node which wraps [expression]. /// @@ -911,6 +890,9 @@ class ResolverVisitor extends ThrowingAstVisitor bool isSwitchExhaustive(AstNode node, DartType expressionType) => switchExhaustiveness!.isExhaustive; + @override + bool isVariablePattern(AstNode pattern) => pattern is VariablePattern; + @override DartType listType(DartType elementType) { throw UnimplementedError('TODO(paulberry)'); diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index cd905c87d04..3379e86ebda 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt @@ -832,6 +832,7 @@ mb mc md me +meanings meeting merely meta