blob: 7846c9ac1a4ff0b44369ffe33d900d32d7c0790d [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart2js;
/**
* If true, print a warning for each method that was resolved, but not
* compiled.
*/
const bool REPORT_EXCESS_RESOLUTION = false;
/**
* Contains backend-specific data that is used throughout the compilation of
* one work item.
*/
class ItemCompilationContext {
}
abstract class WorkItem {
final ItemCompilationContext compilationContext;
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [element] must be a declaration element.
*/
final Element element;
TreeElements resolutionTree;
WorkItem(this.element, this.compilationContext) {
assert(invariant(element, element.isDeclaration));
}
void run(Compiler compiler, Enqueuer world);
}
/// [WorkItem] used exclusively by the [ResolutionEnqueuer].
class ResolutionWorkItem extends WorkItem {
ResolutionWorkItem(Element element,
ItemCompilationContext compilationContext)
: super(element, compilationContext);
void run(Compiler compiler, ResolutionEnqueuer world) {
resolutionTree = compiler.analyze(this, world);
}
bool isAnalyzed() => resolutionTree != null;
}
/// [WorkItem] used exclusively by the [CodegenEnqueuer].
class CodegenWorkItem extends WorkItem {
CodegenWorkItem(Element element,
ItemCompilationContext compilationContext)
: super(element, compilationContext);
void run(Compiler compiler, CodegenEnqueuer world) {
if (world.isProcessed(element)) return;
resolutionTree =
compiler.enqueuer.resolution.getCachedElements(element);
assert(invariant(element, resolutionTree != null,
message: 'Resolution tree is null for $element in codegen work item'));
compiler.codegen(this, world);
}
}
typedef void DeferredAction();
class DeferredTask {
final Element element;
final DeferredAction action;
DeferredTask(this.element, this.action);
}
abstract class Backend {
final Compiler compiler;
final ConstantSystem constantSystem;
Backend(this.compiler,
[ConstantSystem constantSystem = DART_CONSTANT_SYSTEM])
: this.constantSystem = constantSystem;
// Given a [FunctionElement], return a buffer with the code generated for it
// or null if no code was generated.
CodeBuffer codeOf(Element element) => null;
void initializeHelperClasses() {}
void enqueueHelpers(ResolutionEnqueuer world, TreeElements elements);
void codegen(CodegenWorkItem work);
// The backend determines the native resolution enqueuer, with a no-op
// default, so tools like dart2dart can ignore the native classes.
native.NativeEnqueuer nativeResolutionEnqueuer(world) {
return new native.NativeEnqueuer();
}
native.NativeEnqueuer nativeCodegenEnqueuer(world) {
return new native.NativeEnqueuer();
}
void assembleProgram();
List<CompilerTask> get tasks;
void onResolutionComplete() {}
ItemCompilationContext createItemCompilationContext() {
return new ItemCompilationContext();
}
bool classNeedsRti(ClassElement cls);
bool methodNeedsRti(FunctionElement function);
/// Called during codegen when [constant] has been used.
void registerCompileTimeConstant(Constant constant, TreeElements elements) {}
/// Called during post-processing when [constant] has been evaluated.
void registerMetadataConstant(Constant constant, TreeElements elements) {}
/// Called during resolution to notify to the backend that a class is
/// being instantiated.
void registerInstantiatedClass(ClassElement cls,
Enqueuer enqueuer,
TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program uses string interpolation.
void registerStringInterpolation(TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program has a catch statement.
void registerCatchStatement(Enqueuer enqueuer,
TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program explicitly throws an exception.
void registerThrowExpression(TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program has a global variable with a lazy initializer.
void registerLazyField(TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program uses a type variable as an expression.
void registerTypeVariableExpression(TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program uses a type literal.
void registerTypeLiteral(Element element,
Enqueuer enqueuer,
TreeElements elements) {}
/// Called during resolution to notify to the backend that the
/// program has a catch statement with a stack trace.
void registerStackTraceInCatch(TreeElements elements) {}
/// Register an is check to the backend.
void registerIsCheck(DartType type,
Enqueuer enqueuer,
TreeElements elements) {}
/// Register an as check to the backend.
void registerAsCheck(DartType type,
Enqueuer enqueuer,
TreeElements elements) {}
/// Register a runtime type variable bound tests between [typeArgument] and
/// [bound].
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
DartType bound) {}
/// Registers that a type variable bounds check might occur at runtime.
void registerTypeVariableBoundCheck(TreeElements elements) {}
/// Register that the application may throw a [NoSuchMethodError].
void registerThrowNoSuchMethod(TreeElements elements) {}
/// Register that the application may throw a [RuntimeError].
void registerThrowRuntimeError(TreeElements elements) {}
/// Register that the application may throw an
/// [AbstractClassInstantiationError].
void registerAbstractClassInstantiation(TreeElements elements) {}
/// Register that the application may throw a [FallThroughError].
void registerFallThroughError(TreeElements elements) {}
/// Register that a super call will end up calling
/// [: super.noSuchMethod :].
void registerSuperNoSuchMethod(TreeElements elements) {}
/// Register that the application creates a constant map.
void registerConstantMap(TreeElements elements) {}
/**
* Call this to register that an instantiated generic class has a call
* method.
*/
void registerGenericCallMethod(Element callMethod,
Enqueuer enqueuer,
TreeElements elements) {}
/**
* Call this to register that a getter exists for a function on an
* instantiated generic class.
*/
void registerGenericClosure(Element closure,
Enqueuer enqueuer,
TreeElements elements) {}
/**
* Call this to register that the [:runtimeType:] property has been accessed.
*/
void registerRuntimeType(Enqueuer enqueuer, TreeElements elements) {}
/**
* Call this method to enable [noSuchMethod] handling in the
* backend.
*/
void enableNoSuchMethod(Enqueuer enqueuer) {
enqueuer.registerInvocation(compiler.noSuchMethodSelector);
}
void registerRequiredType(DartType type, Element enclosingElement) {}
void registerClassUsingVariableExpression(ClassElement cls) {}
void registerConstSymbol(String name, TreeElements elements) {}
void registerNewSymbol(TreeElements elements) {}
/// Called when resolving the `Symbol` constructor.
void registerSymbolConstructor(TreeElements elements) {}
bool isNullImplementation(ClassElement cls) {
return cls == compiler.nullClass;
}
ClassElement get intImplementation => compiler.intClass;
ClassElement get doubleImplementation => compiler.doubleClass;
ClassElement get numImplementation => compiler.numClass;
ClassElement get stringImplementation => compiler.stringClass;
ClassElement get listImplementation => compiler.listClass;
ClassElement get growableListImplementation => compiler.listClass;
ClassElement get fixedListImplementation => compiler.listClass;
ClassElement get constListImplementation => compiler.listClass;
ClassElement get mapImplementation => compiler.mapClass;
ClassElement get constMapImplementation => compiler.mapClass;
ClassElement get functionImplementation => compiler.functionClass;
ClassElement get typeImplementation => compiler.typeClass;
ClassElement get boolImplementation => compiler.boolClass;
ClassElement get nullImplementation => compiler.nullClass;
ClassElement get uint32Implementation => compiler.intClass;
ClassElement get uint31Implementation => compiler.intClass;
ClassElement get positiveIntImplementation => compiler.intClass;
ClassElement defaultSuperclass(ClassElement element) => compiler.objectClass;
bool isDefaultNoSuchMethodImplementation(Element element) {
assert(element.name == Compiler.NO_SUCH_METHOD);
ClassElement classElement = element.getEnclosingClass();
return classElement == compiler.objectClass;
}
bool isInterceptorClass(ClassElement element) => false;
void registerStaticUse(Element element, Enqueuer enqueuer) {}
Future onLibraryLoaded(LibraryElement library, Uri uri) {
return new Future.value();
}
/// Called by [MirrorUsageAnalyzerTask] after it has merged all @MirrorsUsed
/// annotations. The arguments corresponds to the unions of the corresponding
/// fields of the annotations.
void registerMirrorUsage(Set<String> symbols,
Set<Element> targets,
Set<Element> metaTargets) {}
/// Returns true if this element should be retained for reflection even if it
/// would normally be tree-shaken away.
bool isNeededForReflection(Element element) => false;
/// Returns true if global optimizations such as type inferencing
/// can apply to this element. One category of elements that do not
/// apply is runtime helpers that the backend calls, but the
/// optimizations don't see those calls.
bool canBeUsedForGlobalOptimizations(Element element) => true;
/// Called when [enqueuer]'s queue is empty, but before it is closed.
/// This is used, for example, by the JS backend to enqueue additional
/// elements needed for reflection.
void onQueueEmpty(Enqueuer enqueuer) {}
/// Called after [element] has been resolved.
void onElementResolved(Element element, TreeElements elements) {}
}
/**
* Key class used in [TokenMap] in which the hash code for a token is based
* on the [charOffset].
*/
class TokenKey {
final Token token;
TokenKey(this.token);
int get hashCode => token.charOffset;
operator==(other) => other is TokenKey && token == other.token;
}
/// Map of tokens and the first associated comment.
/*
* This implementation was chosen among several candidates for its space/time
* efficiency by empirical tests of running dartdoc on dartdoc itself. Time
* measurements for the use of [Compiler.commentMap]:
*
* 1) Using [TokenKey] as key (this class): ~80 msec
* 2) Using [TokenKey] as key + storing a separate map in each script: ~120 msec
* 3) Using [Token] as key in a [Map]: ~38000 msec
* 4) Storing comments is new field in [Token]: ~20 msec
* (Abandoned due to the increased memory usage)
* 5) Storing comments in an [Expando]: ~14000 msec
* 6) Storing token/comments pairs in a linked list: ~5400 msec
*/
class TokenMap {
Map<TokenKey,Token> comments = new Map<TokenKey,Token>();
Token operator[] (Token key) {
if (key == null) return null;
return comments[new TokenKey(key)];
}
void operator[]= (Token key, Token value) {
if (key == null) return;
comments[new TokenKey(key)] = value;
}
}
abstract class Compiler implements DiagnosticListener {
final Map<String, LibraryElement> libraries =
new Map<String, LibraryElement>();
final Stopwatch totalCompileTime = new Stopwatch();
int nextFreeClassId = 0;
World world;
String assembledCode;
Types types;
/**
* Map from token to the first preceeding comment token.
*/
final TokenMap commentMap = new TokenMap();
/**
* Records global dependencies, that is, dependencies that don't
* correspond to a particular element.
*
* We should get rid of this and ensure that all dependencies are
* associated with a particular element.
*/
final TreeElements globalDependencies = new TreeElementMapping(null);
final bool enableMinification;
final bool enableTypeAssertions;
final bool enableUserAssertions;
final bool trustTypeAnnotations;
final bool enableConcreteTypeInference;
final bool disableTypeInferenceFlag;
final bool dumpInfo;
/**
* The maximum size of a concrete type before it widens to dynamic during
* concrete type inference.
*/
final int maxConcreteTypeSize;
final bool analyzeAllFlag;
final bool analyzeOnly;
/**
* If true, skip analysis of method bodies and field initializers. Implies
* [analyzeOnly].
*/
final bool analyzeSignaturesOnly;
final bool enableNativeLiveTypeAnalysis;
/**
* If [:true:], comment tokens are collected in [commentMap] during scanning.
*/
final bool preserveComments;
/**
* Is the compiler in verbose mode.
*/
final bool verbose;
/**
* URI of the main source map if the compiler is generating source
* maps.
*/
final Uri sourceMapUri;
/// Emit terse diagnostics without howToFix.
final bool terseDiagnostics;
final api.CompilerOutputProvider outputProvider;
bool disableInlining = false;
List<Uri> librariesToAnalyzeWhenRun;
final Tracer tracer;
CompilerTask measuredTask;
Element _currentElement;
LibraryElement coreLibrary;
LibraryElement isolateLibrary;
LibraryElement isolateHelperLibrary;
LibraryElement jsHelperLibrary;
LibraryElement interceptorsLibrary;
LibraryElement foreignLibrary;
LibraryElement mainApp;
FunctionElement mainFunction;
/// Initialized when dart:mirrors is loaded.
LibraryElement mirrorsLibrary;
/// Initialized when dart:typed_data is loaded.
LibraryElement typedDataLibrary;
ClassElement objectClass;
ClassElement closureClass;
ClassElement boundClosureClass;
ClassElement dynamicClass;
ClassElement boolClass;
ClassElement numClass;
ClassElement intClass;
ClassElement doubleClass;
ClassElement stringClass;
ClassElement functionClass;
ClassElement nullClass;
ClassElement listClass;
ClassElement typeClass;
ClassElement mapClass;
ClassElement symbolClass;
ClassElement stackTraceClass;
ClassElement typedDataClass;
/// The constant for the [proxy] variable defined in dart:core.
Constant proxyConstant;
// Initialized after symbolClass has been resolved.
FunctionElement symbolConstructor;
// Initialized when dart:mirrors is loaded.
ClassElement mirrorSystemClass;
// Initialized when dart:mirrors is loaded.
ClassElement mirrorsUsedClass;
// Initialized after mirrorSystemClass has been resolved.
FunctionElement mirrorSystemGetNameFunction;
// Initialized when dart:_collection-dev is loaded.
ClassElement symbolImplementationClass;
// Initialized when symbolImplementationClass has been resolved.
FunctionElement symbolValidatedConstructor;
// Initialized when mirrorsUsedClass has been resolved.
FunctionElement mirrorsUsedConstructor;
// Initialized when dart:mirrors is loaded.
ClassElement deferredLibraryClass;
ClassElement jsInvocationMirrorClass;
/// Document class from dart:mirrors.
ClassElement documentClass;
Element assertMethod;
Element identicalFunction;
Element functionApplyMethod;
Element invokeOnMethod;
Element intEnvironment;
Element boolEnvironment;
Element stringEnvironment;
fromEnvironment(String name) => null;
Element get currentElement => _currentElement;
String tryToString(object) {
try {
return object.toString();
} catch (_) {
return '<exception in toString()>';
}
}
/**
* Perform an operation, [f], returning the return value from [f]. If an
* error occurs then report it as having occurred during compilation of
* [element]. Can be nested.
*/
withCurrentElement(Element element, f()) {
Element old = currentElement;
_currentElement = element;
try {
return f();
} on SpannableAssertionFailure catch (ex) {
if (!hasCrashed) {
String message = (ex.message != null) ? tryToString(ex.message)
: tryToString(ex);
SourceSpan span = spanFromSpannable(ex.node);
reportError(ex.node, MessageKind.GENERIC, {'text': message});
pleaseReportCrash();
}
hasCrashed = true;
rethrow;
} on CompilerCancelledException catch (ex) {
rethrow;
} on StackOverflowError catch (ex) {
// We cannot report anything useful in this case, because we
// do not have enough stack space.
rethrow;
} catch (ex) {
if (hasCrashed) rethrow;
try {
unhandledExceptionOnElement(element);
} catch (doubleFault) {
// Ignoring exceptions in exception handling.
}
rethrow;
} finally {
_currentElement = old;
}
}
List<CompilerTask> tasks;
ScannerTask scanner;
DietParserTask dietParser;
ParserTask parser;
PatchParserTask patchParser;
LibraryLoader libraryLoader;
TreeValidatorTask validator;
ResolverTask resolver;
closureMapping.ClosureTask closureToClassMapper;
TypeCheckerTask checker;
IrBuilderTask irBuilder;
ti.TypesTask typesTask;
Backend backend;
ConstantHandler constantHandler;
EnqueueTask enqueuer;
DeferredLoadTask deferredLoadTask;
MirrorUsageAnalyzerTask mirrorUsageAnalyzerTask;
DumpInfoTask dumpInfoTask;
String buildId;
static const String MAIN = 'main';
static const String CALL_OPERATOR_NAME = 'call';
static const String NO_SUCH_METHOD = 'noSuchMethod';
static const int NO_SUCH_METHOD_ARG_COUNT = 1;
static const String CREATE_INVOCATION_MIRROR =
'createInvocationMirror';
// TODO(ahe): Rename this field and move this logic to backend, similar to how
// we disable tree-shaking when seeing disableTreeShaking in js_mirrors.dart.
static const String INVOKE_ON =
'_getCachedInvocation';
static const String RUNTIME_TYPE = 'runtimeType';
static const String START_ROOT_ISOLATE =
'startRootIsolate';
static const String UNDETERMINED_BUILD_ID =
"build number could not be determined";
final Selector iteratorSelector =
new Selector.getter('iterator', null);
final Selector currentSelector =
new Selector.getter('current', null);
final Selector moveNextSelector =
new Selector.call('moveNext', null, 0);
final Selector noSuchMethodSelector = new Selector.call(
Compiler.NO_SUCH_METHOD, null, Compiler.NO_SUCH_METHOD_ARG_COUNT);
final Selector symbolValidatedConstructorSelector = new Selector.call(
'validated', null, 1);
final Selector fromEnvironmentSelector = new Selector.callConstructor(
'fromEnvironment', null, 2);
bool enabledNoSuchMethod = false;
bool enabledRuntimeType = false;
bool enabledFunctionApply = false;
bool enabledInvokeOn = false;
Stopwatch progress = new Stopwatch()..start();
static const int PHASE_SCANNING = 0;
static const int PHASE_RESOLVING = 1;
static const int PHASE_DONE_RESOLVING = 2;
static const int PHASE_COMPILING = 3;
int phase;
bool compilationFailed = false;
bool hasCrashed = false;
/// Set by the backend if real reflection is detected in use of dart:mirrors.
bool disableTypeInferenceForMirrors = false;
Compiler({this.tracer: const Tracer(),
this.enableTypeAssertions: false,
this.enableUserAssertions: false,
this.trustTypeAnnotations: false,
this.enableConcreteTypeInference: false,
this.disableTypeInferenceFlag: false,
this.maxConcreteTypeSize: 5,
this.enableMinification: false,
this.enableNativeLiveTypeAnalysis: false,
bool emitJavaScript: true,
bool generateSourceMap: true,
this.analyzeAllFlag: false,
bool analyzeOnly: false,
bool analyzeSignaturesOnly: false,
this.preserveComments: false,
this.verbose: false,
this.sourceMapUri: null,
this.buildId: UNDETERMINED_BUILD_ID,
this.terseDiagnostics: false,
this.dumpInfo: false,
outputProvider,
List<String> strips: const []})
: this.analyzeOnly = analyzeOnly || analyzeSignaturesOnly,
this.analyzeSignaturesOnly = analyzeSignaturesOnly,
this.outputProvider = (outputProvider == null)
? NullSink.outputProvider
: outputProvider {
world = new World(this);
closureMapping.ClosureNamer closureNamer;
if (emitJavaScript) {
js_backend.JavaScriptBackend jsBackend =
new js_backend.JavaScriptBackend(this, generateSourceMap);
closureNamer = jsBackend.namer;
backend = jsBackend;
} else {
closureNamer = new closureMapping.ClosureNamer();
backend = new dart_backend.DartBackend(this, strips);
}
// No-op in production mode.
validator = new TreeValidatorTask(this);
tasks = [
libraryLoader = new LibraryLoaderTask(this),
scanner = new ScannerTask(this),
dietParser = new DietParserTask(this),
parser = new ParserTask(this),
patchParser = new PatchParserTask(this),
resolver = new ResolverTask(this),
closureToClassMapper = new closureMapping.ClosureTask(this, closureNamer),
checker = new TypeCheckerTask(this),
irBuilder = new IrBuilderTask(this),
typesTask = new ti.TypesTask(this),
constantHandler = new ConstantHandler(this, backend.constantSystem),
deferredLoadTask = new DeferredLoadTask(this),
mirrorUsageAnalyzerTask = new MirrorUsageAnalyzerTask(this),
enqueuer = new EnqueueTask(this),
dumpInfoTask = new DumpInfoTask(this)];
tasks.addAll(backend.tasks);
}
Universe get resolverWorld => enqueuer.resolution.universe;
Universe get codegenWorld => enqueuer.codegen.universe;
bool get hasBuildId => buildId != UNDETERMINED_BUILD_ID;
bool get analyzeAll => analyzeAllFlag || compileAll;
bool get compileAll => false;
bool get disableTypeInference => disableTypeInferenceFlag;
int getNextFreeClassId() => nextFreeClassId++;
void unimplemented(String methodName,
{Node node, Token token, HInstruction instruction,
Element element}) {
internalError("$methodName not implemented",
node: node, token: token,
instruction: instruction, element: element);
}
void internalError(String message,
{Node node, Token token, HInstruction instruction,
Element element}) {
cancel('Internal Error: $message',
node: node, token: token,
instruction: instruction, element: element);
}
void internalErrorOnElement(Element element, String message) {
internalError(message, element: element);
}
void unhandledExceptionOnElement(Element element) {
if (hasCrashed) return;
hasCrashed = true;
reportDiagnostic(spanFromElement(element),
MessageKind.COMPILER_CRASHED.error().toString(),
api.Diagnostic.CRASH);
pleaseReportCrash();
}
void pleaseReportCrash() {
print(MessageKind.PLEASE_REPORT_THE_CRASH.message({'buildId': buildId}));
}
void cancel(String reason, {Node node, Token token,
HInstruction instruction, Element element}) {
assembledCode = null; // Compilation failed. Make sure that we
// don't return a bogus result.
Spannable spannable = null;
if (node != null) {
spannable = node;
} else if (token != null) {
spannable = token;
} else if (instruction != null) {
spannable = instruction;
} else if (element != null) {
spannable = element;
} else {
throw 'No error location for error: $reason';
}
reportError(spannable, MessageKind.GENERIC, {'text': reason});
throw new CompilerCancelledException(reason);
}
SourceSpan spanFromSpannable(Spannable node, [Uri uri]) {
if (node == null) return null;
if (node == CURRENT_ELEMENT_SPANNABLE) {
node = currentElement;
}
if (node is SourceSpan) {
return node;
} else if (node is Node) {
return spanFromNode(node, uri);
} else if (node is Token) {
return spanFromTokens(node, node, uri);
} else if (node is HInstruction) {
return spanFromHInstruction(node);
} else if (node is Element) {
return spanFromElement(node);
} else if (node is MetadataAnnotation) {
MetadataAnnotation annotation = node;
uri = annotation.annotatedElement.getCompilationUnit().script.uri;
return spanFromTokens(annotation.beginToken, annotation.endToken, uri);
} else {
throw 'No error location.';
}
}
void log(message) {
reportDiagnostic(null, message, api.Diagnostic.VERBOSE_INFO);
}
Future<bool> run(Uri uri) {
totalCompileTime.start();
return new Future.sync(() => runCompiler(uri)).catchError((error) {
if (error is CompilerCancelledException) {
log('Error: $error');
return false;
}
try {
if (!hasCrashed) {
hasCrashed = true;
reportDiagnostic(new SourceSpan(uri, 0, 0),
MessageKind.COMPILER_CRASHED.error().toString(),
api.Diagnostic.CRASH);
pleaseReportCrash();
}
} catch (doubleFault) {
// Ignoring exceptions in exception handling.
}
throw error;
}).whenComplete(() {
tracer.close();
totalCompileTime.stop();
}).then((_) {
return !compilationFailed;
});
}
bool hasIsolateSupport() => isolateLibrary != null;
/**
* This method is called before [library] import and export scopes have been
* set up.
*/
Future onLibraryLoaded(LibraryElement library, Uri uri) {
if (dynamicClass != null) {
// When loading the built-in libraries, dynamicClass is null. We
// take advantage of this as core imports js_helper and sees [dynamic]
// this way.
withCurrentElement(dynamicClass, () {
library.addToScope(dynamicClass, this);
});
}
if (uri == new Uri(scheme: 'dart', path: 'mirrors')) {
mirrorsLibrary = library;
mirrorSystemClass =
findRequiredElement(library, 'MirrorSystem');
mirrorsUsedClass =
findRequiredElement(library, 'MirrorsUsed');
} else if (uri == new Uri(scheme: 'dart', path: 'typed_data')) {
typedDataLibrary = library;
typedDataClass =
findRequiredElement(library, 'TypedData');
} else if (uri == new Uri(scheme: 'dart', path: '_collection-dev')) {
symbolImplementationClass =
findRequiredElement(library, 'Symbol');
} else if (uri == new Uri(scheme: 'dart', path: 'async')) {
deferredLibraryClass =
findRequiredElement(library, 'DeferredLibrary');
} else if (isolateHelperLibrary == null
&& (uri == new Uri(scheme: 'dart', path: '_isolate_helper'))) {
isolateHelperLibrary = library;
} else if (foreignLibrary == null
&& (uri == new Uri(scheme: 'dart', path: '_foreign_helper'))) {
foreignLibrary = library;
}
return backend.onLibraryLoaded(library, uri);
}
Element findRequiredElement(LibraryElement library, String name) {
var element = library.find(name);
if (element == null) {
internalErrorOnElement(
library,
'The library "${library.canonicalUri}" does not contain required '
'element: $name');
}
return element;
}
void onClassResolved(ClassElement cls) {
if (mirrorSystemClass == cls) {
mirrorSystemGetNameFunction =
cls.lookupLocalMember('getName');
} else if (symbolClass == cls) {
symbolConstructor = cls.constructors.head;
} else if (symbolImplementationClass == cls) {
symbolValidatedConstructor = symbolImplementationClass.lookupConstructor(
symbolValidatedConstructorSelector);
} else if (mirrorsUsedClass == cls) {
mirrorsUsedConstructor = cls.constructors.head;
} else if (intClass == cls) {
intEnvironment = intClass.lookupConstructor(fromEnvironmentSelector);
} else if (stringClass == cls) {
stringEnvironment =
stringClass.lookupConstructor(fromEnvironmentSelector);
} else if (boolClass == cls) {
boolEnvironment = boolClass.lookupConstructor(fromEnvironmentSelector);
}
}
Future<LibraryElement> scanBuiltinLibrary(String filename);
void initializeSpecialClasses() {
final List missingCoreClasses = [];
ClassElement lookupCoreClass(String name) {
ClassElement result = coreLibrary.find(name);
if (result == null) {
missingCoreClasses.add(name);
}
return result;
}
objectClass = lookupCoreClass('Object');
boolClass = lookupCoreClass('bool');
numClass = lookupCoreClass('num');
intClass = lookupCoreClass('int');
doubleClass = lookupCoreClass('double');
stringClass = lookupCoreClass('String');
functionClass = lookupCoreClass('Function');
listClass = lookupCoreClass('List');
typeClass = lookupCoreClass('Type');
mapClass = lookupCoreClass('Map');
nullClass = lookupCoreClass('Null');
stackTraceClass = lookupCoreClass('StackTrace');
if (!missingCoreClasses.isEmpty) {
internalErrorOnElement(coreLibrary,
'dart:core library does not contain required classes: '
'$missingCoreClasses');
}
// The Symbol class may not exist during unit testing.
// TODO(ahe): It is possible that we have to require the presence
// of Symbol as we change how we implement noSuchMethod.
symbolClass = lookupCoreClass('Symbol');
final List missingHelperClasses = [];
ClassElement lookupHelperClass(String name) {
ClassElement result = jsHelperLibrary.find(name);
if (result == null) {
missingHelperClasses.add(name);
}
return result;
}
jsInvocationMirrorClass = lookupHelperClass('JSInvocationMirror');
boundClosureClass = lookupHelperClass('BoundClosure');
closureClass = lookupHelperClass('Closure');
dynamicClass = lookupHelperClass('Dynamic_');
if (!missingHelperClasses.isEmpty) {
internalErrorOnElement(jsHelperLibrary,
'dart:_js_helper library does not contain required classes: '
'$missingHelperClasses');
}
if (types == null) {
types = new Types(this, dynamicClass);
}
backend.initializeHelperClasses();
dynamicClass.ensureResolved(this);
proxyConstant = constantHandler.compileVariable(
coreLibrary.find('proxy'), isConst: true);
}
Element _unnamedListConstructor;
Element get unnamedListConstructor {
if (_unnamedListConstructor != null) return _unnamedListConstructor;
Selector callConstructor = new Selector.callConstructor(
"", listClass.getLibrary());
return _unnamedListConstructor =
listClass.lookupConstructor(callConstructor);
}
Element _filledListConstructor;
Element get filledListConstructor {
if (_filledListConstructor != null) return _filledListConstructor;
Selector callConstructor = new Selector.callConstructor(
"filled", listClass.getLibrary());
return _filledListConstructor =
listClass.lookupConstructor(callConstructor);
}
Future scanBuiltinLibraries() {
return scanBuiltinLibrary('_js_helper').then((LibraryElement library) {
jsHelperLibrary = library;
return scanBuiltinLibrary('_interceptors');
}).then((LibraryElement library) {
interceptorsLibrary = library;
assertMethod = jsHelperLibrary.find('assertHelper');
identicalFunction = coreLibrary.find('identical');
initializeSpecialClasses();
functionClass.ensureResolved(this);
functionApplyMethod =
functionClass.lookupLocalMember('apply');
jsInvocationMirrorClass.ensureResolved(this);
invokeOnMethod = jsInvocationMirrorClass.lookupLocalMember(INVOKE_ON);
if (preserveComments) {
var uri = new Uri(scheme: 'dart', path: 'mirrors');
return libraryLoader.loadLibrary(uri, null, uri).then(
(LibraryElement libraryElement) {
documentClass = libraryElement.find('Comment');
});
}
});
}
void importHelperLibrary(LibraryElement library) {
if (jsHelperLibrary != null) {
libraryLoader.importLibrary(library, jsHelperLibrary, null);
}
}
/**
* Get an [Uri] pointing to a patch for the dart: library with
* the given path. Returns null if there is no patch.
*/
Uri resolvePatchUri(String dartLibraryPath);
Future runCompiler(Uri uri) {
// TODO(ahe): This prevents memory leaks when invoking the compiler
// multiple times. Implement a better mechanism where we can store
// such caches in the compiler and get access to them through a
// suitably maintained static reference to the current compiler.
StringToken.canonicalizedSubstrings.clear();
Selector.canonicalizedValues.clear();
TypedSelector.canonicalizedValues.clear();
assert(uri != null || analyzeOnly);
return scanBuiltinLibraries().then((_) {
if (librariesToAnalyzeWhenRun != null) {
return Future.forEach(librariesToAnalyzeWhenRun, (libraryUri) {
log('analyzing $libraryUri ($buildId)');
return libraryLoader.loadLibrary(libraryUri, null, libraryUri);
});
}
}).then((_) {
if (uri != null) {
if (analyzeOnly) {
log('analyzing $uri ($buildId)');
} else {
log('compiling $uri ($buildId)');
}
return libraryLoader.loadLibrary(uri, null, uri)
.then((LibraryElement library) {
mainApp = library;
});
}
}).then((_) {
compileLoadedLibraries();
});
}
/// Performs the compilation when all libraries have been loaded.
void compileLoadedLibraries() {
Element main = null;
if (mainApp != null) {
main = mainApp.findExported(MAIN);
if (main == null) {
if (!analyzeOnly) {
// Allow analyze only of libraries with no main.
reportFatalError(
mainApp,
MessageKind.GENERIC,
{'text': "Error: Could not find '$MAIN'."});
} else if (!analyzeAll) {
reportFatalError(
mainApp,
MessageKind.GENERIC,
{'text': "Error: Could not find '$MAIN'. "
"No source will be analyzed. "
"Use '--analyze-all' to analyze all code in the library."});
}
} else {
if (main.isErroneous()) {
reportFatalError(
main,
MessageKind.GENERIC,
{'text': "Error: Cannot determine which '$MAIN' to use."});
} else if (!main.isFunction()) {
reportFatalError(
main,
MessageKind.GENERIC,
{'text': "Error: '$MAIN' is not a function."});
}
mainFunction = main;
FunctionSignature parameters = mainFunction.computeSignature(this);
if (parameters.parameterCount > 2) {
int index = 0;
parameters.forEachParameter((Element parameter) {
if (index++ < 2) return;
reportError(
parameter,
MessageKind.GENERIC,
{'text':
"Error: '$MAIN' cannot have more than two parameters."});
});
}
}
mirrorUsageAnalyzerTask.analyzeUsage(mainApp);
// In order to see if a library is deferred, we must compute the
// compile-time constants that are metadata. This means adding
// something to the resolution queue. So we cannot wait with
// this until after the resolution queue is processed.
// TODO(ahe): Clean this up, for example, by not enqueueing
// classes only used for metadata.
deferredLoadTask.findDeferredLibraries(mainApp);
}
log('Resolving...');
phase = PHASE_RESOLVING;
if (analyzeAll) {
libraries.forEach(
(_, lib) => fullyEnqueueLibrary(lib, enqueuer.resolution));
}
// Elements required by enqueueHelpers are global dependencies
// that are not pulled in by a particular element.
backend.enqueueHelpers(enqueuer.resolution, globalDependencies);
resolveLibraryMetadata();
processQueue(enqueuer.resolution, main);
enqueuer.resolution.logSummary(log);
if (compilationFailed) return;
if (analyzeOnly) {
if (!analyzeAll) {
// No point in reporting unused code when [analyzeAll] is true: all
// code is artificially used.
reportUnusedCode();
}
return;
}
assert(main != null);
phase = PHASE_DONE_RESOLVING;
// TODO(ahe): Remove this line. Eventually, enqueuer.resolution
// should know this.
world.populate();
// Compute whole-program-knowledge that the backend needs. (This might
// require the information computed in [world.populate].)
backend.onResolutionComplete();
deferredLoadTask.onResolutionComplete(main);
log('Building IR...');
irBuilder.buildNodes();
log('Inferring types...');
typesTask.onResolutionComplete(main);
log('Compiling...');
phase = PHASE_COMPILING;
// TODO(johnniwinther): Move these to [CodegenEnqueuer].
if (hasIsolateSupport()) {
enqueuer.codegen.addToWorkList(
isolateHelperLibrary.find(Compiler.START_ROOT_ISOLATE));
enqueuer.codegen.registerGetOfStaticFunction(main);
}
if (enabledNoSuchMethod) {
backend.enableNoSuchMethod(enqueuer.codegen);
}
if (compileAll) {
libraries.forEach((_, lib) => fullyEnqueueLibrary(lib,
enqueuer.codegen));
}
processQueue(enqueuer.codegen, main);
enqueuer.codegen.logSummary(log);
if (compilationFailed) return;
backend.assembleProgram();
if (dumpInfo) {
dumpInfoTask.dumpInfo();
}
checkQueues();
if (compilationFailed) {
assembledCode = null; // Signals failure.
}
}
void fullyEnqueueLibrary(LibraryElement library, Enqueuer world) {
void enqueueAll(Element element) {
fullyEnqueueTopLevelElement(element, world);
}
library.implementation.forEachLocalMember(enqueueAll);
}
void fullyEnqueueTopLevelElement(Element element, Enqueuer world) {
if (element.isClass()) {
ClassElement cls = element;
cls.ensureResolved(this);
cls.forEachLocalMember(enqueuer.resolution.addToWorkList);
world.registerInstantiatedClass(element, globalDependencies);
} else {
world.addToWorkList(element);
}
}
// Resolves metadata on library elements. This is necessary in order to
// resolve metadata classes referenced only from metadata on library tags.
// TODO(ahe): Figure out how to do this lazily.
void resolveLibraryMetadata() {
for (LibraryElement library in libraries.values) {
if (library.metadata != null) {
for (MetadataAnnotation metadata in library.metadata) {
metadata.ensureResolved(this);
}
}
}
}
void processQueue(Enqueuer world, Element main) {
world.nativeEnqueuer.processNativeClasses(libraries.values);
if (main != null) {
FunctionElement mainMethod = main;
if (mainMethod.computeSignature(this).parameterCount != 0) {
// TODO(ngeoffray, floitsch): we should also ensure that the
// class IsolateMessage is instantiated. Currently, just enabling
// isolate support works.
world.enableIsolateSupport(main.getLibrary());
world.registerInstantiatedClass(listClass, globalDependencies);
world.registerInstantiatedClass(stringClass, globalDependencies);
}
world.addToWorkList(main);
}
progress.reset();
world.forEach((WorkItem work) {
withCurrentElement(work.element, () => work.run(this, world));
});
world.queueIsClosed = true;
if (compilationFailed) return;
assert(world.checkNoEnqueuedInvokedInstanceMethods());
}
/**
* Perform various checks of the queues. This includes checking that
* the queues are empty (nothing was added after we stopped
* processing the queues). Also compute the number of methods that
* were resolved, but not compiled (aka excess resolution).
*/
checkQueues() {
for (Enqueuer world in [enqueuer.resolution, enqueuer.codegen]) {
world.forEach((WorkItem work) {
internalErrorOnElement(work.element, "Work list is not empty.");
});
}
if (!REPORT_EXCESS_RESOLUTION) return;
var resolved = new Set.from(enqueuer.resolution.resolvedElements.keys);
for (Element e in enqueuer.codegen.generatedCode.keys) {
resolved.remove(e);
}
for (Element e in new Set.from(resolved)) {
if (e.isClass() ||
e.isField() ||
e.isTypeVariable() ||
e.isTypedef() ||
identical(e.kind, ElementKind.ABSTRACT_FIELD)) {
resolved.remove(e);
}
if (identical(e.kind, ElementKind.GENERATIVE_CONSTRUCTOR)) {
ClassElement enclosingClass = e.getEnclosingClass();
resolved.remove(e);
}
if (identical(e.getLibrary(), jsHelperLibrary)) {
resolved.remove(e);
}
if (identical(e.getLibrary(), interceptorsLibrary)) {
resolved.remove(e);
}
}
log('Excess resolution work: ${resolved.length}.');
for (Element e in resolved) {
SourceSpan span = spanFromElement(e);
reportDiagnostic(span, 'Warning: $e resolved but not compiled.',
api.Diagnostic.WARNING);
}
}
TreeElements analyzeElement(Element element) {
assert(invariant(element,
element.impliesType() ||
element.isField() ||
element.isFunction() ||
element.isGenerativeConstructor() ||
element.isGetter() ||
element.isSetter(),
message: 'Unexpected element kind: ${element.kind}'));
assert(invariant(element, element.isDeclaration));
ResolutionEnqueuer world = enqueuer.resolution;
TreeElements elements = world.getCachedElements(element);
if (elements != null) return elements;
assert(parser != null);
Node tree = parser.parse(element);
assert(invariant(element, !element.isSynthesized || tree == null));
if (tree != null) validator.validate(tree);
elements = resolver.resolve(element);
if (tree != null && elements != null && !analyzeSignaturesOnly) {
// Only analyze nodes with a corresponding [TreeElements].
checker.check(elements);
}
world.resolvedElements[element] = elements;
return elements;
}
TreeElements analyze(ResolutionWorkItem work, ResolutionEnqueuer world) {
assert(invariant(work.element, identical(world, enqueuer.resolution)));
assert(invariant(work.element, !work.isAnalyzed(),
message: 'Element ${work.element} has already been analyzed'));
if (progress.elapsedMilliseconds > 500) {
// TODO(ahe): Add structured diagnostics to the compiler API and
// use it to separate this from the --verbose option.
if (phase == PHASE_RESOLVING) {
log('Resolved ${enqueuer.resolution.resolvedElements.length} '
'elements.');
progress.reset();
}
}
Element element = work.element;
TreeElements result = world.getCachedElements(element);
if (result != null) return result;
result = analyzeElement(element);
backend.onElementResolved(element, result);
return result;
}
void codegen(CodegenWorkItem work, CodegenEnqueuer world) {
assert(invariant(work.element, identical(world, enqueuer.codegen)));
if (progress.elapsedMilliseconds > 500) {
// TODO(ahe): Add structured diagnostics to the compiler API and
// use it to separate this from the --verbose option.
log('Compiled ${enqueuer.codegen.generatedCode.length} methods.');
progress.reset();
}
backend.codegen(work);
}
DartType resolveTypeAnnotation(Element element,
TypeAnnotation annotation) {
return resolver.resolveTypeAnnotation(element, annotation);
}
DartType resolveReturnType(Element element,
TypeAnnotation annotation) {
return resolver.resolveReturnType(element, annotation);
}
FunctionSignature resolveSignature(FunctionElement element) {
return withCurrentElement(element,
() => resolver.resolveSignature(element));
}
FunctionSignature resolveFunctionExpression(Element element,
FunctionExpression node) {
return withCurrentElement(element,
() => resolver.resolveFunctionExpression(element, node));
}
void resolveTypedef(TypedefElement element) {
withCurrentElement(element,
() => resolver.resolve(element));
}
FunctionType computeFunctionType(Element element,
FunctionSignature signature) {
return withCurrentElement(element,
() => resolver.computeFunctionType(element, signature));
}
reportWarning(Spannable node, var message) {
if (message is TypeWarning) {
// TODO(ahe): Don't supress these warning when the type checker
// is more complete.
if (message.message.kind == MessageKind.MISSING_RETURN) return;
if (message.message.kind == MessageKind.MAYBE_MISSING_RETURN) return;
}
SourceSpan span = spanFromSpannable(node);
reportDiagnostic(span, '$message', api.Diagnostic.WARNING);
}
void reportError(Spannable node,
MessageKind errorCode,
[Map arguments = const {}]) {
reportMessage(spanFromSpannable(node),
errorCode.error(arguments, terseDiagnostics),
api.Diagnostic.ERROR);
}
void reportFatalError(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
reportError(node, errorCode, arguments);
// TODO(ahe): Make this only abort the current method.
throw new CompilerCancelledException(
'Error: Cannot continue due to previous error.');
}
// TODO(ahe): Rename to reportWarning when that method has been removed.
void reportWarningCode(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
reportMessage(spanFromSpannable(node),
errorCode.error(arguments, terseDiagnostics),
api.Diagnostic.WARNING);
}
void reportInfo(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
reportMessage(spanFromSpannable(node),
errorCode.error(arguments, terseDiagnostics),
api.Diagnostic.INFO);
}
void reportHint(Spannable node, MessageKind errorCode,
[Map arguments = const {}]) {
reportMessage(spanFromSpannable(node),
errorCode.error(arguments, terseDiagnostics),
api.Diagnostic.HINT);
}
/// For debugging only, print a message with a source location.
void reportHere(Spannable node, String debugMessage) {
reportInfo(node, MessageKind.GENERIC, {'text': 'HERE: $debugMessage'});
}
void reportInternalError(Spannable node, String message) {
reportError(
node, MessageKind.GENERIC, {'text': 'Internal Error: $message'});
}
void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) {
// TODO(ahe): The names Diagnostic and api.Diagnostic are in
// conflict. Fix it.
reportDiagnostic(span, "$message", kind);
}
void reportDiagnostic(SourceSpan span, String message, api.Diagnostic kind);
SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) {
if (begin == null || end == null) {
// TODO(ahe): We can almost always do better. Often it is only
// end that is null. Otherwise, we probably know the current
// URI.
throw 'Cannot find tokens to produce error message.';
}
if (uri == null && currentElement != null) {
uri = currentElement.getCompilationUnit().script.uri;
}
return SourceSpan.withCharacterOffsets(begin, end,
(beginOffset, endOffset) => new SourceSpan(uri, beginOffset, endOffset));
}
SourceSpan spanFromNode(Node node, [Uri uri]) {
return spanFromTokens(node.getBeginToken(), node.getEndToken(), uri);
}
SourceSpan spanFromElement(Element element) {
if (Elements.isErroneousElement(element)) {
element = element.enclosingElement;
}
if (element.position() == null &&
!element.isLibrary() &&
!element.isCompilationUnit()) {
// Sometimes, the backend fakes up elements that have no
// position. So we use the enclosing element instead. It is
// not a good error location, but cancel really is "internal
// error" or "not implemented yet", so the vicinity is good
// enough for now.
element = element.enclosingElement;
// TODO(ahe): I plan to overhaul this infrastructure anyways.
}
if (element == null) {
element = currentElement;
}
Token position = element.position();
Uri uri = element.getCompilationUnit().script.uri;
return (position == null)
? new SourceSpan(uri, 0, 0)
: spanFromTokens(position, position, uri);
}
SourceSpan spanFromHInstruction(HInstruction instruction) {
Element element = instruction.sourceElement;
if (element == null) element = currentElement;
var position = instruction.sourcePosition;
if (position == null) return spanFromElement(element);
Token token = position.token;
if (token == null) return spanFromElement(element);
Uri uri = element.getCompilationUnit().script.uri;
return spanFromTokens(token, token, uri);
}
/**
* Translates the [resolvedUri] into a readable URI.
*
* The [importingLibrary] holds the library importing [resolvedUri] or
* [:null:] if [resolvedUri] is loaded as the main library. The
* [importingLibrary] is used to grant access to internal libraries from
* platform libraries and patch libraries.
*
* If the [resolvedUri] is not accessible from [importingLibrary], this method
* is responsible for reporting errors.
*
* See [LibraryLoader] for terminology on URIs.
*/
Uri translateResolvedUri(LibraryElement importingLibrary,
Uri resolvedUri, Node node) {
unimplemented('Compiler.translateResolvedUri');
}
/**
* Reads the script specified by the [readableUri].
*
* See [LibraryLoader] for terminology on URIs.
*/
Future<Script> readScript(Uri readableUri, [Element element, Node node]) {
unimplemented('Compiler.readScript');
}
String get legDirectory {
unimplemented('Compiler.legDirectory');
}
// TODO(karlklose): split into findHelperFunction and findHelperClass and
// add a check that the element has the expected kind.
Element findHelper(String name)
=> jsHelperLibrary.findLocal(name);
Element findInterceptor(String name)
=> interceptorsLibrary.findLocal(name);
Element lookupElementIn(ScopeContainerElement container, String name) {
Element element = container.localLookup(name);
if (element == null) {
throw 'Could not find $name in $container';
}
return element;
}
bool get isMockCompilation => false;
Token processAndStripComments(Token currentToken) {
Token firstToken = currentToken;
Token prevToken;
while (currentToken.kind != EOF_TOKEN) {
if (identical(currentToken.kind, COMMENT_TOKEN)) {
Token firstCommentToken = currentToken;
while (identical(currentToken.kind, COMMENT_TOKEN)) {
currentToken = currentToken.next;
}
commentMap[currentToken] = firstCommentToken;
if (prevToken == null) {
firstToken = currentToken;
} else {
prevToken.next = currentToken;
}
}
prevToken = currentToken;
currentToken = currentToken.next;
}
return firstToken;
}
void reportUnusedCode() {
void checkLive(member) {
if (member.isFunction()) {
if (!enqueuer.resolution.isLive(member)) {
reportHint(member, MessageKind.UNUSED_METHOD,
{'method_name': member.name});
}
} else if (member.isClass() && !member.isMixinApplication) {
member.forEachLocalMember(checkLive);
}
}
libraries.forEach((_, library) {
// TODO(ahe): Implement better heuristics to discover entry points of
// packages and use that to discover unused implementation details in
// packages.
if (library.isPlatformLibrary || library.isPackageLibrary) return;
library.compilationUnits.forEach((unit) {
unit.forEachLocalMember(checkLive);
});
});
}
}
class CompilerTask {
final Compiler compiler;
final Stopwatch watch;
CompilerTask(Compiler compiler)
: this.compiler = compiler,
watch = (compiler.verbose) ? new Stopwatch() : null;
String get name => 'Unknown task';
int get timing => (watch != null) ? watch.elapsedMilliseconds : 0;
measure(action()) {
if (watch == null) return action();
CompilerTask previous = compiler.measuredTask;
if (identical(this, previous)) return action();
compiler.measuredTask = this;
if (previous != null) previous.watch.stop();
watch.start();
try {
return action();
} finally {
watch.stop();
if (previous != null) previous.watch.start();
compiler.measuredTask = previous;
}
}
measureElement(Element element, action()) {
compiler.withCurrentElement(element, () => measure(action));
}
}
class CompilerCancelledException implements Exception {
final String reason;
CompilerCancelledException(this.reason);
String toString() {
String banner = 'compiler cancelled';
return (reason != null) ? '$banner: $reason' : '$banner';
}
}
class Tracer {
final bool enabled = false;
const Tracer();
void traceCompilation(String methodName,
ItemCompilationContext context,
Compiler compiler) {
}
void traceGraph(String name, var graph) {
}
void close() {
}
}
class SourceSpan implements Spannable {
final Uri uri;
final int begin;
final int end;
const SourceSpan(this.uri, this.begin, this.end);
static withCharacterOffsets(Token begin, Token end,
f(int beginOffset, int endOffset)) {
final beginOffset = begin.charOffset;
final endOffset = end.charOffset + end.charCount;
// [begin] and [end] might be the same for the same empty token. This
// happens for instance when scanning '$$'.
assert(endOffset >= beginOffset);
return f(beginOffset, endOffset);
}
String toString() => 'SourceSpan($uri, $begin, $end)';
}
/**
* Throws a [SpannableAssertionFailure] if [condition] is
* [:false:]. [condition] must be either a [:bool:] or a no-arg
* function returning a [:bool:].
*
* Use this method to provide better information for assertion by calling
* [invariant] as the argument to an [:assert:] statement:
*
* assert(invariant(position, isValid));
*
* [spannable] must be non-null and will be used to provide positional
* information in the generated error message.
*/
bool invariant(Spannable spannable, var condition, {var message: null}) {
// TODO(johnniwinther): Use [spannable] and [message] to provide better
// information on assertion errors.
if (spannable == null) {
throw new SpannableAssertionFailure(CURRENT_ELEMENT_SPANNABLE,
"Spannable was null for invariant. Use CURRENT_ELEMENT_SPANNABLE.");
}
if (condition is Function){
condition = condition();
}
if (!condition) {
if (message is Function) {
message = message();
}
throw new SpannableAssertionFailure(spannable, message);
}
return true;
}
/**
* Global
*/
bool isPrivateName(String s) => !s.isEmpty && s.codeUnitAt(0) == $_;
/// A sink that drains into /dev/null.
class NullSink implements EventSink<String> {
final String name;
NullSink(this.name);
add(String value) {}
void addError(Object error, [StackTrace stackTrace]) {}
void close() {}
toString() => name;
/// Convenience method for getting an [api.CompilerOutputProvider].
static NullSink outputProvider(String name, String extension) {
return new NullSink('$name.$extension');
}
}