Version 0.5.20.3
svn merge -r 24213:24214 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
This fixes http://dartbug.com/11375
R=kmillikin@google.com
Review URL: https://codereview.chromium.org//17494002
git-svn-id: http://dart.googlecode.com/svn/trunk@24216 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart
index 1898ad1..bc6b7cf 100644
--- a/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart
+++ b/pkg/analyzer_experimental/lib/src/services/runtime/coverage/coverage_impl.dart
@@ -28,20 +28,20 @@
void runServerApplication(String targetPath, String outPath) {
var targetFolder = pathos.dirname(targetPath);
var targetName = pathos.basename(targetPath);
- new CoverageServer(targetFolder, targetPath, outPath)
- .start()
- .then((port) {
- var options = new Options();
- var targetArgs = ['http://127.0.0.1:$port/$targetName'];
- var dartExecutable = options.executable;
- Process.start(dartExecutable, targetArgs).then((Process process) {
- process.exitCode.then(exit);
- // Redirect process streams.
- stdin.pipe(process.stdin);
- process.stdout.pipe(stdout);
- process.stderr.pipe(stderr);
- });
- });
+ var server = new CoverageServer(targetFolder, targetPath, outPath);
+ server.start().then((port) {
+ var options = new Options();
+ var targetArgs = ['http://127.0.0.1:$port/$targetName'];
+ var dartExecutable = options.executable;
+ return Process.start(dartExecutable, targetArgs);
+ }).then((process) {
+ stdin.pipe(process.stdin);
+ process.stdout.pipe(stdout);
+ process.stderr.pipe(stderr);
+ return process.exitCode;
+ }).then(exit).catchError((e) {
+ log.severe('Error starting $targetPath. $e');
+ });
}
@@ -69,13 +69,12 @@
});
}
- handlePostRequest(HttpRequest request);
+ void handlePostRequest(HttpRequest request);
- handleGetRequest(HttpRequest request) {
+ void handleGetRequest(HttpRequest request) {
var response = request.response;
// Prepare path.
- var path = basePath + '/' + request.uri.path;
- path = pathos.normalize(path);
+ var path = getFilePath(request.uri);
log.info('[$path] Requested.');
// May be serve using just path.
{
@@ -94,30 +93,38 @@
if (found) {
// May be this files should be sent as is.
if (!shouldRewriteFile(path)) {
- sendFile(request, file);
- return;
+ return sendFile(request, file);
}
// Rewrite content of the file.
- file.readAsString().then((content) {
+ return file.readAsString().then((content) {
log.finest('[$path] Done reading ${content.length} characters.');
content = rewriteFileContent(path, content);
log.fine('[$path] Rewritten.');
response.write(content);
- response.close();
+ return response.close();
});
} else {
log.severe('[$path] File not found.');
response.statusCode = HttpStatus.NOT_FOUND;
- response.close();
+ return response.close();
}
+ }).catchError((e) {
+ log.severe('[$path] $e.');
+ response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+ return response.close();
});
}
- void sendFile(HttpRequest request, File file) {
+ String getFilePath(Uri uri) {
+ var path = uri.path;
+ path = pathos.joinAll(uri.pathSegments);
+ path = pathos.join(basePath, path);
+ return pathos.normalize(path);
+ }
+
+ Future sendFile(HttpRequest request, File file) {
file.fullPath().then((fullPath) {
- file.openRead()
- .pipe(request.response)
- .catchError((e) {});
+ return file.openRead().pipe(request.response);
});
}
@@ -134,6 +141,18 @@
}
+/// Here `CCC` means 'code coverage configuration'.
+const TEST_UNIT_CCC = '''
+class __CCC extends __cc_ut.Configuration {
+ void onDone(bool success) {
+ __cc.postStatistics();
+ super.onDone(success);
+ }
+}''';
+
+const TEST_UNIT_CCC_SET = '__cc_ut.unittestConfiguration = new __CCC();';
+
+
/// Server that rewrites Dart code so that it reports execution of statements
/// and other nodes.
class CoverageServer extends RewriteServer {
@@ -160,11 +179,14 @@
}
}).onDone(() {
log.fine('Received all statistics.');
- var sb = new StringBuffer();
- appInfo.write(sb, executedIds);
- new File(outPath).writeAsString(sb.toString());
- log.fine('Results are written to $outPath.');
- request.response.close();
+ var buffer = new StringBuffer();
+ appInfo.write(buffer, executedIds);
+ new File(outPath).writeAsString(buffer.toString()).then((_) {
+ return request.response.close();
+ }).catchError((e) {
+ log.severe('Error in receiving statistics $e.');
+ return request.response.close();
+ });
});
}
@@ -175,8 +197,7 @@
'..', 'lib', 'src', 'services', 'runtime', 'coverage',
'coverage_lib.dart']);
var content = new File(implPath).readAsStringSync();
- content = content.replaceAll('0; // replaced during rewrite', '$port;');
- return content;
+ return content.replaceAll('0; // replaced during rewrite', '$port;');
}
return null;
}
@@ -188,10 +209,7 @@
return true;
}
// TODO(scheglov) use configuration
- if (path.contains('/packages/analyzer_experimental/')) {
- return true;
- }
- return false;
+ return path.contains('/packages/analyzer_experimental/');
}
String rewriteFileContent(String path, String code) {
@@ -212,16 +230,8 @@
if (node is FunctionDeclaration) {
var body = node.functionExpression.body;
if (node.name.name == 'main' && body is BlockFunctionBody) {
- injector.inject(node.offset,
- 'class __CCC extends __cc_ut.Configuration {'
- ' void onDone(bool success) {'
- ' __cc.postStatistics();'
- ' super.onDone(success);'
- ' }'
- '}');
- injector.inject(
- body.offset + 1,
- '__cc_ut.unittestConfiguration = new __CCC();');
+ injector.inject(node.offset, TEST_UNIT_CCC);
+ injector.inject(body.offset + 1, TEST_UNIT_CCC_SET);
}
}
}
diff --git a/pkg/args/test/parse_all_test.dart b/pkg/args/test/parse_all_test.dart
new file mode 100644
index 0000000..a921046
--- /dev/null
+++ b/pkg/args/test/parse_all_test.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2013, 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.
+
+library parse_all_test;
+
+import 'package:unittest/unittest.dart';
+import 'package:args/args.dart';
+
+main() {
+ group('ArgParser.parse() starting with a non-option', () {
+ test('followed by flag', () {
+ var parser = new ArgParser()..addFlag('flag');
+ var args = ['A', '--flag'];
+
+ var results = parser.parse(args);
+ expect(results['flag'], isFalse);
+ expect(results.rest, orderedEquals(args));
+ });
+
+ test('followed by option', () {
+ var parser = new ArgParser()..addOption('opt');
+ var args = ['A', '--opt'];
+
+ var results = parser.parse(args);
+ expect(results['opt'], isNull);
+ expect(results.rest, orderedEquals(args));
+ });
+
+ test('followed by option and value', () {
+ var parser = new ArgParser()..addOption('opt');
+ var args = ['A', '--opt', 'V'];
+
+ var results = parser.parse(args);
+ expect(results['opt'], isNull);
+ expect(results.rest, orderedEquals(args));
+ });
+
+ test('followed by unknown flag', () {
+ var parser = new ArgParser();
+ var args = ['A', '--xflag'];
+ var results = parser.parse(args);
+ expect(results.rest, orderedEquals(args));
+ });
+
+ test('followed by unknown option and value', () {
+ var parser = new ArgParser();
+ var args = ['A', '--xopt', 'V'];
+ var results = parser.parse(args);
+ expect(results.rest, orderedEquals(args));
+ });
+
+ test('followed by command', () {
+ var parser = new ArgParser()..addCommand('com');
+ var args = ['A', 'com'];
+
+ var results = parser.parse(args);
+ expect(results.command, isNull);
+ expect(results.rest, orderedEquals(args));
+ });
+ });
+}
diff --git a/pkg/mdv_observe/test/path_observer_test.dart b/pkg/mdv_observe/test/path_observer_test.dart
new file mode 100644
index 0000000..4222f73
--- /dev/null
+++ b/pkg/mdv_observe/test/path_observer_test.dart
@@ -0,0 +1,249 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:mdv_observe/mdv_observe.dart';
+import 'package:unittest/unittest.dart';
+
+// This file contains code ported from:
+// https://github.com/rafaelw/ChangeSummary/blob/master/tests/test.js
+
+main() {
+ group('PathObserver', observePathTests);
+}
+
+observePath(obj, path) => new PathObserver(obj, path);
+
+sym(x) => new Symbol(x);
+
+toSymbolMap(Map map) {
+ var result = new ObservableMap.linked();
+ map.forEach((key, value) {
+ if (value is Map) value = toSymbolMap(value);
+ result[new Symbol(key)] = value;
+ });
+ return result;
+}
+
+observePathTests() {
+
+ test('Degenerate Values', () {
+ expect(observePath(null, '').value, null);
+ expect(observePath(123, '').value, 123);
+ expect(observePath(123, 'foo.bar.baz').value, null);
+
+ // shouldn't throw:
+ observePath(123, '').values.listen((_) {}).cancel();
+ observePath(null, '').value = null;
+ observePath(123, '').value = 42;
+ observePath(123, 'foo.bar.baz').value = 42;
+
+ var foo = {};
+ expect(observePath(foo, '').value, foo);
+
+ foo = new Object();
+ expect(observePath(foo, '').value, foo);
+
+ expect(observePath(foo, 'a/3!').value, null);
+ });
+
+ test('get value at path ObservableBox', () {
+ var obj = new ObservableBox(new ObservableBox(new ObservableBox(1)));
+
+ expect(observePath(obj, '').value, obj);
+ expect(observePath(obj, 'value').value, obj.value);
+ expect(observePath(obj, 'value.value').value, obj.value.value);
+ expect(observePath(obj, 'value.value.value').value, 1);
+
+ obj.value.value.value = 2;
+ expect(observePath(obj, 'value.value.value').value, 2);
+
+ obj.value.value = new ObservableBox(3);
+ expect(observePath(obj, 'value.value.value').value, 3);
+
+ obj.value = new ObservableBox(4);
+ expect(observePath(obj, 'value.value.value').value, null);
+ expect(observePath(obj, 'value.value').value, 4);
+ });
+
+
+ test('get value at path ObservableMap', () {
+ var obj = toSymbolMap({'a': {'b': {'c': 1}}});
+
+ expect(observePath(obj, '').value, obj);
+ expect(observePath(obj, 'a').value, obj[sym('a')]);
+ expect(observePath(obj, 'a.b').value, obj[sym('a')][sym('b')]);
+ expect(observePath(obj, 'a.b.c').value, 1);
+
+ obj[sym('a')][sym('b')][sym('c')] = 2;
+ expect(observePath(obj, 'a.b.c').value, 2);
+
+ obj[sym('a')][sym('b')] = toSymbolMap({'c': 3});
+ expect(observePath(obj, 'a.b.c').value, 3);
+
+ obj[sym('a')] = toSymbolMap({'b': 4});
+ expect(observePath(obj, 'a.b.c').value, null);
+ expect(observePath(obj, 'a.b').value, 4);
+ });
+
+ test('set value at path', () {
+ var obj = toSymbolMap({});
+ observePath(obj, 'foo').value = 3;
+ expect(obj[sym('foo')], 3);
+
+ var bar = toSymbolMap({ 'baz': 3 });
+ observePath(obj, 'bar').value = bar;
+ expect(obj[sym('bar')], bar);
+
+ observePath(obj, 'bar.baz.bat').value = 'not here';
+ expect(observePath(obj, 'bar.baz.bat').value, null);
+ });
+
+ test('set value back to same', () {
+ var obj = toSymbolMap({});
+ var path = observePath(obj, 'foo');
+ var values = [];
+ path.values.listen((v) { values.add(v); });
+
+ path.value = 3;
+ expect(obj[sym('foo')], 3);
+ expect(path.value, 3);
+
+ observePath(obj, 'foo').value = 2;
+ deliverChangeRecords();
+ expect(path.value, 2);
+ expect(observePath(obj, 'foo').value, 2);
+
+ observePath(obj, 'foo').value = 3;
+ deliverChangeRecords();
+ expect(path.value, 3);
+
+ deliverChangeRecords();
+ expect(values, [2, 3]);
+ });
+
+ test('Observe and Unobserve - Paths', () {
+ var arr = toSymbolMap({});
+
+ arr[sym('foo')] = 'bar';
+ var fooValues = [];
+ var fooPath = observePath(arr, 'foo');
+ var fooSub = fooPath.values.listen((v) {
+ fooValues.add(v);
+ });
+ arr[sym('foo')] = 'baz';
+ arr[sym('bat')] = 'bag';
+ var batValues = [];
+ var batPath = observePath(arr, 'bat');
+ var batSub = batPath.values.listen((v) {
+ batValues.add(v);
+ });
+
+ deliverChangeRecords();
+ expect(fooValues, ['baz']);
+ expect(batValues, []);
+
+ arr[sym('foo')] = 'bar';
+ fooSub.cancel();
+ arr[sym('bat')] = 'boo';
+ batSub.cancel();
+ arr[sym('bat')] = 'boot';
+
+ deliverChangeRecords();
+ expect(fooValues, ['baz']);
+ expect(batValues, []);
+ });
+
+ test('Path Value With Indices', () {
+ var model = toObservable([]);
+ observePath(model, '0').values.listen(expectAsync1((v) {
+ expect(v, 123);
+ }));
+ model.add(123);
+ });
+
+ test('Path Observation', () {
+ var model = new TestModel(const Symbol('a'),
+ new TestModel(const Symbol('b'),
+ new TestModel(const Symbol('c'), 'hello, world')));
+
+ var path = observePath(model, 'a.b.c');
+ var lastValue = null;
+ var sub = path.values.listen((v) { lastValue = v; });
+
+ model.value.value.value = 'hello, mom';
+
+ expect(lastValue, null);
+ deliverChangeRecords();
+ expect(lastValue, 'hello, mom');
+
+ model.value.value = new TestModel(const Symbol('c'), 'hello, dad');
+ deliverChangeRecords();
+ expect(lastValue, 'hello, dad');
+
+ model.value = new TestModel(const Symbol('b'),
+ new TestModel(const Symbol('c'), 'hello, you'));
+ deliverChangeRecords();
+ expect(lastValue, 'hello, you');
+
+ model.value.value = 1;
+ deliverChangeRecords();
+ expect(lastValue, null);
+
+ // Stop observing
+ sub.cancel();
+
+ model.value.value = new TestModel(const Symbol('c'),
+ 'hello, back again -- but not observing');
+ deliverChangeRecords();
+ expect(lastValue, null);
+
+ // Resume observing
+ sub = path.values.listen((v) { lastValue = v; });
+
+ model.value.value.value = 'hello. Back for reals';
+ deliverChangeRecords();
+ expect(lastValue, 'hello. Back for reals');
+ });
+
+ test('observe map', () {
+ var model = toSymbolMap({'a': 1});
+ var path = observePath(model, 'a');
+
+ var values = [path.value];
+ var sub = path.values.listen((v) { values.add(v); });
+ expect(values, [1]);
+
+ model[sym('a')] = 2;
+ deliverChangeRecords();
+ expect(values, [1, 2]);
+
+ sub.cancel();
+ model[sym('a')] = 3;
+ deliverChangeRecords();
+ expect(values, [1, 2]);
+ });
+}
+
+class TestModel extends ObservableBase {
+ final Symbol fieldName;
+ var _value;
+
+ TestModel(this.fieldName, [initialValue]) : _value = initialValue;
+
+ get value => _value;
+
+ void set value(newValue) {
+ _value = notifyPropertyChange(fieldName, _value, newValue);
+ }
+
+ getValueWorkaround(key) {
+ if (key == fieldName) return value;
+ return null;
+ }
+ void setValueWorkaround(key, newValue) {
+ if (key == fieldName) value = newValue;
+ }
+
+ toString() => '#<$runtimeType $fieldName: $_value>';
+}
diff --git a/runtime/vm/flow_graph_builder.cc b/runtime/vm/flow_graph_builder.cc
index c8ae244..ed23d28 100644
--- a/runtime/vm/flow_graph_builder.cc
+++ b/runtime/vm/flow_graph_builder.cc
@@ -1705,33 +1705,27 @@
node->increment()->Visit(&for_increment);
// Join the loop body and increment and then tie the loop.
- JoinEntryInstr* join = node->label()->join_for_continue();
- if ((join != NULL) || for_body.is_open()) {
- JoinEntryInstr* loop_start =
+ JoinEntryInstr* continue_join = node->label()->join_for_continue();
+ if ((continue_join != NULL) || for_body.is_open()) {
+ JoinEntryInstr* loop_entry =
new JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
- if (join != NULL) {
- if (for_body.is_open()) for_body.Goto(join);
- AppendFragment(join, for_increment);
- for_increment.Goto(loop_start);
+ if (continue_join != NULL) {
+ if (for_body.is_open()) for_body.Goto(continue_join);
+ Instruction* current = AppendFragment(continue_join, for_increment);
+ current->Goto(loop_entry);
} else {
for_body.Append(for_increment);
- for_body.Goto(loop_start);
+ for_body.Goto(loop_entry);
}
- Goto(loop_start);
- exit_ = loop_start;
+ Goto(loop_entry);
+ exit_ = loop_entry;
AddInstruction(new CheckStackOverflowInstr(node->token_pos(), true));
}
if (node->condition() == NULL) {
// Endless loop, no test.
- JoinEntryInstr* body_entry =
- new JoinEntryInstr(owner()->AllocateBlockId(), owner()->try_index());
- AppendFragment(body_entry, for_body);
- Goto(body_entry);
- if (node->label()->join_for_break() != NULL) {
- // Control flow of ForLoop continues into join_for_break.
- exit_ = node->label()->join_for_break();
- }
+ Append(for_body);
+ exit_ = node->label()->join_for_break(); // May be NULL.
} else {
TestGraphVisitor for_test(owner(),
temp_index(),
diff --git a/sdk/lib/_internal/compiler/implementation/types/element_type_mask.dart b/sdk/lib/_internal/compiler/implementation/types/element_type_mask.dart
new file mode 100644
index 0000000..0ebb9f0
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/types/element_type_mask.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2013, 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 types;
+
+/**
+ * A [TypeMask] specific to an element: the return type for a
+ * function, or the type for a field.
+ */
+class ElementTypeMask extends ForwardingTypeMask {
+ final Element element;
+ // Callback function to fetch the actual inferred type of the
+ // element. It is used when a user wants to know about the type this
+ // [ForwardingTypeMask] forwards to.
+ final Function fetchForwardTo;
+ final bool isNullable;
+
+ ElementTypeMask(
+ this.fetchForwardTo, this.element, {this.isNullable: true});
+
+ bool get isElement => true;
+
+ TypeMask get forwardTo {
+ TypeMask forward = fetchForwardTo(element);
+ return isNullable ? forward.nullable() : forward.nonNullable();
+ }
+
+ bool operator==(other) {
+ if (other is! ElementTypeMask) return false;
+ return element == other.element && isNullable == other.isNullable;
+ }
+
+ bool equalsDisregardNull(other) {
+ if (other is! ElementTypeMask) return false;
+ return element == other.element;
+ }
+
+ TypeMask nullable() {
+ return isNullable
+ ? this
+ : new ElementTypeMask(fetchForwardTo, element, isNullable: true);
+ }
+
+ TypeMask nonNullable() {
+ return isNullable
+ ? new ElementTypeMask(fetchForwardTo, element, isNullable: false)
+ : this;
+ }
+
+ String toString() {
+ return 'Type for element $element';
+ }
+}
diff --git a/sdk/lib/_internal/compiler/implementation/types/forwarding_type_mask.dart b/sdk/lib/_internal/compiler/implementation/types/forwarding_type_mask.dart
new file mode 100644
index 0000000..2741a99
--- /dev/null
+++ b/sdk/lib/_internal/compiler/implementation/types/forwarding_type_mask.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2013, 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 types;
+
+/**
+ * A type mask that wraps an other one, and delecate all its
+ * implementation methods to it.
+ */
+abstract class ForwardingTypeMask implements TypeMask {
+
+ TypeMask get forwardTo;
+
+ ForwardingTypeMask();
+
+ bool get isEmpty => forwardTo.isEmpty;
+ bool get isNullable => forwardTo.isNullable;
+ bool get isExact => forwardTo.isExact;
+
+ bool get isUnion => false;
+ bool get isContainer => false;
+ bool get isForwarding => true;
+ bool get isElement => false;
+
+ bool containsOnlyInt(Compiler compiler) {
+ return forwardTo.containsOnlyInt(compiler);
+ }
+
+ bool containsOnlyDouble(Compiler compiler) {
+ return forwardTo.containsOnlyDouble(compiler);
+ }
+
+ bool containsOnlyNum(Compiler compiler) {
+ return forwardTo.containsOnlyNum(compiler);
+ }
+
+ bool containsOnlyNull(Compiler compiler) {
+ return forwardTo.containsOnlyNull(compiler);
+ }
+
+ bool containsOnlyBool(Compiler compiler) {
+ return forwardTo.containsOnlyBool(compiler);
+ }
+
+ bool containsOnlyString(Compiler compiler) {
+ return forwardTo.containsOnlyString(compiler);
+ }
+
+ bool containsOnly(ClassElement element) {
+ return forwardTo.containsOnly(element);
+ }
+
+ bool satisfies(ClassElement cls, Compiler compiler) {
+ return forwardTo.satisfies(cls, compiler);
+ }
+
+ bool contains(DartType type, Compiler compiler) {
+ return forwardTo.contains(type, compiler);
+ }
+
+ bool containsAll(Compiler compiler) {
+ return forwardTo.containsAll(compiler);
+ }
+
+ ClassElement singleClass(Compiler compiler) {
+ return forwardTo.singleClass(compiler);
+ }
+
+ Iterable<ClassElement> containedClasses(Compiler compiler) {
+ return forwardTo.containedClasses(compiler);
+ }
+
+ TypeMask union(other, Compiler compiler) {
+ if (this == other) {
+ return this;
+ } else if (equalsDisregardNull(other)) {
+ return other.isNullable ? other : this;
+ } else if (other.isEmpty) {
+ return other.isNullable ? this.nullable() : this;
+ }
+ return forwardTo.union(other, compiler);
+ }
+
+ TypeMask intersection(TypeMask other, Compiler compiler) {
+ return forwardTo.intersection(other, compiler);
+ }
+
+ bool willHit(Selector selector, Compiler compiler) {
+ return forwardTo.willHit(selector, compiler);
+ }
+
+ bool canHit(Element element, Selector selector, Compiler compiler) {
+ return forwardTo.canHit(element, selector, compiler);
+ }
+
+ Element locateSingleElement(Selector selector, Compiler compiler) {
+ return forwardTo.locateSingleElement(selector, compiler);
+ }
+
+ TypeMask simplify(Compiler compiler) => forwardTo.simplify(compiler);
+
+ bool equalsDisregardNull(other);
+}
diff --git a/sdk/lib/_internal/lib/js_names.dart b/sdk/lib/_internal/lib/js_names.dart
new file mode 100644
index 0000000..5e0fc89
--- /dev/null
+++ b/sdk/lib/_internal/lib/js_names.dart
@@ -0,0 +1,64 @@
+// Copyright (c) 2013, 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.
+
+library dart._js_names;
+
+import 'dart:_foreign_helper' show JS;
+
+/// No-op method that is called to inform the compiler that unmangled named
+/// must be preserved.
+preserveNames() {}
+
+/// A map from mangled names to "reflective" names, that is, unmangled names
+/// with some additional information, such as, number of required arguments.
+/// This map is for mangled names used as instance members.
+final Map<String, String> mangledNames =
+ computeMangledNames(JS('', 'init.mangledNames'));
+
+/// A map from "reflective" names to mangled names (the reverse of
+/// [mangledNames]).
+final Map<String, String> reflectiveNames =
+ computeReflectiveNames(mangledNames);
+
+/// A map from mangled names to "reflective" names (see [mangledNames]). This
+/// map is for globals, that is, static and top-level members.
+final Map<String, String> mangledGlobalNames =
+ computeMangledNames(JS('', 'init.mangledGlobalNames'));
+
+/// A map from "reflective" names to mangled names (the reverse of
+/// [mangledGlobalNames]).
+final Map<String, String> reflectiveGlobalNames =
+ computeReflectiveNames(mangledGlobalNames);
+
+/// [jsMangledNames] is a JavaScript object literal. The keys are the mangled
+/// names, and the values are the "reflective" names.
+Map<String, String> computeMangledNames(jsMangledNames) {
+ preserveNames();
+ var keys = extractKeys(jsMangledNames);
+ var result = <String, String>{};
+ for (String key in keys) {
+ result[key] = JS('String', '#[#]', jsMangledNames, key);
+ }
+ return result;
+}
+
+Map<String, String> computeReflectiveNames(Map<String, String> map) {
+ preserveNames();
+ var result = <String, String>{};
+ map.forEach((String mangledName, String reflectiveName) {
+ result[reflectiveName] = mangledName;
+ });
+ return result;
+}
+
+List extractKeys(victim) {
+ return JS('List', '''
+(function(victim, hasOwnProperty) {
+ var result = [];
+ for (var key in victim) {
+ if (hasOwnProperty.call(victim, key)) result.push(key);
+ }
+ return result;
+})(#, Object.prototype.hasOwnProperty)''', victim);
+}
diff --git a/sdk/lib/mdv_observe_impl/path_observer.dart b/sdk/lib/mdv_observe_impl/path_observer.dart
new file mode 100644
index 0000000..5d3fae4
--- /dev/null
+++ b/sdk/lib/mdv_observe_impl/path_observer.dart
@@ -0,0 +1,287 @@
+// Copyright (c) 2013, 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 dart.mdv_observe_impl;
+
+// This code is inspired by ChangeSummary:
+// https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
+// ...which underlies MDV. Since we don't need the functionality of
+// ChangeSummary, we just implement what we need for data bindings.
+// This allows our implementation to be much simpler.
+
+// TODO(jmesserly): should we make these types stronger, and require
+// Observable objects? Currently, it is fine to say something like:
+// var path = new PathObserver(123, '');
+// print(path.value); // "123"
+//
+// Furthermore this degenerate case is allowed:
+// var path = new PathObserver(123, 'foo.bar.baz.qux');
+// print(path.value); // "null"
+//
+// Here we see that any invalid (i.e. not Observable) value will break the
+// path chain without producing an error or exception.
+//
+// Now the real question: should we do this? For the former case, the behavior
+// is correct but we could chose to handle it in the dart:html bindings layer.
+// For the latter case, it might be better to throw an error so users can find
+// the problem.
+
+
+/**
+ * A data-bound path starting from a view-model or model object, for example
+ * `foo.bar.baz`.
+ *
+ * When the [values] stream is being listened to, this will observe changes to
+ * the object and any intermediate object along the path, and send [values]
+ * accordingly. When all listeners are unregistered it will stop observing
+ * the objects.
+ *
+ * This class is used to implement [Node.bind] and similar functionality.
+ */
+// TODO(jmesserly): find a better home for this type.
+class PathObserver {
+ /** The object being observed. */
+ final object;
+
+ /** The path string. */
+ final String path;
+
+ /** True if the path is valid, otherwise false. */
+ final bool _isValid;
+
+ // TODO(jmesserly): same issue here as ObservableMixin: is there an easier
+ // way to get a broadcast stream?
+ StreamController _values;
+ Stream _valueStream;
+
+ _PropertyObserver _observer, _lastObserver;
+
+ Object _lastValue;
+ bool _scheduled = false;
+
+ /**
+ * Observes [path] on [object] for changes. This returns an object that can be
+ * used to get the changes and get/set the value at this path.
+ * See [PathObserver.values] and [PathObserver.value].
+ */
+ PathObserver(this.object, String path)
+ : path = path, _isValid = _isPathValid(path) {
+
+ // TODO(jmesserly): if the path is empty, or the object is! Observable, we
+ // can optimize the PathObserver to be more lightweight.
+
+ _values = new StreamController.broadcast(sync: true,
+ onListen: _observe,
+ onCancel: _unobserve);
+
+ if (_isValid) {
+ var segments = [];
+ for (var segment in path.trim().split('.')) {
+ if (segment == '') continue;
+ var index = int.parse(segment, onError: (_) {});
+ segments.add(index != null ? index : new Symbol(segment));
+ }
+
+ // Create the property observer linked list.
+ // Note that the structure of a path can't change after it is initially
+ // constructed, even though the objects along the path can change.
+ for (int i = segments.length - 1; i >= 0; i--) {
+ _observer = new _PropertyObserver(this, segments[i], _observer);
+ if (_lastObserver == null) _lastObserver = _observer;
+ }
+ }
+ }
+
+ // TODO(jmesserly): we could try adding the first value to the stream, but
+ // that delivers the first record async.
+ /**
+ * Listens to the stream, and invokes the [callback] immediately with the
+ * current [value]. This is useful for bindings, which want to be up-to-date
+ * immediately.
+ */
+ StreamSubscription bindSync(void callback(value)) {
+ var result = values.listen(callback);
+ callback(value);
+ return result;
+ }
+
+ // TODO(jmesserly): should this be a change record with the old value?
+ // TODO(jmesserly): should this be a broadcast stream? We only need
+ // single-subscription in the bindings system, so single sub saves overhead.
+ /**
+ * Gets the stream of values that were observed at this path.
+ * This returns a single-subscription stream.
+ */
+ Stream get values => _values.stream;
+
+ /** Force synchronous delivery of [values]. */
+ void _deliverValues() {
+ _scheduled = false;
+
+ var newValue = value;
+ if (!identical(_lastValue, newValue)) {
+ _values.add(newValue);
+ _lastValue = newValue;
+ }
+ }
+
+ void _observe() {
+ if (_observer != null) {
+ _lastValue = value;
+ _observer.observe();
+ }
+ }
+
+ void _unobserve() {
+ if (_observer != null) _observer.unobserve();
+ }
+
+ void _notifyChange() {
+ if (_scheduled) return;
+ _scheduled = true;
+
+ // TODO(jmesserly): should we have a guarenteed order with respect to other
+ // paths? If so, we could implement this fairly easily by sorting instances
+ // of this class by birth order before delivery.
+ queueChangeRecords(_deliverValues);
+ }
+
+ /** Gets the last reported value at this path. */
+ get value {
+ if (!_isValid) return null;
+ if (_observer == null) return object;
+ _observer.ensureValue(object);
+ return _lastObserver.value;
+ }
+
+ /** Sets the value at this path. */
+ void set value(Object value) {
+ // TODO(jmesserly): throw if property cannot be set?
+ // MDV seems tolerant of these error.
+ if (_observer == null || !_isValid) return;
+ _observer.ensureValue(object);
+ var last = _lastObserver;
+ if (_setObjectProperty(last._object, last._property, value)) {
+ // Technically, this would get updated asynchronously via a change record.
+ // However, it is nice if calling the getter will yield the same value
+ // that was just set. So we use this opportunity to update our cache.
+ last.value = value;
+ }
+ }
+}
+
+// TODO(jmesserly): these should go away in favor of mirrors!
+_getObjectProperty(object, property) {
+ if (object is List && property is int) {
+ if (property >= 0 && property < object.length) {
+ return object[property];
+ } else {
+ return null;
+ }
+ }
+
+ // TODO(jmesserly): what about length?
+ if (object is Map) return object[property];
+
+ if (object is Observable) return object.getValueWorkaround(property);
+
+ return null;
+}
+
+bool _setObjectProperty(object, property, value) {
+ if (object is List && property is int) {
+ object[property] = value;
+ } else if (object is Map) {
+ object[property] = value;
+ } else if (object is Observable) {
+ (object as Observable).setValueWorkaround(property, value);
+ } else {
+ return false;
+ }
+ return true;
+}
+
+
+class _PropertyObserver {
+ final PathObserver _path;
+ final _property;
+ final _PropertyObserver _next;
+
+ // TODO(jmesserly): would be nice not to store both of these.
+ Object _object;
+ Object _value;
+ StreamSubscription _sub;
+
+ _PropertyObserver(this._path, this._property, this._next);
+
+ get value => _value;
+
+ void set value(Object newValue) {
+ _value = newValue;
+ if (_next != null) {
+ if (_sub != null) _next.unobserve();
+ _next.ensureValue(_value);
+ if (_sub != null) _next.observe();
+ }
+ }
+
+ void ensureValue(object) {
+ // If we're observing, values should be up to date already.
+ if (_sub != null) return;
+
+ _object = object;
+ value = _getObjectProperty(object, _property);
+ }
+
+ void observe() {
+ if (_object is Observable) {
+ assert(_sub == null);
+ _sub = (_object as Observable).changes.listen(_onChange);
+ }
+ if (_next != null) _next.observe();
+ }
+
+ void unobserve() {
+ if (_sub == null) return;
+
+ _sub.cancel();
+ _sub = null;
+ if (_next != null) _next.unobserve();
+ }
+
+ void _onChange(List<ChangeRecord> changes) {
+ for (var change in changes) {
+ // TODO(jmesserly): what to do about "new Symbol" here?
+ // Ideally this would only preserve names if the user has opted in to
+ // them being preserved.
+ // TODO(jmesserly): should we drop observable maps with String keys?
+ // If so then we only need one check here.
+ if (change.changes(_property)) {
+ value = _getObjectProperty(_object, _property);
+ _path._notifyChange();
+ return;
+ }
+ }
+ }
+}
+
+// From: https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
+
+const _pathIndentPart = r'[$a-z0-9_]+[$a-z0-9_\d]*';
+final _pathRegExp = new RegExp('^'
+ '(?:#?' + _pathIndentPart + ')?'
+ '(?:'
+ '(?:\\.' + _pathIndentPart + ')'
+ ')*'
+ r'$', caseSensitive: false);
+
+final _spacesRegExp = new RegExp(r'\s');
+
+bool _isPathValid(String s) {
+ s = s.replaceAll(_spacesRegExp, '');
+
+ if (s == '') return true;
+ if (s[0] == '.') return false;
+ return _pathRegExp.hasMatch(s);
+}
diff --git a/tests/compiler/dart2js/list_tracer_node_type_test.dart b/tests/compiler/dart2js/list_tracer_node_type_test.dart
new file mode 100644
index 0000000..e8022e6
--- /dev/null
+++ b/tests/compiler/dart2js/list_tracer_node_type_test.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+import 'compiler_helper.dart';
+
+const String TEST1 = r"""
+main() {
+ var a = [42, null];
+ return a[0] + 42;
+}
+""";
+
+const String TEST2 = r"""
+main() {
+ var a = new List();
+ a.add(42);
+ a.add(null);
+ return a[0] + 42;
+}
+""";
+
+const String TEST3 = r"""
+main() {
+ var a = new List(42);
+ a[0] = 42;
+ return a[0] + 42;
+}
+""";
+
+
+main() {
+ String generated = compileAll(TEST1);
+ // Check that we only do a null check on the receiver for
+ // [: a[0] + 42 :]. We can do a null check because we inferred that
+ // the list is of type int or null.
+ Expect.isFalse(generated.contains('if (typeof t1'));
+ Expect.isTrue(generated.contains('if (t1 == null)'));
+
+ generated = compileAll(TEST2);
+ Expect.isFalse(generated.contains('if (typeof t1'));
+ Expect.isTrue(generated.contains('if (t1 == null)'));
+
+ generated = compileAll(TEST3);
+ Expect.isFalse(generated.contains('if (typeof t1'));
+ Expect.isTrue(generated.contains('if (t1 == null)'));
+}
diff --git a/tests/language/for_test.dart b/tests/language/for_test.dart
index 8576acb..9677399 100644
--- a/tests/language/for_test.dart
+++ b/tests/language/for_test.dart
@@ -31,6 +31,20 @@
}
return i;
}
+
+ static var status;
+ static void f5() {
+ status = 0;
+ for (var stop = false;;) {
+ if (stop) {
+ break;
+ } else {
+ stop = true;
+ continue;
+ }
+ }
+ status = 1;
+ }
}
class ForTest {
@@ -50,6 +64,9 @@
Expect.equals(1, Helper.f4(1));
Expect.equals(6, Helper.f4(6));
Expect.equals(6, Helper.f4(10));
+
+ Helper.f5();
+ Expect.equals(1, Helper.status);
}
}
diff --git a/tests/language/list_in_closure_test.dart b/tests/language/list_in_closure_test.dart
new file mode 100644
index 0000000..3c7bcc3
--- /dev/null
+++ b/tests/language/list_in_closure_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2013, 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.
+
+// Regression test for dart2js that used to see aborting closure
+// bodies as aborting their enclosing element.
+
+import "package:expect/expect.dart";
+
+main() {
+ var c = () { throw 42; };
+ () {
+ // dart2js would not seen this initialization and therefore think
+ // that the argument passed to a is a list of nulls.
+ var a = [42];
+ foo(a);
+ }();
+}
+
+foo(arg) {
+ Expect.isTrue(arg[0] == 42);
+}
diff --git a/tests/lib/mirrors/get_symbol_name_no_such_method_test.dart b/tests/lib/mirrors/get_symbol_name_no_such_method_test.dart
new file mode 100644
index 0000000..7210d98
--- /dev/null
+++ b/tests/lib/mirrors/get_symbol_name_no_such_method_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2013, 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 MirrorSystem.getName works correctly on symbols returned from
+/// Invocation.memberName. This is especially relevant when minifying.
+
+import 'dart:mirrors' show MirrorSystem;
+
+class Foo {
+ String noSuchMethod(Invocation invocation) {
+ return MirrorSystem.getName(invocation.memberName);
+ }
+}
+
+expect(expected, actual) {
+ if (expected != actual) {
+ throw 'Expected: "$expected", but got "$actual"';
+ }
+}
+
+main() {
+ var foo = new Foo();
+ expect('foo', foo.foo);
+ expect('foo', foo.foo());
+ expect('foo', foo.foo(null));
+ // TODO(ahe): Why does the following not work in dart2js/minified.
+ // expect('foo', foo.foo(null, null));
+ expect('foo', foo.foo(a: null, b: null));
+
+ expect('baz', foo.baz);
+ expect('baz', foo.baz());
+ expect('baz', foo.baz(null));
+ // TODO(ahe): Why does the following not work in dart2js/minified.
+ // expect('baz', foo.baz(null, null));
+ expect('baz', foo.baz(a: null, b: null));
+}
diff --git a/tests/standalone/debugger/breakpoint_resolved_test.dart b/tests/standalone/debugger/breakpoint_resolved_test.dart
new file mode 100644
index 0000000..9fef0df
--- /dev/null
+++ b/tests/standalone/debugger/breakpoint_resolved_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2013, 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 "debug_lib.dart";
+
+main() {
+ if (RunScript(testScript)) return;
+
+ bar();
+
+ print("Hello from debuggee");
+}
+
+bar() {
+
+ print("bar");
+}
+
+var testScript = [
+ MatchFrame(0, "main"),
+ SetBreakpoint(10),
+ SetBreakpoint(12),
+ SetBreakpoint(16),
+ Resume(),
+ MatchFrame(0, "main"),
+ Resume(),
+ MatchFrame(0, "bar"),
+ Resume(),
+ MatchFrame(0, "main"),
+ ExpectEvent("breakpointResolved", {"breakpointId": 1}),
+ ExpectEvent("breakpointResolved", {"breakpointId": 2}),
+ ExpectEvent("breakpointResolved", {"breakpointId": 3}),
+ Resume(),
+];
diff --git a/tests/standalone/debugger/tostring_throws_test.dart b/tests/standalone/debugger/tostring_throws_test.dart
new file mode 100644
index 0000000..f25050d
--- /dev/null
+++ b/tests/standalone/debugger/tostring_throws_test.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2013, 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 "debug_lib.dart";
+
+main() {
+ if (RunScript(testScript)) return;
+
+ Foo foo = new Foo();
+
+ print("Hello from debuggee");
+}
+
+class Foo {
+ String toString() {
+ throw 'I always throw';
+ }
+}
+
+var testScript = [
+ MatchFrame(0, "main"),
+ SetBreakpoint(12),
+ Resume(),
+ MatchFrame(0, "main"),
+ MatchLocals({"foo": "#ERROR"}),
+ Resume(),
+];
diff --git a/tools/VERSION b/tools/VERSION
index c06980b..ae9465f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
MAJOR 0
MINOR 5
BUILD 20
-PATCH 2
+PATCH 3