Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(391)

Side by Side Diff: lib/compiler/implementation/resolver.dart

Issue 10854158: Make selector registration in the resolver and code generator more explicit. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Merge from master. Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 interface TreeElements { 5 interface TreeElements {
6 Element operator[](Node node); 6 Element operator[](Node node);
7 Selector getSelector(Send send); 7 Selector getSelector(Send send);
8 Type getType(TypeAnnotation annotation); 8 Type getType(TypeAnnotation annotation);
9 } 9 }
10 10
(...skipping 431 matching lines...) Expand 10 before | Expand all | Expand 10 after
442 error(selector, MessageKind.CANNOT_RESOLVE, [name]); 442 error(selector, MessageKind.CANNOT_RESOLVE, [name]);
443 } else if (target.kind != ElementKind.FIELD) { 443 } else if (target.kind != ElementKind.FIELD) {
444 error(selector, MessageKind.NOT_A_FIELD, [name]); 444 error(selector, MessageKind.NOT_A_FIELD, [name]);
445 } else if (!target.isInstanceMember()) { 445 } else if (!target.isInstanceMember()) {
446 error(selector, MessageKind.INIT_STATIC_FIELD, [name]); 446 error(selector, MessageKind.INIT_STATIC_FIELD, [name]);
447 } 447 }
448 } else { 448 } else {
449 error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); 449 error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER);
450 } 450 }
451 visitor.useElement(init, target); 451 visitor.useElement(init, target);
452 visitor.world.registerStaticUse(target);
452 // Check for duplicate initializers. 453 // Check for duplicate initializers.
453 if (initialized.containsKey(name)) { 454 if (initialized.containsKey(name)) {
454 error(init, MessageKind.DUPLICATE_INITIALIZER, [name]); 455 error(init, MessageKind.DUPLICATE_INITIALIZER, [name]);
455 warning(initialized[name], MessageKind.ALREADY_INITIALIZED, [name]); 456 warning(initialized[name], MessageKind.ALREADY_INITIALIZED, [name]);
456 } 457 }
457 initialized[name] = init; 458 initialized[name] = init;
458 // Resolve initializing value. 459 // Resolve initializing value.
459 visitor.visitInStaticContext(init.arguments.head); 460 visitor.visitInStaticContext(init.arguments.head);
460 } 461 }
461 462
462 Element resolveSuperOrThisForSend(FunctionElement constructor, 463 Element resolveSuperOrThisForSend(FunctionElement constructor,
463 FunctionExpression functionNode, 464 FunctionExpression functionNode,
464 Send call) { 465 Send call) {
465 // Resolve the selector and the arguments. 466 // Resolve the selector and the arguments.
466 ResolverTask resolver = visitor.compiler.resolver; 467 ResolverTask resolver = visitor.compiler.resolver;
467 visitor.inStaticContext(() { 468 visitor.inStaticContext(() {
468 visitor.resolveSelector(call); 469 visitor.resolveSelector(call);
469 visitor.visitArguments(call.argumentsNode); 470 visitor.resolveArguments(call.argumentsNode);
470 }); 471 });
471 Selector selector = visitor.mapping.getSelector(call); 472 Selector selector = visitor.mapping.getSelector(call);
472 bool isSuperCall = Initializers.isSuperConstructorCall(call); 473 bool isSuperCall = Initializers.isSuperConstructorCall(call);
473 SourceString constructorName = resolver.getConstructorName(call); 474 SourceString constructorName = resolver.getConstructorName(call);
474 Element result = resolveSuperOrThis( 475 Element result = resolveSuperOrThis(
475 constructor, isSuperCall, false, constructorName, selector, call); 476 constructor, isSuperCall, false, constructorName, selector, call);
476 visitor.useElement(call, result); 477 visitor.useElement(call, result);
478 visitor.world.registerStaticUse(result);
477 return result; 479 return result;
478 } 480 }
479 481
480 void resolveImplicitSuperConstructorSend(FunctionElement constructor, 482 void resolveImplicitSuperConstructorSend(FunctionElement constructor,
481 FunctionExpression functionNode) { 483 FunctionExpression functionNode) {
482 // If the class has a super resolve the implicit super call. 484 // If the class has a super resolve the implicit super call.
483 ClassElement classElement = constructor.getEnclosingClass(); 485 ClassElement classElement = constructor.getEnclosingClass();
484 ClassElement superClass = classElement.superclass; 486 ClassElement superClass = classElement.superclass;
485 if (classElement != visitor.compiler.objectClass) { 487 if (classElement != visitor.compiler.objectClass) {
486 assert(superClass !== null); 488 assert(superClass !== null);
(...skipping 469 matching lines...) Expand 10 before | Expand all | Expand 10 after
956 if (existing != element) { 958 if (existing != element) {
957 error(node, MessageKind.DUPLICATE_DEFINITION, [node]); 959 error(node, MessageKind.DUPLICATE_DEFINITION, [node]);
958 } 960 }
959 } 961 }
960 return element; 962 return element;
961 } 963 }
962 964
963 Element useElement(Node node, Element element) { 965 Element useElement(Node node, Element element) {
964 if (element === null) return null; 966 if (element === null) return null;
965 mapping[node] = element; 967 mapping[node] = element;
966 if (element.isTopLevel() || element.isMember()) {
967 if (element.isInstanceMember()) {
968 var send = node.asSend();
969 if (send !== null) {
970 Selector selector = mapping.getSelector(send);
971 if (selector !== null) {
972 world.registerDynamicInvocation(element.name, selector);
973 }
974 }
975 } else {
976 if (!element.isPrefix() &&
977 !element.isClass() &&
978 !element.isTypedef() &&
979 !element.isTypeVariable()) {
980 world.registerStaticUse(element);
981 }
982 }
983 }
984 return element; 968 return element;
ahe 2012/08/15 14:32:21 These two lines can be merged. And yay!
kasperl 2012/08/16 10:35:47 Done.
985 } 969 }
986 970
987 Type useType(TypeAnnotation annotation, Type type) { 971 Type useType(TypeAnnotation annotation, Type type) {
988 if (type !== null) { 972 if (type !== null) {
989 mapping.setType(annotation, type); 973 mapping.setType(annotation, type);
990 useElement(annotation, type.element); 974 useElement(annotation, type.element);
991 } 975 }
992 return type; 976 return type;
993 } 977 }
994 978
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after
1178 if (node === null) { 1162 if (node === null) {
1179 // node is of the form !Type. 1163 // node is of the form !Type.
1180 node = argument.asSend().receiver.asTypeAnnotation(); 1164 node = argument.asSend().receiver.asTypeAnnotation();
1181 if (node === null) compiler.cancel("malformed send"); 1165 if (node === null) compiler.cancel("malformed send");
1182 } 1166 }
1183 return resolveTypeRequired(node); 1167 return resolveTypeRequired(node);
1184 } 1168 }
1185 1169
1186 static Selector computeSendSelector(Send node, LibraryElement library) { 1170 static Selector computeSendSelector(Send node, LibraryElement library) {
1187 // First determine if this is part of an assignment. 1171 // First determine if this is part of an assignment.
1188 bool isSet = node is SendSet; 1172 bool isSet = node.asSendSet() !== null;
1189 1173
1190 if (node.isIndex) { 1174 if (node.isIndex) {
1191 return isSet ? new Selector.indexSet() : new Selector.index(); 1175 return isSet ? new Selector.indexSet() : new Selector.index();
1192 } 1176 }
1193 1177
1194 if (node.isOperator) { 1178 if (node.isOperator) {
1195 SourceString source = node.selector.asOperator().source; 1179 SourceString source = node.selector.asOperator().source;
1196 switch (source.stringValue) { 1180 String string = source.stringValue;
1197 case '!' : case '&&' : case '||': 1181 if (string === '!' || string === '&&' || string == '||' ||
1198 case 'is' : case 'as' : 1182 string === 'is' || string === 'as' ||
1199 case '===': case '!==': 1183 string === '===' || string === '!==' ||
1200 case '>>>': 1184 string === '>>>') {
1201 return null; 1185 return null;
1202 } 1186 }
1203 return node.arguments.isEmpty() 1187 return node.arguments.isEmpty()
1204 ? new Selector.unaryOperator(source) 1188 ? new Selector.unaryOperator(source)
1205 : new Selector.binaryOperator(source); 1189 : new Selector.binaryOperator(source);
1206 } 1190 }
1207 1191
1208 Identifier identifier = node.selector.asIdentifier(); 1192 Identifier identifier = node.selector.asIdentifier();
1209 if (node.isPropertyAccess) { 1193 if (node.isPropertyAccess) {
1210 assert(!isSet); 1194 assert(!isSet);
1211 return new Selector.getter(identifier.source, library); 1195 return new Selector.getter(identifier.source, library);
1212 } else if (isSet) { 1196 } else if (isSet) {
1213 return new Selector.setter(identifier.source, library); 1197 return new Selector.setter(identifier.source, library);
1214 } 1198 }
1215 1199
1216 // Compute the arity and the list of named arguments. 1200 // Compute the arity and the list of named arguments.
1217 int arity = 0; 1201 int arity = 0;
1218 List<SourceString> named = <SourceString>[]; 1202 List<SourceString> named = <SourceString>[];
1219 for (Link<Node> link = node.argumentsNode.nodes; 1203 for (Link<Node> link = node.argumentsNode.nodes;
1220 !link.isEmpty(); 1204 !link.isEmpty();
1221 link = link.tail) { 1205 link = link.tail) {
1222 Expression argument = link.head; 1206 Expression argument = link.head;
1223 if (argument.asNamedArgument() != null) { 1207 NamedArgument namedArgument = argument.asNamedArgument();
1224 named.add((argument as NamedArgument).name.source); 1208 if (namedArgument !== null) {
1209 named.add(namedArgument.name.source);
1225 } 1210 }
1226 arity++; 1211 arity++;
1227 } 1212 }
1228 1213
1229 // If we're invoking a closure, we do not have an identifier. In 1214 // If we're invoking a closure, we do not have an identifier.
1230 // that case, we create a wildcard selector that can call any 1215 return (identifier === null)
1231 // method that may have been closurized. 1216 ? new Selector.callClosure(arity, named)
1232 return (identifier == null)
1233 ? new Selector.callAny(arity, named)
1234 : new Selector.call(identifier.source, library, arity, named); 1217 : new Selector.call(identifier.source, library, arity, named);
1235 } 1218 }
1236 1219
1237 Selector resolveSelector(Send node) { 1220 Selector resolveSelector(Send node) {
1238 LibraryElement library = enclosingElement.getLibrary(); 1221 LibraryElement library = enclosingElement.getLibrary();
1239 Selector selector = computeSendSelector(node, library); 1222 Selector selector = computeSendSelector(node, library);
1240 if (selector != null) mapping.setSelector(node, selector); 1223 if (selector != null) mapping.setSelector(node, selector);
1241 return selector; 1224 return selector;
1242 } 1225 }
1243 1226
1244 void visitArguments(NodeList list) { 1227 void resolveArguments(NodeList list) {
1228 if (list === null) return;
1245 bool seenNamedArgument = false; 1229 bool seenNamedArgument = false;
1246 for (Link<Node> link = list.nodes; !link.isEmpty(); link = link.tail) { 1230 for (Link<Node> link = list.nodes; !link.isEmpty(); link = link.tail) {
1247 Expression argument = link.head; 1231 Expression argument = link.head;
1248 visit(argument); 1232 visit(argument);
1249 if (argument.asNamedArgument() != null) { 1233 if (argument.asNamedArgument() != null) {
1250 seenNamedArgument = true; 1234 seenNamedArgument = true;
1251 } else if (seenNamedArgument) { 1235 } else if (seenNamedArgument) {
1252 error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED); 1236 error(argument, MessageKind.INVALID_ARGUMENT_AFTER_NAMED);
1253 } 1237 }
1254 } 1238 }
1255 } 1239 }
1256 1240
1257 visitSend(Send node) { 1241 visitSend(Send node) {
1258 Element target = resolveSend(node); 1242 Element target = resolveSend(node);
1259 if (node.isOperator) {
1260 Operator op = node.selector.asOperator();
1261 if (op.source.stringValue === 'is' || op.source.stringValue === 'as') {
1262 resolveTypeTest(node.arguments.head);
1263 assert(node.arguments.tail.isEmpty());
1264 } else if (node.arguments.isEmpty()) {
1265 assert(op.token.kind !== PLUS_TOKEN);
1266 } else {
1267 visit(node.argumentsNode);
1268 }
1269 } else if (node.isIndex) {
1270 visit(node.argumentsNode);
1271 assert(node.arguments.tail.isEmpty());
1272 } else if (node.isPropertyAccess) {
1273 // Nothing to do here.
1274 } else {
1275 visitArguments(node.argumentsNode);
1276 }
1277
1278 if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) { 1243 if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) {
1279 AbstractFieldElement field = target; 1244 AbstractFieldElement field = target;
1280 target = field.getter; 1245 target = field.getter;
1281 } 1246 }
1247
1248 bool resolvedArguments = false;
1249 if (node.isOperator) {
1250 String operatorString = node.selector.asOperator().source.stringValue;
1251 if (operatorString === 'is' || operatorString === 'as') {
1252 assert(node.arguments.tail.isEmpty());
1253 resolveTypeTest(node.arguments.head);
1254 resolvedArguments = true;
1255 }
1256 }
1257
1258 if (!resolvedArguments) {
1259 resolveArguments(node.argumentsNode);
1260 }
1261
1262 // If the selector is null, it means that we will not be generating
1263 // code for this as a send.
1264 Selector selector = mapping.getSelector(node);
1265 if (selector === null) return;
1266
1267 // If we don't know what we're calling or if we are calling a getter,
1268 // we need to register that fact that we may be calling a closure
1269 // with the same arguments.
1282 if (node.isCall && 1270 if (node.isCall &&
1283 (target === null || 1271 (target === null ||
1284 target.isGetter() || 1272 target.isGetter() ||
1285 Elements.isClosureSend(node, target))) { 1273 Elements.isClosureSend(node, target))) {
1286 Selector selector = mapping.getSelector(node); 1274 Selector call = new Selector.callClosureFrom(selector);
1287 Selector call = new Selector.call( 1275 world.registerDynamicInvocation(call.name, call);
1288 compiler.namer.CLOSURE_INVOCATION_NAME,
1289 selector.library,
1290 selector.argumentCount,
1291 selector.namedArguments);
1292 world.registerDynamicInvocation(compiler.namer.CLOSURE_INVOCATION_NAME,
1293 call);
1294 } 1276 }
1277
1295 // TODO(ngeoffray): We should do the check in 1278 // TODO(ngeoffray): We should do the check in
1296 // visitExpressionStatement instead. 1279 // visitExpressionStatement instead.
1297 if (target === compiler.assertMethod && !node.isCall) { 1280 if (target === compiler.assertMethod && !node.isCall) {
1298 // We can only use assert by calling it. 1281 // We can only use assert by calling it.
1299 if (!inInstanceContext) { 1282 if (!inInstanceContext) {
1300 error(node, MessageKind.MISSING_ARGUMENTS_TO_ASSERT, [node]); 1283 error(node, MessageKind.MISSING_ARGUMENTS_TO_ASSERT, [node]);
1301 } 1284 }
1302 target = null; 1285 target = null;
1303 } 1286 }
1287
1304 // TODO(ngeoffray): Warn if target is null and the send is 1288 // TODO(ngeoffray): Warn if target is null and the send is
1305 // unqualified. 1289 // unqualified.
1306 useElement(node, target); 1290 useElement(node, target);
1307 if (target === null) registerDynamicSend(node); 1291 registerSend(node, selector, target);
1308 if (node.isPropertyAccess) return target; 1292 return node.isPropertyAccess ? target : null;
1309 } 1293 }
1310 1294
1311 visitSendSet(SendSet node) { 1295 visitSendSet(SendSet node) {
1312 Element target = resolveSend(node); 1296 Element target = resolveSend(node);
1313 Element setter = null; 1297 Element setter = target;
1314 Element getter = null; 1298 Element getter = target;
1315 if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) { 1299 if (target != null && target.kind == ElementKind.ABSTRACT_FIELD) {
1316 AbstractFieldElement field = target; 1300 AbstractFieldElement field = target;
1317 setter = field.setter; 1301 setter = field.setter;
1318 getter = field.getter; 1302 getter = field.getter;
1319 } else {
1320 setter = target;
1321 getter = target;
1322 } 1303 }
1304
1305 visit(node.argumentsNode);
1306
1323 // TODO(ngeoffray): Check if the target can be assigned. 1307 // TODO(ngeoffray): Check if the target can be assigned.
1324 Identifier identifier = node.assignmentOperator;
1325 bool compoundAssignment = identifier.source.stringValue !== '=';
1326 if (compoundAssignment) {
1327 useElement(node.selector, getter);
1328 }
1329 visit(node.argumentsNode);
1330 // TODO(ngeoffray): Warn if target is null and the send is 1308 // TODO(ngeoffray): Warn if target is null and the send is
1331 // unqualified. 1309 // unqualified.
1332 registerDynamicSend(node); 1310
1311 Selector selector = mapping.getSelector(node);
1312 Identifier identifier = node.assignmentOperator;
1313 bool isCompound = identifier.source.stringValue !== '=';
floitsch 2012/08/15 14:14:04 this should be a predicate on the SendSet node. No
1314 if (isCompound) {
1315 useElement(node.selector, getter);
1316 registerSend(node, new Selector.getterFrom(selector), getter);
1317 }
1318
1319 registerSend(node, selector, setter);
1333 return useElement(node, setter); 1320 return useElement(node, setter);
1334 } 1321 }
1335 1322
1336 registerDynamicSend(Send node) { 1323 void registerSend(Node node, Selector selector, Element target) {
1337 Identifier id = node.selector.asIdentifier(); 1324 if (target === null || target.isInstanceMember()) {
1338 if (id === null) return; 1325 if (selector.isGetter()) {
1339 SourceString name = node.selector.asIdentifier().source; 1326 world.registerDynamicGetter(selector.name, selector);
1340 if (node.isIndex && node is SendSet) { 1327 } else if (selector.isSetter()) {
1341 name = Elements.constructOperatorName( 1328 world.registerDynamicSetter(selector.name, selector);
1342 const SourceString('operator'), 1329 } else {
1343 const SourceString('[]=')); 1330 world.registerDynamicInvocation(selector.name, selector);
1344 } else if (node.selector.asOperator() != null) {
1345 switch (name.stringValue) {
1346 case '===':
1347 case '!==':
1348 case '!':
1349 case '&&':
1350 case '||':
1351 case 'is':
1352 case 'as':
1353 case '>>>':
1354 return null;
1355 } 1331 }
1356 name = Elements.constructOperatorName( 1332 } else if (Elements.isStaticOrTopLevel(target)) {
1357 const SourceString('operator'), 1333 world.registerStaticUse(target);
1358 name,
1359 node.argumentsNode is Prefix);
1360 } 1334 }
1361 Selector selector = mapping.getSelector(node); 1335
1362 if (selector.isGetter()) { 1336 // TODO(kasperl): Pass the selector directly.
1363 world.registerDynamicGetter(name, selector);
1364 } else if (selector.isSetter()) {
1365 world.registerDynamicSetter(name, selector);
1366 // Also register the getter for compound assignments.
1367 LibraryElement library = enclosingElement.getLibrary();
1368 Selector getter = new Selector.getter(name, library);
1369 world.registerDynamicGetter(name, getter);
1370 } else {
1371 if (selector.isIndexSet()) {
1372 // Also register the index selector for compound assignments.
1373 Selector index = new Selector.index();
1374 world.registerDynamicInvocation(index.name, index);
1375 }
1376 world.registerDynamicInvocation(name, selector);
1377 }
1378 var interceptor = new Interceptors(compiler).getStaticInterceptor( 1337 var interceptor = new Interceptors(compiler).getStaticInterceptor(
1379 name, 1338 selector.name,
1380 node.argumentCount()); 1339 selector.argumentCount);
1381 if (interceptor !== null) { 1340 if (interceptor !== null) {
1382 world.registerStaticUse(interceptor); 1341 world.registerStaticUse(interceptor);
1383 } 1342 }
1384 } 1343 }
1385 1344
1386 visitLiteralInt(LiteralInt node) { 1345 visitLiteralInt(LiteralInt node) {
1387 } 1346 }
1388 1347
1389 visitLiteralDouble(LiteralDouble node) { 1348 visitLiteralDouble(LiteralDouble node) {
1390 } 1349 }
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
1435 1394
1436 visitParenthesizedExpression(ParenthesizedExpression node) { 1395 visitParenthesizedExpression(ParenthesizedExpression node) {
1437 visit(node.expression); 1396 visit(node.expression);
1438 } 1397 }
1439 1398
1440 visitNewExpression(NewExpression node) { 1399 visitNewExpression(NewExpression node) {
1441 Node selector = node.send.selector; 1400 Node selector = node.send.selector;
1442 1401
1443 FunctionElement constructor = resolveConstructor(node); 1402 FunctionElement constructor = resolveConstructor(node);
1444 resolveSelector(node.send); 1403 resolveSelector(node.send);
1445 visitArguments(node.send.argumentsNode); 1404 resolveArguments(node.send.argumentsNode);
1446 if (constructor === null) return null; 1405 if (constructor === null) return null;
1447 // TODO(karlklose): handle optional arguments. 1406 // TODO(karlklose): handle optional arguments.
1448 if (node.send.argumentCount() != constructor.parameterCount(compiler)) { 1407 if (node.send.argumentCount() != constructor.parameterCount(compiler)) {
1449 // TODO(ngeoffray): resolution error with wrong number of 1408 // TODO(ngeoffray): resolution error with wrong number of
1450 // parameters. We cannot do this rigth now because of the 1409 // parameters. We cannot do this rigth now because of the
1451 // List constructor. 1410 // List constructor.
1452 } 1411 }
1453 useElement(node.send, constructor); 1412 useElement(node.send, constructor);
1413 world.registerStaticUse(constructor);
1454 compiler.withCurrentElement(constructor, () { 1414 compiler.withCurrentElement(constructor, () {
1455 FunctionExpression tree = constructor.parseNode(compiler); 1415 FunctionExpression tree = constructor.parseNode(compiler);
1456 compiler.resolver.resolveConstructorImplementation(constructor, tree); 1416 compiler.resolver.resolveConstructorImplementation(constructor, tree);
1457 }); 1417 });
1458 world.registerStaticUse(constructor.defaultImplementation); 1418 world.registerStaticUse(constructor.defaultImplementation);
1459 ClassElement cls = constructor.defaultImplementation.getEnclosingClass(); 1419 ClassElement cls = constructor.defaultImplementation.getEnclosingClass();
1460 world.registerInstantiatedClass(cls); 1420 world.registerInstantiatedClass(cls);
1461 cls.forEachInstanceField( 1421 cls.forEachInstanceField(
1462 includeBackendMembers: false, 1422 includeBackendMembers: false,
1463 includeSuperMembers: true, 1423 includeSuperMembers: true,
(...skipping 995 matching lines...) Expand 10 before | Expand all | Expand 10 after
2459 TopScope(LibraryElement library) : super(null, library); 2419 TopScope(LibraryElement library) : super(null, library);
2460 Element lookup(SourceString name) { 2420 Element lookup(SourceString name) {
2461 return library.find(name); 2421 return library.find(name);
2462 } 2422 }
2463 2423
2464 Element add(Element newElement) { 2424 Element add(Element newElement) {
2465 throw "Cannot add an element in the top scope"; 2425 throw "Cannot add an element in the top scope";
2466 } 2426 }
2467 String toString() => '$element'; 2427 String toString() => '$element';
2468 } 2428 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698