blob: 85b4902026ecb26cf2336b8fc8ffe706cd4e31e9 [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 Resolution;
import '../compile_time_constants.dart';
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,
ParameterElementX,
VariableElementX,
VariableList;
import '../options.dart';
import '../tokens/token.dart' show isUserDefinableOperator;
import '../tree/tree.dart';
import '../universe/call_structure.dart' show CallStructure;
import '../universe/feature.dart' show Feature;
import '../universe/selector.dart' show Selector;
import '../universe/use.dart' show DynamicUse, StaticUse, TypeUse;
import '../util/util.dart' show Link;
import 'access_semantics.dart';
import 'class_members.dart' show MembersCreator;
import 'constructors.dart'
show ConstructorResolver, ConstructorResult, ConstructorResultKind;
import 'label_scope.dart' show StatementScope;
import 'operators.dart';
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 'send_structure.dart';
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 inCatchParameters = false;
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.isRegularParameter) &&
!(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(
Resolution resolution, Element element, ResolutionRegistry registry,
{Scope scope, 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(),
this.scope = 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 = resolution.options.enableTypeAssertions &&
!element.isLibrary &&
!element.isTypedef &&
!element.enclosingElement.isTypedef,
inCatchBlock = false,
constantState = element.isConst
? ConstantState.CONSTANT
: ConstantState.NON_CONSTANT,
super(resolution, registry);
CommonElements get commonElements => resolution.commonElements;
ConstantEnvironment get constants => resolution.constants;
ResolverTask get resolver => resolution.resolver;
CompilerOptions get options => resolution.options;
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 = commonElements.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) {
return new TypeResult(resolveTypeAnnotation(node));
}
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.parsingContext);
// 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);
}
FunctionSignature functionSignature = function.functionSignature;
// Create the scope where the type variables are introduced, if any.
scope = new MethodScope(scope, function);
functionSignature.typeVariables
.forEach((DartType type) => addToScope(type.element));
// Create the scope for the function body, and put the parameters in scope.
scope = new BlockScope(scope);
Link<Node> parameterNodes =
(node.parameters == null) ? const Link<Node>() : node.parameters.nodes;
functionSignature.forEachParameter((ParameterElementX element) {
// TODO(karlklose): should be a list of [FormalElement]s, but the actual
// implementation uses [Element].
List<Element> optionals = functionSignature.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, () {
functionSignature.forEachOptionalParameter((ParameterElementX parameter) {
parameter.constant =
resolver.constantCompiler.compileConstant(parameter);
});
});
registry.registerCheckedModeCheck(functionSignature.returnType);
functionSignature.forEachParameter((ParameterElement element) {
registry.registerCheckedModeCheck(element.type);
});
}
ResolutionResult visitAssert(Assert node) {
// 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});
}
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(resolution, function, registry);
function.functionSignature = SignatureResolver.analyze(
resolution,
scope,
node.typeVariables,
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.isRegularParameter) {
if (target.isFinal || target.isConst) {
return new StaticAccess.finalParameter(target);
} else {
return new StaticAccess.parameter(target);
}
} else if (target.isInitializingFormal) {
return new StaticAccess.finalParameter(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.UNDEFINED_SUPER_SETTER,
{'className': currentClass.name, 'memberName': 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, registerCheckedModeCheck: false);
sendStructure = new IsNotStructure(type);
} else {
// `e is T`.
Node typeNode = node.arguments.head;
type = resolveTypeAnnotation(typeNode, registerCheckedModeCheck: false);
sendStructure = new IsStructure(type);
}
// GENERIC_METHODS: Method type variables are not reified so we must warn
// about the error which will occur at runtime.
if (type is MethodTypeVariableType) {
reporter.reportWarningMessage(
node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED);
}
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, registerCheckedModeCheck: false);
// GENERIC_METHODS: Method type variables are not reified, so we must inform
// the developer about the potentially bug-inducing semantics.
if (type is MethodTypeVariableType) {
reporter.reportHintMessage(
node, MessageKind.TYPE_VARIABLE_FROM_METHOD_CONSIDERED_DYNAMIC);
}
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(commonElements);
switch (operator.kind) {
case UnaryOperatorKind.COMPLEMENT:
isValidConstant = knownExpressionType == commonElements.intType;
break;
case UnaryOperatorKind.NEGATE:
isValidConstant = knownExpressionType == commonElements.intType ||
knownExpressionType == commonElements.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(commonElements) ==
commonElements.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(commonElements) ==
commonElements.boolType &&
rightConstant.getKnownType(commonElements) ==
commonElements.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(commonElements) ==
commonElements.boolType &&
rightConstant.getKnownType(commonElements) ==
commonElements.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.registerConstantLiteral(new NullConstantExpression());
registry.registerDynamicUse(new DynamicUse(Selectors.equals, null));
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(commonElements);
DartType knownRightType = rightConstant.getKnownType(commonElements);
switch (operator.kind) {
case BinaryOperatorKind.EQ:
case BinaryOperatorKind.NOT_EQ:
isValidConstant = (knownLeftType == commonElements.intType ||
knownLeftType == commonElements.doubleType ||
knownLeftType == commonElements.stringType ||
knownLeftType == commonElements.boolType ||
knownLeftType == commonElements.nullType) &&
(knownRightType == commonElements.intType ||
knownRightType == commonElements.doubleType ||
knownRightType == commonElements.stringType ||
knownRightType == commonElements.boolType ||
knownRightType == commonElements.nullType);
break;
case BinaryOperatorKind.ADD:
isValidConstant = (knownLeftType == commonElements.intType ||
knownLeftType == commonElements.doubleType ||
knownLeftType == commonElements.stringType) &&
(knownRightType == commonElements.intType ||
knownRightType == commonElements.doubleType ||
knownRightType == commonElements.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 == commonElements.intType ||
knownLeftType == commonElements.doubleType) &&
(knownRightType == commonElements.intType ||
knownRightType == commonElements.doubleType);
break;
case BinaryOperatorKind.SHL:
case BinaryOperatorKind.SHR:
case BinaryOperatorKind.AND:
case BinaryOperatorKind.OR:
case BinaryOperatorKind.XOR:
isValidConstant = knownLeftType == commonElements.intType &&
knownRightType == commonElements.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:
MethodElement 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.UNDEFINED_GETTER,
{'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.UNDEFINED_GETTER,
{'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(
resolution, 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(
resolution, 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.hasAccessToTypeVariable(enclosingElement, element)) {
// 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 {
// GENERIC_METHODS: Method type variables are not reified so we must warn
// about the error which will occur at runtime.
if (element.type is MethodTypeVariableType) {
reporter.reportWarningMessage(
node, MessageKind.TYPE_VARIABLE_FROM_METHOD_NOT_REIFIED);
}
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.hasAccessToTypeVariable(enclosingElement, element)) {
// 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.
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 ? commonElements.typeType : type);
AccessSemantics semantics = new ConstantAccess.dynamicTypeLiteral(constant);
return handleConstantTypeLiteralAccess(node, const PublicName('dynamic'),
commonElements.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'),
commonElements.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) {
registry.registerConstantLiteral(new NullConstantExpression());
registry.registerDynamicUse(new DynamicUse(Selectors.equals, null));
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) {
registry.registerConstantLiteral(new NullConstantExpression());
registry.registerDynamicUse(new DynamicUse(Selectors.equals, null));
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.isRegularParameter) {
if (element.isFinal) {
error = reportAndCreateErroneousElement(node.selector, name.text,
MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name});
semantics = new StaticAccess.finalParameter(element);
} else {
semantics = new StaticAccess.parameter(element);
}
} else if (element.isInitializingFormal) {
error = reportAndCreateErroneousElement(node.selector, name.text,
MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name});
semantics = new StaticAccess.finalParameter(element);
} else if (element.isVariable) {
if (element.isFinal || element.isConst) {
error = reportAndCreateErroneousElement(node.selector, name.text,
MessageKind.UNDEFINED_STATIC_SETTER_BUT_GETTER, {'name': name});
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 (resolution.commonElements.isMirrorSystemGetNameFunction(member) &&
!resolution.mirrorUsageAnalyzerTask.hasMirrorUsage(enclosingElement)) {
reporter.reportHintMessage(
node.selector, MessageKind.STATIC_FUNCTION_BLOAT, {
'class': resolution.commonElements.mirrorSystemClass.name,
'name': member.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 == resolution.commonElements.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.UNDEFINED_STATIC_GETTER_BUT_SETTER, {'name': name});
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.UNDEFINED_STATIC_GETTER_BUT_SETTER, {'name': name});
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.UNDEFINED_STATIC_SETTER_BUT_GETTER,
{'name': name});
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.UNDEFINED_STATIC_GETTER_BUT_SETTER,
{'name': name});
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)
:<