Validate local resolution scope, and compute initial name scope.

R=brianwilkerson@google.com, paulberry@google.com
BUG=

Review URL: https://codereview.chromium.org/2439583003 .
This commit is contained in:
Konstantin Shcheglov 2016-10-19 14:08:46 -07:00
parent 1e793c96b2
commit bf43e4b43e
3 changed files with 289 additions and 31 deletions

View file

@ -9395,6 +9395,10 @@ enum TypeResolverMode {
/** /**
* Resolve only type names that would be skipped during [api]. * Resolve only type names that would be skipped during [api].
*
* Resolution must start from a unit member or a class member. For example
* it is not allowed to resolve types in a separate statement, or a function
* body.
*/ */
local local
} }
@ -9438,7 +9442,16 @@ class TypeResolverVisitor extends ScopedVisitor {
final TypeResolverMode mode; final TypeResolverMode mode;
bool _visitAllInLocalMode = false; /**
* Is `true` when we are visiting all nodes in [TypeResolverMode.local] mode.
*/
bool _localModeVisitAll = false;
/**
* Is `true` if we are in [TypeResolverMode.local] mode, and the initial
* [nameScope] was computed.
*/
bool _localModeScopeReady = false;
/** /**
* Initialize a newly created visitor to resolve the nodes in an AST node. * Initialize a newly created visitor to resolve the nodes in an AST node.
@ -9794,21 +9807,47 @@ class TypeResolverVisitor extends ScopedVisitor {
// resolve only type names that are local. // resolve only type names that are local.
if (mode == TypeResolverMode.local) { if (mode == TypeResolverMode.local) {
// We are in the state of visiting all nodes. // We are in the state of visiting all nodes.
if (_visitAllInLocalMode) { if (_localModeVisitAll) {
return super.visitNode(node); return super.visitNode(node);
} }
// Ensure that the name scope is ready.
if (!_localModeScopeReady) {
void fillNameScope(AstNode node) {
if (node is FunctionBody ||
node is FormalParameterList ||
node is VariableDeclaration) {
throw new StateError(
'Local type resolution must start from a class or unit member.');
}
// Create enclosing name scopes.
AstNode parent = node.parent;
if (parent != null) {
fillNameScope(parent);
}
// Create the name scope for the node.
if (node is ClassDeclaration) {
ClassElement classElement = node.element;
nameScope = new TypeParameterScope(nameScope, classElement);
nameScope = new ClassScope(nameScope, classElement);
}
}
fillNameScope(node);
_localModeScopeReady = true;
}
/** /**
* Visit the given [node] and all its children. * Visit the given [node] and all its children.
*/ */
void visitAllNodes(AstNode node) { void visitAllNodes(AstNode node) {
if (node != null) { if (node != null) {
bool wasVisitAllInLocalMode = _visitAllInLocalMode; bool wasVisitAllInLocalMode = _localModeVisitAll;
try { try {
_visitAllInLocalMode = true; _localModeVisitAll = true;
node.accept(this); node.accept(this);
} finally { } finally {
_visitAllInLocalMode = wasVisitAllInLocalMode; _localModeVisitAll = wasVisitAllInLocalMode;
} }
} }
} }

View file

@ -8,10 +8,12 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/token.dart'; import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/scanner/reader.dart'; import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart'; import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/generated/parser.dart'; import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/testing/ast_factory.dart'; import 'package:analyzer/src/generated/testing/ast_factory.dart';
import 'package:analyzer/src/generated/testing/token_factory.dart'; import 'package:analyzer/src/generated/testing/token_factory.dart';
import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:analyzer/src/generated/utilities_dart.dart';
@ -2986,6 +2988,21 @@ class ParserTestCase extends EngineTestCase {
return unit; return unit;
} }
/**
* Parse the given [code] as a compilation unit.
*/
static CompilationUnit parseCompilationUnit2(String code,
{AnalysisErrorListener listener, bool parseGenericMethods: false}) {
listener ??= AnalysisErrorListener.NULL_LISTENER;
Scanner scanner = new Scanner(null, new CharSequenceReader(code), listener);
Token token = scanner.tokenize();
Parser parser = new Parser(null, listener);
parser.parseGenericMethods = parseGenericMethods;
CompilationUnit unit = parser.parseCompilationUnit(token);
unit.lineInfo = new LineInfo(scanner.lineStarts);
return unit;
}
/** /**
* Parse the given [source] as a statement. The [errorCodes] are the error * Parse the given [source] as a statement. The [errorCodes] are the error
* codes of the errors that are expected to be found. If * codes of the errors that are expected to be found. If

View file

@ -2695,7 +2695,9 @@ A V = new A();
} }
void test_modeLocal_noContext() { void test_modeLocal_noContext() {
CompilationUnit unit = ParserTestCase.parseCompilationUnit(r''' CompilationUnit unit;
_resolveTypeModeLocal(
r'''
class C { class C {
A f = new A(); A f = new A();
A m([A p = const A()]) { A m([A p = const A()]) {
@ -2710,31 +2712,10 @@ A f([A p = const A()]) {
} }
A V = new A(); A V = new A();
A get G => new A(); A get G => new A();
'''); ''', (CompilationUnit u) {
var unitElement = new CompilationUnitElementImpl('/test.dart'); unit = u;
ClassElementImpl A = ElementFactory.classElement2('A'); return u;
});
// Build API elements.
{
var holder = new ElementHolder();
unit.accept(new ElementBuilder(holder, unitElement));
}
// Resolve API types.
{
MemoryResourceProvider resourceProvider = new MemoryResourceProvider();
InternalAnalysisContext context = AnalysisContextFactory.contextWithCore(
resourceProvider: resourceProvider);
var source = resourceProvider.getFile('/test.dart').createSource();
var libraryElement = new LibraryElementImpl.forNode(context, null)
..definingCompilationUnit = unitElement;
var libraryScope = new LibraryScope(libraryElement);
var visitor = new TypeResolverVisitor(
libraryElement, source, _typeProvider, _listener,
nameScope: libraryScope, mode: TypeResolverMode.local);
libraryScope.define(A);
unit.accept(visitor);
}
// Top-level: C // Top-level: C
{ {
@ -2827,6 +2808,185 @@ A get G => new A();
} }
} }
void test_modeLocal_withContext_bad_methodBody() {
expect(() {
_resolveTypeModeLocal(
r'''
class C<T1> {
A m<T2>() {
T1 v1;
T2 v2;
}
}
''', (CompilationUnit u) {
var c = u.declarations[0] as ClassDeclaration;
var m = c.members[0] as MethodDeclaration;
var mb = m.body as BlockFunctionBody;
return mb;
});
}, throwsStateError);
}
void test_modeLocal_withContext_bad_topLevelVariable_declaration() {
expect(() {
_resolveTypeModeLocal(
r'''
var v = new A();
''', (CompilationUnit u) {
var tlv = u.declarations[0] as TopLevelVariableDeclaration;
return tlv.variables.variables[0];
});
}, throwsStateError);
}
void test_modeLocal_withContext_bad_topLevelVariable_initializer() {
expect(() {
_resolveTypeModeLocal(
r'''
var v = new A();
''', (CompilationUnit u) {
var tlv = u.declarations[0] as TopLevelVariableDeclaration;
return tlv.variables.variables[0].initializer;
});
}, throwsStateError);
}
void test_modeLocal_withContext_class() {
ClassDeclaration c;
_resolveTypeModeLocal(
r'''
class C<T1> {
A m<T2>() {
T1 v1;
T2 v2;
}
}
''', (CompilationUnit u) {
c = u.declarations[0] as ClassDeclaration;
return c;
});
var m = c.members[0] as MethodDeclaration;
// The return type of "m" is not resolved.
expect(m.returnType.type, isNull);
var mb = m.body as BlockFunctionBody;
var ms = mb.block.statements;
// The type of "v1" is resolved.
{
var vd = ms[0] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T1');
}
// The type of "v2" is resolved.
{
var vd = ms[1] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T2');
}
}
void test_modeLocal_withContext_inClass_constructor() {
ConstructorDeclaration cc;
_resolveTypeModeLocal(
r'''
class C<T> {
C() {
T v1;
}
}
''', (CompilationUnit u) {
var c = u.declarations[0] as ClassDeclaration;
cc = c.members[0] as ConstructorDeclaration;
return cc;
});
var ccb = cc.body as BlockFunctionBody;
var ccs = ccb.block.statements;
// The type of "v" is resolved.
{
var vd = ccs[0] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T');
}
}
void test_modeLocal_withContext_inClass_method() {
MethodDeclaration m;
_resolveTypeModeLocal(
r'''
class C<T1> {
A m<T2>() {
T1 v1;
T2 v2;
}
}
''', (CompilationUnit u) {
var c = u.declarations[0] as ClassDeclaration;
m = c.members[0] as MethodDeclaration;
return m;
});
// The return type of "m" is not resolved.
expect(m.returnType.type, isNull);
var mb = m.body as BlockFunctionBody;
var ms = mb.block.statements;
// The type of "v1" is resolved.
{
var vd = ms[0] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T1');
}
// The type of "v2" is resolved.
{
var vd = ms[1] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T2');
}
}
void test_modeLocal_withContext_topLevelFunction() {
FunctionDeclaration f;
_resolveTypeModeLocal(
r'''
A m<T>() {
T v;
}
''', (CompilationUnit u) {
f = u.declarations[0] as FunctionDeclaration;
return f;
});
// The return type of "f" is not resolved.
expect(f.returnType.type, isNull);
var fb = f.functionExpression.body as BlockFunctionBody;
var fs = fb.block.statements;
// The type of "v" is resolved.
var vd = fs[0] as VariableDeclarationStatement;
expect(vd.variables.type.type.toString(), 'T');
}
void test_modeLocal_withContext_topLevelVariable() {
TopLevelVariableDeclaration v;
_resolveTypeModeLocal(
r'''
A v = new A();
''', (CompilationUnit u) {
v = u.declarations[0] as TopLevelVariableDeclaration;
return v;
});
// The type of "v" is not resolved.
expect(v.variables.type.type, isNull);
// The type of "v" initializer is resolved.
var vi = v.variables.variables[0].initializer as InstanceCreationExpression;
expect(vi.constructorName.type.type.toString(), 'A');
}
void test_visitCatchClause_exception() { void test_visitCatchClause_exception() {
// catch (e) // catch (e)
CatchClause clause = AstFactory.catchClause("e"); CatchClause clause = AstFactory.catchClause("e");
@ -3413,6 +3573,48 @@ A get G => new A();
} }
node.accept(_visitor); node.accept(_visitor);
} }
/**
* Parse the given [code], build elements and resolve in the
* [TypeResolverMode.local] mode. The [code] is allowed to use only the type
* named `A`.
*/
void _resolveTypeModeLocal(
String code, AstNode getNodeToResolve(CompilationUnit unit)) {
CompilationUnit unit =
ParserTestCase.parseCompilationUnit2(code, parseGenericMethods: true);
var unitElement = new CompilationUnitElementImpl('/test.dart');
// Build API elements.
{
var holder = new ElementHolder();
unit.accept(new ElementBuilder(holder, unitElement));
}
// Prepare for resolution.
LibraryScope libraryScope;
TypeResolverVisitor visitor;
{
MemoryResourceProvider resourceProvider = new MemoryResourceProvider();
InternalAnalysisContext context = AnalysisContextFactory.contextWithCore(
resourceProvider: resourceProvider);
var source = resourceProvider.getFile('/test.dart').createSource();
var libraryElement = new LibraryElementImpl.forNode(context, null)
..definingCompilationUnit = unitElement;
libraryScope = new LibraryScope(libraryElement);
visitor = new TypeResolverVisitor(
libraryElement, source, _typeProvider, _listener,
nameScope: libraryScope, mode: TypeResolverMode.local);
}
// Define top-level types.
ClassElementImpl A = ElementFactory.classElement2('A');
libraryScope.define(A);
// Perform resolution.
AstNode nodeToResolve = getNodeToResolve(unit);
nodeToResolve.accept(visitor);
}
} }
class _RootScope extends Scope { class _RootScope extends Scope {