blob: a18f76fe36f45b3b82d84d70e6ed8d7196461b05 [file] [log] [blame]
// Copyright (c) 2015, 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.
/// Defines static information collected by the type checker and used later by
/// emitters to generate code.
// TODO(jmesserly): this was ported from package:dev_compiler, and needs to be
// refactored to fit into analyzer.
library analyzer.src.task.strong.info;
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/error.dart';
import 'rules.dart';
// The abstract type of coercions mapping one type to another.
// This class also exposes static builder functions which
// check for errors and reduce redundant coercions to the identity.
abstract class Coercion {
final DartType fromType;
final DartType toType;
Coercion(this.fromType, this.toType);
static Coercion cast(DartType fromT, DartType toT) => new Cast(fromT, toT);
static Coercion identity(DartType type) => new Identity(type);
static Coercion error() => new CoercionError();
}
// Coercion which casts one type to another
class Cast extends Coercion {
Cast(DartType fromType, DartType toType) : super(fromType, toType);
}
// The identity coercion
class Identity extends Coercion {
Identity(DartType fromType) : super(fromType, fromType);
}
// The error coercion. This coercion signals that a coercion
// could not be generated. The code generator should not see
// these.
class CoercionError extends Coercion {
CoercionError() : super(null, null);
}
// TODO(jmesserly): this could use some refactoring. These are essentially
// like ErrorCodes in analyzer, but we're including some details in our message.
// Analyzer instead has template strings, and replaces '{0}' with the first
// argument.
abstract class StaticInfo {
/// AST Node this info is attached to.
AstNode get node;
// TODO(jmesserly): review the usage of error codes. We probably want our own,
// as well as some DDC specific [ErrorType]s.
ErrorCode toErrorCode();
// TODO(jmesserly): what convention to use here?
String get name => 'dev_compiler.$runtimeType';
List<Object> get arguments => [node];
AnalysisError toAnalysisError() {
int begin = node is AnnotatedNode
? (node as AnnotatedNode).firstTokenAfterCommentAndMetadata.offset
: node.offset;
int length = node.end - begin;
var source = (node.root as CompilationUnit).element.source;
return new AnalysisError(source, begin, length, toErrorCode(), arguments);
}
}
/// Implicitly injected expression conversion.
abstract class CoercionInfo extends StaticInfo {
final TypeRules rules;
final Expression node;
DartType get convertedType;
CoercionInfo(this.rules, this.node);
DartType get baseType => rules.getStaticType(node);
DartType get staticType => convertedType;
String get message;
toErrorCode() => new HintCode(name, message);
static const String _propertyName = 'dev_compiler.src.info.CoercionInfo';
/// Gets the coercion info associated with this node.
static CoercionInfo get(AstNode node) => node.getProperty(_propertyName);
/// Sets the coercion info associated with this node.
static CoercionInfo set(AstNode node, CoercionInfo info) {
node.setProperty(_propertyName, info);
return info;
}
}
// Base class for all casts from base type to sub type.
abstract class DownCast extends CoercionInfo {
Cast _cast;
DownCast._internal(TypeRules rules, Expression expression, this._cast)
: super(rules, expression) {
assert(_cast.toType != baseType &&
_cast.fromType == baseType &&
(baseType.isDynamic ||
// Call methods make the following non-redundant
_cast.toType.isSubtypeOf(baseType) ||
baseType.isAssignableTo(_cast.toType)));
}
Cast get cast => _cast;
DartType get convertedType => _cast.toType;
@override List<Object> get arguments => [node, baseType, convertedType];
@override String get message => '{0} ({1}) will need runtime check '
'to cast to type {2}';
// Factory to create correct DownCast variant.
static StaticInfo create(TypeRules rules, Expression expression, Cast cast,
{String reason}) {
final fromT = cast.fromType;
final toT = cast.toType;
// toT <:_R fromT => to <: fromT
// NB: classes with call methods are subtypes of function
// types, but the function type is not assignable to the class
assert(toT.isSubtypeOf(fromT) || fromT.isAssignableTo(toT));
// Handle null call specially.
if (expression is NullLiteral) {
// TODO(vsm): Create a NullCast for this once we revisit nonnullability.
return new DownCastImplicit(rules, expression, cast);
}
// Inference "casts":
if (expression is Literal) {
// fromT should be an exact type - this will almost certainly fail at
// runtime.
return new StaticTypeError(rules, expression, toT, reason: reason);
}
if (expression is FunctionExpression) {
// fromT should be an exact type - this will almost certainly fail at
// runtime.
return new UninferredClosure(rules, expression, cast);
}
if (expression is InstanceCreationExpression) {
// fromT should be an exact type - this will almost certainly fail at
// runtime.
return new StaticTypeError(rules, expression, toT, reason: reason);
}
// Composite cast: these are more likely to fail.
if (!rules.isGroundType(toT)) {
// This cast is (probably) due to our different treatment of dynamic.
// It may be more likely to fail at runtime.
return new DownCastComposite(rules, expression, cast);
}
// Dynamic cast
if (fromT.isDynamic) {
return new DynamicCast(rules, expression, cast);
}
// Assignment cast
var parent = expression.parent;
if (parent is VariableDeclaration && (parent.initializer == expression)) {
return new AssignmentCast(rules, expression, cast);
}
// Other casts
return new DownCastImplicit(rules, expression, cast);
}
}
//
// Standard down casts. These casts are implicitly injected by the compiler.
//
// A down cast from dynamic to T.
class DynamicCast extends DownCast {
DynamicCast(TypeRules rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
toErrorCode() => new HintCode(name, message);
}
// A down cast due to a variable declaration to a ground type. E.g.,
// T x = expr;
// where T is ground. We exclude non-ground types as these behave differently
// compared to standard Dart.
class AssignmentCast extends DownCast {
AssignmentCast(TypeRules rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
toErrorCode() => new HintCode(name, message);
}
//
// Temporary "casts" of allocation sites - literals, constructor invocations,
// and closures. These should be handled by contextual inference. In most
// cases, inference will be sufficient, though in some it may unmask an actual
// error: e.g.,
// List<int> l = [1, 2, 3]; // Inference succeeds
// List<String> l = [1, 2, 3]; // Inference reveals static type error
// We're marking all as warnings for now.
//
// TODO(vsm,leafp): Remove this.
class UninferredClosure extends DownCast {
UninferredClosure(TypeRules rules, FunctionExpression expression, Cast cast)
: super._internal(rules, expression, cast);
toErrorCode() => new StaticTypeWarningCode(name, message);
}
//
// Implicit down casts. These are only injected by the compiler by flag.
//
// A down cast to a non-ground type. These behave differently from standard
// Dart and may be more likely to fail at runtime.
class DownCastComposite extends DownCast {
DownCastComposite(TypeRules rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
toErrorCode() => new StaticTypeWarningCode(name, message);
}
// A down cast to a non-ground type. These behave differently from standard
// Dart and may be more likely to fail at runtime.
class DownCastImplicit extends DownCast {
DownCastImplicit(TypeRules rules, Expression expression, Cast cast)
: super._internal(rules, expression, cast);
toErrorCode() => new HintCode(name, message);
}
// An inferred type for the wrapped expression, which may need to be
// reified into the term
abstract class InferredTypeBase extends CoercionInfo {
final DartType _type;
InferredTypeBase._internal(TypeRules rules, Expression expression, this._type)
: super(rules, expression);
DartType get type => _type;
DartType get convertedType => type;
@override String get message => '{0} has inferred type {1}';
@override List get arguments => [node, type];
toErrorCode() => new HintCode(name, message);
}
// Standard / unspecialized inferred type
class InferredType extends InferredTypeBase {
InferredType(TypeRules rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
// Factory to create correct InferredType variant.
static InferredTypeBase create(
TypeRules rules, Expression expression, DartType type) {
// Specialized inference:
if (expression is Literal) {
return new InferredTypeLiteral(rules, expression, type);
}
if (expression is InstanceCreationExpression) {
return new InferredTypeAllocation(rules, expression, type);
}
if (expression is FunctionExpression) {
return new InferredTypeClosure(rules, expression, type);
}
return new InferredType(rules, expression, type);
}
}
// An infered type for a literal expression.
class InferredTypeLiteral extends InferredTypeBase {
InferredTypeLiteral(TypeRules rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
}
// An inferred type for a non-literal allocation site.
class InferredTypeAllocation extends InferredTypeBase {
InferredTypeAllocation(TypeRules rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
}
// An inferred type for a closure expression
class InferredTypeClosure extends InferredTypeBase {
InferredTypeClosure(TypeRules rules, Expression expression, DartType type)
: super._internal(rules, expression, type);
}
class DynamicInvoke extends CoercionInfo {
DynamicInvoke(TypeRules rules, Expression expression)
: super(rules, expression);
DartType get convertedType => rules.provider.dynamicType;
String get message => '{0} requires dynamic invoke';
toErrorCode() => new HintCode(name, message);
static const String _propertyName = 'dev_compiler.src.info.DynamicInvoke';
/// Whether this [node] is the target of a dynamic operation.
static bool get(AstNode node) {
var value = node.getProperty(_propertyName);
return value != null ? value : false;
}
/// Sets whether this node is the target of a dynamic operation.
static bool set(AstNode node, bool value) {
// Free the storage for things that aren't dynamic.
if (value == false) value = null;
node.setProperty(_propertyName, value);
return value;
}
}
abstract class StaticError extends StaticInfo {
final AstNode node;
StaticError(this.node);
String get message;
toErrorCode() => new CompileTimeErrorCode(name, message);
}
class StaticTypeError extends StaticError {
final DartType baseType;
final DartType expectedType;
String reason = null;
StaticTypeError(TypeRules rules, Expression expression, this.expectedType,
{this.reason})
: baseType = rules.getStaticType(expression),
super(expression);
@override List<Object> get arguments => [node, baseType, expectedType];
@override String get message =>
'Type check failed: {0} ({1}) is not of type {2}' +
((reason == null) ? '' : ' because $reason');
}
class InvalidVariableDeclaration extends StaticError {
final DartType expectedType;
InvalidVariableDeclaration(
TypeRules rules, AstNode declaration, this.expectedType)
: super(declaration);
@override List<Object> get arguments => [expectedType];
@override String get message => 'Type check failed: null is not of type {0}';
}
class InvalidParameterDeclaration extends StaticError {
final DartType expectedType;
InvalidParameterDeclaration(
TypeRules rules, FormalParameter declaration, this.expectedType)
: super(declaration);
@override List<Object> get arguments => [node, expectedType];
@override String get message => 'Type check failed: {0} is not of type {1}';
}
class NonGroundTypeCheckInfo extends StaticInfo {
final DartType type;
final AstNode node;
NonGroundTypeCheckInfo(this.node, this.type) {
assert(node is IsExpression || node is AsExpression);
}
@override List<Object> get arguments => [type];
String get message =>
"Runtime check on non-ground type {0} may throw StrongModeError";
toErrorCode() => new HintCode(name, message);
}
// Invalid override of an instance member of a class.
abstract class InvalidOverride extends StaticError {
/// Member declaration with the invalid override.
final ExecutableElement element;
/// Type (class or interface) that provides the base declaration.
final InterfaceType base;
/// Actual type of the overridden member.
final DartType subType;
/// Actual type of the base member.
final DartType baseType;
/// Whether the error comes from combining a base class and an interface
final bool fromBaseClass;
/// Whether the error comes from a mixin (either overriding a base class or an
/// interface declaration).
final bool fromMixin;
InvalidOverride(
AstNode node, this.element, this.base, this.subType, this.baseType)
: fromBaseClass = node is ExtendsClause,
fromMixin = node.parent is WithClause,
super(node);
ClassElement get parent => element.enclosingElement;
@override List<Object> get arguments =>
[parent.name, element.name, subType, base, baseType];
String _messageHelper(String errorName) {
var lcErrorName = errorName.toLowerCase();
var intro = fromBaseClass
? 'Base class introduces an $lcErrorName'
: (fromMixin ? 'Mixin introduces an $lcErrorName' : errorName);
return '$intro. The type of {0}.{1} ({2}) is not a '
'subtype of {3}.{1} ({4}).';
}
}
// Invalid override due to incompatible type. I.e., the overridden signature
// is not compatible with the original.
class InvalidMethodOverride extends InvalidOverride {
InvalidMethodOverride(AstNode node, ExecutableElement element,
InterfaceType base, FunctionType subType, FunctionType baseType)
: super(node, element, base, subType, baseType);
String get message => _messageHelper('Invalid override');
}
/// Dart constructors have one weird quirk, illustrated with this example:
///
/// class Base {
/// var x;
/// Base() : x = print('Base.1') {
/// print('Base.2');
/// }
/// }
///
/// class Derived extends Base {
/// var y, z;
/// Derived()
/// : y = print('Derived.1'),
/// super(),
/// z = print('Derived.2') {
/// print('Derived.3');
/// }
/// }
///
/// The order will be Derived.1, Base.1, Derived.2, Base.2, Derived.3; this
/// ordering preserves the invariant that code can't observe uninitialized
/// state, however it results in super constructor body not being run
/// immediately after super initializers. Normally this isn't observable, but it
/// could be if initializers have side effects.
///
/// Better to have `super` at the end, as required by the Dart style guide:
/// <http://goo.gl/q1T4BB>
///
/// For now this is the only pattern we support.
class InvalidSuperInvocation extends StaticError {
InvalidSuperInvocation(SuperConstructorInvocation node) : super(node);
@override String get message => "super call must be last in an initializer "
"list (see http://goo.gl/q1T4BB): {0}";
}