blob: d1d04a9d17427cfcbb201bb41ab066d7ceafdddc [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:analyzer/src/fasta/resolution_applier.dart';
import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart';
import 'package:front_end/src/fasta/type_inference/interface_resolver.dart';
import 'package:front_end/src/fasta/type_inference/type_inference_listener.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/type_algebra.dart';
/// TODO(scheglov) document
class FunctionReferenceDartType implements DartType {
final FunctionDeclaration function;
final DartType type;
FunctionReferenceDartType(this.function, this.type);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '(${function.variable}, $type)';
}
}
/// The reference to the import prefix with the [name].
class ImportPrefixNode implements TreeNode {
final String name;
ImportPrefixNode(this.name);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '(prefix-$name)';
}
}
/// The type of [DartType] node that is used as a marker for using `null`
/// as the [FunctionType] for index assignment.
class IndexAssignNullFunctionType implements DartType {
const IndexAssignNullFunctionType();
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return 'IndexAssignNullFunctionType';
}
}
/// Type inference listener that records inferred types and file offsets for
/// later use by [ValidatingResolutionApplier].
class InstrumentedResolutionStorer extends ResolutionStorer {
/// Indicates whether debug messages should be printed.
static const bool _debug = false;
final List<int> _declarationOffsets;
final List<int> _referenceOffsets;
final List<int> _typeOffsets;
final List<int> _deferredReferenceOffsets = [];
final List<int> _deferredTypeOffsets = [];
InstrumentedResolutionStorer(
List<TreeNode> declarations,
List<Node> references,
List<DartType> types,
this._declarationOffsets,
this._referenceOffsets,
this._typeOffsets)
: super(declarations, references, types);
@override
void _deferReference(int offset) {
super._deferReference(offset);
if (_debug) {
_deferredReferenceOffsets.add(offset);
}
}
@override
void _deferType(int offset) {
super._deferType(offset);
if (_debug) {
_deferredTypeOffsets.add(offset);
}
}
@override
void _recordDeclaration(TreeNode declaration, int offset) {
if (_debug) {
print('Recording declaration of $declaration for offset $offset');
}
_declarationOffsets.add(offset);
super._recordDeclaration(declaration, offset);
}
@override
int _recordReference(Node target, int offset) {
if (_debug) {
print('Recording reference to $target for offset $offset');
}
_referenceOffsets.add(offset);
return super._recordReference(target, offset);
}
@override
int _recordType(DartType type, int offset) {
if (_debug) {
print('Recording type $type for offset $offset');
}
assert(_types.length == _typeOffsets.length);
_typeOffsets.add(offset);
return super._recordType(type, offset);
}
@override
void _replaceReference(Node reference) {
if (_debug) {
int offset = _deferredReferenceOffsets.removeLast();
print('Replacing reference $reference for offset $offset');
}
super._replaceReference(reference);
}
@override
void _replaceType(DartType type, [int newOffset = -1]) {
if (newOffset != -1) {
_typeOffsets[_deferredTypeSlots.last] = newOffset;
}
if (_debug) {
if (newOffset != -1) {
_deferredTypeOffsets.removeLast();
_deferredTypeOffsets.add(newOffset);
}
int offset = _deferredTypeOffsets.removeLast();
print('Replacing type $type for offset $offset');
}
super._replaceType(type, newOffset);
}
}
/// A reference to the getter represented by the [member].
/// The [member] might be either a getter itself, or a field.
class MemberGetterNode implements TreeNode {
/// The member representing the getter, or `null` if the getter could not be
/// resolved.
final Member member;
MemberGetterNode(this.member);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '$member.getter';
}
}
/// Information about invocation of the [member] and its instantiated [type].
class MemberInvocationDartType implements DartType {
final Member member;
final FunctionType type;
MemberInvocationDartType(this.member, this.type);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '($member, $type)';
}
}
/// A reference to the setter represented by the [member].
/// The [member] might be either a setter itself, or a field.
class MemberSetterNode implements TreeNode {
/// The member representing the setter, or `null` if the setter could not be
/// resolved.
final Member member;
MemberSetterNode(this.member);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '$member.setter';
}
}
/// The type of [TreeNode] node that is used as a marker for `null`.
class NullNode implements TreeNode {
final String kind;
const NullNode(this.kind);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '(null-$kind)';
}
}
/// The type of [DartType] node that is used as a marker for `null`.
///
/// It is used for import prefix identifiers, which are resolved to elements,
/// but don't have any types.
class NullType implements DartType {
const NullType();
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '(null-type)';
}
}
/// Type inference listener that records inferred types for later use by
/// [ResolutionApplier].
class ResolutionStorer extends TypeInferenceListener {
/// The offset that is used when the actual offset is not know.
/// The consumer of information should not validate this offset.
static const UNKNOWN_OFFSET = -2;
final List<TreeNode> _declarations;
final List<Node> _references;
final List<DartType> _types;
/// Indices into [_references] which need to be filled in later.
final _deferredReferenceSlots = <int>[];
/// Indices into [_types] which need to be filled in later.
final _deferredTypeSlots = <int>[];
ResolutionStorer(this._declarations, this._references, this._types);
@override
void asExpressionExit(AsExpression expression, DartType inferredType) {
_recordType(expression.type, expression.fileOffset);
_recordType(inferredType, expression.fileOffset);
}
@override
void cascadeExpressionExit(Let expression, DartType inferredType) {
// Overridden so that the type of the expression will not be recorded. We
// don't need to record the type because the type is always the same as the
// type of the target, and we don't have the appropriate offset so we can't
// correctly apply the type even if we recorded it.
}
@override
void catchStatementEnter(Catch node) {
_recordType(node.guard, node.fileOffset);
VariableDeclaration exception = node.exception;
if (exception != null) {
_recordDeclaration(exception, exception.fileOffset);
_recordType(exception.type, exception.fileOffset);
}
VariableDeclaration stackTrace = node.stackTrace;
if (stackTrace != null) {
_recordDeclaration(stackTrace, stackTrace.fileOffset);
_recordType(stackTrace.type, stackTrace.fileOffset);
}
}
@override
bool constructorInvocationEnter(
InvocationExpression expression, DartType typeContext) {
_deferReference(expression.fileOffset);
_deferType(expression.fileOffset);
return true;
}
@override
void constructorInvocationExit(
InvocationExpression expression, DartType inferredType) {
_replaceType(inferredType);
if (expression is ConstructorInvocation) {
_replaceReference(expression.target);
} else if (expression is StaticInvocation) {
_replaceReference(expression.target);
} else {
throw new UnimplementedError('${expression.runtimeType}');
}
}
@override
void fieldInitializerEnter(FieldInitializer initializer) {
_recordReference(initializer.field, initializer.fileOffset);
}
/// Verifies that all deferred work has been completed.
void finished() {
assert(_deferredTypeSlots.isEmpty);
}
@override
void forInStatementEnter(ForInStatement statement,
VariableDeclaration variable, Expression write) {
if (variable != null) {
_deferType(variable.fileOffset);
_recordDeclaration(variable, variable.fileOffset);
} else {
if (write is VariableSet) {
_recordReference(write.variable, write.fileOffset);
_recordType(write.variable.type, write.fileOffset);
} else if (write is PropertySet) {
Field field = write.interfaceTarget;
_recordReference(new MemberSetterNode(field), write.fileOffset);
_recordType(field.type, write.fileOffset);
} else if (write is StaticSet) {
Field field = write.target;
_recordReference(new MemberSetterNode(field), write.fileOffset);
_recordType(field.type, write.fileOffset);
} else {
throw new UnimplementedError('(${write.runtimeType}) $write');
}
}
}
@override
void forInStatementExit(
ForInStatement statement, VariableDeclaration variable) {
if (variable != null) {
_replaceType(variable.type);
}
}
void functionDeclarationEnter(FunctionDeclaration statement) {
_recordDeclaration(statement.variable, statement.fileOffset);
super.functionDeclarationEnter(statement);
}
@override
bool functionExpressionEnter(
FunctionExpression expression, DartType typeContext) {
_recordDeclaration(expression, expression.fileOffset);
return super.functionExpressionEnter(expression, typeContext);
}
@override
void functionExpressionExit(
FunctionExpression expression, DartType inferredType) {
// We don't need to record the inferred type.
// It is already set in the function declaration.
}
@override
bool genericExpressionEnter(
String expressionType, Expression expression, DartType typeContext) {
super.genericExpressionEnter(expressionType, expression, typeContext);
return true;
}
@override
void genericExpressionExit(
String expressionType, Expression expression, DartType inferredType) {
_recordType(inferredType, expression.fileOffset);
super.genericExpressionExit(expressionType, expression, inferredType);
}
@override
void ifNullBeforeRhs(Expression expression) {
_deferType(expression.fileOffset);
}
@override
void ifNullExit(Expression expression, DartType inferredType) {
_replaceType(inferredType);
}
@override
void indexAssignAfterReceiver(Expression write, DartType typeContext) {
_deferReference(write.fileOffset);
_recordType(const IndexAssignNullFunctionType(), write.fileOffset);
_deferType(write.fileOffset);
}
@override
void indexAssignExit(Expression expression, Expression write,
Member writeMember, Procedure combiner, DartType inferredType) {
_replaceReference(writeMember);
_replaceType(inferredType);
_recordReference(
combiner ?? const NullNode('assign-combiner'), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@override
void isExpressionExit(IsExpression expression, DartType inferredType) {
_recordType(expression.type, expression.fileOffset);
_recordType(inferredType, expression.fileOffset);
}
void isNotExpressionExit(
Not expression, DartType type, DartType inferredType) {
_recordType(type, expression.fileOffset);
_recordType(inferredType, expression.fileOffset);
}
@override
void logicalExpressionBeforeRhs(LogicalExpression expression) {
_deferType(expression.fileOffset);
}
@override
void logicalExpressionExit(
LogicalExpression expression, DartType inferredType) {
_replaceType(inferredType);
}
@override
void methodInvocationBeforeArgs(Expression expression, bool isImplicitCall) {
if (!isImplicitCall) {
// When the invocation target is `VariableGet`, we record the target
// before arguments. To ensure this order for method invocations, we
// first record `null`, and then replace it on exit.
_deferReference(expression.fileOffset);
// We are visiting a method invocation like: a.f(args). We have visited a
// but we haven't visited the args yet.
//
// The analyzer AST will expect a type for f at this point. (It can't
// wait until later, because for all it knows, a.f might be a property
// access, in which case the appropriate time for the type is now). But
// the type isn't known yet (because it may depend on type inference based
// on arguments).
//
// So we add a `null` to our list of types; we'll update it with the
// actual type later.
_deferType(expression.fileOffset);
}
_deferType(expression.fileOffset);
super.methodInvocationBeforeArgs(expression, isImplicitCall);
}
@override
void methodInvocationExit(
Expression expression,
Arguments arguments,
bool isImplicitCall,
Member interfaceMember,
FunctionType calleeType,
Substitution substitution,
DartType inferredType) {
_replaceType(
inferredType,
arguments.fileOffset != -1
? arguments.fileOffset
: expression.fileOffset);
if (!isImplicitCall) {
if (interfaceMember is ForwardingStub) {
interfaceMember = ForwardingStub.getInterfaceTarget(interfaceMember);
}
_replaceReference(interfaceMember);
FunctionType invokeType = substitution == null
? calleeType
: substitution.substituteType(calleeType.withoutTypeParameters);
_replaceType(new MemberInvocationDartType(interfaceMember, invokeType));
}
super.genericExpressionExit("methodInvocation", expression, inferredType);
}
@override
void methodInvocationExitCall(Expression expression, Arguments arguments,
bool isImplicitCall, DartType inferredType) {
_replaceType(
inferredType,
arguments.fileOffset != -1
? arguments.fileOffset
: expression.fileOffset);
if (!isImplicitCall) {
throw new UnimplementedError(); // TODO(scheglov): handle this case
}
super.genericExpressionExit("methodInvocation", expression, inferredType);
}
@override
bool propertyAssignEnter(
Expression expression, Expression write, DartType typeContext) {
_deferReference(write.fileOffset);
_deferType(write.fileOffset);
return super.propertyAssignEnter(expression, write, typeContext);
}
@override
void propertyAssignExit(
Expression expression,
Expression write,
Member writeMember,
DartType writeContext,
Procedure combiner,
DartType inferredType) {
if (writeMember is ForwardingStub) {
writeMember = ForwardingStub.getInterfaceTarget(writeMember);
}
_replaceReference(new MemberSetterNode(writeMember));
_replaceType(writeContext);
_recordReference(
combiner ?? const NullNode('assign-combiner'), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@override
void propertyGetExit(
Expression expression, Member member, DartType inferredType) {
_recordReference(new MemberGetterNode(member), expression.fileOffset);
super.propertyGetExit(expression, member, inferredType);
}
@override
void propertyGetExitCall(Expression expression, DartType inferredType) {
throw new UnimplementedError(); // TODO(scheglov): handle this case
// super.propertyGetExitCall(expression, inferredType);
}
@override
void redirectingInitializerEnter(RedirectingInitializer initializer) {
_recordReference(initializer.target, initializer.fileOffset);
}
@override
bool staticAssignEnter(
Expression expression,
String prefixName,
int targetOffset,
Class targetClass,
Expression write,
DartType typeContext) {
// if there was an import prefix, record it.
if (prefixName != null) {
_recordReference(new ImportPrefixNode(prefixName), UNKNOWN_OFFSET);
_recordType(const NullType(), UNKNOWN_OFFSET);
}
// If the static target is explicit (and is a class), record it.
if (targetClass != null) {
_recordReference(targetClass, targetOffset);
_recordType(targetClass.rawType, targetOffset);
}
_deferReference(write.fileOffset);
_deferType(write.fileOffset);
return super.staticAssignEnter(
expression, prefixName, targetOffset, targetClass, write, typeContext);
}
@override
void staticAssignExit(
Expression expression,
Expression write,
Member writeMember,
DartType writeContext,
Procedure combiner,
DartType inferredType) {
_replaceReference(new MemberSetterNode(writeMember));
_replaceType(writeContext);
_recordReference(
combiner ?? const NullNode('assign-combiner'), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@override
bool staticGetEnter(StaticGet expression, String prefixName, int targetOffset,
Class targetClass, DartType typeContext) {
// if there was an import prefix, record it.
if (prefixName != null) {
_recordReference(new ImportPrefixNode(prefixName), UNKNOWN_OFFSET);
_recordType(const NullType(), UNKNOWN_OFFSET);
}
// If the static target is explicit (and is a class), record it.
if (targetClass != null) {
_recordReference(targetClass, targetOffset);
_recordType(targetClass.rawType, targetOffset);
}
return super.staticGetEnter(
expression, prefixName, targetOffset, targetClass, typeContext);
}
@override
void staticGetExit(StaticGet expression, DartType inferredType) {
_recordReference(
new MemberGetterNode(expression.target), expression.fileOffset);
super.staticGetExit(expression, inferredType);
}
@override
bool staticInvocationEnter(StaticInvocation expression, String prefixName,
int targetOffset, Class targetClass, DartType typeContext) {
// if there was an import prefix, record it.
if (prefixName != null) {
_recordReference(new ImportPrefixNode(prefixName), UNKNOWN_OFFSET);
_recordType(const NullType(), UNKNOWN_OFFSET);
}
// If the static target is explicit (and is a class), record it.
if (targetClass != null) {
_recordReference(targetClass, targetOffset);
_recordType(targetClass.rawType, targetOffset);
}
// When the invocation target is `VariableGet`, we record the target
// before arguments. To ensure this order for method invocations, we
// first record `null`, and then replace it on exit.
_deferReference(expression.fileOffset);
// We are visiting a static invocation like: f(args), and we haven't visited
// args yet.
//
// The analyzer AST will expect a type for f at this point. (It can't wait
// until later, because for all it knows, f is a method on `this`, and
// methods need a type for f at this point--see comments in
// [methodInvocationBeforeArgs]). But the type isn't known yet (because it
// may depend on type inference based on arguments).
//
// So we add a `null` to our list of types; we'll update it with the actual
// type later.
_deferType(expression.fileOffset);
_deferType(expression.arguments.fileOffset);
return super.staticInvocationEnter(
expression, prefixName, targetOffset, targetClass, typeContext);
}
@override
void staticInvocationExit(
StaticInvocation expression,
FunctionType calleeType,
Substitution substitution,
DartType inferredType) {
_replaceType(inferredType);
_replaceReference(expression.target);
FunctionType invokeType = substitution == null
? calleeType
: substitution.substituteType(calleeType.withoutTypeParameters);
_replaceType(new MemberInvocationDartType(expression.target, invokeType));
super.genericExpressionExit("staticInvocation", expression, inferredType);
}
@override
void stringConcatenationExit(
StringConcatenation expression, DartType inferredType) {
// We don't need the type - we already know that it is String.
// Moreover, the file offset for StringConcatenation is `-1`.
}
@override
void thisExpressionExit(ThisExpression expression, DartType inferredType) {}
bool typeLiteralEnter(@override TypeLiteral expression, String prefixName,
DartType typeContext) {
// if there was an import prefix, record it.
if (prefixName != null) {
_recordReference(new ImportPrefixNode(prefixName), UNKNOWN_OFFSET);
_recordType(const NullType(), UNKNOWN_OFFSET);
}
return super.typeLiteralEnter(expression, prefixName, typeContext);
}
void typeLiteralExit(TypeLiteral expression, DartType inferredType) {
_recordReference(expression.type, expression.fileOffset);
super.typeLiteralExit(expression, inferredType);
}
@override
bool variableAssignEnter(
Expression expression, DartType typeContext, Expression write) {
_deferReference(write.fileOffset);
_deferType(write.fileOffset);
return super.variableAssignEnter(expression, typeContext, write);
}
@override
void variableAssignExit(Expression expression, DartType writeContext,
Expression write, Procedure combiner, DartType inferredType) {
_replaceReference(write is VariableSet
? write.variable
: const NullNode('writable-variable'));
_replaceType(writeContext);
_recordReference(
combiner ?? const NullNode('assign-combiner'), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@override
void variableDeclarationEnter(VariableDeclaration statement) {
_recordDeclaration(statement, statement.fileOffset);
_deferType(statement.fileOffset);
super.variableDeclarationEnter(statement);
}
@override
void variableDeclarationExit(
VariableDeclaration statement, DartType inferredType) {
_replaceType(statement.type);
super.variableDeclarationExit(statement, inferredType);
}
@override
void variableGetExit(VariableGet expression, DartType inferredType) {
/// Return `true` if the given [variable] declaration occurs in a let
/// expression that is, or is part of, a cascade expression.
bool isInCascade(VariableDeclaration variable) {
TreeNode ancestor = variable.parent;
while (ancestor is Let) {
if (ancestor is ShadowCascadeExpression) {
return true;
}
ancestor = ancestor.parent;
}
return false;
}
VariableDeclaration variable = expression.variable;
if (isInCascade(variable)) {
return;
}
_recordReference(variable, expression.fileOffset);
TreeNode function = variable.parent;
if (function is FunctionDeclaration) {
_recordType(new FunctionReferenceDartType(function, inferredType),
expression.fileOffset);
} else {
_recordType(inferredType, expression.fileOffset);
}
}
/// Record `null` as the reference at the given [offset], and put the current
/// slot into the [_deferredReferenceSlots] stack.
void _deferReference(int offset) {
int slot = _recordReference(null, offset);
_deferredReferenceSlots.add(slot);
}
/// Record `null` as the type at the given [offset], and put the current
/// slot into the [_deferredTypeSlots] stack.
void _deferType(int offset) {
int slot = _recordType(null, offset);
_deferredTypeSlots.add(slot);
}
void _recordDeclaration(TreeNode declaration, int offset) {
_declarations.add(declaration);
}
int _recordReference(Node target, int offset) {
int slot = _references.length;
_references.add(target);
return slot;
}
int _recordType(DartType type, int offset) {
int slot = _types.length;
_types.add(type);
return slot;
}
void _replaceReference(Node reference) {
int slot = _deferredReferenceSlots.removeLast();
_references[slot] = reference;
}
void _replaceType(DartType type, [int newOffset = -1]) {
int slot = _deferredTypeSlots.removeLast();
_types[slot] = type;
}
}