diff --git a/pkg/front_end/lib/src/testing/id.dart b/pkg/front_end/lib/src/testing/id.dart index 49079470a9cc..f0b323e7d8c0 100644 --- a/pkg/front_end/lib/src/testing/id.dart +++ b/pkg/front_end/lib/src/testing/id.dart @@ -34,6 +34,9 @@ enum IdKind { /// certain offset. This is used in [NodeId]. moveNext, + /// Id used for the implicit as expression inserted by the compiler. + implicitAs, + /// Id used for the statement at certain offset. This is used in [NodeId]. stmt, @@ -96,6 +99,8 @@ class IdValue { return '$currentPrefix$value'; case IdKind.moveNext: return '$moveNextPrefix$value'; + case IdKind.implicitAs: + return '$implicitAsPrefix$value'; case IdKind.stmt: return '$stmtPrefix$value'; case IdKind.error: @@ -113,6 +118,7 @@ class IdValue { static const String iteratorPrefix = "iterator: "; static const String currentPrefix = "current: "; static const String moveNextPrefix = "moveNext: "; + static const String implicitAsPrefix = "as: "; static const String stmtPrefix = "stmt: "; static const String errorPrefix = "error: "; @@ -159,6 +165,9 @@ class IdValue { } else if (text.startsWith(moveNextPrefix)) { id = new NodeId(offset, IdKind.moveNext); expected = text.substring(moveNextPrefix.length); + } else if (text.startsWith(implicitAsPrefix)) { + id = new NodeId(offset, IdKind.implicitAs); + expected = text.substring(implicitAsPrefix.length); } else if (text.startsWith(stmtPrefix)) { id = new NodeId(offset, IdKind.stmt); expected = text.substring(stmtPrefix.length); @@ -340,17 +349,19 @@ abstract class DataRegistry { /// /// Checks for duplicate data for [id]. void registerValue(Uri uri, int offset, Id id, T value, Object object) { - if (actualMap.containsKey(id)) { - ActualData existingData = actualMap[id]; - report(uri, offset, "Duplicate id ${id}, value=$value, object=$object"); - report( - uri, - offset, - "Duplicate id ${id}, value=${existingData.value}, " - "object=${existingData.object}"); - fail("Duplicate id $id."); - } if (value != null) { + if (actualMap.containsKey(id)) { + // TODO(johnniwinther): Maybe let the test supply a way to merge + // multiple data on the same id? + ActualData existingData = actualMap[id]; + report(uri, offset, "Duplicate id ${id}, value=$value, object=$object"); + report( + uri, + offset, + "Duplicate id ${id}, value=${existingData.value}, " + "object=${existingData.object}"); + fail("Duplicate id $id."); + } actualMap[id] = new ActualData(id, value, uri, offset, object); } } diff --git a/pkg/front_end/lib/src/testing/id_extractor.dart b/pkg/front_end/lib/src/testing/id_extractor.dart index 88ee421f0380..9ea0e06896fa 100644 --- a/pkg/front_end/lib/src/testing/id_extractor.dart +++ b/pkg/front_end/lib/src/testing/id_extractor.dart @@ -136,6 +136,14 @@ abstract class DataExtractor extends Visitor with DataRegistry { NodeId createSwitchCaseId(SwitchCase node) => new NodeId(node.expressionOffsets.first, IdKind.node); + NodeId createImplicitAsId(AsExpression node) { + if (node.fileOffset == TreeNode.noOffset) { + // TODO(johnniwinther): Find out why we something have no offset. + return null; + } + return new NodeId(node.fileOffset, IdKind.implicitAs); + } + void run(Node root) { root.accept(this); } @@ -376,4 +384,55 @@ abstract class DataExtractor extends Visitor with DataRegistry { } super.visitThisExpression(node); } + + @override + visitAwaitExpression(AwaitExpression node) { + computeForNode(node, computeDefaultNodeId(node)); + super.visitAwaitExpression(node); + } + + @override + visitConstructorInvocation(ConstructorInvocation node) { + // Skip synthetic constructor invocations like for enum constants. + // TODO(johnniwinther): Can [skipNodeWithNoOffset] be removed when dart2js + // no longer test with cfe constants? + computeForNode( + node, computeDefaultNodeId(node, skipNodeWithNoOffset: true)); + super.visitConstructorInvocation(node); + } + + @override + visitStaticGet(StaticGet node) { + computeForNode(node, computeDefaultNodeId(node)); + super.visitStaticGet(node); + } + + @override + visitStaticSet(StaticSet node) { + computeForNode(node, createUpdateId(node)); + super.visitStaticSet(node); + } + + @override + visitStaticInvocation(StaticInvocation node) { + computeForNode(node, createInvokeId(node)); + super.visitStaticInvocation(node); + } + + @override + visitAsExpression(AsExpression node) { + if (node.isTypeError) { + computeForNode(node, createImplicitAsId(node)); + } else { + computeForNode(node, computeDefaultNodeId(node)); + } + return super.visitAsExpression(node); + } + + @override + visitArguments(Arguments node) { + computeForNode( + node, computeDefaultNodeId(node, skipNodeWithNoOffset: true)); + return super.visitArguments(node); + } } diff --git a/pkg/front_end/test/static_types/data/issue37439.dart b/pkg/front_end/test/static_types/data/issue37439.dart new file mode 100644 index 000000000000..253ac53c40c7 --- /dev/null +++ b/pkg/front_end/test/static_types/data/issue37439.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2019, 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 A {} + +class B extends A {} + +Future func1(T t) async { + // TODO(37439): We should infer 'T' instead of ''. + return /*invoke: */ func2/*<>*/(/*as: */ /*T*/ t); +} + +T func2(T t) => /*T*/ t; + +main() async { + /*B*/ await /*invoke: Future*/ func1/**/(/*B*/ B()); +} diff --git a/pkg/front_end/test/static_types/static_type_test.dart b/pkg/front_end/test/static_types/static_type_test.dart index d91866a2a6da..608d9485314f 100644 --- a/pkg/front_end/test/static_types/static_type_test.dart +++ b/pkg/front_end/test/static_types/static_type_test.dart @@ -51,6 +51,10 @@ class StaticTypeDataExtractor extends CfeDataExtractor { if (node is Expression) { DartType type = node.getStaticType(_environment); return typeToText(type); + } else if (node is Arguments) { + if (node.types.isNotEmpty) { + return '<${node.types.map(typeToText).join(',')}>'; + } } return null; }