blob: fb5a6f8bcadb12335225af8a9fe637737a7d6024 [file] [log] [blame]
// Copyright (c) 2012, 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.
library dart2js.resolution.members;
import '../common.dart';
import '../common/names.dart' show
Selectors;
import '../common/resolution.dart' show
Feature;
import '../compiler.dart' show
Compiler;
import '../constants/constructors.dart' show
RedirectingFactoryConstantConstructor;
import '../constants/expressions.dart';
import '../constants/values.dart';
import '../core_types.dart';
import '../dart_types.dart';
import '../elements/elements.dart';
import '../elements/modelx.dart' show
ConstructorElementX,
ErroneousElementX,
FunctionElementX,
JumpTargetX,
LocalFunctionElementX,
LocalParameterElementX,
LocalVariableElementX,
MethodElementX,
ParameterElementX,
VariableElementX,
VariableList;
import '../tokens/token.dart' show
isUserDefinableOperator;
import '../tree/tree.dart';
import '../util/util.dart' show
Link;
import '../universe/call_structure.dart' show
CallStructure;
import '../universe/selector.dart' show
Selector;
import '../universe/use.dart' show
DynamicUse,
StaticUse,
TypeUse;
import 'access_semantics.dart';
import 'class_members.dart' show MembersCreator;
import 'operators.dart';
import 'send_structure.dart';
import 'constructors.dart' show
ConstructorResolver,
ConstructorResult,
ConstructorResultKind;
import 'label_scope.dart' show
StatementScope;
import 'registry.dart' show
ResolutionRegistry;
import 'resolution.dart' show
ResolverTask;
import 'resolution_common.dart' show
MappingVisitor;
import 'resolution_result.dart';
import 'scope.dart' show
BlockScope,
MethodScope,
Scope;
import 'signatures.dart' show
SignatureResolver;
import 'variables.dart' show
VariableDefinitionsVisitor;
/// The state of constants in resolutions.
enum ConstantState {
/// Expressions are not required to be constants.
NON_CONSTANT,
/// Expressions are required to be constants.
///
/// For instance the values of a constant list literal.
CONSTANT,
/// Expressions are required to be constants and parameter references are
/// also considered constant.
///
/// This is used for resolving constructor initializers of constant
/// constructors.
CONSTANT_INITIALIZER,
}
/**
* Core implementation of resolution.
*
* Do not subclass or instantiate this class outside this library
* except for testing.
*/
class ResolverVisitor extends MappingVisitor<ResolutionResult> {
/**
* The current enclosing element for the visited AST nodes.
*
* This field is updated when nested closures are visited.
*/
Element enclosingElement;
/// Whether we are in a context where `this` is accessible (this will be false
/// in static contexts, factory methods, and field initializers).
bool inInstanceContext;
bool inCheckContext;
bool inCatchBlock;
ConstantState constantState;
Scope scope;
ClassElement currentClass;
ExpressionStatement currentExpressionStatement;
/// `true` if a [Send] or [SendSet] is visited as the prefix of member access.
/// For instance `Class` in `Class.staticField` or `prefix.Class` in
/// `prefix.Class.staticMethod()`.
bool sendIsMemberAccess = false;
StatementScope statementScope;
int allowedCategory = ElementCategory.VARIABLE | ElementCategory.FUNCTION
| ElementCategory.IMPLIES_TYPE;
/// When visiting the type declaration of the variable in a [ForIn] loop,
/// the initializer of the variable is implicit and we should not emit an
/// error when verifying that all final variables are initialized.
bool inLoopVariable = false;
/// The nodes for which variable access and mutation must be registered in
/// order to determine when the static type of variables types is promoted.
Link<Node> promotionScope = const Link<Node>();
bool isPotentiallyMutableTarget(Element target) {
if (target == null) return false;
return (target.isVariable || target.isParameter) &&
!(target.isFinal || target.isConst);
}
// TODO(ahe): Find a way to share this with runtime implementation.
static final RegExp symbolValidationPattern =
new RegExp(r'^(?:[a-zA-Z$][a-zA-Z$0-9_]*\.)*(?:[a-zA-Z$][a-zA-Z$0-9_]*=?|'
r'-|'
r'unary-|'
r'\[\]=|'
r'~|'
r'==|'
r'\[\]|'
r'\*|'
r'/|'
r'%|'
r'~/|'
r'\+|'
r'<<|'
r'>>|'
r'>=|'
r'>|'
r'<=|'
r'<|'
r'&|'
r'\^|'
r'\|'
r')$');
ResolverVisitor(Compiler compiler,
Element element,
ResolutionRegistry registry,
{bool useEnclosingScope: false})
: this.enclosingElement = element,
// When the element is a field, we are actually resolving its
// initial value, which should not have access to instance
// fields.
inInstanceContext = (element.isInstanceMember && !element.isField)
|| element.isGenerativeConstructor,
this.currentClass = element.isClassMember ? element.enclosingClass
: null,
this.statementScope = new StatementScope(),
scope = useEnclosingScope
? Scope.buildEnclosingScope(element) : element.buildScope(),
// The type annotations on a typedef do not imply type checks.
// TODO(karlklose): clean this up (dartbug.com/8870).
inCheckContext = compiler.enableTypeAssertions &&
!element.isLibrary &&
!element.isTypedef &&
!element.enclosingElement.isTypedef,
inCatchBlock = false,
constantState = element.isConst
? ConstantState.CONSTANT : ConstantState.NON_CONSTANT,
super(compiler, registry);
CoreClasses get coreClasses => compiler.coreClasses;
CoreTypes get coreTypes => compiler.coreTypes;
AsyncMarker get currentAsyncMarker {
if (enclosingElement is FunctionElement) {
FunctionElement function = enclosingElement;
return function.asyncMarker;
}
return AsyncMarker.SYNC;
}
Element reportLookupErrorIfAny(Element result, Node node, String name) {
if (!Elements.isUnresolved(result)) {
if (!inInstanceContext && result.isInstanceMember) {
reporter.reportErrorMessage(
node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': name});
return new ErroneousElementX(MessageKind.NO_INSTANCE_AVAILABLE,
{'name': name},
name, enclosingElement);
} else if (result.isAmbiguous) {
AmbiguousElement ambiguous = result;
return reportAndCreateErroneousElement(
node,
name,
ambiguous.messageKind,
ambiguous.messageArguments,
infos: ambiguous.computeInfos(enclosingElement, reporter),
isError: true);
}
}
return result;
}
// Create, or reuse an already created, target element for a statement.
JumpTarget getOrDefineTarget(Node statement) {
JumpTarget element = registry.getTargetDefinition(statement);
if (element == null) {
element = new JumpTargetX(statement,
statementScope.nestingLevel,
enclosingElement);
registry.defineTarget(statement, element);
}
return element;
}
doInPromotionScope(Node node, action()) {
promotionScope = promotionScope.prepend(node);
var result = action();
promotionScope = promotionScope.tail;
return result;
}
inStaticContext(action(),
{bool inConstantInitializer: false}) {
bool wasInstanceContext = inInstanceContext;
ConstantState oldConstantState = constantState;
constantState = inConstantInitializer
? ConstantState.CONSTANT_INITIALIZER
: constantState;
inInstanceContext = false;
var result = action();
inInstanceContext = wasInstanceContext;
constantState = oldConstantState;
return result;
}
ResolutionResult visitInStaticContext(Node node,
{bool inConstantInitializer: false}) {
return inStaticContext(
() => visit(node),
inConstantInitializer: inConstantInitializer);
}
/// Execute [action] where the constant state is `ConstantState.CONSTANT` if
/// not already `ConstantState.CONSTANT_INITIALIZER`.
inConstantContext(action()) {
ConstantState oldConstantState = constantState;
if (constantState != ConstantState.CONSTANT_INITIALIZER) {
constantState = ConstantState.CONSTANT;
}
var result = action();
constantState = oldConstantState;
return result;
}
/// Visit [node] where the constant state is `ConstantState.CONSTANT` if
/// not already `ConstantState.CONSTANT_INITIALIZER`.
ResolutionResult visitInConstantContext(Node node) {
ResolutionResult result = inConstantContext(() => visit(node));
assert(invariant(node, result != null,
message: "No resolution result for $node."));
return result;
}
ErroneousElement reportAndCreateErroneousElement(
Node node,
String name,
MessageKind kind,
Map arguments,
{List<DiagnosticMessage> infos: const <DiagnosticMessage>[],
bool isError: false}) {
if (isError) {
reporter.reportError(
reporter.createMessage(node, kind, arguments), infos);
} else {
reporter.reportWarning(
reporter.createMessage(node, kind, arguments), infos);
}
// TODO(ahe): Use [allowedCategory] to synthesize a more precise subclass
// of [ErroneousElementX]. For example, [ErroneousFieldElementX],
// [ErroneousConstructorElementX], etc.
return new ErroneousElementX(kind, arguments, name, enclosingElement);
}
/// Report a warning or error on an unresolved access in non-instance context.
///
/// The [ErroneousElement] corresponding to the message is returned.
ErroneousElement reportCannotResolve(Node node, String name) {
assert(invariant(node, !inInstanceContext,
message: "ResolverVisitor.reportCannotResolve must not be called in "
"instance context."));
// We report an error within initializers because `this` is implicitly
// accessed when unqualified identifiers are not resolved. For
// details, see section 16.14.3 of the spec (2nd edition):
// An unqualified invocation `i` of the form `id(a1, ...)`
// ...
// If `i` does not occur inside a top level or static function, `i`
// is equivalent to `this.id(a1 , ...)`.
bool inInitializer =
enclosingElement.isGenerativeConstructor ||
(enclosingElement.isInstanceMember && enclosingElement.isField);
MessageKind kind;
Map arguments = {'name': name};
if (inInitializer) {
kind = MessageKind.CANNOT_RESOLVE_IN_INITIALIZER;
} else if (name == 'await') {
var functionName = enclosingElement.name;
if (functionName == '') {
kind = MessageKind.CANNOT_RESOLVE_AWAIT_IN_CLOSURE;
} else {
kind = MessageKind.CANNOT_RESOLVE_AWAIT;
arguments['functionName'] = functionName;
}
} else {
kind = MessageKind.CANNOT_RESOLVE;
}
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
return reportAndCreateErroneousElement(
node, name, kind, arguments, isError: inInitializer);
}
ResolutionResult visitIdentifier(Identifier node) {
if (node.isThis()) {
if (!inInstanceContext) {
reporter.reportErrorMessage(
node, MessageKind.NO_INSTANCE_AVAILABLE, {'name': node});
}
return const NoneResult();
} else if (node.isSuper()) {
if (!inInstanceContext) {
reporter.reportErrorMessage(
node, MessageKind.NO_SUPER_IN_STATIC);
}
if ((ElementCategory.SUPER & allowedCategory) == 0) {
reporter.reportErrorMessage(
node, MessageKind.INVALID_USE_OF_SUPER);
}
return const NoneResult();
} else {
String name = node.source;
Element element = lookupInScope(reporter, node, scope, name);
if (Elements.isUnresolved(element) && name == 'dynamic') {
// TODO(johnniwinther): Remove this hack when we can return more complex
// objects than [Element] from this method.
element = coreClasses.typeClass;
// Set the type to be `dynamic` to mark that this is a type literal.
registry.setType(node, const DynamicType());
}
element = reportLookupErrorIfAny(element, node, name);
if (element == null) {
if (!inInstanceContext) {
element = reportCannotResolve(node, name);
}
} else if (element.isMalformed) {
// Use the malformed element.
} else {
if ((element.kind.category & allowedCategory) == 0) {
element = reportAndCreateErroneousElement(
node, name, MessageKind.GENERIC,
// TODO(ahe): Improve error message. Need UX input.
{'text': "is not an expression $element"});
}
}
if (!Elements.isUnresolved(element) && element.isClass) {
ClassElement classElement = element;
classElement.ensureResolved(resolution);
}
if (element != null) {
registry.useElement(node, element);
if (element.isPrefix) {
return new PrefixResult(element, null);
} else if (element.isClass && sendIsMemberAccess) {
return new PrefixResult(null, element);
}
return new ElementResult(element);
}
return const NoneResult();
}
}
TypeResult visitTypeAnnotation(TypeAnnotation node) {
DartType type = resolveTypeAnnotation(node);
if (inCheckContext) {
registry.registerTypeUse(new TypeUse.checkedModeCheck(type));
}
return new TypeResult(type);
}
bool isNamedConstructor(Send node) => node.receiver != null;
Name getRedirectingThisOrSuperConstructorName(Send node) {
if (isNamedConstructor(node)) {
String constructorName = node.selector.asIdentifier().source;
return new Name(constructorName, enclosingElement.library);
} else {
return const PublicName('');
}
}
FunctionElement resolveConstructorRedirection(FunctionElementX constructor) {
FunctionExpression node = constructor.parseNode(resolution.parsing);
// A synthetic constructor does not have a node.
if (node == null) return null;
if (node.initializers == null) return null;
Link<Node> initializers = node.initializers.nodes;
if (!initializers.isEmpty &&
Initializers.isConstructorRedirect(initializers.head)) {
Name name =
getRedirectingThisOrSuperConstructorName(initializers.head);
final ClassElement classElement = constructor.enclosingClass;
return classElement.lookupConstructor(name.text);
}
return null;
}
void setupFunction(FunctionExpression node, FunctionElement function) {
Element enclosingElement = function.enclosingElement;
if (node.modifiers.isStatic &&
enclosingElement.kind != ElementKind.CLASS) {
reporter.reportErrorMessage(node, MessageKind.ILLEGAL_STATIC);
}
scope = new MethodScope(scope, function);
// Put the parameters in scope.
FunctionSignature functionParameters = function.functionSignature;
Link<Node> parameterNodes = (node.parameters == null)
? const Link<Node>() : node.parameters.nodes;
functionParameters.forEachParameter((ParameterElementX element) {
// TODO(karlklose): should be a list of [FormalElement]s, but the actual
// implementation uses [Element].
List<Element> optionals = functionParameters.optionalParameters;
if (!optionals.isEmpty && element == optionals.first) {
NodeList nodes = parameterNodes.head;
parameterNodes = nodes.nodes;
}
if (element.isOptional) {
if (element.initializer != null) {
ResolutionResult result = visitInConstantContext(element.initializer);
if (result.isConstant) {
element.constant = result.constant;
}
} else {
element.constant = new NullConstantExpression();
}
}
VariableDefinitions variableDefinitions = parameterNodes.head;
Node parameterNode = variableDefinitions.definitions.nodes.head;
// Field parameters (this.x) are not visible inside the constructor. The
// fields they reference are visible, but must be resolved independently.
if (element.isInitializingFormal) {
registry.useElement(parameterNode, element);
} else {
LocalParameterElementX parameterElement = element;
defineLocalVariable(parameterNode, parameterElement);
addToScope(parameterElement);
}
parameterNodes = parameterNodes.tail;
});
addDeferredAction(enclosingElement, () {
functionParameters.forEachOptionalParameter(
(ParameterElementX parameter) {
parameter.constant =
compiler.resolver.constantCompiler.compileConstant(parameter);
});
});
if (inCheckContext) {
functionParameters.forEachParameter((ParameterElement element) {
registry.registerTypeUse(new TypeUse.checkedModeCheck(element.type));
});
}
}
ResolutionResult visitAssert(Assert node) {
if (!compiler.enableAssertMessage) {
if (node.hasMessage) {
reporter.reportErrorMessage(
node, MessageKind.EXPERIMENTAL_ASSERT_MESSAGE);
}
}
// TODO(sra): We could completely ignore the assert in production mode if we
// didn't need it to be resolved for type checking.
registry.registerFeature(
node.hasMessage ? Feature.ASSERT_WITH_MESSAGE : Feature.ASSERT);
visit(node.condition);
visit(node.message);
return const NoneResult();
}
ResolutionResult visitCascade(Cascade node) {
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitCascadeReceiver(CascadeReceiver node) {
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitIn(Node node, Scope nestedScope) {
Scope oldScope = scope;
scope = nestedScope;
ResolutionResult result = visit(node);
scope = oldScope;
return result;
}
/**
* Introduces new default targets for break and continue
* before visiting the body of the loop
*/
void visitLoopBodyIn(Loop loop, Node body, Scope bodyScope) {
JumpTarget element = getOrDefineTarget(loop);
statementScope.enterLoop(element);
visitIn(body, bodyScope);
statementScope.exitLoop();
if (!element.isTarget) {
registry.undefineTarget(loop);
}
}
ResolutionResult visitBlock(Block node) {
visitIn(node.statements, new BlockScope(scope));
return const NoneResult();
}
ResolutionResult visitDoWhile(DoWhile node) {
visitLoopBodyIn(node, node.body, new BlockScope(scope));
visit(node.condition);
return const NoneResult();
}
ResolutionResult visitEmptyStatement(EmptyStatement node) {
return const NoneResult();
}
ResolutionResult visitExpressionStatement(ExpressionStatement node) {
ExpressionStatement oldExpressionStatement = currentExpressionStatement;
currentExpressionStatement = node;
visit(node.expression);
currentExpressionStatement = oldExpressionStatement;
return const NoneResult();
}
ResolutionResult visitFor(For node) {
Scope blockScope = new BlockScope(scope);
visitIn(node.initializer, blockScope);
visitIn(node.condition, blockScope);
visitIn(node.update, blockScope);
visitLoopBodyIn(node, node.body, blockScope);
return const NoneResult();
}
ResolutionResult visitFunctionDeclaration(FunctionDeclaration node) {
assert(node.function.name != null);
visitFunctionExpression(node.function, inFunctionDeclaration: true);
return const NoneResult();
}
/// Process a local function declaration or an anonymous function expression.
///
/// [inFunctionDeclaration] is `true` when the current node is the immediate
/// child of a function declaration.
///
/// This is used to distinguish local function declarations from anonymous
/// function expressions.
ResolutionResult visitFunctionExpression(
FunctionExpression node,
{bool inFunctionDeclaration: false}) {
bool doAddToScope = inFunctionDeclaration;
if (!inFunctionDeclaration && node.name != null) {
reporter.reportErrorMessage(
node.name,
MessageKind.NAMED_FUNCTION_EXPRESSION,
{'name': node.name});
}
visit(node.returnType);
String name;
if (node.name == null) {
name = "";
} else {
name = node.name.asIdentifier().source;
}
LocalFunctionElementX function = new LocalFunctionElementX(
name, node, ElementKind.FUNCTION, Modifiers.EMPTY,
enclosingElement);
ResolverTask.processAsyncMarker(compiler, function, registry);
function.functionSignature = SignatureResolver.analyze(
compiler,
node.parameters,
node.returnType,
function,
registry,
createRealParameters: true,
isFunctionExpression: !inFunctionDeclaration);
checkLocalDefinitionName(node, function);
registry.defineFunction(node, function);
if (doAddToScope) {
addToScope(function);
}
Scope oldScope = scope; // The scope is modified by [setupFunction].
setupFunction(node, function);
Element previousEnclosingElement = enclosingElement;
enclosingElement = function;
// Run the body in a fresh statement scope.
StatementScope oldStatementScope = statementScope;
statementScope = new StatementScope();
visit(node.body);
statementScope = oldStatementScope;
scope = oldScope;
enclosingElement = previousEnclosingElement;
registry.registerStaticUse(new StaticUse.closure(function));
return const NoneResult();
}
ResolutionResult visitIf(If node) {
doInPromotionScope(node.condition.expression, () => visit(node.condition));
doInPromotionScope(node.thenPart,
() => visitIn(node.thenPart, new BlockScope(scope)));
visitIn(node.elsePart, new BlockScope(scope));
return const NoneResult();
}
static Selector computeSendSelector(Send node,
LibraryElement library,
Element element) {
// First determine if this is part of an assignment.
bool isSet = node.asSendSet() != null;
if (node.isIndex) {
return isSet ? new Selector.indexSet() : new Selector.index();
}
if (node.isOperator) {
String source = node.selector.asOperator().source;
String string = source;
if (identical(string, '!') ||
identical(string, '&&') || identical(string, '||') ||
identical(string, 'is') || identical(string, 'as') ||
identical(string, '?') || identical(string, '??')) {
return null;
}
String op = source;
if (!isUserDefinableOperator(source)) {
op = Elements.mapToUserOperatorOrNull(source);
}
if (op == null) {
// Unsupported operator. An error has been reported during parsing.
return new Selector.call(
new Name(source, library),
new CallStructure.unnamed(node.argumentsNode.slowLength()));
}
return node.arguments.isEmpty
? new Selector.unaryOperator(op)
: new Selector.binaryOperator(op);
}
Identifier identifier = node.selector.asIdentifier();
if (node.isPropertyAccess) {
assert(!isSet);
return new Selector.getter(
new Name(identifier.source, library));
} else if (isSet) {
return new Selector.setter(
new Name(identifier.source, library, isSetter: true));
}
// Compute the arity and the list of named arguments.
int arity = 0;
List<String> named = <String>[];
for (Link<Node> link = node.argumentsNode.nodes;
!link.isEmpty;
link = link.tail) {
Expression argument = link.head;
NamedArgument namedArgument = argument.asNamedArgument();
if (namedArgument != null) {
named.add(namedArgument.name.source);
}
arity++;
}
if (element != null && element.isConstructor) {
return new Selector.callConstructor(
new Name(element.name, library), arity, named);
}
// If we're invoking a closure, we do not have an identifier.
return (identifier == null)
? new Selector.callClosure(arity, named)
: new Selector.call(new Name(identifier.source, library),
new CallStructure(arity, named));
}
Selector resolveSelector(Send node, Element element) {
LibraryElement library = enclosingElement.library;
Selector selector = computeSendSelector(node, library, element);
if (selector != null) registry.setSelector(node, selector);
return selector;
}
ArgumentsResult resolveArguments(NodeList list) {
if (list == null) return null;
bool isValidAsConstant = true;
List<ResolutionResult> argumentResults = <ResolutionResult>[];
bool oldSendIsMemberAccess = sendIsMemberAccess;
sendIsMemberAccess = false;
Map<String, Node> seenNamedArguments = new Map<String, Node>();
int argumentCount = 0;
List<String> namedArguments = <String>[];
for (Link<Node> link = list.nodes; !link.isEmpty; link = link.tail) {
Expression argument = link.head;
ResolutionResult result = visit(argument);
if (!result.isConstant) {
isValidAsConstant = false;
}
argumentResults.add(result);
NamedArgument namedArgument = argument.asNamedArgument();
if (namedArgument != null) {
String source = namedArgument.name.source;
namedArguments.add(source);
if (seenNamedArguments.containsKey(source)) {
reportDuplicateDefinition(
source,
argument,
seenNamedArguments[source]);
isValidAsConstant = false;
} else {
seenNamedArguments[source] = namedArgument;
}
} else if (!seenNamedArguments.isEmpty) {
reporter.reportErrorMessage(
argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED);
isValidAsConstant = false;
}
argumentCount++;
}
sendIsMemberAccess = oldSendIsMemberAccess;
return new ArgumentsResult(
new CallStructure(argumentCount, namedArguments),
argumentResults,
isValidAsConstant: isValidAsConstant);
}
/// Check that access to `super` is currently allowed. Returns an
/// [AccessSemantics] in case of an error, `null` otherwise.
AccessSemantics checkSuperAccess(Send node) {
if (!inInstanceContext) {
ErroneousElement error = reportAndCreateErroneousElement(
node, 'super',
MessageKind.NO_SUPER_IN_STATIC, {},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return new StaticAccess.invalid(error);
}
if (node.isConditional) {
// `super?.foo` is not allowed.
ErroneousElement error = reportAndCreateErroneousElement(
node, 'super',
MessageKind.INVALID_USE_OF_SUPER, {},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return new StaticAccess.invalid(error);
}
if (currentClass.supertype == null) {
// This is just to guard against internal errors, so no need
// for a real error message.
ErroneousElement error = reportAndCreateErroneousElement(
node, 'super',
MessageKind.GENERIC,
{'text': "Object has no superclass"},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return new StaticAccess.invalid(error);
}
registry.registerSuperUse(reporter.spanFromSpannable(node));
return null;
}
/// Check that access to `this` is currently allowed. Returns an
/// [AccessSemantics] in case of an error, `null` otherwise.
AccessSemantics checkThisAccess(Send node) {
if (!inInstanceContext) {
ErroneousElement error = reportAndCreateErroneousElement(
node, 'this',
MessageKind.NO_THIS_AVAILABLE, const {},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return new StaticAccess.invalid(error);
}
return null;
}
/// Compute the [AccessSemantics] corresponding to a super access of [target].
AccessSemantics computeSuperAccessSemantics(Spannable node, Element target) {
if (target.isMalformed) {
return new StaticAccess.unresolvedSuper(target);
} else if (target.isGetter) {
return new StaticAccess.superGetter(target);
} else if (target.isSetter) {
return new StaticAccess.superSetter(target);
} else if (target.isField) {
if (target.isFinal) {
return new StaticAccess.superFinalField(target);
} else {
return new StaticAccess.superField(target);
}
} else {
assert(invariant(node, target.isFunction,
message: "Unexpected super target '$target'."));
return new StaticAccess.superMethod(target);
}
}
/// Compute the [AccessSemantics] corresponding to a compound super access
/// reading from [getter] and writing to [setter].
AccessSemantics computeCompoundSuperAccessSemantics(
Spannable node,
Element getter,
Element setter,
{bool isIndex: false}) {
if (getter.isMalformed) {
if (setter.isMalformed) {
return new StaticAccess.unresolvedSuper(getter);
} else if (setter.isFunction) {
assert(invariant(node, setter.name == '[]=',
message: "Unexpected super setter '$setter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter);
} else {
assert(invariant(node, setter.isSetter,
message: "Unexpected super setter '$setter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.UNRESOLVED_SUPER_GETTER, getter, setter);
}
} else if (getter.isField) {
if (setter.isMalformed) {
assert(invariant(node, getter.isFinal,
message: "Unexpected super setter '$setter' for getter '$getter."));
return new StaticAccess.superFinalField(getter);
} else if (setter.isField) {
if (getter == setter) {
return new StaticAccess.superField(getter);
} else {
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_FIELD_FIELD, getter, setter);
}
} else {
// Either the field is accessible directly, or a setter shadows the
// setter access. If there was another instance member it would shadow
// the field.
assert(invariant(node, setter.isSetter,
message: "Unexpected super setter '$setter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_FIELD_SETTER, getter, setter);
}
} else if (getter.isGetter) {
if (setter.isMalformed) {
return new CompoundAccessSemantics(
CompoundAccessKind.UNRESOLVED_SUPER_SETTER, getter, setter);
} else if (setter.isField) {
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_GETTER_FIELD, getter, setter);
} else {
assert(invariant(node, setter.isSetter,
message: "Unexpected super setter '$setter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter);
}
} else {
assert(invariant(node, getter.isFunction,
message: "Unexpected super getter '$getter'."));
if (setter.isMalformed) {
if (isIndex) {
return new CompoundAccessSemantics(
CompoundAccessKind.UNRESOLVED_SUPER_SETTER, getter, setter);
} else {
return new StaticAccess.superMethod(getter);
}
} else if (setter.isFunction) {
assert(invariant(node, setter.name == '[]=',
message: "Unexpected super setter '$setter'."));
assert(invariant(node, getter.name == '[]',
message: "Unexpected super getter '$getter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_GETTER_SETTER, getter, setter);
} else {
assert(invariant(node, setter.isSetter,
message: "Unexpected super setter '$setter'."));
return new CompoundAccessSemantics(
CompoundAccessKind.SUPER_METHOD_SETTER, getter, setter);
}
}
}
/// Compute the [AccessSemantics] corresponding to a local access of [target].
AccessSemantics computeLocalAccessSemantics(Spannable node,
LocalElement target) {
if (target.isParameter) {
if (target.isFinal || target.isConst) {
return new StaticAccess.finalParameter(target);
} else {
return new StaticAccess.parameter(target);
}
} else if (target.isVariable) {
if (target.isFinal || target.isConst) {
return new StaticAccess.finalLocalVariable(target);
} else {
return new StaticAccess.localVariable(target);
}
} else {
assert(invariant(node, target.isFunction,
message: "Unexpected local target '$target'."));
return new StaticAccess.localFunction(target);
}
}
/// Compute the [AccessSemantics] corresponding to a static or toplevel access
/// of [target].
AccessSemantics computeStaticOrTopLevelAccessSemantics(
Spannable node,
Element target) {
target = target.declaration;
if (target.isMalformed) {
// This handles elements with parser errors.
return new StaticAccess.unresolved(target);
}
if (target.isStatic) {
if (target.isGetter) {
return new StaticAccess.staticGetter(target);
} else if (target.isSetter) {
return new StaticAccess.staticSetter(target);
} else if (target.isField) {
if (target.isFinal || target.isConst) {
return new StaticAccess.finalStaticField(target);
} else {
return new StaticAccess.staticField(target);
}
} else {
assert(invariant(node, target.isFunction,
message: "Unexpected static target '$target'."));
return new StaticAccess.staticMethod(target);
}
} else {
assert(invariant(node, target.isTopLevel,
message: "Unexpected statically resolved target '$target'."));
if (target.isGetter) {
return new StaticAccess.topLevelGetter(target);
} else if (target.isSetter) {
return new StaticAccess.topLevelSetter(target);
} else if (target.isField) {
if (target.isFinal) {
return new StaticAccess.finalTopLevelField(target);
} else {
return new StaticAccess.topLevelField(target);
}
} else {
assert(invariant(node, target.isFunction,
message: "Unexpected top level target '$target'."));
return new StaticAccess.topLevelMethod(target);
}
}
}
/// Compute the [AccessSemantics] for accessing the name of [selector] on the
/// super class.
///
/// If no matching super member is found and error is reported and
/// `noSuchMethod` on `super` is registered. Furthermore, if [alternateName]
/// is provided, the [AccessSemantics] corresponding to the alternate name is
/// returned. For instance, the access of a super setter for an unresolved
/// getter:
///
/// class Super {
/// set name(_) {}
/// }
/// class Sub extends Super {
/// foo => super.name; // Access to the setter.
/// }
///
AccessSemantics computeSuperAccessSemanticsForSelector(
Spannable node,
Selector selector,
{Name alternateName}) {
Name name = selector.memberName;
// TODO(johnniwinther): Ensure correct behavior if currentClass is a
// patch.
Element target = currentClass.lookupSuperByName(name);
// [target] may be null which means invoking noSuchMethod on super.
if (target == null) {
if (alternateName != null) {
target = currentClass.lookupSuperByName(alternateName);
}
Element error;
if (selector.isSetter) {
error = reportAndCreateErroneousElement(
node, name.text, MessageKind.SETTER_NOT_FOUND_IN_SUPER,
{'className': currentClass.name, 'name': name});
} else {
error = reportAndCreateErroneousElement(
node, name.text, MessageKind.NO_SUCH_SUPER_MEMBER,
{'className': currentClass.name, 'memberName': name});
}
if (target == null) {
// If a setter wasn't resolved, use the [ErroneousElement].
target = error;
}
// We still need to register the invocation, because we might
// call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn].
registry.registerDynamicUse(new DynamicUse(selector, null));
registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD);
}
return computeSuperAccessSemantics(node, target);
}
/// Compute the [AccessSemantics] for accessing the name of [selector] on the
/// super class.
///
/// If no matching super member is found and error is reported and
/// `noSuchMethod` on `super` is registered. Furthermore, if [alternateName]
/// is provided, the [AccessSemantics] corresponding to the alternate name is
/// returned. For instance, the access of a super setter for an unresolved
/// getter:
///
/// class Super {
/// set name(_) {}
/// }
/// class Sub extends Super {
/// foo => super.name; // Access to the setter.
/// }
///
AccessSemantics computeSuperAccessSemanticsForSelectors(
Spannable node,
Selector getterSelector,
Selector setterSelector,
{bool isIndex: false}) {
bool getterError = false;
bool setterError = false;
// TODO(johnniwinther): Ensure correct behavior if currentClass is a
// patch.
Element getter = currentClass.lookupSuperByName(getterSelector.memberName);
// [target] may be null which means invoking noSuchMethod on super.
if (getter == null) {
getter = reportAndCreateErroneousElement(
node, getterSelector.name, MessageKind.NO_SUCH_SUPER_MEMBER,
{'className': currentClass.name, 'memberName': getterSelector.name});
getterError = true;
}
Element setter = currentClass.lookupSuperByName(setterSelector.memberName);
// [target] may be null which means invoking noSuchMethod on super.
if (setter == null) {
setter = reportAndCreateErroneousElement(
node, setterSelector.name, MessageKind.NO_SUCH_SUPER_MEMBER,
{'className': currentClass.name, 'memberName': setterSelector.name});
setterError = true;
} else if (getter == setter) {
if (setter.isFunction) {
setter = reportAndCreateErroneousElement(
node, setterSelector.name,
MessageKind.ASSIGNING_METHOD_IN_SUPER,
{'superclassName': setter.enclosingClass.name,
'name': setterSelector.name});
setterError = true;
} else if (setter.isField && setter.isFinal) {
setter = reportAndCreateErroneousElement(
node, setterSelector.name,
MessageKind.ASSIGNING_FINAL_FIELD_IN_SUPER,
{'superclassName': setter.enclosingClass.name,
'name': setterSelector.name});
setterError = true;
}
}
if (getterError) {
// We still need to register the invocation, because we might
// call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn].
registry.registerDynamicUse(
new DynamicUse(getterSelector, null));
}
if (setterError) {
// We still need to register the invocation, because we might
// call [:super.noSuchMethod:] which calls [JSInvocationMirror._invokeOn].
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
}
if (getterError || setterError) {
registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD);
}
return computeCompoundSuperAccessSemantics(
node, getter, setter, isIndex: isIndex);
}
/// Resolve [node] as a subexpression that is _not_ the prefix of a member
/// access. For instance `a` in `a + b`, as opposed to `a` in `a.b`.
ResolutionResult visitExpression(Node node) {
bool oldSendIsMemberAccess = sendIsMemberAccess;
sendIsMemberAccess = false;
ResolutionResult result = visit(node);
sendIsMemberAccess = oldSendIsMemberAccess;
return result;
}
/// Resolve [node] as a subexpression that _is_ the prefix of a member access.
/// For instance `a` in `a.b`, as opposed to `a` in `a + b`.
ResolutionResult visitExpressionPrefix(Node node) {
int oldAllowedCategory = allowedCategory;
bool oldSendIsMemberAccess = sendIsMemberAccess;
allowedCategory |= ElementCategory.PREFIX | ElementCategory.SUPER;
sendIsMemberAccess = true;
ResolutionResult result = visit(node);
sendIsMemberAccess = oldSendIsMemberAccess;
allowedCategory = oldAllowedCategory;
return result;
}
/// Handle a type test expression, like `a is T` and `a is! T`.
ResolutionResult handleIs(Send node) {
Node expression = node.receiver;
visitExpression(expression);
// TODO(johnniwinther): Use seen type tests to avoid registration of
// mutation/access to unpromoted variables.
Send notTypeNode = node.arguments.head.asSend();
DartType type;
SendStructure sendStructure;
if (notTypeNode != null) {
// `e is! T`.
Node typeNode = notTypeNode.receiver;
type = resolveTypeAnnotation(typeNode);
sendStructure = new IsNotStructure(type);
} else {
// `e is T`.
Node typeNode = node.arguments.head;
type = resolveTypeAnnotation(typeNode);
sendStructure = new IsStructure(type);
}
registry.registerTypeUse(new TypeUse.isCheck(type));
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
}
/// Handle a type cast expression, like `a as T`.
ResolutionResult handleAs(Send node) {
Node expression = node.receiver;
visitExpression(expression);
Node typeNode = node.arguments.head;
DartType type = resolveTypeAnnotation(typeNode);
registry.registerTypeUse(new TypeUse.asCast(type));
registry.registerSendStructure(node, new AsStructure(type));
return const NoneResult();
}
/// Handle the unary expression of an unresolved unary operator [text], like
/// the no longer supported `+a`.
ResolutionResult handleUnresolvedUnary(Send node, String text) {
Node expression = node.receiver;
if (node.isSuperCall) {
checkSuperAccess(node);
} else {
visitExpression(expression);
}
registry.registerSendStructure(node, const InvalidUnaryStructure());
return const NoneResult();
}
/// Handle the unary expression of a user definable unary [operator], like
/// `-a`, and `-super`.
ResolutionResult handleUserDefinableUnary(Send node, UnaryOperator operator) {
ResolutionResult result = const NoneResult();
Node expression = node.receiver;
Selector selector = operator.selector;
// TODO(23998): Remove this when all information goes through the
// [SendStructure].
registry.setSelector(node, selector);
AccessSemantics semantics;
if (node.isSuperCall) {
semantics = checkSuperAccess(node);
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelector(node, selector);
// TODO(johnniwinther): Add information to [AccessSemantics] about
// whether it is erroneous.
if (semantics.kind == AccessKind.SUPER_METHOD) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.element.declaration,
selector.callStructure));
}
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.useElement(node, semantics.element);
}
} else {
ResolutionResult expressionResult = visitExpression(expression);
semantics = const DynamicAccess.expression();
registry.registerDynamicUse(new DynamicUse(selector, null));
if (expressionResult.isConstant) {
bool isValidConstant;
ConstantExpression expressionConstant = expressionResult.constant;
DartType knownExpressionType =
expressionConstant.getKnownType(coreTypes);
switch (operator.kind) {
case UnaryOperatorKind.COMPLEMENT:
isValidConstant =
knownExpressionType == coreTypes.intType;
break;
case UnaryOperatorKind.NEGATE:
isValidConstant =
knownExpressionType == coreTypes.intType ||
knownExpressionType == coreTypes.doubleType;
break;
case UnaryOperatorKind.NOT:
reporter.internalError(node,
"Unexpected user definable unary operator: $operator");
}
if (isValidConstant) {
// TODO(johnniwinther): Handle potentially invalid constant
// expressions.
ConstantExpression constant =
new UnaryConstantExpression(operator, expressionConstant);
registry.setConstant(node, constant);
result = new ConstantResult(node, constant);
}
}
}
if (semantics != null) {
registry.registerSendStructure(node,
new UnaryStructure(semantics, operator));
}
return result;
}
/// Handle a not expression, like `!a`.
ResolutionResult handleNot(Send node, UnaryOperator operator) {
assert(invariant(node, operator.kind == UnaryOperatorKind.NOT));
Node expression = node.receiver;
ResolutionResult result = visitExpression(expression);
registry.registerSendStructure(node, const NotStructure());
if (result.isConstant) {
ConstantExpression expressionConstant = result.constant;
if (expressionConstant.getKnownType(coreTypes) == coreTypes.boolType) {
// TODO(johnniwinther): Handle potentially invalid constant expressions.
ConstantExpression constant =
new UnaryConstantExpression(operator, expressionConstant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
}
return const NoneResult();
}
/// Handle a logical and expression, like `a && b`.
ResolutionResult handleLogicalAnd(Send node) {
Node left = node.receiver;
Node right = node.arguments.head;
ResolutionResult leftResult =
doInPromotionScope(left, () => visitExpression(left));
ResolutionResult rightResult =
doInPromotionScope(right, () => visitExpression(right));
registry.registerSendStructure(node, const LogicalAndStructure());
if (leftResult.isConstant && rightResult.isConstant) {
ConstantExpression leftConstant = leftResult.constant;
ConstantExpression rightConstant = rightResult.constant;
if (leftConstant.getKnownType(coreTypes) == coreTypes.boolType &&
rightConstant.getKnownType(coreTypes) == coreTypes.boolType) {
// TODO(johnniwinther): Handle potentially invalid constant expressions.
ConstantExpression constant = new BinaryConstantExpression(
leftConstant,
BinaryOperator.LOGICAL_AND,
rightConstant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
}
return const NoneResult();
}
/// Handle a logical or expression, like `a || b`.
ResolutionResult handleLogicalOr(Send node) {
Node left = node.receiver;
Node right = node.arguments.head;
ResolutionResult leftResult = visitExpression(left);
ResolutionResult rightResult = visitExpression(right);
registry.registerSendStructure(node, const LogicalOrStructure());
if (leftResult.isConstant && rightResult.isConstant) {
ConstantExpression leftConstant = leftResult.constant;
ConstantExpression rightConstant = rightResult.constant;
if (leftConstant.getKnownType(coreTypes) == coreTypes.boolType &&
rightConstant.getKnownType(coreTypes) == coreTypes.boolType) {
// TODO(johnniwinther): Handle potentially invalid constant expressions.
ConstantExpression constant = new BinaryConstantExpression(
leftConstant,
BinaryOperator.LOGICAL_OR,
rightConstant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
}
return const NoneResult();
}
/// Handle an if-null expression, like `a ?? b`.
ResolutionResult handleIfNull(Send node) {
Node left = node.receiver;
Node right = node.arguments.head;
visitExpression(left);
visitExpression(right);
registry.registerSendStructure(node, const IfNullStructure());
return const NoneResult();
}
/// Handle the binary expression of an unresolved binary operator [text], like
/// the no longer supported `a === b`.
ResolutionResult handleUnresolvedBinary(Send node, String text) {
Node left = node.receiver;
Node right = node.arguments.head;
if (node.isSuperCall) {
checkSuperAccess(node);
} else {
visitExpression(left);
}
visitExpression(right);
registry.registerSendStructure(node, const InvalidBinaryStructure());
return const NoneResult();
}
/// Handle the binary expression of a user definable binary [operator], like
/// `a + b`, `super + b`, `a == b` and `a != b`.
ResolutionResult handleUserDefinableBinary(Send node,
BinaryOperator operator) {
ResolutionResult result = const NoneResult();
Node left = node.receiver;
Node right = node.arguments.head;
AccessSemantics semantics;
Selector selector;
if (operator.kind == BinaryOperatorKind.INDEX) {
selector = new Selector.index();
} else {
selector = new Selector.binaryOperator(operator.selectorName);
}
// TODO(23998): Remove this when all information goes through the
// [SendStructure].
registry.setSelector(node, selector);
if (node.isSuperCall) {
semantics = checkSuperAccess(node);
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelector(node, selector);
// TODO(johnniwinther): Add information to [AccessSemantics] about
// whether it is erroneous.
if (semantics.kind == AccessKind.SUPER_METHOD) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.element.declaration,
selector.callStructure));
}
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.useElement(node, semantics.element);
}
visitExpression(right);
} else {
ResolutionResult leftResult = visitExpression(left);
ResolutionResult rightResult = visitExpression(right);
registry.registerDynamicUse(new DynamicUse(selector, null));
semantics = const DynamicAccess.expression();
if (leftResult.isConstant && rightResult.isConstant) {
bool isValidConstant;
ConstantExpression leftConstant = leftResult.constant;
ConstantExpression rightConstant = rightResult.constant;
DartType knownLeftType = leftConstant.getKnownType(coreTypes);
DartType knownRightType = rightConstant.getKnownType(coreTypes);
switch (operator.kind) {
case BinaryOperatorKind.EQ:
case BinaryOperatorKind.NOT_EQ:
isValidConstant =
(knownLeftType == coreTypes.intType ||
knownLeftType == coreTypes.doubleType ||
knownLeftType == coreTypes.stringType ||
knownLeftType == coreTypes.boolType ||
knownLeftType == coreTypes.nullType) &&
(knownRightType == coreTypes.intType ||
knownRightType == coreTypes.doubleType ||
knownRightType == coreTypes.stringType ||
knownRightType == coreTypes.boolType ||
knownRightType == coreTypes.nullType);
break;
case BinaryOperatorKind.ADD:
isValidConstant =
(knownLeftType == coreTypes.intType ||
knownLeftType == coreTypes.doubleType ||
knownLeftType == coreTypes.stringType) &&
(knownRightType == coreTypes.intType ||
knownRightType == coreTypes.doubleType ||
knownRightType == coreTypes.stringType);
break;
case BinaryOperatorKind.SUB:
case BinaryOperatorKind.MUL:
case BinaryOperatorKind.DIV:
case BinaryOperatorKind.IDIV:
case BinaryOperatorKind.MOD:
case BinaryOperatorKind.GTEQ:
case BinaryOperatorKind.GT:
case BinaryOperatorKind.LTEQ:
case BinaryOperatorKind.LT:
isValidConstant =
(knownLeftType == coreTypes.intType ||
knownLeftType == coreTypes.doubleType) &&
(knownRightType == coreTypes.intType ||
knownRightType == coreTypes.doubleType);
break;
case BinaryOperatorKind.SHL:
case BinaryOperatorKind.SHR:
case BinaryOperatorKind.AND:
case BinaryOperatorKind.OR:
case BinaryOperatorKind.XOR:
isValidConstant =
knownLeftType == coreTypes.intType &&
knownRightType == coreTypes.intType;
break;
case BinaryOperatorKind.INDEX:
isValidConstant = false;
break;
case BinaryOperatorKind.LOGICAL_AND:
case BinaryOperatorKind.LOGICAL_OR:
case BinaryOperatorKind.IF_NULL:
reporter.internalError(
node, "Unexpected binary operator '${operator}'.");
break;
}
if (isValidConstant) {
// TODO(johnniwinther): Handle potentially invalid constant
// expressions.
ConstantExpression constant = new BinaryConstantExpression(
leftResult.constant,
operator,
rightResult.constant);
registry.setConstant(node, constant);
result = new ConstantResult(node, constant);
}
}
}
if (semantics != null) {
// TODO(johnniwinther): Support invalid super access as an
// [AccessSemantics].
SendStructure sendStructure;
switch (operator.kind) {
case BinaryOperatorKind.EQ:
sendStructure = new EqualsStructure(semantics);
break;
case BinaryOperatorKind.NOT_EQ:
sendStructure = new NotEqualsStructure(semantics);
break;
case BinaryOperatorKind.INDEX:
sendStructure = new IndexStructure(semantics);
break;
case BinaryOperatorKind.ADD:
case BinaryOperatorKind.SUB:
case BinaryOperatorKind.MUL:
case BinaryOperatorKind.DIV:
case BinaryOperatorKind.IDIV:
case BinaryOperatorKind.MOD:
case BinaryOperatorKind.SHL:
case BinaryOperatorKind.SHR:
case BinaryOperatorKind.GTEQ:
case BinaryOperatorKind.GT:
case BinaryOperatorKind.LTEQ:
case BinaryOperatorKind.LT:
case BinaryOperatorKind.AND:
case BinaryOperatorKind.OR:
case BinaryOperatorKind.XOR:
sendStructure = new BinaryStructure(semantics, operator);
break;
case BinaryOperatorKind.LOGICAL_AND:
case BinaryOperatorKind.LOGICAL_OR:
case BinaryOperatorKind.IF_NULL:
reporter.internalError(
node, "Unexpected binary operator '${operator}'.");
break;
}
registry.registerSendStructure(node, sendStructure);
}
return result;
}
/// Handle an invocation of an expression, like `(){}()` or `(foo)()`.
ResolutionResult handleExpressionInvoke(Send node) {
assert(invariant(node, node.isCall,
message: "Unexpected expression: $node"));
Node expression = node.selector;
visitExpression(expression);
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
Selector selector = callStructure.callSelector;
// TODO(23998): Remove this when all information goes through the
// [SendStructure].
registry.setSelector(node, selector);
registry.registerDynamicUse(new DynamicUse(selector, null));
registry.registerSendStructure(node,
new InvokeStructure(const DynamicAccess.expression(), selector));
return const NoneResult();
}
/// Handle access of a property of [name] on `this`, like `this.name` and
/// `this.name()`, or `name` and `name()` in instance context.
ResolutionResult handleThisPropertyAccess(Send node, Name name) {
AccessSemantics semantics = new DynamicAccess.thisProperty(name);
return handleDynamicAccessSemantics(node, name, semantics);
}
/// Handle update of a property of [name] on `this`, like `this.name = b` and
/// `this.name++`, or `name = b` and `name++` in instance context.
ResolutionResult handleThisPropertyUpdate(
SendSet node, Name name, Element element) {
AccessSemantics semantics = new DynamicAccess.thisProperty(name);
return handleDynamicUpdateSemantics(node, name, element, semantics);
}
/// Handle access on `this`, like `this()` and `this` when it is parsed as a
/// [Send] node.
ResolutionResult handleThisAccess(Send node) {
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
Selector selector = callStructure.callSelector;
// TODO(johnniwinther): Handle invalid this access as an
// [AccessSemantics].
AccessSemantics accessSemantics = checkThisAccess(node);
if (accessSemantics == null) {
accessSemantics = const DynamicAccess.thisAccess();
registry.registerDynamicUse(
new DynamicUse(selector, null));
}
registry.registerSendStructure(node,
new InvokeStructure(accessSemantics, selector));
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.setSelector(node, selector);
return const NoneResult();
} else {
// TODO(johnniwinther): Handle get of `this` when it is a [Send] node.
reporter.internalError(
node, "Unexpected node '$node'.");
}
return const NoneResult();
}
/// Handle access of a super property, like `super.foo` and `super.foo()`.
ResolutionResult handleSuperPropertyAccess(Send node, Name name) {
Element target;
Selector selector;
CallStructure callStructure;
if (node.isCall) {
callStructure = resolveArguments(node.argumentsNode).callStructure;
selector = new Selector.call(name, callStructure);
} else {
selector = new Selector.getter(name);
}
AccessSemantics semantics = checkSuperAccess(node);
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelector(
node, selector, alternateName: name.setter);
}
if (node.isCall) {
bool isIncompatibleInvoke = false;
switch (semantics.kind) {
case AccessKind.SUPER_METHOD:
MethodElementX superMethod = semantics.element;
superMethod.computeType(resolution);
if (!callStructure.signatureApplies(
superMethod.functionSignature)) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
registry.registerDynamicUse(
new DynamicUse(selector, null));
registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD);
isIncompatibleInvoke = true;
} else {
registry.registerStaticUse(
new StaticUse.superInvoke(semantics.element, callStructure));
}
break;
case AccessKind.SUPER_FIELD:
case AccessKind.SUPER_FINAL_FIELD:
case AccessKind.SUPER_GETTER:
registry.registerStaticUse(
new StaticUse.superGet(semantics.element));
selector = callStructure.callSelector;
registry.registerDynamicUse(
new DynamicUse(selector, null));
break;
case AccessKind.SUPER_SETTER:
case AccessKind.UNRESOLVED_SUPER:
// NoSuchMethod registered in [computeSuperSemantics].
break;
case AccessKind.INVALID:
// 'super' is not allowed.
break;
default:
reporter.internalError(
node, "Unexpected super property access $semantics.");
break;
}
registry.registerSendStructure(node,
isIncompatibleInvoke
? new IncompatibleInvokeStructure(semantics, selector)
: new InvokeStructure(semantics, selector));
} else {
switch (semantics.kind) {
case AccessKind.SUPER_METHOD:
// TODO(johnniwinther): Method this should be registered as a
// closurization.
registry.registerStaticUse(
new StaticUse.superTearOff(semantics.element));
break;
case AccessKind.SUPER_FIELD:
case AccessKind.SUPER_FINAL_FIELD:
case AccessKind.SUPER_GETTER:
registry.registerStaticUse(
new StaticUse.superGet(semantics.element));
break;
case AccessKind.SUPER_SETTER:
case AccessKind.UNRESOLVED_SUPER:
// NoSuchMethod registered in [computeSuperSemantics].
break;
case AccessKind.INVALID:
// 'super' is not allowed.
break;
default:
reporter.internalError(
node, "Unexpected super property access $semantics.");
break;
}
registry.registerSendStructure(node, new GetStructure(semantics));
}
target = semantics.element;
// TODO(23998): Remove these when all information goes through
// the [SendStructure].
registry.useElement(node, target);
registry.setSelector(node, selector);
return const NoneResult();
}
/// Handle a [Send] whose selector is an [Operator], like `a && b`, `a is T`,
/// `a + b`, and `~a`.
ResolutionResult handleOperatorSend(Send node) {
String operatorText = node.selector.asOperator().source;
if (operatorText == 'is') {
return handleIs(node);
} else if (operatorText == 'as') {
return handleAs(node);
} else if (node.arguments.isEmpty) {
UnaryOperator operator = UnaryOperator.parse(operatorText);
if (operator == null) {
return handleUnresolvedUnary(node, operatorText);
} else {
switch (operator.kind) {
case UnaryOperatorKind.NOT:
return handleNot(node, operator);
case UnaryOperatorKind.COMPLEMENT:
case UnaryOperatorKind.NEGATE:
assert(invariant(node, operator.isUserDefinable,
message: "Unexpected unary operator '${operator}'."));
return handleUserDefinableUnary(node, operator);
}
}
} else {
BinaryOperator operator = BinaryOperator.parse(operatorText);
if (operator == null) {
return handleUnresolvedBinary(node, operatorText);
} else {
switch (operator.kind) {
case BinaryOperatorKind.LOGICAL_AND:
return handleLogicalAnd(node);
case BinaryOperatorKind.LOGICAL_OR:
return handleLogicalOr(node);
case BinaryOperatorKind.IF_NULL:
return handleIfNull(node);
case BinaryOperatorKind.EQ:
case BinaryOperatorKind.NOT_EQ:
case BinaryOperatorKind.INDEX:
case BinaryOperatorKind.ADD:
case BinaryOperatorKind.SUB:
case BinaryOperatorKind.MUL:
case BinaryOperatorKind.DIV:
case BinaryOperatorKind.IDIV:
case BinaryOperatorKind.MOD:
case BinaryOperatorKind.SHL:
case BinaryOperatorKind.SHR:
case BinaryOperatorKind.GTEQ:
case BinaryOperatorKind.GT:
case BinaryOperatorKind.LTEQ:
case BinaryOperatorKind.LT:
case BinaryOperatorKind.AND:
case BinaryOperatorKind.OR:
case BinaryOperatorKind.XOR:
return handleUserDefinableBinary(node, operator);
}
}
}
}
/// Handle qualified access to an unresolved static class member, like `a.b`
/// or `a.b()` where `a` is a class and `b` is unresolved.
ResolutionResult handleUnresolvedStaticMemberAccess(
Send node, Name name, ClassElement receiverClass) {
// TODO(johnniwinther): Share code with [handleStaticInstanceMemberAccess]
// and [handlePrivateStaticMemberAccess].
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): Produce a different error if [name] is resolves to
// a constructor.
// TODO(johnniwinther): With the simplified [TreeElements] invariant,
// try to resolve injected elements if [currentClass] is in the patch
// library of [receiverClass].
// TODO(karlklose): this should be reported by the caller of
// [resolveSend] to select better warning messages for getters and
// setters.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.MEMBER_NOT_FOUND,
{'className': receiverClass.name, 'memberName': name.text});
// TODO(johnniwinther): Add an [AccessSemantics] for unresolved static
// member access.
return handleErroneousAccess(
node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified update to an unresolved static class member, like
/// `a.b = c` or `a.b++` where `a` is a class and `b` is unresolved.
ResolutionResult handleUnresolvedStaticMemberUpdate(
SendSet node, Name name, ClassElement receiverClass) {
// TODO(johnniwinther): Share code with [handleStaticInstanceMemberUpdate]
// and [handlePrivateStaticMemberUpdate].
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): Produce a different error if [name] is resolves to
// a constructor.
// TODO(johnniwinther): With the simplified [TreeElements] invariant,
// try to resolve injected elements if [currentClass] is in the patch
// library of [receiverClass].
// TODO(johnniwinther): Produce a different error for complex update.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.MEMBER_NOT_FOUND,
{'className': receiverClass.name, 'memberName': name.text});
// TODO(johnniwinther): Add an [AccessSemantics] for unresolved static
// member access.
return handleUpdate(node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified access of an instance member, like `a.b` or `a.b()` where
/// `a` is a class and `b` is a non-static member.
ResolutionResult handleStaticInstanceMemberAccess(
Send node, Name name, ClassElement receiverClass, Element member) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): With the simplified [TreeElements] invariant,
// try to resolve injected elements if [currentClass] is in the patch
// library of [receiverClass].
// TODO(karlklose): this should be reported by the caller of
// [resolveSend] to select better warning messages for getters and
// setters.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.MEMBER_NOT_STATIC,
{'className': receiverClass.name, 'memberName': name});
// TODO(johnniwinther): Add an [AccessSemantics] for statically accessed
// instance members.
return handleErroneousAccess(
node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified update of an instance member, like `a.b = c` or `a.b++`
/// where `a` is a class and `b` is a non-static member.
ResolutionResult handleStaticInstanceMemberUpdate(
SendSet node, Name name, ClassElement receiverClass, Element member) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): With the simplified [TreeElements] invariant,
// try to resolve injected elements if [currentClass] is in the patch
// library of [receiverClass].
// TODO(johnniwinther): Produce a different error for complex update.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.MEMBER_NOT_STATIC,
{'className': receiverClass.name, 'memberName': name});
// TODO(johnniwinther): Add an [AccessSemantics] for statically accessed
// instance members.
return handleUpdate(node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified access of an inaccessible private static class member,
/// like `a._b` or `a._b()` where `a` is class, `_b` is static member of `a`
/// but `a` is not defined in the current library.
ResolutionResult handlePrivateStaticMemberAccess(
Send node, Name name, ClassElement receiverClass, Element member) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.PRIVATE_ACCESS,
{'libraryName': member.library.libraryOrScriptName,
'name': name});
// TODO(johnniwinther): Add an [AccessSemantics] for unresolved static
// member access.
return handleErroneousAccess(
node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified update of an inaccessible private static class member,
/// like `a._b = c` or `a._b++` where `a` is class, `_b` is static member of
/// `a` but `a` is not defined in the current library.
ResolutionResult handlePrivateStaticMemberUpdate(
SendSet node, Name name, ClassElement receiverClass, Element member) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text, MessageKind.PRIVATE_ACCESS,
{'libraryName': member.library.libraryOrScriptName,
'name': name});
// TODO(johnniwinther): Add an [AccessSemantics] for unresolved static
// member access.
return handleUpdate(node, name, new StaticAccess.unresolved(error));
}
/// Handle qualified access to a static member, like `a.b` or `a.b()` where
/// `a` is a class and `b` is a static member of `a`.
ResolutionResult handleStaticMemberAccess(
Send node, Name memberName, ClassElement receiverClass) {
String name = memberName.text;
receiverClass.ensureResolved(resolution);
if (node.isOperator) {
// When the resolved receiver is a class, we can have two cases:
// 1) a static send: C.foo, or
// 2) an operator send, where the receiver is a class literal: 'C + 1'.
// The following code that looks up the selector on the resolved
// receiver will treat the second as the invocation of a static operator
// if the resolved receiver is not null.
return const NoneResult();
}
MembersCreator.computeClassMembersByName(
compiler, receiverClass.declaration, name);
Element member = receiverClass.lookupLocalMember(name);
if (member == null) {
return handleUnresolvedStaticMemberAccess(
node, memberName, receiverClass);
} else if (member.isAmbiguous) {
return handleAmbiguousSend(node, memberName, member);
} else if (member.isInstanceMember) {
return handleStaticInstanceMemberAccess(
node, memberName, receiverClass, member);
} else if (memberName.isPrivate && memberName.library != member.library) {
return handlePrivateStaticMemberAccess(
node, memberName, receiverClass, member);
} else {
return handleStaticOrTopLevelAccess(node, memberName, member);
}
}
/// Handle qualified update to a static member, like `a.b = c` or `a.b++`
/// where `a` is a class and `b` is a static member of `a`.
ResolutionResult handleStaticMemberUpdate(
Send node, Name memberName, ClassElement receiverClass) {
String name = memberName.text;
receiverClass.ensureResolved(resolution);
MembersCreator.computeClassMembersByName(
compiler, receiverClass.declaration, name);
Element member = receiverClass.lookupLocalMember(name);
if (member == null) {
return handleUnresolvedStaticMemberUpdate(
node, memberName, receiverClass);
} else if (member.isAmbiguous) {
return handleAmbiguousUpdate(node, memberName, member);
} else if (member.isInstanceMember) {
return handleStaticInstanceMemberUpdate(
node, memberName, receiverClass, member);
} else if (memberName.isPrivate && memberName.library != member.library) {
return handlePrivateStaticMemberUpdate(
node, memberName, receiverClass, member);
} else {
return handleStaticOrTopLevelUpdate(node, memberName, member);
}
}
/// Handle access to a type literal of type variable [element]. Like `T` or
/// `T()` where 'T' is type variable.
// TODO(johnniwinther): Remove [name] when [Selector] is not required for the
// the [GetStructure].
// TODO(johnniwinther): Remove [element] when it is no longer needed for
// evaluating constants.
ResolutionResult handleTypeVariableTypeLiteralAccess(
Send node,
Name name,
TypeVariableElement element) {
AccessSemantics semantics;
if (!Elements.hasAccessToTypeVariables(enclosingElement)) {
// TODO(johnniwinther): Add another access semantics for this.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text,
MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER,
{'typeVariableName': name},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
semantics = new StaticAccess.invalid(error);
// TODO(johnniwinther): Clean up registration of elements and selectors
// for this case.
} else {
semantics = new StaticAccess.typeParameterTypeLiteral(element);
}
registry.useElement(node, element);
registry.registerTypeLiteral(node, element.type);
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
Selector selector = callStructure.callSelector;
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.setSelector(node, selector);
registry.registerSendStructure(node,
new InvokeStructure(semantics, selector));
} else {
// TODO(johnniwinther): Avoid the need for a [Selector] here.
registry.registerSendStructure(node, new GetStructure(semantics));
}
return const NoneResult();
}
/// Handle access to a type literal of type variable [element]. Like `T = b`,
/// `T++` or `T += b` where 'T' is type variable.
ResolutionResult handleTypeVariableTypeLiteralUpdate(
SendSet node,
Name name,
TypeVariableElement element) {
AccessSemantics semantics;
if (!Elements.hasAccessToTypeVariables(enclosingElement)) {
// TODO(johnniwinther): Add another access semantics for this.
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text,
MessageKind.TYPE_VARIABLE_WITHIN_STATIC_MEMBER,
{'typeVariableName': name},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
semantics = new StaticAccess.invalid(error);
} else {
ErroneousElement error;
if (node.isIfNullAssignment) {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.IF_NULL_ASSIGNING_TYPE, const {});
// TODO(23998): Remove these when all information goes through
// the [SendStructure].
registry.useElement(node.selector, element);
} else {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.ASSIGNING_TYPE, const {});
}
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.useElement(node, error);
// TODO(johnniwinther): Register only on read?
registry.registerTypeLiteral(node, element.type);
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
semantics = new StaticAccess.typeParameterTypeLiteral(element);
}
return handleUpdate(node, name, semantics);
}
/// Handle access to a constant type literal of [type].
// TODO(johnniwinther): Remove [name] when [Selector] is not required for the
// the [GetStructure].
// TODO(johnniwinther): Remove [element] when it is no longer needed for
// evaluating constants.
ResolutionResult handleConstantTypeLiteralAccess(
Send node,
Name name,
TypeDeclarationElement element,
DartType type,
ConstantAccess semantics) {
registry.useElement(node, element);
registry.registerTypeLiteral(node, type);
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
Selector selector = callStructure.callSelector;
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.setSelector(node, selector);
// The node itself is not a constant but we register the selector (the
// identifier that refers to the class/typedef) as a constant.
registry.useElement(node.selector, element);
analyzeConstantDeferred(node.selector, enforceConst: false);
registry.registerSendStructure(node,
new InvokeStructure(semantics, selector));
return const NoneResult();
} else {
analyzeConstantDeferred(node, enforceConst: false);
registry.setConstant(node, semantics.constant);
registry.registerSendStructure(node, new GetStructure(semantics));
return new ConstantResult(node, semantics.constant);
}
}
/// Handle access to a constant type literal of [type].
// TODO(johnniwinther): Remove [name] when [Selector] is not required for the
// the [GetStructure].
// TODO(johnniwinther): Remove [element] when it is no longer needed for
// evaluating constants.
ResolutionResult handleConstantTypeLiteralUpdate(
SendSet node,
Name name,
TypeDeclarationElement element,
DartType type,
ConstantAccess semantics) {
// TODO(johnniwinther): Remove this when all constants are evaluated.
compiler.resolver.constantCompiler.evaluate(semantics.constant);
ErroneousElement error;
if (node.isIfNullAssignment) {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.IF_NULL_ASSIGNING_TYPE, const {});
// TODO(23998): Remove these when all information goes through
// the [SendStructure].
registry.setConstant(node.selector, semantics.constant);
registry.useElement(node.selector, element);
} else {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.ASSIGNING_TYPE, const {});
}
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.useElement(node, error);
registry.registerTypeLiteral(node, type);
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
return handleUpdate(node, name, semantics);
}
/// Handle access to a type literal of a typedef. Like `F` or
/// `F()` where 'F' is typedef.
ResolutionResult handleTypedefTypeLiteralAccess(
Send node,
Name name,
TypedefElement typdef) {
typdef.ensureResolved(resolution);
DartType type = typdef.rawType;
ConstantExpression constant = new TypeConstantExpression(type);
AccessSemantics semantics = new ConstantAccess.typedefTypeLiteral(constant);
return handleConstantTypeLiteralAccess(node, name, typdef, type, semantics);
}
/// Handle access to a type literal of a typedef. Like `F = b`, `F++` or
/// `F += b` where 'F' is typedef.
ResolutionResult handleTypedefTypeLiteralUpdate(
SendSet node,
Name name,
TypedefElement typdef) {
typdef.ensureResolved(resolution);
DartType type = typdef.rawType;
ConstantExpression constant = new TypeConstantExpression(type);
AccessSemantics semantics = new ConstantAccess.typedefTypeLiteral(constant);
return handleConstantTypeLiteralUpdate(node, name, typdef, type, semantics);
}
/// Handle access to a type literal of the type 'dynamic'. Like `dynamic` or
/// `dynamic()`.
ResolutionResult handleDynamicTypeLiteralAccess(Send node) {
DartType type = const DynamicType();
ConstantExpression constant = new TypeConstantExpression(
// TODO(johnniwinther): Use [type] when evaluation of constants is done
// directly on the constant expressions.
node.isCall ? coreTypes.typeType : type);
AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant);
return handleConstantTypeLiteralAccess(
node, const PublicName('dynamic'),
coreClasses.typeClass, type, semantics);
}
/// Handle update to a type literal of the type 'dynamic'. Like `dynamic++` or
/// `dynamic = 0`.
ResolutionResult handleDynamicTypeLiteralUpdate(SendSet node) {
DartType type = const DynamicType();
ConstantExpression constant =
new TypeConstantExpression(const DynamicType());
AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant);
return handleConstantTypeLiteralUpdate(
node, const PublicName('dynamic'),
coreClasses.typeClass, type, semantics);
}
/// Handle access to a type literal of a class. Like `C` or
/// `C()` where 'C' is class.
ResolutionResult handleClassTypeLiteralAccess(
Send node,
Name name,
ClassElement cls) {
cls.ensureResolved(resolution);
DartType type = cls.rawType;
ConstantExpression constant = new TypeConstantExpression(type);
AccessSemantics semantics = new ConstantAccess.classTypeLiteral(constant);
return handleConstantTypeLiteralAccess(node, name, cls, type, semantics);
}
/// Handle access to a type literal of a class. Like `C = b`, `C++` or
/// `C += b` where 'C' is class.
ResolutionResult handleClassTypeLiteralUpdate(
SendSet node,
Name name,
ClassElement cls) {
cls.ensureResolved(resolution);
DartType type = cls.rawType;
ConstantExpression constant = new TypeConstantExpression(type);
AccessSemantics semantics = new ConstantAccess.classTypeLiteral(constant);
return handleConstantTypeLiteralUpdate(node, name, cls, type, semantics);
}
/// Handle a [Send] that resolves to a [prefix]. Like `prefix` in
/// `prefix.Class` or `prefix` in `prefix()`, the latter being a compile time
/// error.
ResolutionResult handleClassSend(
Send node,
Name name,
ClassElement cls) {
cls.ensureResolved(resolution);
if (sendIsMemberAccess) {
registry.useElement(node, cls);
return new PrefixResult(null, cls);
} else {
// `C` or `C()` where 'C' is a class.
return handleClassTypeLiteralAccess(node, name, cls);
}
}
/// Compute a [DeferredPrefixStructure] for [node].
ResolutionResult handleDeferredAccess(
Send node,
PrefixElement prefix,
ResolutionResult result) {
assert(invariant(node, prefix.isDeferred,
message: "Prefix $prefix is not deferred."));
SendStructure sendStructure = registry.getSendStructure(node);
assert(invariant(node, sendStructure != null,
message: "No SendStructure for $node."));
registry.registerSendStructure(node,
new DeferredPrefixStructure(prefix, sendStructure));
if (result.isConstant) {
ConstantExpression constant =
new DeferredConstantExpression(result.constant, prefix);
registry.setConstant(node, constant);
result = new ConstantResult(node, constant);
}
return result;
}
/// Handle qualified [Send] where the receiver resolves to a [prefix],
/// like `prefix.toplevelFunction()` or `prefix.Class.staticField` where
/// `prefix` is a library prefix.
ResolutionResult handleLibraryPrefixSend(
Send node, Name name, PrefixElement prefix) {
ResolutionResult result;
Element member = prefix.lookupLocalMember(name.text);
if (member == null) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
Element error = reportAndCreateErroneousElement(
node, name.text, MessageKind.NO_SUCH_LIBRARY_MEMBER,
{'libraryName': prefix.name, 'memberName': name});
result = handleUnresolvedAccess(node, name, error);
} else {
result = handleResolvedSend(node, name, member);
}
if (result.kind == ResultKind.PREFIX) {
// [member] is a class prefix of a static access like `prefix.Class` of
// `prefix.Class.foo`. No need to call [handleDeferredAccess]; it will
// called on the parent `prefix.Class.foo` node.
result = new PrefixResult(prefix, result.element);
} else if (prefix.isDeferred &&
(member == null || !member.isDeferredLoaderGetter)) {
result = handleDeferredAccess(node, prefix, result);
}
return result;
}
/// Handle qualified [SendSet] where the receiver resolves to a [prefix],
/// like `prefix.toplevelField = b` or `prefix.Class.staticField++` where
/// `prefix` is a library prefix.
ResolutionResult handleLibraryPrefixSendSet(
SendSet node, Name name, PrefixElement prefix) {
ResolutionResult result;
Element member = prefix.lookupLocalMember(name.text);
if (member == null) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
Element error = reportAndCreateErroneousElement(
node, name.text, MessageKind.NO_SUCH_LIBRARY_MEMBER,
{'libraryName': prefix.name, 'memberName': name});
return handleUpdate(node, name, new StaticAccess.unresolved(error));
} else {
result = handleResolvedSendSet(node, name, member);
}
if (result.kind == ResultKind.PREFIX) {
// [member] is a class prefix of a static access like `prefix.Class` of
// `prefix.Class.foo`. No need to call [handleDeferredAccess]; it will
// called on the parent `prefix.Class.foo` node.
result = new PrefixResult(prefix, result.element);
} else if (prefix.isDeferred &&
(member == null || !member.isDeferredLoaderGetter)) {
result = handleDeferredAccess(node, prefix, result);
}
return result;
}
/// Handle a [Send] that resolves to a [prefix]. Like `prefix` in
/// `prefix.Class` or `prefix` in `prefix()`, the latter being a compile time
/// error.
ResolutionResult handleLibraryPrefix(
Send node,
Name name,
PrefixElement prefix) {
if ((ElementCategory.PREFIX & allowedCategory) == 0) {
ErroneousElement error = reportAndCreateErroneousElement(
node,
name.text,
MessageKind.PREFIX_AS_EXPRESSION,
{'prefix': name},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return handleErroneousAccess(
node, name, new StaticAccess.invalid(error));
}
if (prefix.isDeferred) {
// TODO(23998): Remove this when deferred access is detected
// through a [SendStructure].
registry.useElement(node.selector, prefix);
}
registry.useElement(node, prefix);
return new PrefixResult(prefix, null);
}
/// Handle qualified [Send] where the receiver resolves to an [Element], like
/// `a.b` where `a` is a prefix or a class.
ResolutionResult handlePrefixSend(
Send node, Name name, PrefixResult prefixResult) {
Element element = prefixResult.element;
if (element.isPrefix) {
if (node.isConditional) {
return handleLibraryPrefix(node, name, element);
} else {
return handleLibraryPrefixSend(node, name, element);
}
} else {
assert(element.isClass);
ResolutionResult result = handleStaticMemberAccess(node, name, element);
if (prefixResult.isDeferred) {
result = handleDeferredAccess(node, prefixResult.prefix, result);
}
return result;
}
}
/// Handle qualified [SendSet] where the receiver resolves to an [Element],
/// like `a.b = c` where `a` is a prefix or a class.
ResolutionResult handlePrefixSendSet(
SendSet node, Name name, PrefixResult prefixResult) {
Element element = prefixResult.element;
if (element.isPrefix) {
if (node.isConditional) {
return handleLibraryPrefix(node, name, element);
} else {
return handleLibraryPrefixSendSet(node, name, element);
}
} else {
assert(element.isClass);
ResolutionResult result = handleStaticMemberUpdate(node, name, element);
if (prefixResult.isDeferred) {
result = handleDeferredAccess(node, prefixResult.prefix, result);
}
return result;
}
}
/// Handle dynamic access of [semantics].
ResolutionResult handleDynamicAccessSemantics(
Send node, Name name, AccessSemantics semantics) {
SendStructure sendStructure;
Selector selector;
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
selector = new Selector.call(name, callStructure);
registry.registerDynamicUse(
new DynamicUse(selector, null));
sendStructure = new InvokeStructure(semantics, selector);
} else {
assert(invariant(node, node.isPropertyAccess));
selector = new Selector.getter(name);
registry.registerDynamicUse(
new DynamicUse(selector, null));
sendStructure = new GetStructure(semantics);
}
registry.registerSendStructure(node, sendStructure);
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.setSelector(node, selector);
return const NoneResult();
}
/// Handle dynamic update of [semantics].
ResolutionResult handleDynamicUpdateSemantics(
SendSet node, Name name, Element element, AccessSemantics semantics) {
Selector getterSelector = new Selector.getter(name);
Selector setterSelector = new Selector.setter(name.setter);
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
if (node.isComplex) {
registry.registerDynamicUse(
new DynamicUse(getterSelector, null));
}
// TODO(23998): Remove these when elements are only accessed through the
// send structure.
Element getter = element;
Element setter = element;
if (element != null && element.isAbstractField) {
AbstractFieldElement abstractField = element;
getter = abstractField.getter;
setter = abstractField.setter;
}
if (setter != null) {
registry.useElement(node, setter);
if (getter != null && node.isComplex) {
registry.useElement(node.selector, getter);
}
}
return handleUpdate(node, name, semantics);
}
/// Handle `this` as a qualified property, like `a.this`.
ResolutionResult handleQualifiedThisAccess(Send node, Name name) {
ErroneousElement error = reportAndCreateErroneousElement(
node.selector,
name.text,
MessageKind.THIS_PROPERTY, {},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
AccessSemantics accessSemantics = new StaticAccess.invalid(error);
return handleErroneousAccess(node, name, accessSemantics);
}
/// Handle a qualified [Send], that is where the receiver is non-null, like
/// `a.b`, `a.b()`, `this.a()` and `super.a()`.
ResolutionResult handleQualifiedSend(Send node) {
Identifier selector = node.selector.asIdentifier();
String text = selector.source;
Name name = new Name(text, enclosingElement.library);
if (text == 'this') {
return handleQualifiedThisAccess(node, name);
} else if (node.isSuperCall) {
return handleSuperPropertyAccess(node, name);
} else if (node.receiver.isThis()) {
AccessSemantics semantics = checkThisAccess(node);
if (semantics == null) {
return handleThisPropertyAccess(node, name);
} else {
// TODO(johnniwinther): Handle invalid this access as an
// [AccessSemantics].
return handleErroneousAccess(node, name, semantics);
}
}
ResolutionResult result = visitExpressionPrefix(node.receiver);
if (result.kind == ResultKind.PREFIX) {
return handlePrefixSend(node, name, result);
} else if (node.isConditional) {
return handleDynamicAccessSemantics(
node, name, new DynamicAccess.ifNotNullProperty(name));
} else {
// Handle dynamic property access, like `a.b` or `a.b()` where `a` is not
// a prefix or class.
// TODO(johnniwinther): Use the `element` of [result].
return handleDynamicAccessSemantics(
node, name, new DynamicAccess.dynamicProperty(name));
}
}
/// Handle a qualified [SendSet], that is where the receiver is non-null, like
/// `a.b = c`, `a.b++`, and `a.b += c`.
ResolutionResult handleQualifiedSendSet(SendSet node) {
Identifier selector = node.selector.asIdentifier();
String text = selector.source;
Name name = new Name(text, enclosingElement.library);
if (text == 'this') {
return handleQualifiedThisAccess(node, name);
} else if (node.receiver.isThis()) {
AccessSemantics semantics = checkThisAccess(node);
if (semantics == null) {
return handleThisPropertyUpdate(node, name, null);
} else {
// TODO(johnniwinther): Handle invalid this access as an
// [AccessSemantics].
return handleUpdate(node, name, semantics);
}
}
ResolutionResult result = visitExpressionPrefix(node.receiver);
if (result.kind == ResultKind.PREFIX) {
return handlePrefixSendSet(node, name, result);
} else if (node.isConditional) {
return handleDynamicUpdateSemantics(
node, name, null, new DynamicAccess.ifNotNullProperty(name));
} else {
// Handle dynamic property access, like `a.b = c`, `a.b++` or `a.b += c`
// where `a` is not a prefix or class.
// TODO(johnniwinther): Use the `element` of [result].
return handleDynamicUpdateSemantics(
node, name, null, new DynamicAccess.dynamicProperty(name));
}
}
/// Handle access unresolved access to [name] in a non-instance context.
ResolutionResult handleUnresolvedAccess(
Send node, Name name, Element element) {
// TODO(johnniwinther): Support unresolved top level access as an
// [AccessSemantics].
AccessSemantics semantics = new StaticAccess.unresolved(element);
return handleErroneousAccess(node, name, semantics);
}
/// Handle erroneous access of [element] of the given [semantics].
ResolutionResult handleErroneousAccess(
Send node, Name name, AccessSemantics semantics) {
SendStructure sendStructure;
Selector selector;
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
selector = new Selector.call(name, callStructure);
registry.registerDynamicUse(new DynamicUse(selector, null));
sendStructure = new InvokeStructure(semantics, selector);
} else {
assert(invariant(node, node.isPropertyAccess));
selector = new Selector.getter(name);
registry.registerDynamicUse(new DynamicUse(selector, null));
sendStructure = new GetStructure(semantics);
}
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.setSelector(node, selector);
registry.useElement(node, semantics.element);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
}
/// Handle access to an ambiguous element, that is, a name imported twice.
ResolutionResult handleAmbiguousSend(
Send node,
Name name,
AmbiguousElement element) {
ErroneousElement error = reportAndCreateErroneousElement(
node,
name.text,
element.messageKind,
element.messageArguments,
infos: element.computeInfos(enclosingElement, reporter));
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): Support ambiguous access as an [AccessSemantics].
AccessSemantics semantics = new StaticAccess.unresolved(error);
return handleErroneousAccess(node, name, semantics);
}
/// Handle update to an ambiguous element, that is, a name imported twice.
ResolutionResult handleAmbiguousUpdate(
SendSet node,
Name name,
AmbiguousElement element) {
ErroneousElement error = reportAndCreateErroneousElement(
node,
name.text,
element.messageKind,
element.messageArguments,
infos: element.computeInfos(enclosingElement, reporter));
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(johnniwinther): Support ambiguous access as an [AccessSemantics].
AccessSemantics accessSemantics = new StaticAccess.unresolved(error);
return handleUpdate(node, name, accessSemantics);
}
/// Report access of an instance [member] from a non-instance context.
AccessSemantics reportStaticInstanceAccess(Send node, Name name) {
ErroneousElement error = reportAndCreateErroneousElement(
node, name.text,
MessageKind.NO_INSTANCE_AVAILABLE, {'name': name},
isError: true);
// TODO(johnniwinther): Support static instance access as an
// [AccessSemantics].
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return new StaticAccess.invalid(error);
}
/// Handle access of a parameter, local variable or local function.
ResolutionResult handleLocalAccess(Send node, Name name, Element element) {
ResolutionResult result = const NoneResult();
AccessSemantics semantics = computeLocalAccessSemantics(node, element);
Selector selector;
if (node.isCall) {
CallStructure callStructure =
resolveArguments(node.argumentsNode).callStructure;
selector = new Selector.call(name, callStructure);
bool isIncompatibleInvoke = false;
switch (semantics.kind) {
case AccessKind.LOCAL_FUNCTION:
LocalFunctionElementX function = semantics.element;
function.computeType(resolution);
if (!callStructure.signatureApplies(function.functionSignature)) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
registry.registerDynamicUse(
new DynamicUse(selector, null));
isIncompatibleInvoke = true;
}
break;
case AccessKind.PARAMETER:
case AccessKind.FINAL_PARAMETER:
case AccessKind.LOCAL_VARIABLE:
case AccessKind.FINAL_LOCAL_VARIABLE:
selector = callStructure.callSelector;
registry.registerDynamicUse(
new DynamicUse(selector, null));
break;
default:
reporter.internalError(node,
"Unexpected local access $semantics.");
break;
}
registry.registerSendStructure(node,
isIncompatibleInvoke
? new IncompatibleInvokeStructure(semantics, selector)
: new InvokeStructure(semantics, selector));
} else {
switch (semantics.kind) {
case AccessKind.LOCAL_VARIABLE:
case AccessKind.LOCAL_FUNCTION:
result = new ElementResult(element);
break;
case AccessKind.PARAMETER:
case AccessKind.FINAL_PARAMETER:
if (constantState == ConstantState.CONSTANT_INITIALIZER) {
ParameterElement parameter = element;
if (parameter.isNamed) {
result = new ConstantResult(
node,
new NamedArgumentReference(parameter.name),
element: element);
} else {
result = new ConstantResult(
node,
new PositionalArgumentReference(
parameter.functionDeclaration.parameters.indexOf(
parameter)),
element: element);
}
} else {
result = new ElementResult(element);
}
break;
case AccessKind.FINAL_LOCAL_VARIABLE:
if (element.isConst) {
result = new ConstantResult(
node,
new VariableConstantExpression(element),
element: element);
} else {
result = new ElementResult(element);
}
break;
default:
reporter.internalError(node,
"Unexpected local access $semantics.");
break;
}
selector = new Selector.getter(name);
registry.registerSendStructure(node, new GetStructure(semantics));
}
// TODO(23998): Remove these when all information goes through
// the [SendStructure].
registry.useElement(node, element);
registry.setSelector(node, selector);
registerPotentialAccessInClosure(node, element);
return result;
}
/// Handle update of a parameter, local variable or local function.
ResolutionResult handleLocalUpdate(Send node, Name name, Element element) {
AccessSemantics semantics;
ErroneousElement error;
if (element.isParameter) {
if (element.isFinal) {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_SETTER, const {});
semantics = new StaticAccess.finalParameter(element);
} else {
semantics = new StaticAccess.parameter(element);
}
} else if (element.isVariable) {
if (element.isFinal || element.isConst) {
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_SETTER, const {});
semantics = new StaticAccess.finalLocalVariable(element);
} else {
semantics = new StaticAccess.localVariable(element);
}
} else {
assert(invariant(node, element.isFunction,
message: "Unexpected local $element."));
error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.ASSIGNING_METHOD, const {});
semantics = new StaticAccess.localFunction(element);
}
if (isPotentiallyMutableTarget(element)) {
registry.registerPotentialMutation(element, node);
if (enclosingElement != element.enclosingElement) {
registry.registerPotentialMutationInClosure(element, node);
}
for (Node scope in promotionScope) {
registry.registerPotentialMutationIn(scope, element, node);
}
}
ResolutionResult result = handleUpdate(node, name, semantics);
if (error != null) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// TODO(23998): Remove this when all information goes through
// the [SendStructure].
registry.useElement(node, error);
}
return result;
}
/// Handle access of a static or top level [element].
ResolutionResult handleStaticOrTopLevelAccess(
Send node, Name name, Element element) {
ResolutionResult result = const NoneResult();
MemberElement member;
if (element.isAbstractField) {
AbstractFieldElement abstractField = element;
if (abstractField.getter != null) {
member = abstractField.getter;
} else {
member = abstractField.setter;
}
} else {
member = element;
}
// TODO(johnniwinther): Needed to provoke a parsing and with it discovery
// of parse errors to make [element] erroneous. Fix this!
member.computeType(resolution);
if (member == compiler.mirrorSystemGetNameFunction &&
!compiler.mirrorUsageAnalyzerTask.hasMirrorUsage(enclosingElement)) {
reporter.reportHintMessage(
node.selector, MessageKind.STATIC_FUNCTION_BLOAT,
{'class': compiler.mirrorSystemClass.name,
'name': compiler.mirrorSystemGetNameFunction.name});
}
Selector selector;
AccessSemantics semantics =
computeStaticOrTopLevelAccessSemantics(node, member);
if (node.isCall) {
ArgumentsResult argumentsResult =
resolveArguments(node.argumentsNode);
CallStructure callStructure = argumentsResult.callStructure;
selector = new Selector.call(name, callStructure);
bool isIncompatibleInvoke = false;
switch (semantics.kind) {
case AccessKind.STATIC_METHOD:
case AccessKind.TOPLEVEL_METHOD:
MethodElement method = semantics.element;
method.computeType(resolution);
if (!callStructure.signatureApplies(method.functionSignature)) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
registry.registerDynamicUse(
new DynamicUse(selector, null));
isIncompatibleInvoke = true;
} else {
registry.registerStaticUse(
new StaticUse.staticInvoke(semantics.element, callStructure));
handleForeignCall(node, semantics.element, callStructure);
if (method == compiler.identicalFunction &&
argumentsResult.isValidAsConstant) {
result = new ConstantResult(node,
new IdenticalConstantExpression(
argumentsResult.argumentResults[0].constant,
argumentsResult.argumentResults[1].constant));
}
}
break;
case AccessKind.STATIC_FIELD:
case AccessKind.FINAL_STATIC_FIELD:
case AccessKind.STATIC_GETTER:
case AccessKind.TOPLEVEL_FIELD:
case AccessKind.FINAL_TOPLEVEL_FIELD:
case AccessKind.TOPLEVEL_GETTER:
registry.registerStaticUse(
new StaticUse.staticGet(semantics.element));
selector = callStructure.callSelector;
registry.registerDynamicUse(
new DynamicUse(selector, null));
break;
case AccessKind.STATIC_SETTER:
case AccessKind.TOPLEVEL_SETTER:
case AccessKind.UNRESOLVED:
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
member = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_GETTER, const {});
break;
default:
reporter.internalError(node,
"Unexpected statically resolved access $semantics.");
break;
}
registry.registerSendStructure(node,
isIncompatibleInvoke
? new IncompatibleInvokeStructure(semantics, selector)
: new InvokeStructure(semantics, selector));
} else {
selector = new Selector.getter(name);
switch (semantics.kind) {
case AccessKind.STATIC_METHOD:
case AccessKind.TOPLEVEL_METHOD:
registry.registerStaticUse(
new StaticUse.staticTearOff(semantics.element));
break;
case AccessKind.STATIC_FIELD:
case AccessKind.FINAL_STATIC_FIELD:
case AccessKind.STATIC_GETTER:
case AccessKind.TOPLEVEL_FIELD:
case AccessKind.FINAL_TOPLEVEL_FIELD:
case AccessKind.TOPLEVEL_GETTER:
registry.registerStaticUse(
new StaticUse.staticGet(semantics.element));
break;
case AccessKind.STATIC_SETTER:
case AccessKind.TOPLEVEL_SETTER:
case AccessKind.UNRESOLVED:
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
member = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_GETTER, const {});
break;
default:
reporter.internalError(node,
"Unexpected statically resolved access $semantics.");
break;
}
registry.registerSendStructure(node, new GetStructure(semantics));
if (member.isConst) {
FieldElement field = member;
result = new ConstantResult(
node, new VariableConstantExpression(field), element: field);
} else {
result = new ElementResult(member);
}
}
// TODO(23998): Remove these when all information goes through
// the [SendStructure].
registry.useElement(node, member);
registry.setSelector(node, selector);
return result;
}
/// Handle update of a static or top level [element].
ResolutionResult handleStaticOrTopLevelUpdate(
SendSet node, Name name, Element element) {
AccessSemantics semantics;
if (element.isAbstractField) {
AbstractFieldElement abstractField = element;
if (abstractField.setter == null) {
ErroneousElement error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_SETTER, const {});
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
if (node.isComplex) {
// `a++` or `a += b` where `a` has no setter.
semantics = new CompoundAccessSemantics(
element.isTopLevel
? CompoundAccessKind.UNRESOLVED_TOPLEVEL_SETTER
: CompoundAccessKind.UNRESOLVED_STATIC_SETTER,
abstractField.getter,
error);
} else {
// `a = b` where `a` has no setter.
semantics = element.isTopLevel
? new StaticAccess.topLevelGetter(abstractField.getter)
: new StaticAccess.staticGetter(abstractField.getter);
}
registry.registerStaticUse(
new StaticUse.staticGet(abstractField.getter));
} else if (node.isComplex) {
if (abstractField.getter == null) {
ErroneousElement error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_GETTER, const {});
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
// `a++` or `a += b` where `a` has no getter.
semantics = new CompoundAccessSemantics(
element.isTopLevel
? CompoundAccessKind.UNRESOLVED_TOPLEVEL_GETTER
: CompoundAccessKind.UNRESOLVED_STATIC_GETTER,
error,
abstractField.setter);
registry.registerStaticUse(
new StaticUse.staticSet(abstractField.setter));
} else {
// `a++` or `a += b` where `a` has both a getter and a setter.
semantics = new CompoundAccessSemantics(
element.isTopLevel
? CompoundAccessKind.TOPLEVEL_GETTER_SETTER
: CompoundAccessKind.STATIC_GETTER_SETTER,
abstractField.getter,
abstractField.setter);
registry.registerStaticUse(
new StaticUse.staticGet(abstractField.getter));
registry.registerStaticUse(
new StaticUse.staticSet(abstractField.setter));
}
} else {
// `a = b` where `a` has a setter.
semantics = element.isTopLevel
? new StaticAccess.topLevelSetter(abstractField.setter)
: new StaticAccess.staticSetter(abstractField.setter);
registry.registerStaticUse(
new StaticUse.staticSet(abstractField.setter));
}
} else {
MemberElement member = element;
// TODO(johnniwinther): Needed to provoke a parsing and with it discovery
// of parse errors to make [element] erroneous. Fix this!
member.computeType(resolution);
if (member.isMalformed) {
// [member] has parse errors.
semantics = new StaticAccess.unresolved(member);
} else if (member.isFunction) {
// `a = b`, `a++` or `a += b` where `a` is a function.
ErroneousElement error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.ASSIGNING_METHOD, const {});
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
if (node.isComplex) {
// `a++` or `a += b` where `a` is a function.
registry.registerStaticUse(
new StaticUse.staticTearOff(element));
}
semantics = member.isTopLevel
? new StaticAccess.topLevelMethod(member)
: new StaticAccess.staticMethod(member);
} else {
// `a = b`, `a++` or `a += b` where `a` is a field.
assert(invariant(node, member.isField,
message: "Unexpected element: $member."));
if (node.isComplex) {
// `a++` or `a += b` where `a` is a field.
registry.registerStaticUse(new StaticUse.staticGet(member));
}
if (member.isFinal || member.isConst) {
ErroneousElement error = reportAndCreateErroneousElement(
node.selector, name.text,
MessageKind.CANNOT_RESOLVE_SETTER, const {});
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
semantics = member.isTopLevel
? new StaticAccess.finalTopLevelField(member)
: new StaticAccess.finalStaticField(member);
} else {
registry.registerStaticUse(new StaticUse.staticSet(member));
semantics = member.isTopLevel
? new StaticAccess.topLevelField(member)
: new StaticAccess.staticField(member);
}
}
}
return handleUpdate(node, name, semantics);
}
/// Handle access to resolved [element].
ResolutionResult handleResolvedSend(Send node, Name name, Element element) {
if (element.isAmbiguous) {
return handleAmbiguousSend(node, name, element);
}
if (element.isMalformed) {
// This handles elements with parser errors.
assert(invariant(node, element is! ErroneousElement,
message: "Unexpected erroneous element $element."));
return handleErroneousAccess(node, name,
new StaticAccess.unresolved(element));
}
if (element.isInstanceMember) {
if (inInstanceContext) {
// TODO(johnniwinther): Maybe use the found [element].
return handleThisPropertyAccess(node, name);
} else {
return handleErroneousAccess(
node, name, reportStaticInstanceAccess(node, name));
}
}
if (element.isClass) {
// `C`, `C()`, or 'C.b` where 'C' is a class.
return handleClassSend(node, name, element);
} else if (element.isTypedef) {
// `F` or `F()` where 'F' is a typedef.
return handleTypedefTypeLiteralAccess(node, name, element);
} else if (element.isTypeVariable) {
return handleTypeVariableTypeLiteralAccess(node, name, element);
} else if (element.isPrefix) {
return handleLibraryPrefix(node, name, element);
} else if (element.isLocal) {
return handleLocalAccess(node, name, element);
} else if (element.isStatic || element.isTopLevel) {
return handleStaticOrTopLevelAccess(node, name, element);
}
return reporter.internalError(node, "Unexpected resolved send: $element");
}
/// Handle update to resolved [element].
ResolutionResult handleResolvedSendSet(
SendSet node, Name name, Element element) {
if (element.isAmbiguous) {
return handleAmbiguousUpdate(node, name, element);
}
if (element.isMalformed) {
// This handles elements with parser errors..
assert(invariant(node, element is! ErroneousElement,
message: "Unexpected erroneous element $element."));
return handleUpdate(node, name,new StaticAccess.unresolved(element));
}
if (element.isInstanceMember) {
if (inInstanceContext) {
return handleThisPropertyUpdate(node, name, element);
} else {
return handleUpdate(node, name, reportStaticInstanceAccess(node, name));
}
}
if (element.isClass) {
// `C = b`, `C++`, or 'C += b` where 'C' is a class.
return handleClassTypeLiteralUpdate(node, name, element);
} else if (element.isTypedef) {
// `C = b`, `C++`, or 'C += b` where 'F' is a typedef.
return handleTypedefTypeLiteralUpdate(node, name, element);
} else if (element.isTypeVariable) {
// `T = b`, `T++`, or 'T += b` where 'T' is a type variable.
return handleTypeVariableTypeLiteralUpdate(node, name, element);
} else if (element.isPrefix) {
// `p = b` where `p` is a prefix.
ErroneousElement error = reportAndCreateErroneousElement(
node,
name.text,
MessageKind.PREFIX_AS_EXPRESSION,
{'prefix': name},
isError: true);
registry.registerFeature(Feature.COMPILE_TIME_ERROR);
return handleUpdate(
node, name, new StaticAccess.invalid(error));
} else if (element.isLocal) {
return handleLocalUpdate(node, name, element);
} else if (element.isStatic || element.isTopLevel) {
return handleStaticOrTopLevelUpdate(node, name, element);
}
return reporter.internalError(node, "Unexpected resolved send: $element");
}
/// Handle an unqualified [Send], that is where the `node.receiver` is null,
/// like `a`, `a()`, `this()`, `assert()`, and `(){}()`.
ResolutionResult handleUnqualifiedSend(Send node) {
Identifier selector = node.selector.asIdentifier();
if (selector == null) {
// `(){}()` and `(foo)()`.
return handleExpressionInvoke(node);
}
String text = selector.source;
if (text == 'this') {
// `this()`.
return handleThisAccess(node);
}
// `name` or `name()`
Name name = new Name(text, enclosingElement.library);
Element element = lookupInScope(reporter, node, scope, text);
if (element == null) {
if (text == 'dynamic') {
// `dynamic` or `dynamic()` where 'dynamic' is not declared in the
// current scope.
return handleDynamicTypeLiteralAccess(node);
} else if (inInstanceContext) {
// Implicitly `this.name`.
return handleThisPropertyAccess(node, name);
} else {
// Create [ErroneousElement] for unresolved access.
ErroneousElement error = reportCannotResolve(node, text);
return handleUnresolvedAccess(node, name, error);
}
} else {
return handleResolvedSend(node, name, element);
}
}
/// Handle an unqualified [SendSet], that is where the `node.receiver` is
/// null, like `a = b`, `a++`, and `a += b`.
ResolutionResult handleUnqualifiedSendSet(SendSet node) {
Identifier selector = node.selector.asIdentifier();
String text = selector.source;
Name name = new Name(text, enclosingElement.library);
Element element = lookupInScope(reporter, node, scope, text);
if (element == null) {
if (text == 'dynamic') {
// `dynamic = b`, `dynamic++`, or `dynamic += b` where 'dynamic' is not
// declared in the current scope.
return handleDynamicTypeLiteralUpdate(node);
} else if (inInstanceContext) {
// Left-hand side is implicitly `this.name`.
return handleThisPropertyUpdate(node, name, null);
} else {
// Create [ErroneousElement] for unresolved access.
ErroneousElement error = reportCannotResolve(node, text);
return handleUpdate(node, name, new StaticAccess.unresolved(error));
}
} else {
return handleResolvedSendSet(node, name, element);
}
}
ResolutionResult visitSend(Send node) {
if (node.isOperator) {
// `a && b`, `a + b`, `-a`, or `a is T`.
return handleOperatorSend(node);
} else if (node.receiver != null) {
// `a.b`.
return handleQualifiedSend(node);
} else {
// `a`.
return handleUnqualifiedSend(node);
}
}
/// Register read access of [target] inside a closure.
void registerPotentialAccessInClosure(Send node, Element target) {
if (isPotentiallyMutableTarget(target)) {
if (enclosingElement != target.enclosingElement) {
for (Node scope in promotionScope) {
registry.setAccessedByClosureIn(scope, target, node);
}
}
}
}
// TODO(johnniwinther): Move this to the backend resolution callbacks.
void handleForeignCall(Send node,
Element target,
CallStructure callStructure) {
if (target != null && compiler.backend.isForeign(target)) {
registry.registerForeignCall(node, target, callStructure, this);
}
}
/// Callback for native enqueuer to parse a type. Returns [:null:] on error.
DartType resolveTypeFromString(Node node, String typeName) {
Element element = lookupInScope(reporter, node, scope, typeName);
if (element == null) return null;
if (element is! ClassElement) return null;
ClassElement cls = element;
cls.ensureResolved(resolution);
return cls.computeType(resolution);
}
/// Handle index operations like `a[b] = c`, `a[b] += c`, and `a[b]++`.
ResolutionResult handleIndexSendSet(SendSet node) {
String operatorText = node.assignmentOperator.source;
Node receiver = node.receiver;
Node index = node.arguments.head;
visitExpression(receiver);
visitExpression(index);
AccessSemantics semantics = const DynamicAccess.expression();
if (node.isPrefix || node.isPostfix) {
// `a[b]++` or `++a[b]`.
IncDecOperator operator = IncDecOperator.parse(operatorText);
Selector getterSelector = new Selector.index();
Selector setterSelector = new Selector.indexSet();
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
registry.registerDynamicUse(
new DynamicUse(getterSelector, null));
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure = node.isPrefix
? new IndexPrefixStructure(semantics, operator)
: new IndexPostfixStructure(semantics, operator);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
} else {
Node rhs = node.arguments.tail.head;
visitExpression(rhs);
AssignmentOperator operator = AssignmentOperator.parse(operatorText);
if (operator.kind == AssignmentOperatorKind.ASSIGN) {
// `a[b] = c`.
Selector setterSelector = new Selector.indexSet();
// TODO(23998): Remove this when selectors are only accessed
// through the send structure.
registry.setSelector(node, setterSelector);
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
SendStructure sendStructure = new IndexSetStructure(semantics);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
} else {
// `a[b] += c`.
Selector getterSelector = new Selector.index();
Selector setterSelector = new Selector.indexSet();
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
registry.registerDynamicUse(
new DynamicUse(getterSelector, null));
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure =
new CompoundIndexSetStructure(semantics, operator);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
}
}
}
/// Handle super index operations like `super[a] = b`, `super[a] += b`, and
/// `super[a]++`.
// TODO(johnniwinther): Share code with [handleIndexSendSet].
ResolutionResult handleSuperIndexSendSet(SendSet node) {
String operatorText = node.assignmentOperator.source;
Node index = node.arguments.head;
visitExpression(index);
AccessSemantics semantics = checkSuperAccess(node);
if (node.isPrefix || node.isPostfix) {
// `super[a]++` or `++super[a]`.
IncDecOperator operator = IncDecOperator.parse(operatorText);
Selector getterSelector = new Selector.index();
Selector setterSelector = new Selector.indexSet();
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelectors(
node, getterSelector, setterSelector, isIndex: true);
if (!semantics.getter.isError) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.getter, getterSelector.callStructure));
}
if (!semantics.setter.isError) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.setter, setterSelector.callStructure));
}
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
registry.useElement(node.selector, semantics.getter);
}
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure = node.isPrefix
? new IndexPrefixStructure(semantics, operator)
: new IndexPostfixStructure(semantics, operator);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
} else {
Node rhs = node.arguments.tail.head;
visitExpression(rhs);
AssignmentOperator operator = AssignmentOperator.parse(operatorText);
if (operator.kind == AssignmentOperatorKind.ASSIGN) {
// `super[a] = b`.
Selector setterSelector = new Selector.indexSet();
if (semantics == null) {
semantics =
computeSuperAccessSemanticsForSelector(node, setterSelector);
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
}
// TODO(23998): Remove this when selectors are only accessed
// through the send structure.
registry.setSelector(node, setterSelector);
if (!semantics.setter.isError) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.setter, setterSelector.callStructure));
}
SendStructure sendStructure = new IndexSetStructure(semantics);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
} else {
// `super[a] += b`.
Selector getterSelector = new Selector.index();
Selector setterSelector = new Selector.indexSet();
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelectors(
node, getterSelector, setterSelector, isIndex: true);
if (!semantics.getter.isError) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.getter, getterSelector.callStructure));
}
if (!semantics.setter.isError) {
registry.registerStaticUse(
new StaticUse.superInvoke(
semantics.setter, setterSelector.callStructure));
}
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
registry.useElement(node.selector, semantics.getter);
}
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure =
new CompoundIndexSetStructure(semantics, operator);
registry.registerSendStructure(node, sendStructure);
return const NoneResult();
}
}
}
/// Handle super index operations like `super.a = b`, `super.a += b`, and
/// `super.a++`.
// TODO(johnniwinther): Share code with [handleSuperIndexSendSet].
ResolutionResult handleSuperSendSet(SendSet node) {
Identifier selector = node.selector.asIdentifier();
String text = selector.source;
Name name = new Name(text, enclosingElement.library);
String operatorText = node.assignmentOperator.source;
Selector getterSelector = new Selector.getter(name);
Selector setterSelector = new Selector.setter(name);
void registerStaticUses(AccessSemantics semantics) {
switch (semantics.kind) {
case AccessKind.SUPER_METHOD:
registry.registerStaticUse(
new StaticUse.superTearOff(semantics.element));
break;
case AccessKind.SUPER_GETTER:
registry.registerStaticUse(new StaticUse.superGet(semantics.getter));
break;
case AccessKind.SUPER_SETTER:
registry.registerStaticUse(
new StaticUse.superSetterSet(semantics.setter));
break;
case AccessKind.SUPER_FIELD:
registry.registerStaticUse(
new StaticUse.superGet(semantics.element));
registry.registerStaticUse(
new StaticUse.superFieldSet(semantics.element));
break;
case AccessKind.SUPER_FINAL_FIELD:
registry.registerStaticUse(
new StaticUse.superGet(semantics.element));
break;
case AccessKind.COMPOUND:
CompoundAccessSemantics compoundSemantics = semantics;
switch (compoundSemantics.compoundAccessKind) {
case CompoundAccessKind.SUPER_GETTER_FIELD:
case CompoundAccessKind.SUPER_FIELD_FIELD:
registry.registerStaticUse(
new StaticUse.superGet(semantics.getter));
registry.registerStaticUse(
new StaticUse.superFieldSet(semantics.setter));
break;
case CompoundAccessKind.SUPER_FIELD_SETTER:
case CompoundAccessKind.SUPER_GETTER_SETTER:
registry.registerStaticUse(
new StaticUse.superGet(semantics.getter));
registry.registerStaticUse(
new StaticUse.superSetterSet(semantics.setter));
break;
case CompoundAccessKind.SUPER_METHOD_SETTER:
registry.registerStaticUse(
new StaticUse.superSetterSet(semantics.setter));
break;
case CompoundAccessKind.UNRESOLVED_SUPER_GETTER:
registry.registerStaticUse(
new StaticUse.superSetterSet(semantics.setter));
break;
case CompoundAccessKind.UNRESOLVED_SUPER_SETTER:
registry.registerStaticUse(
new StaticUse.superGet(semantics.getter));
break;
default:
break;
}
break;
default:
break;
}
}
AccessSemantics semantics = checkSuperAccess(node);
if (node.isPrefix || node.isPostfix) {
// `super.a++` or `++super.a`.
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelectors(
node, getterSelector, setterSelector);
registerStaticUses(semantics);
}
return handleUpdate(node, name, semantics);
} else {
AssignmentOperator operator = AssignmentOperator.parse(operatorText);
if (operator.kind == AssignmentOperatorKind.ASSIGN) {
// `super.a = b`.
if (semantics == null) {
semantics =
computeSuperAccessSemanticsForSelector(
node, setterSelector, alternateName: name);
switch (semantics.kind) {
case AccessKind.SUPER_FINAL_FIELD:
reporter.reportWarningMessage(
node,
MessageKind.ASSIGNING_FINAL_FIELD_IN_SUPER,
{'name': name,
'superclassName': semantics.setter.enclosingClass.name});
// TODO(johnniwinther): This shouldn't be needed.
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD);
break;
case AccessKind.SUPER_METHOD:
reporter.reportWarningMessage(
node, MessageKind.ASSIGNING_METHOD_IN_SUPER,
{'name': name,
'superclassName': semantics.setter.enclosingClass.name});
// TODO(johnniwinther): This shouldn't be needed.
registry.registerDynamicUse(
new DynamicUse(setterSelector, null));
registry.registerFeature(Feature.SUPER_NO_SUCH_METHOD);
break;
case AccessKind.SUPER_FIELD:
registry.registerStaticUse(
new StaticUse.superFieldSet(semantics.setter));
break;
case AccessKind.SUPER_SETTER:
registry.registerStaticUse(
new StaticUse.superSetterSet(semantics.setter));
break;
default:
break;
}
}
return handleUpdate(node, name, semantics);
} else {
// `super.a += b`.
if (semantics == null) {
semantics = computeSuperAccessSemanticsForSelectors(
node, getterSelector, setterSelector);
registerStaticUses(semantics);
}
return handleUpdate(node, name, semantics);
}
}
}
/// Handle update of an entity defined by [semantics]. For instance `a = b`,
/// `a++` or `a += b` where [semantics] describe `a`.
ResolutionResult handleUpdate(
SendSet node,
Name name,
AccessSemantics semantics) {
SendStructure sendStructure;
String operatorText = node.assignmentOperator.source;
Selector getterSelector = new Selector.getter(name);
Selector setterSelector = new Selector.setter(name);
if (node.isPrefix || node.isPostfix) {
// `e++` or `++e`.
IncDecOperator operator = IncDecOperator.parse(operatorText);
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
registry.useElement(node.selector, semantics.getter);
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure = node.isPrefix
? new PrefixStructure(semantics, operator)
: new PostfixStructure(semantics, operator);
registry.registerSendStructure(node, sendStructure);
registry.registerFeature(Feature.INC_DEC_OPERATION);
} else {
Node rhs = node.arguments.head;
visitExpression(rhs);
AssignmentOperator operator = AssignmentOperator.parse(operatorText);
if (operator.kind == AssignmentOperatorKind.ASSIGN) {
// `e1 = e2`.
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
// TODO(23998): Remove this when selectors are only accessed
// through the send structure.
registry.setSelector(node, setterSelector);
SendStructure sendStructure = new SetStructure(semantics);
registry.registerSendStructure(node, sendStructure);
} else {
// `e1 += e2`.
Selector operatorSelector =
new Selector.binaryOperator(operator.selectorName);
// TODO(23998): Remove these when elements are only accessed
// through the send structure.
registry.useElement(node, semantics.setter);
registry.useElement(node.selector, semantics.getter);
// TODO(23998): Remove these when selectors are only accessed
// through the send structure.
registry.setGetterSelectorInComplexSendSet(node, getterSelector);
registry.setSelector(node, setterSelector);
registry.setOperatorSelectorInComplexSendSet(node, operatorSelector);
registry.registerDynamicUse(
new DynamicUse(operatorSelector, null));
SendStructure sendStructure;
if (operator.kind == AssignmentOperatorKind.IF_NULL) {
sendStructure = new SetIfNullStructure(semantics);
} else {
sendStructure = new CompoundStructure(semantics, operator);
}
registry.registerSendStructure(node, sendStructure);
}
}
return new ResolutionResult.forElement(semantics.setter);
}
ResolutionResult visitSendSet(SendSet node) {
if (node.isIndex) {
// `a[b] = c`
if (node.isSuperCall) {
// `super[b] = c`
return handleSuperIndexSendSet(node);
} else {
return handleIndexSendSet(node);
}
} else if (node.isSuperCall) {
// `super.a = c`
return handleSuperSendSet(node);
} else if (node.receiver == null) {
// `a = c`
return handleUnqualifiedSendSet(node);
} else {
// `a.b = c`
return handleQualifiedSendSet(node);
}
}
ConstantResult visitLiteralInt(LiteralInt node) {
ConstantExpression constant = new IntConstantExpression(node.value);
registry.registerConstantLiteral(constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
ConstantResult visitLiteralDouble(LiteralDouble node) {
ConstantExpression constant = new DoubleConstantExpression(node.value);
registry.registerConstantLiteral(constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
ConstantResult visitLiteralBool(LiteralBool node) {
ConstantExpression constant = new BoolConstantExpression(node.value);
registry.registerConstantLiteral(constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
ResolutionResult visitLiteralString(LiteralString node) {
if (node.dartString != null) {
// [dartString] might be null on parser errors.
ConstantExpression constant =
new StringConstantExpression(node.dartString.slowToString());
registry.registerConstantLiteral(constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
return const NoneResult();
}
ConstantResult visitLiteralNull(LiteralNull node) {
ConstantExpression constant = new NullConstantExpression();
registry.registerConstantLiteral(constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
ConstantResult visitLiteralSymbol(LiteralSymbol node) {
String name = node.slowNameString;
// TODO(johnniwinther): Use [registerConstantLiteral] instead.
registry.registerConstSymbol(name);
if (!validateSymbol(node, name, reportError: false)) {
reporter.reportErrorMessage(
node,
MessageKind.UNSUPPORTED_LITERAL_SYMBOL,
{'value': name});
}
analyzeConstantDeferred(node);
ConstantExpression constant = new SymbolConstantExpression(name);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
ResolutionResult visitStringJuxtaposition(StringJuxtaposition node) {
registry.registerFeature(Feature.STRING_JUXTAPOSITION);
ResolutionResult first = visit(node.first);
ResolutionResult second = visit(node.second);
if (first.isConstant && second.isConstant) {
ConstantExpression constant = new ConcatenateConstantExpression(
<ConstantExpression>[first.constant, second.constant]);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
return const NoneResult();
}
ResolutionResult visitNodeList(NodeList node) {
for (Link<Node> link = node.nodes; !link.isEmpty; link = link.tail) {
visit(link.head);
}
return const NoneResult();
}
ResolutionResult visitRethrow(Rethrow node) {
if (!inCatchBlock && node.throwToken.stringValue == 'rethrow') {
reporter.reportErrorMessage(
node, MessageKind.RETHROW_OUTSIDE_CATCH);
}
return const NoneResult();
}
ResolutionResult visitReturn(Return node) {
Node expression = node.expression;
if (expression != null) {
if (enclosingElement.isGenerativeConstructor) {
// It is a compile-time error if a return statement of the form
// `return e;` appears in a generative constructor. (Dart Language
// Specification 13.12.)
reporter.reportErrorMessage(
expression,
MessageKind.RETURN_IN_GENERATIVE_CONSTRUCTOR);
} else if (!node.isArrowBody && currentAsyncMarker.isYielding) {
reporter.reportErrorMessage(
node,
MessageKind.RETURN_IN_GENERATOR,
{'modifier': currentAsyncMarker});
}
}
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitYield(Yield node) {
if (!currentAsyncMarker.isYielding) {
reporter.reportErrorMessage(node, MessageKind.INVALID_YIELD);
}
if (currentAsyncMarker.isAsync) {
coreClasses.streamClass.ensureResolved(resolution);
} else {
coreClasses.iterableClass.ensureResolved(resolution);
}
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitRedirectingFactoryBody(RedirectingFactoryBody node) {
if (!enclosingElement.isFactoryConstructor) {
reporter.reportErrorMessage(
node, MessageKind.FACTORY_REDIRECTION_IN_NON_FACTORY);
reporter.reportHintMessage(
enclosingElement, MessageKind.MISSING_FACTORY_KEYWORD);
}
ConstructorElementX constructor = enclosingElement;
bool isConstConstructor = constructor.isConst;
bool isValidAsConstant = isConstConstructor;
ConstructorResult result = resolveRedirectingFactory(
node, inConstContext: isConstConstructor);
ConstructorElement redirectionTarget = result.element;
constructor.immediateRedirectionTarget = redirectionTarget;
Node constructorReference = node.constructorReference;
if (result.isDeferred) {
constructor.redirectionDeferredPrefix = result.prefix;
}
registry.setRedirectingTargetConstructor(node, redirectionTarget);
switch (result.kind) {
case ConstructorResultKind.GENERATIVE:
case ConstructorResultKind.FACTORY:
// Register a post process to check for cycles in the redirection chain
// and set the actual generative constructor at the end of the chain.
addDeferredAction(constructor, () {
compiler.resolver.resolveRedirectionChain(constructor, node);
});
break;
case ConstructorResultKind.ABSTRACT:
case ConstructorResultKind.INVALID_TYPE:
case ConstructorResultKind.UNRESOLVED_CONSTRUCTOR:
case ConstructorResultKind.NON_CONSTANT:
isValidAsConstant = false;
constructor.setEffectiveTarget(
result.element, result.type, isMalformed: true);
break;
}
if (Elements.isUnresolved(redirectionTarget)) {
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
return const NoneResult();
} else {
if (isConstConstructor &&
!redirectionTarget.isConst) {
reporter.reportErrorMessage(
node, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
isValidAsConstant = false;
}
if (redirectionTarget == constructor) {
reporter.reportErrorMessage(
node, MessageKind.CYCLIC_REDIRECTING_FACTORY);
// TODO(johnniwinther): Create constant constructor for this case and
// let evaluation detect the cyclicity.
isValidAsConstant = false;
}
}
// Check that the target constructor is type compatible with the
// redirecting constructor.
ClassElement targetClass = redirectionTarget.enclosingClass;
InterfaceType type = registry.getType(node);
FunctionType targetConstructorType =
redirectionTarget.computeType(resolution)
.subst(type.typeArguments, targetClass.typeVariables);
FunctionType constructorType = constructor.computeType(resolution);
bool isSubtype = compiler.types.isSubtype(
targetConstructorType, constructorType);
if (!isSubtype) {
reporter.reportWarningMessage(
node,
MessageKind.NOT_ASSIGNABLE,
{'fromType': targetConstructorType, 'toType': constructorType});
// TODO(johnniwinther): Handle this (potentially) erroneous case.
isValidAsConstant = false;
}
redirectionTarget.computeType(resolution);
FunctionSignature targetSignature = redirectionTarget.functionSignature;
constructor.computeType(resolution);
FunctionSignature constructorSignature = constructor.functionSignature;
if (!targetSignature.isCompatibleWith(constructorSignature)) {
assert(!isSubtype);
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
isValidAsConstant = false;
}
registry.registerStaticUse(
new StaticUse.constructorRedirect(redirectionTarget));
// TODO(johnniwinther): Register the effective target type as part of the
// static use instead.
registry.registerTypeUse(new TypeUse.instantiation(
redirectionTarget.enclosingClass.thisType
.subst(type.typeArguments, targetClass.typeVariables)));
if (enclosingElement == compiler.symbolConstructor) {
registry.registerFeature(Feature.SYMBOL_CONSTRUCTOR);
}
if (isValidAsConstant) {
List<String> names = <String>[];
List<ConstantExpression> arguments = <ConstantExpression>[];
int index = 0;
constructorSignature.forEachParameter((ParameterElement parameter) {
if (parameter.isNamed) {
String name = parameter.name;
names.add(name);
arguments.add(new NamedArgumentReference(name));
} else {
arguments.add(new PositionalArgumentReference(index));
}
index++;
});
CallStructure callStructure =
new CallStructure(constructorSignature.parameterCount, names);
constructor.constantConstructor =
new RedirectingFactoryConstantConstructor(
new ConstructedConstantExpression(
type,
redirectionTarget,
callStructure,
arguments));
}
return const NoneResult();
}
ResolutionResult visitThrow(Throw node) {
registry.registerFeature(Feature.THROW_EXPRESSION);
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitAwait(Await node) {
if (!currentAsyncMarker.isAsync) {
reporter.reportErrorMessage(node, MessageKind.INVALID_AWAIT);
}
coreClasses.futureClass.ensureResolved(resolution);
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitVariableDefinitions(VariableDefinitions node) {
DartType type;
if (node.type != null) {
type = resolveTypeAnnotation(node.type);
} else {
type = const DynamicType();
}
VariableList variables = new VariableList.node(node, type);
VariableDefinitionsVisitor visitor =
new VariableDefinitionsVisitor(compiler, node, this, variables);
Modifiers modifiers = node.modifiers;
void reportExtraModifier(String modifier) {
Node modifierNode;
for (Link<Node> nodes = modifiers.nodes.nodes;
!nodes.isEmpty;
nodes = nodes.tail) {
if (modifier == nodes.head.asIdentifier().source) {
modifierNode = nodes.head;
break;
}
}
assert(modifierNode != null);
reporter.reportErrorMessage(
modifierNode, MessageKind.EXTRANEOUS_MODIFIER,
{'modifier': modifier});
}
if (modifiers.isFinal && (modifiers.isConst || modifiers.isVar)) {
reportExtraModifier('final');
}
if (modifiers.isVar && (modifiers.isConst || node.type != null)) {
reportExtraModifier('var');
}
if (enclosingElement.isFunction || enclosingElement.isConstructor) {
if (modifiers.isAbstract) {
reportExtraModifier('abstract');
}
if (modifiers.isStatic) {
reportExtraModifier('static');
}
}
if (node.metadata != null) {
variables.metadataInternal =
compiler.resolver.resolveMetadata(enclosingElement, node);
}
visitor.visit(node.definitions);
return const NoneResult();
}
ResolutionResult visitWhile(While node) {
visit(node.condition);
visitLoopBodyIn(node, node.body, new BlockScope(scope));
return const NoneResult();
}
ResolutionResult visitParenthesizedExpression(ParenthesizedExpression node) {
bool oldSendIsMemberAccess = sendIsMemberAccess;
sendIsMemberAccess = false;
var oldCategory = allowedCategory;
allowedCategory =
ElementCategory.VARIABLE |
ElementCategory.FUNCTION |
ElementCategory.IMPLIES_TYPE;
ResolutionResult result = visit(node.expression);
allowedCategory = oldCategory;
sendIsMemberAccess = oldSendIsMemberAccess;
if (result.kind == ResultKind.CONSTANT) {
return result;
}
return const NoneResult();
}
ResolutionResult visitNewExpression(NewExpression node) {
ConstructorResult result = resolveConstructor(node);
ConstructorElement constructor = result.element;
ArgumentsResult argumentsResult;
if (node.isConst) {
argumentsResult =
inConstantContext(() => resolveArguments(node.send.argumentsNode));
} else {
argumentsResult = resolveArguments(node.send.argumentsNode);
}
// TODO(johnniwinther): Avoid the need for a [Selector].
Selector selector = resolveSelector(node.send, constructor);
CallStructure callStructure = selector.callStructure;
registry.useElement(node.send, constructor);
DartType type = result.type;
ConstructorAccessKind kind;
NewStructure newStructure;
bool isInvalid = false;
switch (result.kind) {
case ConstructorResultKind.GENERATIVE:
// Ensure that the signature of [constructor] has been computed.
constructor.computeType(resolution);
if (!callStructure.signatureApplies(constructor.functionSignature)) {
isInvalid = true;
kind = ConstructorAccessKind.INCOMPATIBLE;
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
} else {
kind = ConstructorAccessKind.GENERATIVE;
}
break;
case ConstructorResultKind.FACTORY:
// Ensure that the signature of [constructor] has been computed.
constructor.computeType(resolution);
if (!callStructure.signatureApplies(constructor.functionSignature)) {
// The effective target might still be valid(!) so the is not an
// invalid case in itself. For instance
//
// class A {
// factory A() = A.a;
// A.a(a);
// }
// m() => new A(0); // This creates a warning but works at runtime.
//
registry.registerFeature(Feature.THROW_NO_SUCH_METHOD);
}
kind = ConstructorAccessKind.FACTORY;
break;
case ConstructorResultKind.ABSTRACT:
isInvalid = true;
kind = ConstructorAccessKind.ABSTRACT;
break;
case ConstructorResultKind.INVALID_TYPE:
isInvalid = true;
kind = ConstructorAccessKind.UNRESOLVED_TYPE;
break;
case ConstructorResultKind.UNRESOLVED_CONSTRUCTOR:
// TODO(johnniwinther): Unify codepaths to only have one return.
registry.registerNewStructure(node,
new NewInvokeStructure(
new ConstructorAccessSemantics(
ConstructorAccessKind.UNRESOLVED_CONSTRUCTOR,
constructor,
type),
selector));
return new ResolutionResult.forElement(constructor);
case ConstructorResultKind.NON_CONSTANT:
registry.registerNewStructure(node,
new NewInvokeStructure(
new ConstructorAccessSemantics(
ConstructorAccessKind.NON_CONSTANT_CONSTRUCTOR,
constructor,
type),
selector));
return new ResolutionResult.forElement(constructor);
}
if (!isInvalid) {
// [constructor] might be the implementation element
// and only declaration elements may be registered.
registry.registerStaticUse(
new StaticUse.constructorInvoke(
constructor.declaration, callStructure));
// TODO(johniwinther): Avoid registration of `type` in face of redirecting
// factory constructors.
registry.registerTypeUse(new TypeUse.instantiation(type));
}
if (node.isConst) {
bool isValidAsConstant = !isInvalid && constructor.isConst;
if (constructor == compiler.symbolConstructor) {
Node argumentNode = node.send.arguments.head;
ConstantExpression constant =
compiler.resolver.constantCompiler.compileNode(
argumentNode, registry.mapping);
ConstantValue name = compiler.constants.getConstantValue(constant);
if (!name.isString) {
DartType type = name.getType(coreTypes);
reporter.reportErrorMessage(
argumentNode,
MessageKind.STRING_EXPECTED,
{'type': type});
} else {
StringConstantValue stringConstant = name;
String nameString = stringConstant.toDartString().slowToString();
if (validateSymbol(argumentNode, nameString)) {
registry.registerConstSymbol(nameString);
}
}
} else if (constructor == compiler.mirrorsUsedConstructor) {
compiler.mirrorUsageAnalyzerTask.validate(node, registry.mapping);
}
analyzeConstantDeferred(node);
if (type.containsTypeVariables) {
reporter.reportErrorMessage(
node.send.selector,
MessageKind.TYPE_VARIABLE_IN_CONSTANT);
isValidAsConstant = false;
isInvalid = true;
}
if (result.isDeferred) {
isValidAsConstant = false;
}
if (isValidAsConstant &&
argumentsResult.isValidAsConstant &&
// TODO(johnniwinther): Remove this when all constants are computed
// in resolution.
!constructor.isFromEnvironmentConstructor) {
CallStructure callStructure = argumentsResult.callStructure;
List<ConstantExpression> arguments = argumentsResult.constantArguments;
ConstructedConstantExpression constant =
new ConstructedConstantExpression(
type,
constructor,
callStructure,
arguments);
registry.registerNewStructure(node,
new ConstInvokeStructure(ConstantInvokeKind.CONSTRUCTED, constant));
return new ConstantResult(node, constant);
} else if (isInvalid) {
// Known to be non-constant.
kind == ConstructorAccessKind.NON_CONSTANT_CONSTRUCTOR;
registry.registerNewStructure(node,
new NewInvokeStructure(
new ConstructorAccessSemantics(kind, constructor, type),
selector));
} else {
// Might be valid but we don't know for sure. The compile-time constant
// evaluator will compute the actual constant as a deferred action.
registry.registerNewStructure(node,
new LateConstInvokeStructure(registry.mapping));
}
} else {
// Not constant.
if (constructor == compiler.symbolConstructor &&
!compiler.mirrorUsageAnalyzerTask.hasMirrorUsage(enclosingElement)) {
reporter.reportHintMessage(
node.newToken, MessageKind.NON_CONST_BLOAT,
{'name': coreClasses.symbolClass.name});
}
registry.registerNewStructure(node,
new NewInvokeStructure(
new ConstructorAccessSemantics(kind, constructor, type),
selector));
}
return const NoneResult();
}
void checkConstMapKeysDontOverrideEquals(Spannable spannable,
MapConstantValue map) {
for (ConstantValue key in map.keys) {
if (!key.isObject) continue;
ObjectConstantValue objectConstant = key;
DartType keyType = objectConstant.type;
ClassElement cls = keyType.element;
if (cls == coreClasses.stringClass) continue;
Element equals = cls.lookupMember('==');
if (equals.enclosingClass != coreClasses.objectClass) {
reporter.reportErrorMessage(
spannable,
MessageKind.CONST_MAP_KEY_OVERRIDES_EQUALS,
{'type': keyType});
}
}
}
void analyzeConstant(Node node, {enforceConst: true}) {
ConstantExpression constant =
compiler.resolver.constantCompiler.compileNode(
node, registry.mapping, enforceConst: enforceConst);
if (constant == null) {
assert(invariant(node, compiler.compilationFailed));
return;
}
ConstantValue value = compiler.constants.getConstantValue(constant);
if (value.isMap) {
checkConstMapKeysDontOverrideEquals(node, value);
}
}
void analyzeConstantDeferred(Node node, {bool enforceConst: true}) {
addDeferredAction(enclosingElement, () {
analyzeConstant(node, enforceConst: enforceConst);
});
}
bool validateSymbol(Node node, String name, {bool reportError: true}) {
if (name.isEmpty) return true;
if (name.startsWith('_')) {
if (reportError) {
reporter.reportErrorMessage(
node, MessageKind.PRIVATE_IDENTIFIER, {'value': name});
}
return false;
}
if (!symbolValidationPattern.hasMatch(name)) {
if (reportError) {
reporter.reportErrorMessage(
node, MessageKind.INVALID_SYMBOL, {'value': name});
}
return false;
}
return true;
}
/**
* Try to resolve the constructor that is referred to by [node].
* Note: this function may return an ErroneousFunctionElement instead of
* [:null:], if there is no corresponding constructor, class or library.
*/
ConstructorResult resolveConstructor(NewExpression node) {
return node.accept(new ConstructorResolver(
compiler, this, inConstContext: node.isConst));
}
ConstructorResult resolveRedirectingFactory(RedirectingFactoryBody node,
{bool inConstContext: false}) {
return node.accept(new ConstructorResolver(
compiler, this, inConstContext: inConstContext));
}
DartType resolveTypeAnnotation(TypeAnnotation node,
{bool malformedIsError: false,
bool deferredIsMalformed: true}) {
DartType type = typeResolver.resolveTypeAnnotation(
this, node, malformedIsError: malformedIsError,
deferredIsMalformed: deferredIsMalformed);
if (inCheckContext) {
registry.registerTypeUse(new TypeUse.checkedModeCheck(type));
}
return type;
}
ResolutionResult visitLiteralList(LiteralList node) {
bool isValidAsConstant = true;
sendIsMemberAccess = false;
NodeList arguments = node.typeArguments;
DartType typeArgument;
if (arguments != null) {
Link<Node> nodes = arguments.nodes;
if (nodes.isEmpty) {
// The syntax [: <>[] :] is not allowed.
reporter.reportErrorMessage(
arguments, MessageKind.MISSING_TYPE_ARGUMENT);
isValidAsConstant = false;
} else {
typeArgument = resolveTypeAnnotation(nodes.head);
for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) {
reporter.reportWarningMessage(
nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
resolveTypeAnnotation(nodes.head);
}
}
}
DartType listType;
if (typeArgument != null) {
if (node.isConst && typeArgument.containsTypeVariables) {
reporter.reportErrorMessage(
arguments.nodes.head,
MessageKind.TYPE_VARIABLE_IN_CONSTANT);
isValidAsConstant = false;
}
listType = coreTypes.listType(typeArgument);
} else {
listType = coreTypes.listType();
}
registry.registerLiteralList(
node,
listType,
isConstant: node.isConst,
isEmpty: node.elements.isEmpty);
if (node.isConst) {
List<ConstantExpression> constantExpressions = <ConstantExpression>[];
inConstantContext(() {
for (Node element in node.elements) {
ResolutionResult elementResult = visit(element);
if (isValidAsConstant && elementResult.isConstant) {
constantExpressions.add(elementResult.constant);
} else {
isValidAsConstant = false;
}
}
});
analyzeConstantDeferred(node);
sendIsMemberAccess = false;
if (isValidAsConstant) {
ConstantExpression constant =
new ListConstantExpression(listType, constantExpressions);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
} else {
visit(node.elements);
sendIsMemberAccess = false;
}
return const NoneResult();
}
ResolutionResult visitConditional(Conditional node) {
ResolutionResult conditionResult =
doInPromotionScope(node.condition, () => visit(node.condition));
ResolutionResult thenResult =
doInPromotionScope(node.thenExpression, () => visit(node.thenExpression));
ResolutionResult elseResult = visit(node.elseExpression);
if (conditionResult.isConstant &&
thenResult.isConstant &&
elseResult.isConstant) {
ConstantExpression constant = new ConditionalConstantExpression(
conditionResult.constant,
thenResult.constant,
elseResult.constant);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
return const NoneResult();
}
ResolutionResult visitStringInterpolation(StringInterpolation node) {
// TODO(johnniwinther): This should be a consequence of the registration
// of [registerStringInterpolation].
registry.registerTypeUse(new TypeUse.instantiation(coreTypes.stringType));
registry.registerFeature(Feature.STRING_INTERPOLATION);
registerImplicitInvocation(Selectors.toString_);
bool isValidAsConstant = true;
List<ConstantExpression> parts = <ConstantExpression>[];
void resolvePart(Node subnode) {
ResolutionResult result = visit(subnode);
if (isValidAsConstant && result.isConstant) {
parts.add(result.constant);
} else {
isValidAsConstant = false;
}
}
resolvePart(node.string);
for (StringInterpolationPart part in node.parts) {
resolvePart(part.expression);
resolvePart(part.string);
}
if (isValidAsConstant) {
ConstantExpression constant = new ConcatenateConstantExpression(parts);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
return const NoneResult();
}
ResolutionResult visitBreakStatement(BreakStatement node) {
JumpTarget target;
if (node.target == null) {
target = statementScope.currentBreakTarget();
if (target == null) {
reporter.reportErrorMessage(
node, MessageKind.NO_BREAK_TARGET);
return const NoneResult();
}
target.isBreakTarget = true;
} else {
String labelName = node.target.source;
LabelDefinition label = statementScope.lookupLabel(labelName);
if (label == null) {
reporter.reportErrorMessage(
node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
return const NoneResult();
}
target = label.target;
if (!target.statement.isValidBreakTarget()) {
reporter.reportErrorMessage(
node.target, MessageKind.INVALID_BREAK);
return const NoneResult();
}
label.setBreakTarget();
registry.useLabel(node, label);
}
registry.registerTargetOf(node, target);
return const NoneResult();
}
ResolutionResult visitContinueStatement(ContinueStatement node) {
JumpTarget target;
if (node.target == null) {
target = statementScope.currentContinueTarget();
if (target == null) {
reporter.reportErrorMessage(
node, MessageKind.NO_CONTINUE_TARGET);
return const NoneResult();
}
target.isContinueTarget = true;
} else {
String labelName = node.target.source;
LabelDefinition label = statementScope.lookupLabel(labelName);
if (label == null) {
reporter.reportErrorMessage(
node.target, MessageKind.UNBOUND_LABEL, {'labelName': labelName});
return const NoneResult();
}
target = label.target;
if (!target.statement.isValidContinueTarget()) {
reporter.reportErrorMessage(
node.target, MessageKind.INVALID_CONTINUE);
}
label.setContinueTarget();
registry.useLabel(node, label);
}
registry.registerTargetOf(node, target);
return const NoneResult();
}
registerImplicitInvocation(Selector selector) {
registry.registerDynamicUse(new DynamicUse(selector, null));
}
ResolutionResult visitAsyncForIn(AsyncForIn node) {
if (!currentAsyncMarker.isAsync) {
reporter.reportErrorMessage(
node.awaitToken, MessageKind.INVALID_AWAIT_FOR_IN);
}
registry.registerFeature(Feature.ASYNC_FOR_IN);
registry.registerDynamicUse(
new DynamicUse(Selectors.current, null));
registry.registerDynamicUse(
new DynamicUse(Selectors.moveNext, null));
visit(node.expression);
Scope blockScope = new BlockScope(scope);
visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope);
visitLoopBodyIn(node, node.body, blockScope);
return const NoneResult();
}
ResolutionResult visitSyncForIn(SyncForIn node) {
registry.registerFeature(Feature.SYNC_FOR_IN);
registry.registerDynamicUse(
new DynamicUse(Selectors.iterator, null));
registry.registerDynamicUse(
new DynamicUse(Selectors.current, null));
registry.registerDynamicUse(
new DynamicUse(Selectors.moveNext, null));
visit(node.expression);
Scope blockScope = new BlockScope(scope);
visitForInDeclaredIdentifierIn(node.declaredIdentifier, node, blockScope);
visitLoopBodyIn(node, node.body, blockScope);
return const NoneResult();
}
void visitForInDeclaredIdentifierIn(
Node declaration,
ForIn node,
Scope blockScope) {
LibraryElement library = enclosingElement.library;
bool oldAllowFinalWithoutInitializer = inLoopVariable;
inLoopVariable = true;
visitIn(declaration, blockScope);
inLoopVariable = oldAllowFinalWithoutInitializer;
Send send = declaration.asSend();
VariableDefinitions variableDefinitions =
declaration.asVariableDefinitions();
Element loopVariable;
Selector loopVariableSelector;
if (send != null) {
loopVariable = registry.getDefinition(send);
Identifier identifier = send.selector.asIdentifier();
if (identifier == null) {
reporter.reportErrorMessage(
send.selector, MessageKind.INVALID_FOR_IN);
} else {
loopVariableSelector = new Selector.setter(
new Name(identifier.source, library));
}
if (send.receiver != null) {
reporter.reportErrorMessage(
send.receiver, MessageKind.INVALID_FOR_IN);
}
} else if (variableDefinitions != null) {
Link<Node> nodes = variableDefinitions.definitions.nodes;
if (!nodes.tail.isEmpty) {
reporter.reportErrorMessage(
nodes.tail.head, MessageKind.INVALID_FOR_IN);
}
Node first = nodes.head;
Identifier identifier = first.asIdentifier();
if (identifier == null) {
reporter.reportErrorMessage(
first, MessageKind.INVALID_FOR_IN);
} else {
loopVariableSelector = new Selector.setter(
new Name(identifier.source, library));
loopVariable = registry.getDefinition(identifier);
}
} else {
reporter.reportErrorMessage(
declaration, MessageKind.INVALID_FOR_IN);
}
if (loopVariableSelector != null) {
registry.setSelector(declaration, loopVariableSelector);
if (loopVariable == null || loopVariable.isInstanceMember) {
registry.registerDynamicUse(
new DynamicUse(loopVariableSelector, null));
} else if (loopVariable.isStatic || loopVariable.isTopLevel) {
registry.registerStaticUse(
new StaticUse.staticSet(loopVariable.declaration));
}
} else {
// The selector may only be null if we reported an error.
assert(invariant(declaration, compiler.compilationFailed));
}
if (loopVariable != null) {
// loopVariable may be null if it could not be resolved.
registry.setForInVariable(node, loopVariable);
}
}
visitLabel(Label node) {
// Labels are handled by their containing statements/cases.
}
ResolutionResult visitLabeledStatement(LabeledStatement node) {
Statement body = node.statement;
JumpTarget targetElement = getOrDefineTarget(body);
Map<String, LabelDefinition> labelElements = <String, LabelDefinition>{};
for (Label label in node.labels) {
String labelName = label.labelName;
if (labelElements.containsKey(labelName)) continue;
LabelDefinition element = targetElement.addLabel(label, labelName);
labelElements[labelName] = element;
}
statementScope.enterLabelScope(labelElements);
visit(node.statement);
statementScope.exitLabelScope();
labelElements.forEach((String labelName, LabelDefinition element) {
if (element.isTarget) {
registry.defineLabel(element.label, element);
} else {
reporter.reportWarningMessage(
element.label,
MessageKind.UNUSED_LABEL,
{'labelName': labelName});
}
});
if (!targetElement.isTarget) {
registry.undefineTarget(body);
}
return const NoneResult();
}
ResolutionResult visitLiteralMap(LiteralMap node) {
bool isValidAsConstant = true;
sendIsMemberAccess = false;
NodeList arguments = node.typeArguments;
DartType keyTypeArgument;
DartType valueTypeArgument;
if (arguments != null) {
Link<Node> nodes = arguments.nodes;
if (nodes.isEmpty) {
// The syntax [: <>{} :] is not allowed.
reporter.reportErrorMessage(
arguments, MessageKind.MISSING_TYPE_ARGUMENT);
isValidAsConstant = false;
} else {
keyTypeArgument = resolveTypeAnnotation(nodes.head);
nodes = nodes.tail;
if (nodes.isEmpty) {
reporter.reportWarningMessage(
arguments, MessageKind.MISSING_TYPE_ARGUMENT);
} else {
valueTypeArgument = resolveTypeAnnotation(nodes.head);
for (nodes = nodes.tail; !nodes.isEmpty; nodes = nodes.tail) {
reporter.reportWarningMessage(
nodes.head, MessageKind.ADDITIONAL_TYPE_ARGUMENT);
resolveTypeAnnotation(nodes.head);
}
}
}
}
DartType mapType;
if (valueTypeArgument != null) {
mapType = coreTypes.mapType(keyTypeArgument, valueTypeArgument);
} else {
mapType = coreTypes.mapType();
}
if (node.isConst && mapType.containsTypeVariables) {
reporter.reportErrorMessage(
arguments,
MessageKind.TYPE_VARIABLE_IN_CONSTANT);
isValidAsConstant = false;
}
registry.registerMapLiteral(
node,
mapType,
isConstant: node.isConst,
isEmpty: node.entries.isEmpty);
if (node.isConst) {
List<ConstantExpression> keyExpressions = <ConstantExpression>[];
List<ConstantExpression> valueExpressions = <ConstantExpression>[];
inConstantContext(() {
for (LiteralMapEntry entry in node.entries) {
ResolutionResult keyResult = visit(entry.key);
ResolutionResult valueResult = visit(entry.value);
if (isValidAsConstant &&
keyResult.isConstant &&
valueResult.isConstant) {
keyExpressions.add(keyResult.constant);
valueExpressions.add(valueResult.constant);
} else {
isValidAsConstant = false;
}
}
});
analyzeConstantDeferred(node);
sendIsMemberAccess = false;
if (isValidAsConstant) {
ConstantExpression constant = new MapConstantExpression(
mapType, keyExpressions, valueExpressions);
registry.setConstant(node, constant);
return new ConstantResult(node, constant);
}
} else {
node.visitChildren(this);
sendIsMemberAccess = false;
}
return const NoneResult();
}
ResolutionResult visitLiteralMapEntry(LiteralMapEntry node) {
node.visitChildren(this);
return const NoneResult();
}
ResolutionResult visitNamedArgument(NamedArgument node) {
return visit(node.expression);
}
DartType typeOfConstant(ConstantValue constant) {
if (constant.isInt) return coreTypes.intType;
if (constant.isBool) return coreTypes.boolType;
if (constant.isDouble) return coreTypes.doubleType;
if (constant.isString) return coreTypes.stringType;
if (constant.isNull) return coreTypes.nullType;
if (constant.isFunction) return coreTypes.functionType;
assert(constant.isObject);
ObjectConstantValue objectConstant = constant;
return objectConstant.type;
}
bool overridesEquals(DartType type) {
ClassElement cls = type.element;
Element equals = cls.lookupMember('==');
return equals.enclosingClass != coreClasses.objectClass;
}
void checkCaseExpressions(SwitchStatement node) {
CaseMatch firstCase = null;
DartType firstCaseType = null;
DiagnosticMessage error;
List<DiagnosticMessage> infos = <DiagnosticMessage>[];
for (Link<Node> cases = node.cases.nodes;
!cases.isEmpty;
cases = cases.tail) {
SwitchCase switchCase = cases.head;
for (Node labelOrCase in switchCase.labelsAndCases) {
CaseMatch caseMatch = labelOrCase.asCaseMatch();
if (caseMatch == null) continue;
// Analyze the constant.
ConstantExpression constant =
registry.getConstant(caseMatch.expression);
assert(invariant(node, constant != null,
message: 'No constant computed for $node'));
ConstantValue value = compiler.constants.getConstantValue(constant);
DartType caseType = value.getType(coreTypes);//typeOfConstant(value);
if (firstCaseType == null) {
firstCase = caseMatch;
firstCaseType = caseType;
// We only report the bad type on the first class element. All others
// get a "type differs" error.
if (caseType == coreTypes.doubleType) {
reporter.reportErrorMessage(
node,
MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS,
{'type': "double"});
} else if (caseType == coreTypes.functionType) {
reporter.reportErrorMessage(
node, MessageKind.SWITCH_CASE_FORBIDDEN,
{'type': "Function"});
} else if (value.isObject && overridesEquals(caseType)) {
reporter.reportErrorMessage(
firstCase.expression,
MessageKind.SWITCH_CASE_VALUE_OVERRIDES_EQUALS,
{'type': caseType});
}
} else {
if (caseType != firstCaseType) {
if (error == null) {
error = reporter.createMessage(
node,
MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL,
{'type': firstCaseType});
infos.add(reporter.createMessage(
firstCase.expression,
MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
{'type': firstCaseType}));
}
infos.add(reporter.createMessage(
caseMatch.expression,
MessageKind.SWITCH_CASE_TYPES_NOT_EQUAL_CASE,
{'type': caseType}));
}
}
}
}
if (error != null) {
reporter.reportError(error, infos);
}
}
ResolutionResult visitSwitchStatement(SwitchStatement node) {
node.expression.accept(this);
JumpTarget breakElement = getOrDefineTarget(node);
Map<String, LabelDefinition> continueLabels = <String, LabelDefinition>{};
Link<Node> cases = node.cases.nodes;
while (!cases.isEmpty) {
SwitchCase switchCase = cases.head;
for (Node labelOrCase in switchCase.labelsAndCases) {
CaseMatch caseMatch = labelOrCase.asCaseMatch();
if (caseMatch != null) {
analyzeConstantDeferred(caseMatch.expression);
continue;
}
Label label = labelOrCase;
String labelName = label.labelName;
LabelDefinition existingElement = continueLabels[labelName];
if (existingElement != null) {
// It's an error if the same label occurs twice in the same switch.
reporter.reportError(
reporter.createMessage(
label,
MessageKind.DUPLICATE_LABEL,
{'labelName': labelName}),
<DiagnosticMessage>[
reporter.createMessage(
existingElement.label,
MessageKind.EXISTING_LABEL,
{'labelName': labelName}),
]);
} else {
// It's only a warning if it shadows another label.
existingElement = statementScope.lookupLabel(labelName);
if (existingElement != null) {
reporter.reportWarning(
reporter.createMessage(
label,
MessageKind.DUPLICATE_LABEL,
{'labelName': labelName}),
<DiagnosticMessage>[
reporter.createMessage(
existingElement.label,
MessageKind.EXISTING_LABEL,
{'labelName': labelName}),
]);
}
}
JumpTarget targetElement = getOrDefineTarget(switchCase);
LabelDefinition labelElement = targetElement.addLabel(label, labelName);
registry.defineLabel(label, labelElement);
continueLabels[labelName] = labelElement;
}
cases = cases.tail;
// Test that only the last case, if any, is a default case.
if (switchCase.defaultKeyword != null && !cases.isEmpty) {
reporter.reportErrorMessage(
switchCase, MessageKind.INVALID_CASE_DEFAULT);
}
}
addDeferredAction(enclosingElement, () {
checkCaseExpressions(node);
});
statementScope.enterSwitch(breakElement, continueLabels);
node.cases.accept(this);
statementScope.exitSwitch();
// Clean-up unused labels.
continueLabels.forEach((String key, LabelDefinition label) {
if (!label.isContinueTarget) {
JumpTarget targetElement = label.target;
SwitchCase switchCase = targetElement.statement;
registry.undefineTarget(switchCase);
registry.undefineLabel(label.label);
}
});
// TODO(15575): We should warn if we can detect a fall through
// error.
registry.registerFeature(Feature.FALL_THROUGH_ERROR);
return const NoneResult();
}
ResolutionResult visitSwitchCase(SwitchCase node) {
node.labelsAndCases.accept(this);
visitIn(node.statements, new BlockScope(scope));
return const NoneResult();
}
ResolutionResult visitCaseMatch(CaseMatch node) {
visit(node.expression);
return const NoneResult();
}
ResolutionResult visitTryStatement(TryStatement node) {
// TODO(karlklose): also track the information about mutated variables,
// catch, and finally-block.
registry.registerTryStatement();
visit(node.tryBlock);
if (node.catchBlocks.isEmpty && node.finallyBlock == null) {
reporter.reportErrorMessage(
node.getEndToken().next, MessageKind.NO_CATCH_NOR_FINALLY);
}
visit(node.catchBlocks);
visit(node.finallyBlock);
return const NoneResult();
}
ResolutionResult visitCatchBlock(CatchBlock node) {
registry.registerFeature(Feature.CATCH_STATEMENT);
// Check that if catch part is present, then
// it has one or two formal parameters.
VariableDefinitions exceptionDefinition;
VariableDefinitions stackTraceDefinition;
if (node.formals != null) {
Link<Node> formalsToProcess = node.formals.nodes;
if (formalsToProcess.isEmpty) {
reporter.reportErrorMessage(
node, MessageKind.EMPTY_CATCH_DECLARATION);
} else {
exceptionDefinition = formalsToProcess.head.asVariableDefinitions();
formalsToProcess = formalsToProcess.tail;
if (!formalsToProcess.isEmpty) {
stackTraceDefinition = formalsToProcess.head.asVariableDefinitions();
formalsToProcess = formalsToProcess.tail;
if (!formalsToProcess.isEmpty) {
for (Node extra in formalsToProcess) {
reporter.reportErrorMessage(
extra, MessageKind.EXTRA_CATCH_DECLARATION);
}
}
registry.registerFeature(Feature.STACK_TRACE_IN_CATCH);
}
}
// Check that the formals aren't optional and that they have no
// modifiers or type.
for (Link<Node> link = node.formals.nodes;
!link.isEmpty;
link = link.tail) {
// If the formal parameter is a node list, it means that it is a
// sequence of optional parameters.
NodeList nodeList = link.head.asNodeList();
if (nodeList != null) {
reporter.reportErrorMessage(
nodeList, MessageKind.OPTIONAL_PARAMETER_IN_CATCH);
} else {
VariableDefinitions declaration = link.head;
for (Node modifier in declaration.modifiers.nodes) {
reporter.reportErrorMessage(
modifier, MessageKind.PARAMETER_WITH_MODIFIER_IN_CATCH);
}
TypeAnnotation type = declaration.type;
if (type != null) {
reporter.reportErrorMessage(
type, MessageKind.PARAMETER_WITH_TYPE_IN_CATCH);
}
}
}
}
Scope blockScope = new BlockScope(scope);
TypeResult exceptionTypeResult = visitIn(node.type, blockScope);
visitIn(node.formals, blockScope);
var oldInCatchBlock = inCatchBlock;
inCatchBlock = true;
visitIn(node.block, blockScope);
inCatchBlock = oldInCatchBlock;
if (exceptionTypeResult != null) {
DartType exceptionType = exceptionTypeResult.type;
if (exceptionDefinition != null) {
Node exceptionVariable = exceptionDefinition.definitions.nodes.head;
VariableElementX exceptionElement =
registry.getDefinition(exceptionVariable);
exceptionElement.variables.type = exceptionType;
}
registry.registerTypeUse(new TypeUse.catchType(exceptionType));
}
if (stackTraceDefinition != null) {
Node stackTraceVariable = stackTraceDefinition.definitions.nodes.head;
VariableElementX stackTraceElement =
registry.getDefinition(stackTraceVariable);
InterfaceType stackTraceType = coreTypes.stackTraceType;
stackTraceElement.variables.type = stackTraceType;
}
return const NoneResult();
}
}
/// Looks up [name] in [scope] and unwraps the result.
Element lookupInScope(DiagnosticReporter reporter, Node node,
Scope scope, String name) {
return Elements.unwrap(scope.lookup(name), reporter, node);
}