blob: 90fcf2d98f64aeeee7316b34f846c627a7f309b5 [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/type_inference/type_inference_listener.dart';
import 'package:kernel/ast.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 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<Statement> 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(Statement 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 {
final Member member;
MemberGetterNode(this.member);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '$member.getter';
}
}
/// TODO(scheglov) document
class MemberReferenceDartType implements DartType {
final Member member;
final List<DartType> typeArguments;
MemberReferenceDartType(this.member, this.typeArguments);
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '<${typeArguments.join(', ')}>$member';
}
}
/// A reference to the setter represented by the [member].
/// The [member] might be either a setter itself, or a field.
class MemberSetterNode implements TreeNode {
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 using `null`
/// combiner for not compound assignments.
class NullAssignmentCombinerNode implements TreeNode {
const NullAssignmentCombinerNode();
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
String toString() {
return '(null-assignment-combiner)';
}
}
/// Type inference listener that records inferred types for later use by
/// [ResolutionApplier].
class ResolutionStorer extends TypeInferenceListener {
final List<Statement> _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
bool constructorInvocationEnter(
InvocationExpression expression, DartType typeContext) {
return super.constructorInvocationEnter(expression, typeContext);
}
@override
void constructorInvocationExit(
InvocationExpression expression, DartType inferredType) {
if (expression is ConstructorInvocation) {
_recordReference(expression.target, expression.fileOffset);
} else if (expression is StaticInvocation) {
_recordReference(expression.target, expression.fileOffset);
} else {
throw new UnimplementedError('${expression.runtimeType}');
}
super.constructorInvocationExit(expression, inferredType);
}
/// Verifies that all deferred work has been completed.
void finished() {
assert(_deferredTypeSlots.isEmpty);
}
@override
void functionDeclarationExit(FunctionDeclaration statement) {
_recordDeclaration(statement.variable, statement.fileOffset);
_recordType(statement.function.returnType, statement.fileOffset);
super.functionDeclarationExit(statement);
}
@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 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 NullAssignmentCombinerNode(), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@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, Object interfaceMember, DartType inferredType) {
_replaceType(
inferredType,
arguments.fileOffset != -1
? arguments.fileOffset
: expression.fileOffset);
if (!isImplicitCall) {
_replaceReference(interfaceMember);
_replaceType(new MemberReferenceDartType(
interfaceMember as Member, arguments.types));
}
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) {
_replaceReference(new MemberSetterNode(writeMember));
_replaceType(writeContext);
_recordReference(
combiner ?? const NullAssignmentCombinerNode(), write.fileOffset);
_recordType(inferredType, write.fileOffset);
}
@override
void propertyGetExit(
Expression expression, Object member, DartType inferredType) {
_recordReference(new MemberGetterNode(member), expression.fileOffset);
super.propertyGetExit(expression, member, inferredType);
}
@override
void staticGetExit(StaticGet expression, DartType inferredType) {
_recordReference(expression.target, expression.fileOffset);
super.staticGetExit(expression, inferredType);
}
@override
bool staticInvocationEnter(
StaticInvocation expression, DartType typeContext) {
// 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, typeContext);
}
@override
void staticInvocationExit(
StaticInvocation expression, DartType inferredType) {
_replaceType(inferredType);
_replaceReference(expression.target);
// TODO(paulberry): get the actual callee function type from the inference
// engine
var calleeType = const DynamicType();
_replaceType(calleeType);
super.genericExpressionExit("staticInvocation", expression, inferredType);
}
void typeLiteralExit(TypeLiteral expression, DartType inferredType) {
_recordReference(expression.type, expression.fileOffset);
super.typeLiteralExit(expression, inferredType);
}
@override
bool variableAssignEnter(Expression expression, DartType typeContext) {
_deferReference(expression.fileOffset);
_deferType(expression.fileOffset);
return super.variableAssignEnter(expression, typeContext);
}
@override
void variableAssignExit(Expression expression, DartType writeContext,
Procedure combiner, DartType inferredType) {
VariableSet variableSet = expression;
_replaceReference(variableSet.variable);
_replaceType(writeContext);
_recordReference(
combiner ?? const NullAssignmentCombinerNode(), variableSet.fileOffset);
super.variableAssignExit(expression, writeContext, combiner, inferredType);
}
@override
void variableDeclarationEnter(VariableDeclaration statement) {
_deferType(statement.fileOffset);
super.variableDeclarationEnter(statement);
}
@override
void variableDeclarationExit(
VariableDeclaration statement, DartType inferredType) {
_recordDeclaration(statement, statement.fileOffset);
_replaceType(statement.type);
super.variableDeclarationExit(statement, inferredType);
}
@override
void variableGetExit(VariableGet expression, DartType inferredType) {
VariableDeclaration variable = expression.variable;
_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(Statement 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;
}
}