fix #31782, implement not-null analysis for dartdevk

Conceptually this is a port of analyzer/nullable_type_inference.dart.
It contains some refactoring and other improvements, as well as tests.

Change-Id: I448440793f11e2e87583d9d47c0e6a1e54ae21f7
Reviewed-on: https://dart-review.googlesource.com/34308
Commit-Queue: Jenny Messerly <jmesserly@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
This commit is contained in:
Jenny Messerly 2018-01-12 22:40:50 +00:00 committed by commit-bot@chromium.org
parent 93abeeb47e
commit f17ccdfbea
19 changed files with 1144 additions and 185 deletions

View file

@ -71,10 +71,10 @@ bool isNativeAnnotation(DartObjectImpl value) =>
isBuiltinAnnotation(value, '_js_helper', 'Native');
bool isNotNullAnnotation(DartObjectImpl value) =>
isBuiltinAnnotation(value, '_js_helper', 'NotNull');
isBuiltinAnnotation(value, '_js_helper', '_NotNull');
bool isNullCheckAnnotation(DartObjectImpl value) =>
isBuiltinAnnotation(value, '_js_helper', 'NullCheck');
isBuiltinAnnotation(value, '_js_helper', '_NullCheck');
/// Returns the name value of the `JSExportName` annotation (when compiling
/// the SDK), or `null` if there's none. This is used to control the name

View file

