Avoid inlining constants that are used via a deferred import.

In order to avoid values from a deferred library to leak into the source code, we create an intermediate constant and refer via that. This is also done for non-primitive constants, so that for a case like:

main.dart:
import "lib.dart" deferred as lib;
class C { const C(); }
main() {
  print(const C());
  print(lib.C1);
}

lib.dart:
import "main.dart" as main;
const C1 = const main.C();

The main output file will not reveal that we are printing the same constant twice.

This CL also changes the constant emitter to be split between a literal emitter and a reference emitter, instead of a initializer emitter and a reference emitter.

R=johnniwinther@google.com

Review URL: https://codereview.chromium.org//256453004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@36286 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
sigurdm@google.com
2014-05-19 08:19:53 +00:00
parent e0a62bb44d
commit ddfe681eb3
10 changed files with 378 additions and 134 deletions
@@ -18,6 +18,7 @@ abstract class ConstantVisitor<R> {
R visitType(TypeConstant constant);
R visitInterceptor(InterceptorConstant constant);
R visitDummy(DummyConstant constant);
R visitDeferred(DeferredConstant constant);
}
abstract class Constant {
@@ -652,3 +653,36 @@ class ConstructedConstant extends ObjectConstant {
return sb.toString();
}
}
/// A reference to a constant in another output unit.
/// Used for referring to deferred constants.
class DeferredConstant extends Constant {
DeferredConstant(this.referenced, this.prefix);
final Constant referenced;
final PrefixElement prefix;
bool get isReference => true;
bool operator ==(other) {
return other is DeferredConstant
&& referenced == other.referenced
&& prefix == other.prefix;
}
get hashCode => (referenced.hashCode * 17 + prefix.hashCode) & 0x3fffffff;
List<Constant> getDependencies() => <Constant>[referenced];
accept(ConstantVisitor visitor) => visitor.visitDeferred(this);
DartType computeType(Compiler compiler) => referenced.computeType(compiler);
ti.TypeMask computeMask(Compiler compiler) {
return referenced.computeMask(compiler);
}
String toString() {
return 'DeferredConstant($referenced)';
}
}
@@ -38,7 +38,7 @@ import 'resolution/resolution.dart';
import 'resolution/class_members.dart' show MembersCreator;
import 'source_file.dart' show SourceFile;
import 'js/js.dart' as js;
import 'deferred_load.dart' show DeferredLoadTask;
import 'deferred_load.dart' show DeferredLoadTask, OutputUnit;
import 'mirrors_used.dart' show MirrorUsageAnalyzerTask;
import 'dump_info.dart';
import 'tracer.dart' show Tracer;
@@ -11,6 +11,7 @@ import 'dart2jslib.dart' show
Constant,
ConstructedConstant,
MessageKind,
DeferredConstant,
StringConstant,
invariant;
@@ -189,6 +190,13 @@ class DeferredLoadTask extends CompilerTask {
return outputUnitForElement(e1) == outputUnitForElement(e2);
}
void registerConstantDeferredUse(DeferredConstant constant,
PrefixElement prefix) {
OutputUnit outputUnit = new OutputUnit();
outputUnit.imports.add(prefix.deferredImport);
_constantToOutputUnit[constant] = outputUnit;
}
/// Mark that [import] is part of the [OutputputUnit] for [element].
///
/// [element] can be either a [Constant] or an [Element].
@@ -4,14 +4,13 @@
part of js_backend;
class ConstantEmitter {
class ConstantEmitter {
ConstantReferenceEmitter _referenceEmitter;
ConstantInitializerEmitter _initializerEmitter;
ConstantLiteralEmitter _literalEmitter;
ConstantEmitter(Compiler compiler, Namer namer) {
_referenceEmitter = new ConstantReferenceEmitter(compiler, namer);
_initializerEmitter = new ConstantInitializerEmitter(
compiler, namer, _referenceEmitter);
_literalEmitter = new ConstantLiteralEmitter(compiler, namer, this);
_referenceEmitter = new ConstantReferenceEmitter(compiler, namer, this);
}
/**
@@ -23,19 +22,28 @@ class ConstantEmitter {
return _referenceEmitter.generate(constant);
}
/**
* Constructs a literal expression that evaluates to the constant. Uses a
* canonical name unless the constant can be emitted multiple times (as for
* numbers and strings).
*/
jsAst.Expression literal(Constant constant) {
return _literalEmitter.generate(constant);
}
/**
* Constructs an expression like [reference], but the expression is valid
* during isolate initialization.
*/
jsAst.Expression referenceInInitializationContext(Constant constant) {
return _referenceEmitter.generateInInitializationContext(constant);
return _referenceEmitter.generate(constant);
}
/**
* Constructs an expression used to initialize a canonicalized constant.
*/
jsAst.Expression initializationExpression(Constant constant) {
return _initializerEmitter.generate(constant);
return _literalEmitter.generate(constant);
}
}
@@ -47,13 +55,112 @@ class ConstantReferenceEmitter implements ConstantVisitor<jsAst.Expression> {
final Compiler compiler;
final Namer namer;
ConstantReferenceEmitter(this.compiler, this.namer);
final ConstantEmitter constantEmitter;
ConstantReferenceEmitter(this.compiler, this.namer, this.constantEmitter);
jsAst.Expression generate(Constant constant) {
return _visit(constant);
}
jsAst.Expression generateInInitializationContext(Constant constant) {
jsAst.Expression _visit(Constant constant) {
return constant.accept(this);
}
jsAst.Expression emitCanonicalVersion(Constant constant) {
String name = namer.constantName(constant);
return new jsAst.PropertyAccess.field(
new jsAst.VariableUse(namer.globalObjectForConstant(constant)), name);
}
jsAst.Expression literal(Constant constant) {
return constantEmitter.literal(constant);
}
jsAst.Expression visitFunction(FunctionConstant constant) {
return namer.isolateStaticClosureAccess(constant.element);
}
jsAst.Expression visitNull(NullConstant constant) {
return literal(constant);
}
jsAst.Expression visitInt(IntConstant constant) {
return literal(constant);
}
jsAst.Expression visitDouble(DoubleConstant constant) {
return literal(constant);
}
jsAst.Expression visitTrue(TrueConstant constant) {
return literal(constant);
}
jsAst.Expression visitFalse(FalseConstant constant) {
return literal(constant);
}
/**
* Write the contents of the quoted string to a [CodeBuffer] in
* a form that is valid as JavaScript string literal content.
* The string is assumed quoted by double quote characters.
*/
jsAst.Expression visitString(StringConstant constant) {
// TODO(sra): If the string is long *and repeated* (and not on a hot path)
// then it should be assigned to a name. We don't have reference counts (or
// profile information) here, so this is the wrong place.
return literal(constant);
}
jsAst.Expression visitList(ListConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitMap(MapConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitType(TypeConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitConstructed(ConstructedConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitInterceptor(InterceptorConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitDummy(DummyConstant constant) {
return literal(constant);
}
jsAst.Expression visitDeferred(DeferredConstant constant) {
return emitCanonicalVersion(constant);
}
}
/**
* Visitor for generating JavaScript expressions that litterally represent
* [Constant]s. These can be used for inlining constants or in initializers.
* Do not use directly, use methods from [ConstantEmitter].
*/
class ConstantLiteralEmitter implements ConstantVisitor<jsAst.Expression> {
// Matches blank lines, comment lines and trailing comments that can't be part
// of a string.
static final RegExp COMMENT_RE =
new RegExp(r'''^ *(//.*)?\n| *//[^''"\n]*$''' , multiLine: true);
final Compiler compiler;
final Namer namer;
final ConstantEmitter constantEmitter;
ConstantLiteralEmitter(this.compiler, this.namer, this.constantEmitter);
jsAst.Expression generate(Constant constant) {
return _visit(constant);
}
@@ -62,7 +169,9 @@ class ConstantReferenceEmitter implements ConstantVisitor<jsAst.Expression> {
}
jsAst.Expression visitFunction(FunctionConstant constant) {
return namer.isolateStaticClosureAccess(constant.element);
compiler.internalError(NO_LOCATION_SPANNABLE,
"The function constant does not need specific JS code.");
return null;
}
jsAst.Expression visitNull(NullConstant constant) {
@@ -110,104 +219,11 @@ class ConstantReferenceEmitter implements ConstantVisitor<jsAst.Expression> {
* The string is assumed quoted by double quote characters.
*/
jsAst.Expression visitString(StringConstant constant) {
// TODO(sra): If the string is long *and repeated* (and not on a hot path)
// then it should be assigned to a name. We don't have reference counts (or
// profile information) here, so this is the wrong place.
StringBuffer sb = new StringBuffer();
writeJsonEscapedCharsOn(constant.value.slowToString(), sb);
return new jsAst.LiteralString('"$sb"');
}
jsAst.Expression emitCanonicalVersion(Constant constant) {
String name = namer.constantName(constant);
return new jsAst.PropertyAccess.field(
new jsAst.VariableUse(namer.globalObjectForConstant(constant)), name);
}
jsAst.Expression visitList(ListConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitMap(MapConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitType(TypeConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitConstructed(ConstructedConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitInterceptor(InterceptorConstant constant) {
return emitCanonicalVersion(constant);
}
jsAst.Expression visitDummy(DummyConstant constant) {
return new jsAst.LiteralNumber('0');
}
}
/**
* Visitor for generating JavaScript expressions to initialize [Constant]s.
* Do not use directly; use methods from [ConstantEmitter].
*/
class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
final Compiler compiler;
final Namer namer;
final ConstantReferenceEmitter referenceEmitter;
// Matches blank lines, comment lines and trailing comments that can't be part
// of a string.
static final RegExp COMMENT_RE =
new RegExp(r'''^ *(//.*)?\n| *//[^''"\n]*$''' , multiLine: true);
ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter);
jsAst.Expression generate(Constant constant) {
return _visit(constant);
}
jsAst.Expression _visit(Constant constant) {
return constant.accept(this);
}
jsAst.Expression _reference(Constant constant) {
return referenceEmitter.generateInInitializationContext(constant);
}
jsAst.Expression visitFunction(FunctionConstant constant) {
compiler.internalError(NO_LOCATION_SPANNABLE,
"The function constant does not need specific JS code.");
return null;
}
jsAst.Expression visitNull(NullConstant constant) {
return _reference(constant);
}
jsAst.Expression visitInt(IntConstant constant) {
return _reference(constant);
}
jsAst.Expression visitDouble(DoubleConstant constant) {
return _reference(constant);
}
jsAst.Expression visitTrue(TrueConstant constant) {
return _reference(constant);
}
jsAst.Expression visitFalse(FalseConstant constant) {
return _reference(constant);
}
jsAst.Expression visitString(StringConstant constant) {
// TODO(sra): Some larger strings are worth sharing.
return _reference(constant);
}
jsAst.Expression visitList(ListConstant constant) {
jsAst.Expression value = new jsAst.Call(
new jsAst.PropertyAccess.field(
@@ -231,7 +247,7 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
// Keys in literal maps must be emitted in place.
jsAst.Literal keyExpression = _visit(key);
jsAst.Expression valueExpression =
_reference(constant.values[i]);
constantEmitter.reference(constant.values[i]);
properties.add(new jsAst.Property(keyExpression, valueExpression));
}
return new jsAst.ObjectInitializer(properties);
@@ -241,9 +257,9 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
List<jsAst.Expression> data = <jsAst.Expression>[];
for (int i = 0; i < constant.keys.entries.length; i++) {
jsAst.Expression keyExpression =
_reference(constant.keys.entries[i]);
constantEmitter.reference(constant.keys.entries[i]);
jsAst.Expression valueExpression =
_reference(constant.values[i]);
constantEmitter.reference(constant.values[i]);
data.add(keyExpression);
data.add(valueExpression);
}
@@ -266,10 +282,10 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
} else if (field.name == MapConstant.JS_OBJECT_NAME) {
arguments.add(jsMap());
} else if (field.name == MapConstant.KEYS_NAME) {
arguments.add(_reference(constant.keys));
arguments.add(constantEmitter.reference(constant.keys));
} else if (field.name == MapConstant.PROTO_VALUE) {
assert(constant.protoValue != null);
arguments.add(_reference(constant.protoValue));
arguments.add(constantEmitter.reference(constant.protoValue));
} else if (field.name == MapConstant.JS_DATA_NAME) {
arguments.add(jsGeneralMap());
} else {
@@ -316,7 +332,7 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
}
jsAst.Expression visitDummy(DummyConstant constant) {
return _reference(constant);
return new jsAst.LiteralNumber('0');
}
jsAst.Expression visitConstructed(ConstructedConstant constant) {
@@ -340,7 +356,7 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
List<jsAst.Expression> _array(List<Constant> values) {
List<jsAst.Expression> valueList = <jsAst.Expression>[];
for (int i = 0; i < values.length; i++) {
valueList.add(_reference(values[i]));
valueList.add(constantEmitter.reference(values[i]));
}
return valueList;
}
@@ -363,4 +379,8 @@ class ConstantInitializerEmitter implements ConstantVisitor<jsAst.Expression> {
}
return value;
}
jsAst.Expression visitDeferred(DeferredConstant constant) {
return constantEmitter.reference(constant.referenced);
}
}
@@ -1163,6 +1163,10 @@ class ConstantNamingVisitor implements ConstantVisitor {
visitDummy(DummyConstant constant) {
add('dummy_receiver');
}
visitDeferred(DeferredConstant constant) {
addRoot('Deferred');
}
}
/**
@@ -1245,6 +1249,11 @@ class ConstantCanonicalHasher implements ConstantVisitor<int> {
'DummyReceiverConstant should never be named and never be subconstant');
}
visitDeferred(DeferredConstant constant) {
int hash = constant.prefix.hashCode;
return _combine(hash, constant.referenced.accept(this));
}
int _hashString(int hash, String s) {
int length = s.length;
hash = _combine(hash, length);
@@ -33,6 +33,8 @@ class CodeEmitterTask extends CompilerTask {
final Set<ClassElement> neededClasses = new Set<ClassElement>();
final Map<OutputUnit, List<ClassElement>> outputClassLists =
new Map<OutputUnit, List<ClassElement>>();
final Map<OutputUnit, List<Constant>> outputConstantLists =
new Map<OutputUnit, List<Constant>>();
final List<ClassElement> nativeClasses = <ClassElement>[];
final Map<String, String> mangledFieldNames = <String, String>{};
final Map<String, String> mangledGlobalFieldNames = <String, String>{};
@@ -890,26 +892,9 @@ class CodeEmitterTask extends CompilerTask {
}
void emitCompileTimeConstants(CodeBuffer buffer, OutputUnit outputUnit) {
JavaScriptConstantCompiler handler = backend.constants;
List<Constant> constants = handler.getConstantsForEmission(
compareConstants);
Set<Constant> outputUnitConstants = null;
// TODO(sigurdm): We shouldn't run through all constants for every
// outputUnit.
List<Constant> constants = outputConstantLists[outputUnit];
if (constants == null) return;
for (Constant constant in constants) {
if (isConstantInlinedOrAlreadyEmitted(constant)) continue;
OutputUnit constantUnit =
compiler.deferredLoadTask.outputUnitForConstant(constant);
if (constantUnit != outputUnit && constantUnit != null) continue;
if (outputUnit != compiler.deferredLoadTask.mainOutputUnit
&& constantUnit == null) {
// The back-end introduces some constants, like "InterceptorConstant" or
// some list constants. They are emitted in the main output-unit, and
// ignored otherwise.
// TODO(sigurdm): We should track those constants.
continue;
}
String name = namer.constantName(constant);
if (constant.isList) emitMakeConstantListIfNotEmitted(buffer);
jsAst.Expression init = js('#.# = #',
@@ -1093,6 +1078,28 @@ class CodeEmitterTask extends CompilerTask {
addComment('END invoke [main].', buffer);
}
/**
* Compute all the constants that must be emitted.
*/
void computeNeededConstants() {
JavaScriptConstantCompiler handler = backend.constants;
List<Constant> constants = handler.getConstantsForEmission(
compareConstants);
for (Constant constant in constants) {
if (isConstantInlinedOrAlreadyEmitted(constant)) continue;
OutputUnit constantUnit =
compiler.deferredLoadTask.outputUnitForConstant(constant);
if (constantUnit == null) {
// The back-end introduces some constants, like "InterceptorConstant" or
// some list constants. They are emitted in the main output-unit.
// TODO(sigurdm): We should track those constants.
constantUnit = compiler.deferredLoadTask.mainOutputUnit;
}
outputConstantLists.putIfAbsent(constantUnit, () => new List<Constant>())
.add(constant);
}
}
/**
* Compute all the classes that must be emitted.
*/
@@ -1464,6 +1471,7 @@ class CodeEmitterTask extends CompilerTask {
// which may need getInterceptor (and one-shot interceptor) methods, so
// we have to make sure that [emitGetInterceptorMethods] and
// [emitOneShotInterceptors] have been called.
computeNeededConstants();
emitCompileTimeConstants(mainBuffer, mainOutputUnit);
// Write a javascript mapping from Deferred import load ids (derrived from
@@ -2996,7 +2996,16 @@ class SsaBuilder extends ResolvedVisitor {
value = backend.constants.getConstantForVariable(element);
}
if (value != null) {
HConstant instruction = graph.addConstant(value, compiler);
HConstant instruction;
// Constants that are referred via a deferred prefix should be referred
// by reference.
PrefixElement prefix = compiler.deferredLoadTask
.deferredPrefixElement(send, elements);
if (prefix != null) {
instruction = graph.addDeferredConstant(value, prefix, compiler);
} else {
instruction = graph.addConstant(value, compiler);
}
stack.add(instruction);
// The inferrer may have found a better type than the constant
// handler in the case of lists, because the constant handler
@@ -184,6 +184,13 @@ class HGraph {
return result;
}
HConstant addDeferredConstant(Constant constant, PrefixElement prefix,
Compiler compiler) {
Constant wrapper = new DeferredConstant(constant, prefix);
compiler.deferredLoadTask.registerConstantDeferredUse(wrapper, prefix);
return addConstant(wrapper, compiler);
}
HConstant addConstantInt(int i, Compiler compiler) {
return addConstant(compiler.backend.constantSystem.createInt(i), compiler);
}
@@ -670,9 +670,15 @@ class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase {
return range;
}
Range visitConstant(HConstant constant) {
if (!constant.isInteger(compiler)) return info.newUnboundRange();
NumConstant constantNum = constant.constant;
Range visitConstant(HConstant hConstant) {
if (!hConstant.isInteger(compiler)) return info.newUnboundRange();
Constant constant = hConstant.constant;
NumConstant constantNum;
if (constant is DeferredConstant) {
constantNum = constant.referenced;
} else {
constantNum = constant;
}
if (constantNum.isMinusZero) constantNum = new IntConstant(0);
Value value = info.newIntValue(constantNum.value);
return info.newNormalizedRange(value, value);
@@ -0,0 +1,143 @@
// Copyright (c) 2014, 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.
// Test that the additional runtime type support is output to the right
// Files when using deferred loading.
import 'package:expect/expect.dart';
import "package:async_helper/async_helper.dart";
import 'memory_source_file_helper.dart';
import "dart:async";
import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart'
as dart2js;
class MemoryOutputSink extends EventSink<String> {
StringBuffer mem = new StringBuffer();
void add(String event) {
mem.write(event);
}
void addError(String event, [StackTrace stackTrace]) {
Expect.isTrue(false);
}
void close() {}
}
void main() {
Uri script = currentDirectory.resolveUri(Platform.script);
Uri libraryRoot = script.resolve('../../../sdk/');
Uri packageRoot = script.resolve('./packages/');
var provider = new MemorySourceFileProvider(MEMORY_SOURCE_FILES);
var handler = new FormattingDiagnosticHandler(provider);
Map<String, MemoryOutputSink> outputs = new Map<String, MemoryOutputSink>();
MemoryOutputSink outputSaver(name, extension) {
if (name == '') {
name = 'main';
}
return outputs.putIfAbsent("$name.$extension", () {
return new MemoryOutputSink();
});
}
Compiler compiler = new Compiler(provider.readStringFromUri,
outputSaver,
handler.diagnosticHandler,
libraryRoot,
packageRoot,
[],
{});
asyncTest(() => compiler.run(Uri.parse('memory:main.dart')).then((_) {
String mainOutput = outputs['main.js'].mem.toString();
String lib1Output = outputs['out_lib1.part.js'].mem.toString();
String lib2Output = outputs['out_lib2.part.js'].mem.toString();
String lib12Output = outputs['out_lib1_lib2.part.js'].mem.toString();
// Test that the deferred constants are not inlined into the main file.
RegExp re1 = new RegExp(r"= .string1");
RegExp re2 = new RegExp(r"= .string2");
RegExp re3 = new RegExp(r"= 1010");
Expect.isTrue(re1.hasMatch(lib1Output));
Expect.isTrue(re2.hasMatch(lib1Output));
Expect.isTrue(re3.hasMatch(lib1Output));
Expect.isFalse(re1.hasMatch(mainOutput));
Expect.isFalse(re2.hasMatch(mainOutput));
Expect.isFalse(re3.hasMatch(mainOutput));
// Test that the non-deferred constant is inlined.
Expect.isTrue(new RegExp(r"print\(.string3.\)").hasMatch(mainOutput));
Expect.isFalse(new RegExp(r"= .string3").hasMatch(mainOutput));
Expect.isTrue(new RegExp(r"print\(.string4.\)").hasMatch(mainOutput));
// C(1) is shared between main, lib1 and lib2. Test that lib1 and lib2 each
// has a reference to it. It is defined in the main output file.
Expect.isTrue(new RegExp(r"C.C_1 =").hasMatch(mainOutput));
Expect.isFalse(new RegExp(r"= C.C_1").hasMatch(mainOutput));
Expect.isTrue(new RegExp(r"= C.C_1").hasMatch(lib1Output));
Expect.isTrue(new RegExp(r"= C.C_1").hasMatch(lib2Output));
// C(2) is shared between lib1 and lib2, each of them has their own
// reference to it.
Expect.isFalse(new RegExp(r"= C.C_2").hasMatch(mainOutput));
Expect.isTrue(new RegExp(r"= C.C_2").hasMatch(lib1Output));
Expect.isTrue(new RegExp(r"= C.C_2").hasMatch(lib2Output));
Expect.isTrue(new RegExp(r"C.C_2 =").hasMatch(lib12Output));
// "string4" is shared between lib1 and lib2, but it can be inlined.
Expect.isTrue(new RegExp(r"= .string4").hasMatch(lib1Output));
Expect.isTrue(new RegExp(r"= .string4").hasMatch(lib2Output));
Expect.isFalse(new RegExp(r"= .string4").hasMatch(lib12Output));
}));
}
// Make sure that deferred constants are not inlined into the main hunk.
const Map MEMORY_SOURCE_FILES = const {"main.dart": """
import "dart:async";
import 'lib1.dart' deferred as lib1;
import 'lib2.dart' deferred as lib2;
const c = "string3";
class C {
final p;
const C(this.p);
}
void main() {
lib1.loadLibrary().then((_) {
lib2.loadLibrary().then((_) {
print(lib1.C1);
print(lib1.C2);
print(lib1.C.C3);
print(c);
print(lib1.C4);
print(lib2.C4);
print(lib1.C5);
print(lib2.C5);
print(lib1.C6);
print(lib2.C6);
print("string4");
print(const C(1));
});
});
}
""", "lib1.dart": """
import "main.dart" as main;
const C1 = "string1";
const C2 = 1010;
class C {
static const C3 = "string2";
}
const C4 = "string4";
const C5 = const main.C(1);
const C6 = const main.C(2);
""", "lib2.dart": """
import "main.dart" as main;
const C4 = "string4";
const C5 = const main.C(1);
const C6 = const main.C(2);
"""};