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 182f9194ffa..1ecb6b5d175 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 @@ -1180,7 +1180,9 @@ mixin TypeAnalyzer< required void Function() dispatchBody, }) { // Stack: () - Type expressionType = analyzeExpression(expression, unknownType); + Type patternTypeSchema = dispatchPatternSchema(pattern); + Type expressionTypeSchema = iterableType(patternTypeSchema); + Type expressionType = analyzeExpression(expression, expressionTypeSchema); // Stack: (Expression) Type? elementType = operations.matchIterableType(expressionType); @@ -1950,6 +1952,9 @@ mixin TypeAnalyzer< /// Queries whether [pattern] is a variable pattern. bool isVariablePattern(Node pattern); + /// Returns the type `Iterable`, with type argument [elementType]. + Type iterableType(Type elementType); + /// Returns the type `List`, with type argument [elementType]. Type listType(Type elementType); diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart index 03184e06671..2fde01395db 100644 --- a/pkg/_fe_analyzer_shared/test/mini_ast.dart +++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart @@ -1067,10 +1067,10 @@ class MiniAstOperations @override Type? matchIterableType(Type type) { - if (type is PrimaryType && - type.name == 'Iterable' && - type.args.length == 1) { - return type.args[0]; + if (type is PrimaryType && type.args.length == 1) { + if (type.name == 'Iterable' || type.name == 'List') { + return type.args[0]; + } } return null; } @@ -3832,6 +3832,11 @@ class _MiniAstTypeAnalyzer @override bool isVariablePattern(Node pattern) => pattern is _VariablePattern; + @override + Type iterableType(Type elementType) { + return PrimaryType('Iterable', args: [elementType]); + } + Type leastUpperBound(Type t1, Type t2) => _harness._operations._lub(t1, t2); @override 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 c80bbaaabe2..2103a1d5c56 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 @@ -1634,6 +1634,26 @@ main() { }); group('Pattern-for-in:', () { + group('Expression context type schema:', () { + test('Pattern has type', () { + var x = Var('x'); + h.run([ + patternForIn(x.pattern(type: 'num'), + expr('List').checkContext('Iterable'), []) + .checkIr('forEach(expr(List), varPattern(x, ' + 'matchedType: int, staticType: num), block())'), + ]); + }); + test('Pattern does not have type', () { + var x = Var('x'); + h.run([ + patternForIn(x.pattern(), + expr('List').checkContext('Iterable'), []) + .checkIr('forEach(expr(List), varPattern(x, ' + 'matchedType: int, staticType: int), block())'), + ]); + }); + }); group('Expression type:', () { test('Iterable', () { var x = Var('x'); diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index d3ca15c6dfe..bab3d94fe97 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -1211,6 +1211,11 @@ class ResolverVisitor extends ThrowingAstVisitor @override bool isVariablePattern(AstNode pattern) => pattern is DeclaredVariablePattern; + @override + DartType iterableType(DartType elementType) { + return typeProvider.iterableType(elementType); + } + @override DartType listType(DartType elementType) { return typeProvider.listType(elementType); diff --git a/pkg/analyzer/test/src/dart/resolution/for_element_test.dart b/pkg/analyzer/test/src/dart/resolution/for_element_test.dart index 0feb6c8f084..f8e7161a1c9 100644 --- a/pkg/analyzer/test/src/dart/resolution/for_element_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/for_element_test.dart @@ -176,6 +176,98 @@ ForElement '''); } + test_iterableContextType_patternVariable_typed() async { + await assertNoErrorsInCode(r''' +void f() { + [for (var (int a) in g()) a]; +} + +T g() => throw 0; +'''); + var node = findNode.singleForElement; + assertResolvedNodeText(node, r''' +ForElement + forKeyword: for + leftParenthesis: ( + forLoopParts: ForEachPartsWithPattern + keyword: var + pattern: ParenthesizedPattern + leftParenthesis: ( + pattern: DeclaredVariablePattern + type: NamedType + name: SimpleIdentifier + token: int + staticElement: dart:core::@class::int + staticType: null + type: int + name: a + declaredElement: a@28 + type: int + rightParenthesis: ) + inKeyword: in + iterable: MethodInvocation + methodName: SimpleIdentifier + token: g + staticElement: self::@function::g + staticType: T Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + staticInvokeType: Iterable Function() + staticType: Iterable + typeArgumentTypes + Iterable + rightParenthesis: ) + body: SimpleIdentifier + token: a + staticElement: a@28 + staticType: int +'''); + } + + test_iterableContextType_patternVariable_untyped() async { + await assertNoErrorsInCode(r''' +void f() { + [for (var (a) in g()) a]; +} + +T g() => throw 0; +'''); + var node = findNode.singleForElement; + assertResolvedNodeText(node, r''' +ForElement + forKeyword: for + leftParenthesis: ( + forLoopParts: ForEachPartsWithPattern + keyword: var + pattern: ParenthesizedPattern + leftParenthesis: ( + pattern: DeclaredVariablePattern + name: a + declaredElement: hasImplicitType a@24 + type: Object? + rightParenthesis: ) + inKeyword: in + iterable: MethodInvocation + methodName: SimpleIdentifier + token: g + staticElement: self::@function::g + staticType: T Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + staticInvokeType: Iterable Function() + staticType: Iterable + typeArgumentTypes + Iterable + rightParenthesis: ) + body: SimpleIdentifier + token: a + staticElement: a@24 + staticType: Object? +'''); + } + test_keyword_final_patternVariable() async { await assertNoErrorsInCode(r''' void f(List x) { diff --git a/pkg/analyzer/test/src/dart/resolution/for_statement_test.dart b/pkg/analyzer/test/src/dart/resolution/for_statement_test.dart index 8998fcf4bd0..ca79e66e743 100644 --- a/pkg/analyzer/test/src/dart/resolution/for_statement_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/for_statement_test.dart @@ -532,6 +532,100 @@ ForStatement '''); } + test_iterableContextType_patternVariable_typed() async { + await assertErrorsInCode(r''' +void f() { + for (var (int a) in g()) {} +} + +T g() => throw 0; +''', [ + error(HintCode.UNUSED_LOCAL_VARIABLE, 27, 1), + ]); + var node = findNode.forStatement('for'); + assertResolvedNodeText(node, r''' +ForStatement + forKeyword: for + leftParenthesis: ( + forLoopParts: ForEachPartsWithPattern + keyword: var + pattern: ParenthesizedPattern + leftParenthesis: ( + pattern: DeclaredVariablePattern + type: NamedType + name: SimpleIdentifier + token: int + staticElement: dart:core::@class::int + staticType: null + type: int + name: a + declaredElement: a@27 + type: int + rightParenthesis: ) + inKeyword: in + iterable: MethodInvocation + methodName: SimpleIdentifier + token: g + staticElement: self::@function::g + staticType: T Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + staticInvokeType: Iterable Function() + staticType: Iterable + typeArgumentTypes + Iterable + rightParenthesis: ) + body: Block + leftBracket: { + rightBracket: } +'''); + } + + test_iterableContextType_patternVariable_untyped() async { + await assertErrorsInCode(r''' +void f() { + for (var (a) in g()) {} +} + +T g() => throw 0; +''', [ + error(HintCode.UNUSED_LOCAL_VARIABLE, 23, 1), + ]); + var node = findNode.forStatement('for'); + assertResolvedNodeText(node, r''' +ForStatement + forKeyword: for + leftParenthesis: ( + forLoopParts: ForEachPartsWithPattern + keyword: var + pattern: ParenthesizedPattern + leftParenthesis: ( + pattern: DeclaredVariablePattern + name: a + declaredElement: hasImplicitType a@23 + type: Object? + rightParenthesis: ) + inKeyword: in + iterable: MethodInvocation + methodName: SimpleIdentifier + token: g + staticElement: self::@function::g + staticType: T Function() + argumentList: ArgumentList + leftParenthesis: ( + rightParenthesis: ) + staticInvokeType: Iterable Function() + staticType: Iterable + typeArgumentTypes + Iterable + rightParenthesis: ) + body: Block + leftBracket: { + rightBracket: } +'''); + } + test_keyword_final_patternVariable() async { await assertNoErrorsInCode(r''' void f(List x) { 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 68b46feef2a..11c4bf94bb7 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 @@ -9425,6 +9425,12 @@ class InferenceVisitorImpl extends InferenceVisitorBase throw new UnimplementedError('TODO(paulberry)'); } + @override + DartType iterableType(DartType elementType) { + return new InterfaceType(coreTypes.iterableClass, Nullability.nonNullable, + [elementType]); + } + @override DartType listType(DartType elementType) { return new InterfaceType(