@ -11,7 +11,6 @@ import 'package:dev_compiler/src/kernel/target.dart';
import 'package:front_end/src/api_prototype/standard_file_system.dart';
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
import 'package:front_end/src/multi_root_file_system.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:path/path.dart' as path;
import 'package:source_maps/source_maps.dart';
@ -152,9 +151,7 @@ Future<CompilerResult> _compile(List<String> args,
.map((s) => stringToCustomUri(s, multiRoots, customScheme))
.toList();
var sdkSummaryPath = argResults['dart-sdk-summary'] ??
path.join(path.dirname(path.dirname(Platform.resolvedExecutable)), 'lib',
'_internal', 'ddc_sdk.dill');
var sdkSummaryPath = argResults['dart-sdk-summary'] ?? defaultSdkSummaryPath;
var packageFile =
argResults['packages'] ?? path.absolute(ddcPath, '..', '..', '.packages');
@ -219,7 +216,7 @@ Future<CompilerResult> _compile(List<String> args,
JS.Program compileToJSModule(Program p, List<Program> summaries,
List<Uri> summaryUris, Map<String, String> declaredVariables) {
var compiler = new ProgramCompiler(new NativeTypeSet(p, new CoreTypes(p)),
var compiler = new ProgramCompiler(new NativeTypeSet(p),
declaredVariables: declaredVariables);
return compiler.emitProgram(p, summaries, summaryUris);
}
@ -375,3 +372,10 @@ Map<String, String> parseAndRemoveDeclaredVariables(List<String> args) {
return declaredVariables;
}
/// The default path of the kernel summary for the Dart SDK.
final defaultSdkSummaryPath = path.join(
path.dirname(path.dirname(Platform.resolvedExecutable)),
'lib',
'_internal',
'ddc_sdk.dill');

View file

@ -22,6 +22,7 @@ import 'js_interop.dart';
import 'js_typerep.dart';
import 'kernel_helpers.dart';
import 'native_types.dart';
import 'nullable_inference.dart';
import 'property_model.dart';
import 'type_table.dart';
@ -188,6 +189,8 @@ class ProgramCompiler
final ConstantVisitor _constants;
NullableInference _nullableInference;
ProgramCompiler(NativeTypeSet nativeTypes,
{this.emitMetadata: true,
this.replCompile: false,
@ -197,22 +200,24 @@ class ProgramCompiler
_constants = new ConstantVisitor(nativeTypes.coreTypes),
types = new TypeSchemaEnvironment(
nativeTypes.coreTypes, new IncrementalClassHierarchy(), true),
_jsArrayClass = nativeTypes.getClass('dart:_interceptors', 'JSArray'),
_jsArrayClass =
nativeTypes.sdk.getClass('dart:_interceptors', 'JSArray'),
_asyncStreamIteratorClass =
nativeTypes.getClass('dart:async', 'StreamIterator'),
nativeTypes.sdk.getClass('dart:async', 'StreamIterator'),
privateSymbolClass =
nativeTypes.getClass('dart:_js_helper', 'PrivateSymbol'),
nativeTypes.sdk.getClass('dart:_js_helper', 'PrivateSymbol'),
linkedHashMapImplClass =
nativeTypes.getClass('dart:_js_helper', 'LinkedMap'),
nativeTypes.sdk.getClass('dart:_js_helper', 'LinkedMap'),
identityHashMapImplClass =
nativeTypes.getClass('dart:_js_helper', 'IdentityMap'),
nativeTypes.sdk.getClass('dart:_js_helper', 'IdentityMap'),
linkedHashSetImplClass =
nativeTypes.getClass('dart:collection', '_HashSet'),
nativeTypes.sdk.getClass('dart:collection', '_HashSet'),
identityHashSetImplClass =
nativeTypes.getClass('dart:collection', '_IdentityHashSet'),
nativeTypes.sdk.getClass('dart:collection', '_IdentityHashSet'),
syncIterableClass =
nativeTypes.getClass('dart:_js_helper', 'SyncIterable') {
_typeRep = new JSTypeRep(types, coreTypes);
nativeTypes.sdk.getClass('dart:_js_helper', 'SyncIterable') {
_typeRep = new JSTypeRep(types, nativeTypes.sdk);
_nullableInference = new NullableInference(_typeRep);
}
ClassHierarchy get hierarchy => types.hierarchy;
@ -240,6 +245,7 @@ class ProgramCompiler
// There is JS code in dart:* that depends on their names.
_runtimeModule = new JS.Identifier('dart');
_extensionSymbolsModule = new JS.Identifier('dartx');
_nullableInference.allowNotNullDeclarations = true;
} else {
// Otherwise allow these to be renamed so users can write them.
_runtimeModule = new JS.TemporaryId('dart');
@ -1263,18 +1269,10 @@ class ProgramCompiler
JS.Expression _emitConstructor(Constructor node, List<Field> fields,
bool isCallable, JS.Expression className) {
var params = _emitFormalParameters(node.function);
var savedFunction = _currentFunction;
var savedLetVariables = _letVariables;
_currentFunction = node.function;
_letVariables = [];
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
var body = _emitConstructorBody(node, fields, className);
_letVariables = savedLetVariables;
_superAllowed = savedSuperAllowed;
_currentFunction = savedFunction;
var body = _withCurrentFunction(
node.function,
() => _superDisallowed(
() => _emitConstructorBody(node, fields, className)));
return _finishConstructorFunction(params, body, isCallable);
}
@ -1732,8 +1730,7 @@ class ProgramCompiler
///
/// Same technique is applied if interface I has fields, and C doesn't declare
/// neither the fields nor the corresponding getters and setters.
Iterable<JS.Method> _addMockMembers(
Member member, Class c, List<JS.Method> jsMethods) {
void _addMockMembers(Member member, Class c, List<JS.Method> jsMethods) {
JS.Method implementMockMember(
List<TypeParameter> typeParameters,
List<VariableDeclaration> namedParameters,
@ -2690,30 +2687,28 @@ class ProgramCompiler
// using ES6 generators.
emitGeneratorFn(Iterable<JS.Expression> getParameters(JS.Block jsBody)) {
var savedSuperAllowed = _superAllowed;
var savedController = _asyncStarController;
_superAllowed = false;
_asyncStarController = function.asyncMarker == AsyncMarker.AsyncStar
? new JS.TemporaryId('stream')
: null;
// Visit the body with our async* controller set.
//
// TODO(jmesserly): this will emit argument initializers (for default
// values) inside the generator function body. Is that the best place?
var jsBody = _emitFunctionBody(function)..sourceInformation = function;
JS.Expression gen =
new JS.Fun(getParameters(jsBody), jsBody, isGenerator: true);
JS.Expression gen;
_superDisallowed(() {
// Visit the body with our async* controller set.
//
// TODO(jmesserly): this will emit argument initializers (for default
// values) inside the generator function body. Is that the best place?
var jsBody = _emitFunctionBody(function)..sourceInformation = function;
gen = new JS.Fun(getParameters(jsBody), jsBody, isGenerator: true);
// Name the function if possible, to get better stack traces.
if (name != null) {
name = JS.friendlyNameForDartOperator[name] ?? name;
gen = new JS.NamedFunction(new JS.TemporaryId(name), gen);
}
if (JS.This.foundIn(gen)) gen = js.call('#.bind(this)', gen);
// Name the function if possible, to get better stack traces.
if (name != null) {
name = JS.friendlyNameForDartOperator[name] ?? name;
gen = new JS.NamedFunction(new JS.TemporaryId(name), gen);
}
if (JS.This.foundIn(gen)) gen = js.call('#.bind(this)', gen);
});
_superAllowed = savedSuperAllowed;
_asyncStarController = savedController;
return gen;
}
@ -2795,18 +2790,13 @@ class ProgramCompiler
}
JS.Block _emitFunctionBody(FunctionNode f) {
var savedFunction = _currentFunction;
_currentFunction = f;
var savedLetVariables = _letVariables;
_letVariables = [];
var block = _emitArgumentInitializers(f);
var jsBody = _visitStatement(f.body);
if (jsBody != null) addStatementToList(jsBody, block);
_initTempVars(block);
_currentFunction = savedFunction;
_letVariables = savedLetVariables;
List<JS.Statement> block;
_withCurrentFunction(f, () {
block = _emitArgumentInitializers(f);
var jsBody = _visitStatement(f.body);
if (jsBody != null) addStatementToList(jsBody, block);
_initTempVars(block);
});
if (f.asyncMarker == AsyncMarker.Sync) {
// It is a JS syntax error to use let or const to bind two variables with
@ -2826,6 +2816,29 @@ class ProgramCompiler
return new JS.Block(block);
}
T _withCurrentFunction<T>(FunctionNode fn, T action()) {
var savedFunction = _currentFunction;
_currentFunction = fn;
var savedLetVariables = _letVariables;
_letVariables = [];
_nullableInference.enterFunction(fn);
var result = action();
_nullableInference.exitFunction(fn);
_currentFunction = savedFunction;
_letVariables = savedLetVariables;
return result;
}
T _superDisallowed<T>(T action()) {
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
var result = action();
_superAllowed = savedSuperAllowed;
return result;
}
/// Emits argument initializers, which handles optional/named args, as well
/// as generic type checks needed due to our covariance.
List<JS.Statement> _emitArgumentInitializers(FunctionNode f) {
@ -2889,7 +2902,7 @@ class ProgramCompiler
}
bool _annotatedNullCheck(List<Expression> annotations) =>
annotations.any(isNullCheckAnnotation);
annotations.any(_nullableInference.isNullCheckAnnotation);
JS.Statement _nullParameterCheck(JS.Expression param) {
var call = _callHelper('argumentError((#))', [param]);
@ -3122,7 +3135,7 @@ class ProgramCompiler
// target.
var current = node.target;
while (current is LabeledStatement) {
current = (current as LabeledStatement).body;
current = current.body;
}
if (identical(current, target)) {
return new JS.Break(name);
@ -3419,12 +3432,10 @@ class ProgramCompiler
@override
visitTryFinally(TryFinally node) {
var body = _visitStatement(node.body);
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
var finallyBlock = _visitStatement(node.finalizer).toBlock();
_superAllowed = savedSuperAllowed;
var finallyBlock =
_superDisallowed(() => _visitStatement(node.finalizer).toBlock());
if (body is JS.Try && (body as JS.Try).finallyPart == null) {
if (body is JS.Try && body.finallyPart == null) {
// Kernel represents Dart try/catch/finally as try/catch nested inside of
// try/finally. Flatten that pattern in the output into JS try/catch/
// finally.
@ -4256,45 +4267,7 @@ class ProgramCompiler
///
/// If [localIsNullable] is not supplied, this will use the known list of
/// [_notNullLocals].
bool isNullable(Expression expr) {
// TODO(jmesserly): we do recursive calls in a few places. This could
// leads to O(depth) cost for calling this function. We could store the
// resulting value if that becomes an issue, so we maintain the invariant
// that each node is visited once.
if (expr is PropertyGet) {
var target = expr.interfaceTarget;
// tear-offs are not null, other accessors are nullable.
return target is Procedure && target.isAccessor;
}
if (expr is StaticGet) {
var target = expr.target;
// tear-offs are not null, other accessors are nullable.
return target is Field || (target is Procedure && target.isGetter);
}
if (expr is TypeLiteral) return false;
if (expr is BasicLiteral) return expr.value == null;
if (expr is IsExpression) return false;
if (expr is FunctionExpression) return false;
if (expr is ThisExpression) return false;
if (expr is ConditionalExpression) {
return isNullable(expr.then) || isNullable(expr.otherwise);
}
if (expr is ConstructorInvocation) return false;
if (expr is LogicalExpression) return false;
if (expr is Not) return false;
if (expr is StaticInvocation) {
return expr.target != coreTypes.identicalProcedure;
}
if (expr is DirectMethodInvocation) {
// TODO(jmesserly): this is to capture that our primitive classes
// (int, double, num, bool, String) do not return null from their
// operator methods.
return !isPrimitiveType(expr.target.enclosingClass.rawType);
}
// TODO(jmesserly): handle other cases.
return true;
}
bool isNullable(Expression expr) => _nullableInference.isNullable(expr);
bool isPrimitiveType(DartType t) => _typeRep.isPrimitive(t);
@ -4570,8 +4543,6 @@ class ProgramCompiler
var isTypeError = node.isTypeError;
if (!isTypeError && types.isSubtypeOf(from, to)) return jsFrom;
// TODO(jmesserly): implicit function type instantiation for kernel?
// All Dart number types map to a JS double.
if (_typeRep.isNumber(from) && _typeRep.isNumber(to)) {
// Make sure to check when converting to int.

View file

@ -75,12 +75,6 @@ bool isJsPeerInterface(Expression value) =>
bool isNativeAnnotation(Expression value) =>
_isBuiltinAnnotation(value, '_js_helper', 'Native');
bool isNotNullAnnotation(Expression value) =>
_isBuiltinAnnotation(value, '_js_helper', 'NotNull');
bool isNullCheckAnnotation(Expression value) =>
_isBuiltinAnnotation(value, '_js_helper', 'NullCheck');
bool isJSAnonymousType(Class namedClass) {
return _isJSNative(namedClass) &&
findAnnotation(namedClass, isJSAnonymousAnnotation) != null;

View file

@ -2,8 +2,9 @@
// 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:kernel/kernel.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
import 'package:kernel/type_environment.dart';
/// An abstraction of the JS types
@ -88,8 +89,17 @@ class JSUnknown extends JSType {
class JSTypeRep {
final TypeEnvironment types;
final CoreTypes coreTypes;
final LibraryIndex sdk;
JSTypeRep(this.types, this.coreTypes);
final Class _jsBool;
final Class _jsNumber;
final Class _jsString;
JSTypeRep(this.types, this.sdk)
: coreTypes = types.coreTypes,
_jsBool = sdk.getClass('dart:_interceptors', 'JSBool'),
_jsNumber = sdk.getClass('dart:_interceptors', 'JSNumber'),
_jsString = sdk.getClass('dart:_interceptors', 'JSString');
JSType typeFor(DartType type) {
while (type is TypeParameterType) {
@ -107,8 +117,8 @@ class JSTypeRep {
c == coreTypes.doubleClass) {
return JSType.jsNumber;
}
if (c == coreTypes.boolClass.rawType) return JSType.jsBoolean;
if (c == coreTypes.stringClass.rawType) return JSType.jsString;
if (c == coreTypes.boolClass) return JSType.jsBoolean;
if (c == coreTypes.stringClass) return JSType.jsString;
if (c == coreTypes.objectClass) return JSType.jsUnknown;
if (c == coreTypes.futureOrClass) {
var argumentRep = typeFor(type.typeArguments[0]);
@ -124,6 +134,19 @@ class JSTypeRep {
return JSType.jsObject;
}
/// Given a Dart type return the known implementation type, if any.
/// Given `bool`, `String`, or `num`/`int`/`double`,
/// returns the corresponding class in `dart:_interceptors`:
/// `JSBool`, `JSString`, and `JSNumber` respectively, otherwise null.
Class getImplementationClass(DartType t) {
JSType rep = typeFor(t);
// Number, String, and Bool are final
if (rep == JSType.jsNumber) return _jsNumber;
if (rep == JSType.jsBoolean) return _jsBool;
if (rep == JSType.jsString) return _jsString;
return null;
}
/// If the type [t] is [int] or [double], or a type parameter
/// bounded by [int], [double] or [num] returns [num].
/// Otherwise returns [t].

View file

@ -3,8 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:collection';
import 'package:kernel/kernel.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
/// Contains information about native JS types (those types provided by the
/// implementation) that are also provided by the Dart SDK.
@ -24,8 +25,8 @@ import 'package:kernel/core_types.dart';
/// This will provide the [Iterable.first] property, without needing to add
/// `first` to the `Array.prototype`.
class NativeTypeSet {
final sdk = new Map<String, Library>();
final CoreTypes coreTypes;
final LibraryIndex sdk;
// Abstract types that may be implemented by both native and non-native
// classes.
@ -35,12 +36,9 @@ class NativeTypeSet {
final _nativeTypes = new HashSet<Class>.identity();
final _pendingLibraries = new HashSet<Library>.identity();
NativeTypeSet(Program program, this.coreTypes) {
for (var l in program.libraries) {
var uri = l.importUri;
if (uri.scheme == 'dart') sdk[uri.toString()] = l;
}
NativeTypeSet(Program program)
: sdk = new LibraryIndex.coreLibraries(program),
coreTypes = new CoreTypes(program) {
// First, core types:
// TODO(vsm): If we're analyzing against the main SDK, those
// types are not explicitly annotated.
@ -49,25 +47,21 @@ class NativeTypeSet {
_addExtensionType(coreTypes.doubleClass, true);
_addExtensionType(coreTypes.boolClass, true);
_addExtensionType(coreTypes.stringClass, true);
_addExtensionTypes(sdk['dart:_interceptors']);
_addExtensionTypes(sdk['dart:_native_typed_data']);
_addExtensionTypes(sdk.getLibrary('dart:_interceptors'));
_addExtensionTypes(sdk.getLibrary('dart:_native_typed_data'));
// These are used natively by dart:html but also not annotated.
_addExtensionTypesForLibrary(coreTypes.coreLibrary, ['Comparable', 'Map']);
_addExtensionTypesForLibrary(sdk['dart:collection'], ['ListMixin']);
_addExtensionTypesForLibrary(sdk['dart:math'], ['Rectangle']);
_addExtensionTypesForLibrary('dart:core', ['Comparable', 'Map']);
_addExtensionTypesForLibrary('dart:collection', ['ListMixin']);
_addExtensionTypesForLibrary('dart:math', ['Rectangle']);
// Second, html types - these are only searched if we use dart:html, etc.:
_addPendingExtensionTypes(sdk['dart:html']);
_addPendingExtensionTypes(sdk['dart:indexed_db']);
_addPendingExtensionTypes(sdk['dart:svg']);
_addPendingExtensionTypes(sdk['dart:web_audio']);
_addPendingExtensionTypes(sdk['dart:web_gl']);
_addPendingExtensionTypes(sdk['dart:web_sql']);
}
Class getClass(String library, String name) {
return sdk[library].classes.firstWhere((c) => c.name == name);
_addPendingExtensionTypes(sdk.getLibrary('dart:html'));
_addPendingExtensionTypes(sdk.getLibrary('dart:indexed_db'));
_addPendingExtensionTypes(sdk.getLibrary('dart:svg'));
_addPendingExtensionTypes(sdk.getLibrary('dart:web_audio'));
_addPendingExtensionTypes(sdk.getLibrary('dart:web_gl'));
_addPendingExtensionTypes(sdk.getLibrary('dart:web_sql'));
}
bool _isNative(Class c) {
@ -98,11 +92,9 @@ class NativeTypeSet {
}
}
void _addExtensionTypesForLibrary(Library library, List<String> typeNames) {
for (var c in library.classes) {
if (typeNames.contains(c.name)) {
_addExtensionType(c, true);
}
void _addExtensionTypesForLibrary(String library, List<String> classNames) {
for (var className in classNames) {
_addExtensionType(sdk.getClass(library, className), true);
}
}

View file

@ -0,0 +1,395 @@
// Copyright (c) 2018, 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 'dart:collection';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/type_environment.dart';
import 'js_typerep.dart';
import 'kernel_helpers.dart';
/// Determines whether a given expression [isNullable].
///
/// This class can also analyze the nullability of local variables, if
/// [enterFunction] and [exitFunction] are used.
class NullableInference extends ExpressionVisitor<bool> {
final TypeEnvironment types;
final JSTypeRep jsTypeRep;
final CoreTypes coreTypes;
/// Whether `@notNull` and `@nullCheck` declarations are honored.
///
/// This is set when compiling the SDK and in tests. Even when set to `false`,
/// non-SDK code will still benefit from `@notNull` annotations declared on
/// SDK APIs.
///
/// Since `@notNull` cannot be imported by user code, this flag is only
/// a performance optimization, so we don't spend time looking for
/// annotations that cannot exist in user code.
bool allowNotNullDeclarations = false;
/// Allows `@notNull` and `@nullCheck` to be declared in `package:meta`
/// instead of requiring a private SDK library.
///
/// This is currently used by tests, in conjunction with
/// [allowNotNullDeclarations].
bool allowPackageMetaAnnotations = false;
final _variableInference = new _NullableVariableInference();
NullableInference(this.jsTypeRep)
: types = jsTypeRep.types,
coreTypes = jsTypeRep.coreTypes {
_variableInference._nullInference = this;
}
/// Call when entering a function to enable [isNullable] to recognize local
/// variables that cannot be null.
void enterFunction(FunctionNode fn) => _variableInference.enterFunction(fn);
/// Call when exiting a function to clear out the information recorded by
/// [enterFunction].
void exitFunction(FunctionNode fn) => _variableInference.exitFunction(fn);
/// Returns true if [expr] can be null.
bool isNullable(Expression expr) => expr != null ? expr.accept(this) : false;
@override
defaultExpression(Expression node) => true;
@override
defaultBasicLiteral(BasicLiteral node) => false;
@override
visitNullLiteral(NullLiteral node) => true;
@override
visitVariableGet(VariableGet node) {
return _variableInference.variableIsNullable(node.variable);
}
@override
visitVariableSet(VariableSet node) => isNullable(node.value);
@override
visitPropertyGet(PropertyGet node) => _getterIsNullable(node.interfaceTarget);
@override
visitPropertySet(PropertySet node) => isNullable(node.value);
@override
visitDirectPropertyGet(DirectPropertyGet node) =>
_getterIsNullable(node.target);
@override
visitDirectPropertySet(DirectPropertySet node) => isNullable(node.value);
@override
visitSuperPropertyGet(SuperPropertyGet node) =>
_getterIsNullable(node.interfaceTarget);
@override
visitSuperPropertySet(SuperPropertySet node) => isNullable(node.value);
@override
visitStaticGet(StaticGet node) => _getterIsNullable(node.target);
@override
visitStaticSet(StaticSet node) => isNullable(node.value);
@override
visitMethodInvocation(MethodInvocation node) =>
_invocationIsNullable(node.interfaceTarget, node.receiver);
@override
visitDirectMethodInvocation(DirectMethodInvocation node) =>
_invocationIsNullable(node.target, node.receiver);
@override
visitSuperMethodInvocation(SuperMethodInvocation node) =>
_invocationIsNullable(node.interfaceTarget);
bool _invocationIsNullable(Member target, [Expression receiver]) {
if (target == null) return true;
if (target.name.name == 'toString' &&
receiver != null &&
receiver.getStaticType(types) == coreTypes.stringClass.rawType) {
// TODO(jmesserly): `class String` in dart:core does not explicitly
// declare `toString`, which results in a target of `Object.toString` even
// when the reciever type is known to be `String`. So we work around it.
// (The Analyzer backend of DDC probably has the same issue.)
return false;
}
return _returnValueIsNullable(target);
}
bool _getterIsNullable(Member target) {
if (target == null) return true;
// tear-offs are not null
if (target is Procedure && !target.isAccessor) return false;
return _returnValueIsNullable(target);
}
bool _returnValueIsNullable(Member target) {
// TODO(jmesserly): this is not a valid assumption for user-defined equality
// but it is added to match the behavior of the Analyzer backend.
// https://github.com/dart-lang/sdk/issues/31854
if (target.name.name == '==') return false;
var targetClass = target.enclosingClass;
if (targetClass != null) {
// Convert `int` `double` `num` `String` and `bool` to their corresponding
// implementation class in dart:_interceptors, for example `JSString`.
//
// This allows us to find the `@notNull` annotation if it exists.
var implClass = jsTypeRep.getImplementationClass(targetClass.rawType);
if (implClass != null) {
var member = types.hierarchy.getDispatchTarget(implClass, target.name);
if (member != null) target = member;
}
}
// If the method or function is annotated as returning a non-null value
// then the result of the call is non-null.
var annotations = target.annotations;
if (annotations.isNotEmpty && annotations.any(isNotNullAnnotation)) {
return false;
}
return true;
}
@override
visitStaticInvocation(StaticInvocation node) {
var target = node.target;
if (target == types.coreTypes.identicalProcedure) return false;
if (isInlineJS(target)) {
// Fix types for JS builtin calls.
//
// This code was taken from analyzer. It's not super sophisticated:
// only looks for the type name in dart:core, so we just copy it here.
//
// TODO(jmesserly): we'll likely need something that can handle a wider
// variety of types, especially when we get to JS interop.
var args = node.arguments.positional;
var first = args.isNotEmpty ? args.first : null;
if (first is StringLiteral) {
var types = first.value;
return types == '' ||
types == 'var' ||
types.split('|').contains('Null');
}
}
return _invocationIsNullable(target);
}
@override
visitConstructorInvocation(ConstructorInvocation node) => false;
@override
visitNot(Not node) => false;
@override
visitLogicalExpression(LogicalExpression node) => false;
@override
visitConditionalExpression(ConditionalExpression node) =>
isNullable(node.then) || isNullable(node.otherwise);
@override
visitStringConcatenation(StringConcatenation node) => false;
@override
visitIsExpression(IsExpression node) => false;
@override
visitAsExpression(AsExpression node) => isNullable(node.operand);
@override
visitSymbolLiteral(SymbolLiteral node) => false;
@override
visitTypeLiteral(TypeLiteral node) => false;
@override
visitThisExpression(ThisExpression node) => false;
@override
visitRethrow(Rethrow node) => false;
@override
visitThrow(Throw node) => false;
@override
visitListLiteral(ListLiteral node) => false;
@override
visitMapLiteral(MapLiteral node) => false;
@override
visitAwaitExpression(AwaitExpression node) => true;
@override
visitFunctionExpression(FunctionExpression node) => false;
@override
visitConstantExpression(ConstantExpression node) {
var c = node.constant;
return c is PrimitiveConstant && c.value == null;
}
@override
visitLet(Let node) => isNullable(node.body);
@override
visitInstantiation(Instantiation node) => false;
bool isNotNullAnnotation(Expression value) =>
_isInternalAnnotationField(value, 'notNull');
bool isNullCheckAnnotation(Expression value) =>
_isInternalAnnotationField(value, 'nullCheck');
bool _isInternalAnnotationField(Expression value, String name) {
if (value is StaticGet) {
var t = value.target;
if (t is Field && t.name.name == name) {
var uri = t.enclosingLibrary.importUri;
return uri.scheme == 'dart' && uri.pathSegments[0] == '_js_helper' ||
allowPackageMetaAnnotations &&
uri.scheme == 'package' &&
uri.pathSegments[0] == 'meta';
}
}
return false;
}
}
/// A visitor that determines which local variables cannot be null using
/// flow-insensitive inference.
///
/// The entire body of a function (including any local functions) is visited
/// recursively, and we collect variables that are tentatively believed to be
/// not-null. If the assumption turns out to be incorrect (based on a later
/// assignment of a nullable value), we remove it from the not-null set, along
/// with any variable it was assigned to.
///
/// This does not track nullable locals, so we can avoid doing work on any
/// variables that have already been determined to be nullable.
///
// TODO(jmesserly): Introduce flow analysis.
class _NullableVariableInference extends RecursiveVisitor<void> {
NullableInference _nullInference;
/// Variables that are currently believed to be not-null.
final _notNullLocals = new HashSet<VariableDeclaration>.identity();
/// For each variable currently believed to be not-null ([_notNullLocals]),
/// this collects variables that it is assigned to, so we update them if we
/// later determine that the variable can be null.
final _assignedTo =
new HashMap<VariableDeclaration, List<VariableDeclaration>>.identity();
/// All functions that have been analyzed with [analyzeFunction].
///
/// In practice this will include the outermost function (typically a
/// [Procedure]) as well as an local functions it contains.
final _functions = new HashSet<FunctionNode>.identity();
/// The current variable we are setting/initializing, so we can track if it
/// is [_assignedTo] from another variable.
VariableDeclaration _variableAssignedTo;
void enterFunction(FunctionNode node) {
if (_functions.contains(node)) return; // local function already analyzed.
visitFunctionNode(node);
_assignedTo.clear();
}
void exitFunction(FunctionNode node) {
var removed = _functions.remove(node);
assert(removed);
if (_functions.isEmpty) _notNullLocals.clear();
}
@override
visitFunctionDeclaration(FunctionDeclaration node) {
_notNullLocals.add(node.variable);
node.function?.accept(this);
}
@override
visitFunctionNode(FunctionNode node) {
_functions.add(node);
if (_nullInference.allowNotNullDeclarations) {
visitList(node.positionalParameters, this);
visitList(node.namedParameters, this);
}
node.body?.accept(this);
}
@override
visitCatch(Catch node) {
// The stack trace variable is not nullable, but the exception can be.
var stackTrace = node.stackTrace;
if (stackTrace != null) _notNullLocals.add(stackTrace);
super.visitCatch(node);
}
@override
visitVariableDeclaration(VariableDeclaration node) {
if (_nullInference.allowNotNullDeclarations) {
var annotations = node.annotations;
if (annotations.isNotEmpty &&
(annotations.any(_nullInference.isNotNullAnnotation) ||
annotations.any(_nullInference.isNullCheckAnnotation))) {
_notNullLocals.add(node);
}
}
var initializer = node.initializer;
if (initializer != null) {
var savedVariable = _variableAssignedTo;
_variableAssignedTo = node;
if (!_nullInference.isNullable(initializer)) _notNullLocals.add(node);
_variableAssignedTo = savedVariable;
}
initializer?.accept(this);
}
@override
visitVariableGet(VariableGet node) {}
@override
visitVariableSet(VariableSet node) {
var variable = node.variable;
var value = node.value;
if (_notNullLocals.contains(variable)) {
var savedVariable = _variableAssignedTo;
_variableAssignedTo = variable;
if (_nullInference.isNullable(node.value)) {
markNullable(VariableDeclaration v) {
_notNullLocals.remove(v);
_assignedTo.remove(v)?.forEach(markNullable);
}
markNullable(variable);
}
_variableAssignedTo = savedVariable;
}
value.accept(this);
}
bool variableIsNullable(VariableDeclaration variable) {
if (_notNullLocals.contains(variable)) {
if (_variableAssignedTo != null) {
_assignedTo.putIfAbsent(variable, () => []).add(_variableAssignedTo);
}
return false;
}
return true;
}
}

View file

@ -0,0 +1,581 @@
// Copyright (c) 2018, 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 'dart:async';
import 'dart:io';
import 'package:front_end/src/api_prototype/memory_file_system.dart';
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
import 'package:kernel/src/incremental_class_hierarchy.dart';
import 'package:test/test.dart';
import 'package:dev_compiler/src/kernel/command.dart';
import 'package:dev_compiler/src/kernel/nullable_inference.dart';
import 'package:dev_compiler/src/kernel/js_typerep.dart';
import 'package:dev_compiler/src/kernel/target.dart';
void main() {
test('empty main', () async {
await expectNotNull('main() {}', '');
});
group('literal', () {
test('null', () async {
await expectNotNull('main() { print(null); }', '');
});
test('bool', () async {
await expectNotNull('main() { print(false); }', 'false');
});
test('int', () async {
await expectNotNull('main() { print(42); }', '42');
});
test('double', () async {
await expectNotNull('main() { print(123.0); }', '123.0');
});
test('String', () async {
await expectNotNull('main() { print("hi"); }', '"hi"');
});
test('List', () async {
await expectNotNull(
'main() { print([42, null]); }', '<dart.core::int>[42, null], 42');
});
test('Map', () async {
await expectNotNull('main() { print({"x": null}); }',
'<dart.core::String, dart.core::Null>{"x": null}, "x"');
});
test('Symbol', () async {
await expectNotNull('main() { print(#hi); }', '#hi');
});
test('Type', () async {
await expectNotNull('main() { print(Object); }', 'dart.core::Object');
});
});
test('this', () async {
await expectNotNull('class C { m() { return this; } }', 'this');
});
test('is', () async {
await expectNotNull('main() { 42 is int; null is int; }',
'42 is dart.core::int, 42, null is dart.core::int');
});
test('as', () async {
await expectNotNull(
'main() { 42 as int; null as int; }', '42 as dart.core::int, 42');
});
test('constructor', () async {
await expectNotNull(
'library a; class C {} main() { new C(); }', 'new a::C::•()');
});
group('operator', () {
test('==', () async {
// This is not a correct non-null assumption when user-defined operators,
// are present, see: https://github.com/dart-lang/sdk/issues/31854
await expectAllNotNull('main() { 1 == 1; }');
});
test('!', () async {
await expectAllNotNull('main() { !false; }');
});
test('!=', () async {
await expectAllNotNull('main() { 1 != 2; }');
});
test('&&', () async {
await expectAllNotNull('main() { true && true; }');
});
test('||', () async {
await expectAllNotNull('main() { true || true; }');
});
test('? :', () async {
await expectAllNotNull('main() { true ? true : false; }');
});
});
test('bool', () async {
await expectAllNotNull('main() { true.toString(); false.hashCode; }');
});
group('int', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { -0; 1 + 2; 3 - 4; 5 * 6; 7 / 8; 9 % 10; 11 ~/ 12; }');
});
test('bitwise', () async {
await expectAllNotNull(
'main() { 1 & 2; 3 | 4; 5 ^ 6; ~7; 8 << 9; 10 >> 11; }');
});
test('comparison', () async {
await expectAllNotNull('main() { 1 < 2; 3 > 4; 5 <= 6; 7 >= 8; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { 1.isOdd; 1.isEven; 1.isNegative; 1.isNaN; 1.isInfinite; '
'1.isFinite; 1.sign; 1.bitLength; 1.hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { 1.compareTo(2); 1.remainder(2); 1.abs(); 1.toInt(); '
'1.ceil(); 1.floor(); 1.truncate(); 1.round(); 1.ceilToDouble(); '
'1.floorToDouble(); 1.truncateToDouble(); 1.roundToDouble(); '
'1.toDouble(); 1.clamp(2); 1.toStringAsFixed(2); '
'1.toStringAsExponential(); 1.toStringAsPrecision(2); 1.toString(); '
'1.toRadixString(2); 1.toUnsigned(2); 1.toSigned(2); 1.modPow(2); '
'1.modInverse(2); 1.gcd(2); }');
});
});
group('double', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { -0.0; 1.0 + 2.0; 3.0 - 4.0; 5.0 * 6.0; 7.0 / 8.0; '
'9.0 % 10.0; 11.0 ~/ 12.0; }');
});
test('comparison', () async {
await expectAllNotNull(
'main() { 1.0 < 2.0; 3.0 > 4.0; 5.0 <= 6.0; 7.0 >= 8.0; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { (1.0).isNegative; (1.0).isNaN; (1.0).isInfinite; '
'(1.0).isFinite; (1.0).sign; (1.0).hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { (1.0).compareTo(2.0); (1.0).remainder(2.0); (1.0).abs(); '
'(1.0).toInt(); (1.0).ceil(); (1.0).floor(); (1.0).truncate(); '
'(1.0).round(); (1.0).ceilToDouble(); (1.0).floorToDouble(); '
'(1.0).truncateToDouble(); (1.0).roundToDouble(); (1.0).toDouble(); '
'(1.0).clamp(2.0); (1.0).toStringAsFixed(2); (1.0).toString(); '
'(1.0).toStringAsExponential(); (1.0).toStringAsPrecision(2); }');
});
});
group('num', () {
test('arithmetic', () async {
await expectAllNotNull(
'main() { num n = 1; -n; n + n; n - n; n * n; n / n; n % n; n % n; '
'n ~/ n; }');
});
test('comparison', () async {
await expectAllNotNull(
'main() { num n = 1; n < n; n > n; n <= n; n >= n; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { num n = 1; n.isNegative; n.isNaN; n.isInfinite; '
'n.isFinite; n.sign; n.hashCode; }');
});
test('methods', () async {
await expectAllNotNull(
'main() { num n = 1; n.compareTo(n); n.remainder(n); n.abs(); '
'n.toInt(); n.ceil(); n.floor(); n.truncate(); '
'n.round(); n.ceilToDouble(); n.floorToDouble(); '
'n.truncateToDouble(); n.roundToDouble(); n.toDouble(); '
'n.clamp(n); n.toStringAsFixed(n); n.toString(); '
'n.toStringAsExponential(); n.toStringAsPrecision(n); }');
});
});
group('String', () {
test('concatenation', () async {
await expectAllNotNull('main() { "1" "2"; }');
});
test('interpolation', () async {
await expectAllNotNull('main() { "1${2}"; }');
});
test('getters', () async {
await expectAllNotNull(
'main() { "".codeUnits; "".hashCode; "".isEmpty; "".isNotEmpty; '
'"".length; "".runes; }');
});
test('operators', () async {
await expectAllNotNull('main() { "" + ""; "" * 2; "" == ""; "x"[0]; }');
});
test('methods', () async {
await expectAllNotNull('''main() {
String s = '';
s.codeUnitAt(0);
s.contains(s);
s.endsWith(s);
s.indexOf(s);
s.lastIndexOf(s);
s.padLeft(1);
s.padRight(1);
s.replaceAll(s, s);
s.replaceAllMapped(s, (_) => s);
s.replaceFirst(s, s);
s.replaceFirstMapped(s, (_) => s);
s.replaceRange(1, 2, s);
s.split(s);
s.splitMapJoin(s, (_) => s, (_) => s);
s.startsWith(s);
s.substring(1);
s.toLowerCase();
s.toUpperCase();
s.trim();
s.trimLeft();
s.trimRight();
s.compareTo(s);
s.toString();
// Pattern methods (allMatches, matchAsPrefix) are not recognized.
}''');
});
});
test('identical', () async {
await expectNotNull('main() { identical(null, null); }',
'dart.core::identical(null, null)');
});
test('throw', () async {
await expectNotNull('main() { print(throw null); }', 'throw null');
});
test('rethrow', () async {
await expectNotNull('main() { try {} catch (e) { rethrow; } }', 'rethrow');
});
test('function expression', () async {
await expectNotNull(
'main() { () => null; f() {}; f; }', '() → dart.core::Null => null, f');
});
test('cascades (kernel let)', () async {
// `null..toString()` evaluates to `null` so it is nullable.
await expectNotNull('main() { null..toString(); }', '');
await expectAllNotNull('main() { 1..toString(); }');
});
group('variable', () {
test('declaration not-null', () async {
await expectNotNull('main() { var x = 42; print(x); }', '42, x');
});
test('declaration null', () async {
await expectNotNull('main() { var x = null; print(x); }', '');
});
test('declaration without initializer', () async {
await expectNotNull('main() { var x; x = 1; print(x); }', 'x = 1, 1');
});
test('assignment non-null', () async {
await expectNotNull(
'main() { var x = 42; x = 1; print(x); }', '42, x = 1, 1, x');
});
test('assignment null', () async {
await expectNotNull('main() { var x = 42; x = null; print(x); }', '42');
});
test('flow insensitive', () async {
await expectNotNull('''main() {
var x = 1;
if (true) {
print(x);
} else {
x = null;
print(x);
}
}''', '1, true');
});
test('declaration from variable', () async {
await expectNotNull('''main() {
var x = 1;
var y = x;
print(y);
x = null;
}''', '1');
});
test('declaration from variable nested', () async {
await expectNotNull('''main() {
var x = 1;
var y = (x = null) == null;
print(x);
print(y);
}''', '1, (x = null).{dart.core::Object::==}(null), y');
});
test('declaration from variable transitive', () async {
await expectNotNull('''main() {
var x = 1;
var y = x;
var z = y;
print(z);
x = null;
}''', '1');
});
test('declaration between variable transitive nested', () async {
await expectNotNull('''main() {
var x = 1;
var y = 1;
var z = y = x;
print(z);
x = null;
}''', '1, 1');
});
test('for not-null', () async {
await expectAllNotNull('''main() {
for (var i = 0; i < 10; i++) {
i;
}
}''');
});
test('for nullable', () async {
await expectNotNull(
'''main() {
for (var i = 0; i < 10; i++) {
if (i >= 10) i = null;
}
}''',
// arithmetic operation results on `i` are themselves not null, even
// though `i` is nullable.
'0, i.{dart.core::num::<}(10), 10, i = i.{dart.core::num::+}(1), '
'i.{dart.core::num::+}(1), 1, i.{dart.core::num::>=}(10), 10');
});
test('for-in', () async {
await expectNotNull('''main() {
for (var i in []) {
print(i);
}
}''', '<dynamic>[]');
});
test('inner functions', () async {
await expectNotNull('''main() {
var y = 0;
f(x) {
var g = () => print('g');
g();
print(x);
print(y);
var z = 1;
print(z);
}
f(42);
}''', '0, () → void => dart.core::print("g"), "g", g, y, 1, z, f, 42');
});
test('assignment to closure variable', () async {
await expectNotNull('''main() {
var y = 0;
f(x) {
y = x;
}
f(42);
print(y);
}''', '0, f, 42');
});
test('declaration visits initializer', () async {
await expectAllNotNull('''main() {
var x = () { var y = 1; return y; };
x;
}''');
});
test('assignment visits value', () async {
await expectAllNotNull('''main() {
var x = () => 42;
x = () { var y = 1; return y; };
}''');
});
test('assignment visits value with closure variable set', () async {
await expectNotNull('''main() {
var x = () => 42;
var y = (() => x = null);
}''', '() → dart.core::int => 42, 42, () → dart.core::Null => x = null');
});
test('do not depend on unrelated variables', () async {
await expectNotNull('''main() {
var x;
var y = identical(x, null);
y; // this is still non-null even though `x` is nullable
}''', 'dart.core::identical(x, null), y');
});
test('do not depend on unrelated variables updated later', () async {
await expectNotNull('''main() {
var x = 1;
var y = identical(x, 1);
x = null;
y; // this is still non-null even though `x` is nullable
}''', '1, dart.core::identical(x, 1), 1, y');
});
});
group('notNull', () {
setUp(() {
useAnnotations = true;
});
tearDown(() {
useAnnotations = false;
});
var imports = "import 'package:meta/meta.dart';";
group('(kernel annotation bug)', () {
test('variable wihout initializer', () async {
await expectNotNull('$imports main() { @notNull var x; print(x); }',
''); // should be: 'x'
});
test('variable with initializer', () async {
// TODO(jmesserly): this does not work in the Analyzer backend.
await expectNotNull(
'$imports main() { @notNull var x = null; print(x); }',
''); // should be: 'x'
});
test('parameters', () async {
await expectNotNull(
'$imports f(@notNull x, [@notNull y, @notNull z = 42]) '
'{ x; y; z; }',
'42, z'); // should be: '42, x, y, z'
});
test('named parameters', () async {
await expectNotNull(
'$imports f({@notNull x, @notNull y: 42}) { x; y; }',
'42, y'); // should be: '42, x, y'
});
});
test('top-level field', () async {
await expectNotNull(
'library a; $imports @notNull int x; main() { x; }', 'a::x');
});
test('getter', () async {
await expectNotNull(
'library b; $imports @notNull get x => null; main() { x; }', 'b::x');
});
test('function', () async {
await expectNotNull(
'library a; $imports @notNull f() {} main() { f(); }', 'a::f()');
});
test('method', () async {
await expectNotNull(
'library b; $imports class C { @notNull m() {} } '
'main() { var c = new C(); c.m(); }',
'new b::C::•(), c.{b::C::m}(), c');
});
});
}
/// Given the Dart [code], expects the [expectedNotNull] kernel expression list
/// to be produced in the set of expressions that cannot be null by DDC's null
/// inference.
Future expectNotNull(String code, String expectedNotNull) async {
var program = await kernelCompile(code);
var collector = new NotNullCollector();
program.accept(collector);
var actualNotNull =
collector.notNullExpressions.map((e) => e.toString()).join(', ');
expect(actualNotNull, equals(expectedNotNull));
}
/// Given the Dart [code], expects all the expressions inferred to be not-null.
Future expectAllNotNull(String code) async {
(await kernelCompile(code)).accept(new ExpectAllNotNull());
}
bool useAnnotations = false;
NullableInference inference;
class _TestRecursiveVisitor extends RecursiveVisitor<void> {
int _functionNesting = 0;
@override
visitProgram(Program node) {
inference ??= new NullableInference(new JSTypeRep(
new TypeSchemaEnvironment(
new CoreTypes(node), new IncrementalClassHierarchy(), true),
new LibraryIndex.coreLibraries(node)));
if (useAnnotations) {
inference.allowNotNullDeclarations = useAnnotations;
inference.allowPackageMetaAnnotations = useAnnotations;
}
super.visitProgram(node);
}
@override
visitLibrary(Library node) {
if (node.isExternal ||
node.importUri.scheme == 'package' &&
node.importUri.pathSegments[0] == 'meta') {
return;
}
super.visitLibrary(node);
}
@override
visitFunctionNode(FunctionNode node) {
_functionNesting++;
if (_functionNesting == 1) inference.enterFunction(node);
super.visitFunctionNode(node);
if (_functionNesting == 1) inference.exitFunction(node);
_functionNesting--;
}
}
class NotNullCollector extends _TestRecursiveVisitor {
final notNullExpressions = <Expression>[];
@override
defaultExpression(Expression node) {
if (!inference.isNullable(node)) {
notNullExpressions.add(node);
}
super.defaultExpression(node);
}
}
class ExpectAllNotNull extends _TestRecursiveVisitor {
@override
defaultExpression(Expression node) {
expect(inference.isNullable(node), false,
reason: 'expression `$node` should be inferred as not-null');
super.defaultExpression(node);
}
}
fe.InitializedCompilerState _compilerState;
final _fileSystem = new MemoryFileSystem(new Uri.file('/memory/'));
Future<Program> kernelCompile(String code) async {
var succeeded = true;
void errorHandler(fe.CompilationMessage error) {
if (error.severity == fe.Severity.error) {
succeeded = false;
}
}
var sdkUri = new Uri.file('/memory/dart_sdk.dill');
var sdkFile = _fileSystem.entityForUri(sdkUri);
if (!await sdkFile.exists()) {
sdkFile.writeAsBytesSync(new File(defaultSdkSummaryPath).readAsBytesSync());
}
var packagesUri = new Uri.file('/memory/.packages');
var packagesFile = _fileSystem.entityForUri(packagesUri);
if (!await packagesFile.exists()) {
packagesFile.writeAsStringSync('meta:/memory/meta/lib');
_fileSystem
.entityForUri(new Uri.file('/memory/meta/lib/meta.dart'))
.writeAsStringSync('''
class _NotNull { const _NotNull(); }
const notNull = const _NotNull();
class _NullCheck { const _NullCheck(); }
const nullCheck = const _NullCheck();
''');
}
var mainUri = new Uri.file('/memory/test.dart');
_fileSystem.entityForUri(mainUri).writeAsStringSync(code);
_compilerState = await fe.initializeCompiler(
_compilerState, sdkUri, packagesUri, [], new DevCompilerTarget(),
fileSystem: _fileSystem);
fe.DdcResult result =
await fe.compile(_compilerState, [mainUri], errorHandler);
expect(succeeded, true);
return result.program;
}

View file

@ -13,8 +13,8 @@ import 'package:testing/testing.dart';
import 'common.dart';
abstract class DdcRunner {
Future<Null> runDDC(Uri inputFile, Uri outputFile, Uri outWrapperPath);
abstract class CompilerRunner {
Future<Null> run(Uri inputFile, Uri outputFile, Uri outWrapperPath);
}
abstract class WithCompilerState {
@ -22,9 +22,9 @@ abstract class WithCompilerState {
}
class Compile extends Step<Data, Data, ChainContext> {
final DdcRunner ddcRunner;
final CompilerRunner runner;
const Compile(this.ddcRunner);
const Compile(this.runner);
String get name => "compile";
@ -42,18 +42,18 @@ class Compile extends Step<Data, Data, ChainContext> {
var outputFile = outDirUri.resolve(outputFilename);
var outWrapperPath = outDirUri.resolve("wrapper.js");
await ddcRunner.runDDC(testFile, outputFile, outWrapperPath);
await runner.run(testFile, outputFile, outWrapperPath);
return pass(data);
}
}
class TestStackTrace extends Step<Data, Data, ChainContext> {
final DdcRunner ddcRunner;
final CompilerRunner runner;
final String marker;
final List<String> knownMarkers;
const TestStackTrace(this.ddcRunner, this.marker, this.knownMarkers);
const TestStackTrace(this.runner, this.marker, this.knownMarkers);
String get name => "TestStackTrace";
@ -71,8 +71,7 @@ class TestStackTrace extends Step<Data, Data, ChainContext> {
Future<bool> _compile(String input, String output) async {
var outWrapperPath = _getWrapperPathFromDirectoryFile(new Uri.file(input));
await ddcRunner.runDDC(
new Uri.file(input), new Uri.file(output), outWrapperPath);
await runner.run(new Uri.file(input), new Uri.file(output), outWrapperPath);
return true;
}

View file

@ -21,24 +21,22 @@ class SourceMapContext extends ChainContextWithCleanupHelper {
List<Step> _steps;
List<Step> get steps {
return _steps ??= <Step>[
const Setup(),
new Compile(new RunDdc(environment.containsKey("debug"))),
const StepWithD8(),
new CheckSteps(environment.containsKey("debug")),
];
}
List<Step> get steps => _steps ??= <Step>[
const Setup(),
new Compile(new DevCompilerRunner(environment.containsKey("debug"))),
const StepWithD8(),
new CheckSteps(environment.containsKey("debug")),
];
bool debugging() => environment.containsKey("debug");
}
class RunDdc implements DdcRunner {
class DevCompilerRunner implements CompilerRunner {
final bool debugging;
const RunDdc([this.debugging = false]);
const DevCompilerRunner([this.debugging = false]);
Future<Null> runDDC(Uri inputFile, Uri outputFile, Uri outWrapperFile) async {
Future<Null> run(Uri inputFile, Uri outputFile, Uri outWrapperFile) async {
Uri outDir = outputFile.resolve(".");
String outputFilename = outputFile.pathSegments.last;

View file

@ -30,7 +30,7 @@ class SourceMapContext extends ChainContextWithCleanupHelper
List<Step> get steps {
return _steps ??= <Step>[
const Setup(),
new Compile(new RunDdc(this, debugging())),
new Compile(new DevCompilerRunner(this, debugging())),
const StepWithD8(),
new CheckSteps(debugging()),
];
@ -39,13 +39,13 @@ class SourceMapContext extends ChainContextWithCleanupHelper
bool debugging() => environment.containsKey("debug");
}
class RunDdc implements DdcRunner {
class DevCompilerRunner implements CompilerRunner {
final WithCompilerState context;
final bool debugging;
const RunDdc(this.context, [this.debugging = false]);
const DevCompilerRunner(this.context, [this.debugging = false]);
Future<Null> runDDC(Uri inputFile, Uri outputFile, Uri outWrapperFile) async {
Future<Null> run(Uri inputFile, Uri outputFile, Uri outWrapperFile) async {
Uri outDir = outputFile.resolve(".");
String outputFilename = outputFile.pathSegments.last;

View file

@ -14,7 +14,7 @@ class StackTraceContext extends ChainContextWithCleanupHelper {
const Setup(),
const SetCwdToSdkRoot(),
const TestStackTrace(
const ddc.RunDdc(false), "ddc.", const ["ddc.", "ddk."]),
const ddc.DevCompilerRunner(false), "ddc.", const ["ddc.", "ddk."]),
];
}

View file

@ -20,8 +20,8 @@ class StackTraceContext extends ChainContextWithCleanupHelper
return _steps ??= <Step>[
const Setup(),
const SetCwdToSdkRoot(),
new TestStackTrace(
new ddk.RunDdc(this, false), "ddk.", const ["ddk.", "ddc."]),
new TestStackTrace(new ddk.DevCompilerRunner(this, false), "ddk.",
const ["ddk.", "ddc."]),
];
}
}

View file

@ -21,7 +21,7 @@ main() {
/*bc:10*/ print("hex is int");
// ignore: unnecessary_cast
int x = hex /*bc:11*/ as int;
if (x. /*bc:12*/ isEven) {
/*bc:12*/ if (x.isEven) {
/*bc:13*/ print("it's even even!");
} else {
print("but it's not even even!");

View file

@ -30,7 +30,7 @@ fi
if [ "$KERNEL" = true ]; then
dart -c $DDC_PATH/bin/dartdevk.dart --modules=node \
-o $BASENAME.js $*
-o $LIBROOT/$BASENAME.js $*
else
dart -c $DDC_PATH/bin/dartdevc.dart --modules=node --library-root=$LIBROOT \
--dart-sdk-summary=$DDC_PATH/gen/sdk/ddc_sdk.sum \

View file

@ -9,6 +9,10 @@ class ForceInline {
const ForceInline();
}
class _NotNull {
const _NotNull();
}
/// Marks a variable or API to be non-nullable.
/// ****CAUTION******
/// This is currently unchecked, and hence should never be used
@ -16,11 +20,7 @@ class ForceInline {
/// or otherwise cause the contract to be violated.
/// TODO(leafp): Consider adding static checking and exposing
/// this to user code.
class NotNull {
const NotNull();
}
const notNull = const NotNull();
const notNull = const _NotNull();
/// Marks a generic function or static method API to be not reified.
/// ****CAUTION******
@ -39,17 +39,17 @@ class ReifyFunctionTypes {
const ReifyFunctionTypes(this.value);
}
class _NullCheck {
const _NullCheck();
}
/// Tells the development compiler to check a variable for null at its
/// declaration point, and then to assume that the variable is non-null
/// from that point forward.
/// ****CAUTION******
/// This is currently unchecked, and hence will not catch re-assignments
/// of a variable with null
class NullCheck {
const NullCheck();
}
const nullCheck = const NullCheck();
const nullCheck = const _NullCheck();
/// Tells the optimizing compiler that the annotated method cannot throw.
/// Requires @NoInline() to function correctly.

View file

@ -34,10 +34,12 @@ class JSBool extends Interceptor implements bool {
const JSBool();
// Note: if you change this, also change the function [S].
@notNull
String toString() => JS('String', r'String(#)', this);
// The values here are SMIs, co-prime and differ about half of the bit
// positions, including the low bit, so they are different mod 2^k.
@notNull
int get hashCode => this ? (2 * 3 * 23 * 3761) : (269 * 811);
Type get runtimeType => bool;

View file

@ -554,5 +554,6 @@ class JSNumber extends Interceptor implements int, double {
return i;
}
@notNull
int operator ~() => JS('int', r'(~#) >>> 0', this);
}

View file

@ -728,7 +728,6 @@ enum_private_test/01: RuntimeError # NoSuchMethodError: method not found: '<Unex
enum_private_test/none: RuntimeError # NoSuchMethodError: method not found: '<Unexpected Null Value>'
enum_test: RuntimeError # NoSuchMethodError: method not found: '<Unexpected Null Value>'
f_bounded_equality_test: RuntimeError # Expect.equals(expected: <dynamic>, actual: <Real>) fails.
field_override_optimization_test: RuntimeError # Expect.fail('This should also be unreachable')
first_class_types_test: RuntimeError # Expect.equals(expected: <List>, actual: <List<int>>) fails.
forwarding_stub_tearoff_test: RuntimeError
function_subtype_bound_closure1_test: RuntimeError # Expect.isTrue(false, 'foo is Foo') fails.