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 6731df0938db..4f22b52c2315 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 @@ -697,16 +697,23 @@ mixin TypeAnalyzer< Type matchedType, MatchContext context, Pattern node, { - required Type? requiredType, required List> fields, }) { _reportDuplicateRecordPatternFields(fields); - requiredType ??= downwardInferObjectPatternRequiredType( + Type requiredType = downwardInferObjectPatternRequiredType( matchedType: matchedType, pattern: node, ); + // If the required type is `dynamic` or `Never`, then every getter is + // treated as having the same type. + Type? overridePropertyGetType; + if (typeOperations.isDynamic(requiredType) || + typeOperations.isNever(requiredType)) { + overridePropertyGetType = requiredType; + } + Node? irrefutableContext = context.irrefutableContext; if (irrefutableContext != null && !typeOperations.isAssignableTo(matchedType, requiredType)) { @@ -720,10 +727,11 @@ mixin TypeAnalyzer< // Stack: () for (RecordPatternField field in fields) { - Type propertyType = resolveObjectPatternPropertyGet( - receiverType: requiredType, - field: field, - ); + Type propertyType = overridePropertyGetType ?? + resolveObjectPatternPropertyGet( + receiverType: requiredType, + field: field, + ); dispatchPattern(propertyType, context, field.pattern); } // Stack: (n * Pattern) where n = fields.length diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index 7672b67293b9..71090e2c4100 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -265,11 +265,15 @@ Statement match(Pattern pattern, Expression initializer, isLate: isLate, isFinal: isFinal, location: computeLocation()); Pattern objectPattern({ - required ObjectPatternRequiredType requiredType, + required String requiredType, required List fields, }) { + var parsedType = Type(requiredType); + if (parsedType is! PrimaryType) { + fail('Expected a primary type, got $parsedType'); + } return _ObjectPattern( - requiredType: requiredType, + requiredType: parsedType, fields: fields, location: computeLocation(), ); @@ -754,6 +758,7 @@ class MiniAstOperations 'int? <: Object': false, 'int? <: Object?': true, 'List <: Object': true, + 'Never <: Object': true, 'Never <: Object?': true, 'Null <: double?': true, 'Null <: int': false, @@ -881,7 +886,11 @@ class MiniAstOperations }; static final Map _coreDownwardInferenceResults = { + 'dynamic <: int': Type('dynamic'), + 'int <: num': Type('int'), 'List <: Iterable': Type('List'), + 'Never <: int': Type('Never'), + 'num <: int': Type('num'), }; static final Map _coreNormalizeResults = { @@ -1155,27 +1164,6 @@ class Node { String toString() => 'Node#$id'; } -/// Either the type, or the name of a type constructor. -class ObjectPatternRequiredType { - final Type? type; - final String? name; - - ObjectPatternRequiredType.name(this.name) : type = null; - - ObjectPatternRequiredType.type(String type) - : type = Type(type), - name = null; - - @override - String toString() { - if (type != null) { - return '(type: $type)'; - } else { - return '(name: $name)'; - } - } -} - abstract class Pattern extends Node with PossiblyGuardedPattern implements ListPatternElement { @@ -3239,11 +3227,12 @@ class _MiniAstTypeAnalyzer required Type matchedType, required covariant _ObjectPattern pattern, }) { - var name = pattern.requiredType.name; - if (name == null) { - fail('Expected type constructor name at ${pattern.location}'); + var requiredType = pattern.requiredType; + if (requiredType.args.isNotEmpty) { + return requiredType; + } else { + return typeOperations.downwardInfer(requiredType.name, matchedType); } - return typeOperations.downwardInfer(name, matchedType); } void finish() { @@ -3675,7 +3664,7 @@ class _NullLiteral extends Expression { } class _ObjectPattern extends Pattern { - final ObjectPatternRequiredType requiredType; + final PrimaryType requiredType; final List fields; _ObjectPattern({ @@ -3686,7 +3675,7 @@ class _ObjectPattern extends Pattern { @override Type computeSchema(Harness h) { - return h.typeAnalyzer.analyzeObjectPatternSchema(requiredType.type!); + return h.typeAnalyzer.analyzeObjectPatternSchema(requiredType); } @override @@ -3705,9 +3694,8 @@ class _ObjectPattern extends Pattern { Type matchedType, SharedMatchContext context, ) { - var requiredType = h.typeAnalyzer.analyzeObjectPattern( - matchedType, context, this, - requiredType: this.requiredType.type, fields: fields); + var requiredType = h.typeAnalyzer + .analyzeObjectPattern(matchedType, context, this, fields: fields); h.irBuilder.atom(matchedType.type, Kind.type, location: location); h.irBuilder.atom(requiredType.type, Kind.type, location: location); h.irBuilder.apply( diff --git a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart index c0a5da218f25..cc7121d99570 100644 --- a/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart +++ b/pkg/_fe_analyzer_shared/test/type_inference/type_inference_test.dart @@ -2090,7 +2090,7 @@ main() { ifCase( expr('A').checkContext('?'), objectPattern( - requiredType: ObjectPatternRequiredType.name('B'), + requiredType: 'B', fields: [ Var('foo', errorId: 'foo').pattern().recordField('foo'), ], @@ -2100,13 +2100,46 @@ main() { 'requiredType: B), variables(foo), true, block(), noop)'), ]); }); + + test('dynamic type', () { + h.run([ + ifCase( + expr('int').checkContext('?'), + objectPattern( + requiredType: 'dynamic', + fields: [ + Var('foo', errorId: 'foo').pattern().recordField('foo'), + ], + ), + ).checkIr('ifCase(expr(int), objectPattern(varPattern(foo, ' + 'matchedType: dynamic, staticType: dynamic), matchedType: int, ' + 'requiredType: dynamic), variables(foo), true, block(), noop)'), + ]); + }); + + test('Never type', () { + h.run([ + ifCase( + expr('int').checkContext('?'), + objectPattern( + requiredType: 'Never', + fields: [ + Var('foo', errorId: 'foo').pattern().recordField('foo'), + ], + ), + ).checkIr('ifCase(expr(int), objectPattern(varPattern(foo, ' + 'matchedType: Never, staticType: Never), matchedType: int, ' + 'requiredType: Never), variables(foo), true, block(), noop)'), + ]); + }); + test('duplicate field name', () { h.addMember('A', 'foo', 'int'); h.run([ ifCase( expr('A'), objectPattern( - requiredType: ObjectPatternRequiredType.type('A'), + requiredType: 'A', fields: [ Var('a', errorId: 'a').pattern().recordField('foo') ..errorId = 'ORIGINAL', @@ -2128,7 +2161,7 @@ main() { h.run([ match( objectPattern( - requiredType: ObjectPatternRequiredType.type('num'), + requiredType: 'num', fields: [ Var('foo').pattern().recordField('foo'), ], @@ -2145,7 +2178,7 @@ main() { h.run([ (match( objectPattern( - requiredType: ObjectPatternRequiredType.type('int'), + requiredType: 'int', fields: [ Var('foo').pattern().recordField('foo'), ], diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index f7688d7daeb2..b7447b07d737 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart @@ -9452,7 +9452,6 @@ class ObjectPatternImpl extends DartPatternImpl implements ObjectPattern { matchedType, context, this, - requiredType: null, fields: resolverVisitor.buildSharedRecordPatternFields(fields), ); }