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

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

Issue 10947024: Made dart2js constructor lookup logic "private"-aware, fixed 4740 bug. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Incorporated Peter's comments and feedback. Created 8 years, 2 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 abstract class TreeElements { 5 abstract class TreeElements {
6 Element operator[](Node node); 6 Element operator[](Node node);
7 Selector getSelector(Send send); 7 Selector getSelector(Send send);
8 DartType getType(TypeAnnotation annotation); 8 DartType getType(TypeAnnotation annotation);
9 bool isParameterChecked(Element element); 9 bool isParameterChecked(Element element);
10 } 10 }
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 if (kind === ElementKind.PARAMETER || 80 if (kind === ElementKind.PARAMETER ||
81 kind === ElementKind.FIELD_PARAMETER) { 81 kind === ElementKind.FIELD_PARAMETER) {
82 return resolveParameter(element); 82 return resolveParameter(element);
83 } 83 }
84 84
85 compiler.unimplemented("resolve($element)", 85 compiler.unimplemented("resolve($element)",
86 node: element.parseNode(compiler)); 86 node: element.parseNode(compiler));
87 }); 87 });
88 } 88 }
89 89
90 bool isNamedConstructor(Send node) => node.receiver !== null;
91
90 SourceString getConstructorName(Send node) { 92 SourceString getConstructorName(Send node) {
91 if (node.receiver !== null) { 93 return node.selector.asIdentifier().source;
92 return node.selector.asIdentifier().source;
93 } else {
94 return const SourceString('');
95 }
96 } 94 }
97 95
98 FunctionElement resolveConstructorRedirection(FunctionElement constructor) { 96 String constructorNameForDiagnostics(SourceString className,
97 SourceString constructorName) {
98 String classNameString = className.slowToString();
99 String constructorNameString = constructorName.slowToString();
100 return (constructorName === const SourceString(''))
101 ? classNameString
102 : "$classNameString.$constructorNameString";
103 }
104
105 FunctionElement resolveConstructorRedirection(InitializerResolver resolver,
106 FunctionElement constructor) {
99 if (constructor.isPatched) { 107 if (constructor.isPatched) {
100 checkMatchingPatchSignatures(constructor, constructor.patch); 108 checkMatchingPatchSignatures(constructor, constructor.patch);
101 constructor = constructor.patch; 109 constructor = constructor.patch;
102 } 110 }
103 FunctionExpression node = constructor.parseNode(compiler); 111 FunctionExpression node = constructor.parseNode(compiler);
104 112
105 // A synthetic constructor does not have a node. 113 // A synthetic constructor does not have a node.
106 if (node === null) return null; 114 if (node === null) return null;
107 if (node.initializers === null) return null; 115 if (node.initializers === null) return null;
108 Link<Node> initializers = node.initializers.nodes; 116 Link<Node> initializers = node.initializers.nodes;
109 if (!initializers.isEmpty() && 117 if (!initializers.isEmpty() &&
110 Initializers.isConstructorRedirect(initializers.head)) { 118 Initializers.isConstructorRedirect(initializers.head)) {
111 final ClassElement classElement = constructor.getEnclosingClass(); 119 final ClassElement classElement = constructor.getEnclosingClass();
112 final SourceString constructorName = 120 Selector selector;
113 getConstructorName(initializers.head); 121 if (isNamedConstructor(initializers.head)) {
114 final SourceString className = classElement.name; 122 SourceString constructorName = getConstructorName(initializers.head);
115 return classElement.lookupConstructor(className, constructorName); 123 selector = new Selector.callConstructor(
124 constructorName,
125 resolver.visitor.enclosingElement.getLibrary());
126 } else {
127 selector = new Selector.callDefaultConstructor(
128 resolver.visitor.enclosingElement.getLibrary());
129 }
130 return classElement.lookupConstructor(selector);
116 } 131 }
117 return null; 132 return null;
118 } 133 }
119 134
120 void resolveRedirectingConstructor(InitializerResolver resolver, 135 void resolveRedirectingConstructor(InitializerResolver resolver,
121 Node node, 136 Node node,
122 FunctionElement constructor, 137 FunctionElement constructor,
123 FunctionElement redirection) { 138 FunctionElement redirection) {
124 Set<FunctionElement> seen = new Set<FunctionElement>(); 139 Set<FunctionElement> seen = new Set<FunctionElement>();
125 seen.add(constructor); 140 seen.add(constructor);
126 while (redirection !== null) { 141 while (redirection !== null) {
127 if (seen.contains(redirection)) { 142 if (seen.contains(redirection)) {
128 resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE); 143 resolver.visitor.error(node, MessageKind.REDIRECTING_CONSTRUCTOR_CYCLE);
129 return; 144 return;
130 } 145 }
131 seen.add(redirection); 146 seen.add(redirection);
132 redirection = resolveConstructorRedirection(redirection); 147 redirection = resolveConstructorRedirection(resolver, redirection);
133 } 148 }
134 } 149 }
135 150
136 void checkMatchingPatchParameters(FunctionElement origin, 151 void checkMatchingPatchParameters(FunctionElement origin,
137 Link<Element> originParameters, 152 Link<Element> originParameters,
138 Link<Element> patchParameters) { 153 Link<Element> patchParameters) {
139 while (!originParameters.isEmpty()) { 154 while (!originParameters.isEmpty()) {
140 Element originParameter = originParameters.head; 155 Element originParameter = originParameters.head;
141 Element patchParameter = patchParameters.head; 156 Element patchParameter = patchParameters.head;
142 // Hack: Use unparser to test parameter equality. This only works because 157 // Hack: Use unparser to test parameter equality. This only works because
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 assert(defaultClass.resolutionState == STATE_DONE); 298 assert(defaultClass.resolutionState == STATE_DONE);
284 assert(defaultClass.supertypeLoadState == STATE_DONE); 299 assert(defaultClass.supertypeLoadState == STATE_DONE);
285 if (defaultClass.isInterface()) { 300 if (defaultClass.isInterface()) {
286 error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE, 301 error(node, MessageKind.CANNOT_INSTANTIATE_INTERFACE,
287 [defaultClass.name]); 302 [defaultClass.name]);
288 } 303 }
289 // We have now established the following: 304 // We have now established the following:
290 // [intrface] is an interface, let's say "MyInterface". 305 // [intrface] is an interface, let's say "MyInterface".
291 // [defaultClass] is a class, let's say "MyClass". 306 // [defaultClass] is a class, let's say "MyClass".
292 307
308 Selector selector;
293 // If the default class implements the interface then we must use the 309 // If the default class implements the interface then we must use the
294 // default class' name. Otherwise we look for a factory with the name 310 // default class' name. Otherwise we look for a factory with the name
295 // of the interface. 311 // of the interface.
296 SourceString name;
297 if (defaultClass.implementsInterface(intrface)) { 312 if (defaultClass.implementsInterface(intrface)) {
298 // TODO(ahe): Don't use string replacement here. 313 var constructorNameString = constructor.name.slowToString();
299 name = new SourceString(constructor.name.slowToString().replaceFirst( 314 // Create selector based on constructor.name but where interface
300 intrface.name.slowToString(), 315 // is replaced with default class name.
301 defaultClass.name.slowToString())); 316 // TODO(ahe): Don't use string manipulations here.
317 int classNameSeparatorIndex = constructorNameString.indexOf('\$');
318 if (classNameSeparatorIndex < 0) {
319 selector = new Selector.callDefaultConstructor(
320 defaultClass.getLibrary());
321 } else {
322 selector = new Selector.callConstructor(
323 new SourceString(
324 constructorNameString.substring(classNameSeparatorIndex + 1)),
325 defaultClass.getLibrary());
326 }
327 constructor.defaultImplementation =
328 defaultClass.lookupConstructor(selector);
302 } else { 329 } else {
303 name = constructor.name; 330 selector =
331 new Selector.callConstructor(constructor.name,
332 defaultClass.getLibrary());
333 constructor.defaultImplementation =
334 defaultClass.lookupFactoryConstructor(selector);
304 } 335 }
305 constructor.defaultImplementation = defaultClass.lookupConstructor(name);
306
307 if (constructor.defaultImplementation === null) { 336 if (constructor.defaultImplementation === null) {
308 // We failed to find a constructor named either 337 // We failed to find a constructor named either
309 // "MyInterface.name" or "MyClass.name". 338 // "MyInterface.name" or "MyClass.name".
339 // TODO(aprelev@gmail.com): Use constructorNameForDiagnostics in
340 // the error message below.
310 error(node, 341 error(node,
311 MessageKind.CANNOT_FIND_CONSTRUCTOR2, 342 MessageKind.CANNOT_FIND_CONSTRUCTOR2,
312 [name, defaultClass.name]); 343 [selector.name, defaultClass.name]);
313 } 344 }
314 } 345 }
315 346
316 TreeElements resolveField(VariableElement element) { 347 TreeElements resolveField(VariableElement element) {
317 Node tree = element.parseNode(compiler); 348 Node tree = element.parseNode(compiler);
318 if(element.modifiers.isStatic() && element.variables.isTopLevel()) { 349 if(element.modifiers.isStatic() && element.variables.isTopLevel()) {
319 error(element.modifiers.getStatic(), MessageKind.TOP_LEVEL_VARIABLE_DECLAR ED_STATIC); 350 error(element.modifiers.getStatic(), MessageKind.TOP_LEVEL_VARIABLE_DECLAR ED_STATIC);
320 } 351 }
321 ResolverVisitor visitor = new ResolverVisitor(compiler, element); 352 ResolverVisitor visitor = new ResolverVisitor(compiler, element);
322 initializerDo(tree, visitor.visit); 353 initializerDo(tree, visitor.visit);
(...skipping 352 matching lines...) Expand 10 before | Expand all | Expand 10 after
675 } else { 706 } else {
676 error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER); 707 error(init, MessageKind.INVALID_RECEIVER_IN_INITIALIZER);
677 } 708 }
678 visitor.useElement(init, target); 709 visitor.useElement(init, target);
679 visitor.world.registerStaticUse(target); 710 visitor.world.registerStaticUse(target);
680 checkForDuplicateInitializers(name, init); 711 checkForDuplicateInitializers(name, init);
681 // Resolve initializing value. 712 // Resolve initializing value.
682 visitor.visitInStaticContext(init.arguments.head); 713 visitor.visitInStaticContext(init.arguments.head);
683 } 714 }
684 715
716 ClassElement getSuperOrThisLookupTarget(FunctionElement constructor,
717 bool isSuperCall,
718 Node diagnosticNode) {
719 ClassElement lookupTarget = constructor.getEnclosingClass();
720 if (isSuperCall) {
721 // Calculate correct lookup target and constructor name.
722 if (lookupTarget === visitor.compiler.objectClass) {
723 error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT);
724 } else {
725 return lookupTarget.supertype.element;
726 }
727 }
728 return lookupTarget;
729 }
730
685 Element resolveSuperOrThisForSend(FunctionElement constructor, 731 Element resolveSuperOrThisForSend(FunctionElement constructor,
686 FunctionExpression functionNode, 732 FunctionExpression functionNode,
687 Send call) { 733 Send call) {
688 // Resolve the selector and the arguments. 734 // Resolve the selector and the arguments.
689 ResolverTask resolver = visitor.compiler.resolver; 735 ResolverTask resolver = visitor.compiler.resolver;
690 visitor.inStaticContext(() { 736 visitor.inStaticContext(() {
691 visitor.resolveSelector(call); 737 visitor.resolveSelector(call);
692 visitor.resolveArguments(call.argumentsNode); 738 visitor.resolveArguments(call.argumentsNode);
693 }); 739 });
694 Selector selector = visitor.mapping.getSelector(call); 740 Selector selector = visitor.mapping.getSelector(call);
695 bool isSuperCall = Initializers.isSuperConstructorCall(call); 741 bool isSuperCall = Initializers.isSuperConstructorCall(call);
696 SourceString constructorName = resolver.getConstructorName(call); 742
697 Element result = resolveSuperOrThis( 743 ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
698 constructor, isSuperCall, false, constructorName, selector, call); 744 isSuperCall,
699 visitor.useElement(call, result); 745 call);
700 visitor.world.registerStaticUse(result); 746 final SourceString className = lookupTarget.name;
701 return result; 747
748 SourceString constructorName;
749 Selector lookupSelector;
750 if (resolver.isNamedConstructor(call)) {
751 constructorName = resolver.getConstructorName(call);
752 lookupSelector = new Selector.callConstructor(
753 constructorName,
754 visitor.enclosingElement.getLibrary());
755 } else {
756 constructorName = const SourceString('');
757 lookupSelector = new Selector.callDefaultConstructor(
758 visitor.enclosingElement.getLibrary());
759 }
760
761 FunctionElement lookedupConstructor =
762 lookupTarget.lookupConstructor(lookupSelector);
763
764 final bool isImplicitSuperCall = false;
765 verifyThatConstructorMatchesCall(lookedupConstructor,
766 selector,
767 isImplicitSuperCall,
768 call,
769 constructorName,
770 className);
771
772 visitor.useElement(call, lookedupConstructor);
773 visitor.world.registerStaticUse(lookedupConstructor);
774 return lookedupConstructor;
702 } 775 }
703 776
704 void resolveImplicitSuperConstructorSend(FunctionElement constructor, 777 void resolveImplicitSuperConstructorSend(FunctionElement constructor,
705 FunctionExpression functionNode) { 778 FunctionExpression functionNode) {
706 // If the class has a super resolve the implicit super call. 779 // If the class has a super resolve the implicit super call.
707 ClassElement classElement = constructor.getEnclosingClass(); 780 ClassElement classElement = constructor.getEnclosingClass();
708 ClassElement superClass = classElement.superclass; 781 ClassElement superClass = classElement.superclass;
709 if (classElement != visitor.compiler.objectClass) { 782 if (classElement != visitor.compiler.objectClass) {
710 assert(superClass !== null); 783 assert(superClass !== null);
711 assert(superClass.resolutionState == STATE_DONE); 784 assert(superClass.resolutionState == STATE_DONE);
712 SourceString name = const SourceString(''); 785 SourceString constructorName = const SourceString('');
713 Selector call = new Selector.call(name, classElement.getLibrary(), 0); 786 Selector callToMatch = new Selector.call(
714 var element = resolveSuperOrThis(constructor, true, true, 787 constructorName,
715 name, call, functionNode); 788 classElement.getLibrary(),
716 visitor.world.registerStaticUse(element); 789 0);
790
791 final bool isSuperCall = true;
792 ClassElement lookupTarget = getSuperOrThisLookupTarget(constructor,
793 isSuperCall,
794 functionNode);
795 final SourceString className = lookupTarget.name;
796 Element calledConstructor = lookupTarget.lookupConstructor(
797 new Selector.callDefaultConstructor(
798 visitor.enclosingElement.getLibrary()));
799
800 final bool isImplicitSuperCall = true;
801 verifyThatConstructorMatchesCall(calledConstructor,
802 callToMatch,
803 isImplicitSuperCall,
804 functionNode,
805 className,
806 const SourceString(''));
807
808 visitor.world.registerStaticUse(calledConstructor);
717 } 809 }
718 } 810 }
719 811
720 Element resolveSuperOrThis(FunctionElement constructor, 812 void verifyThatConstructorMatchesCall(
721 bool isSuperCall, 813 FunctionElement lookedupConstructor,
722 bool isImplicitSuperCall, 814 Selector call,
723 SourceString constructorName, 815 bool isImplicitSuperCall,
724 Selector selector, 816 Node diagnosticNode,
725 Node diagnosticNode) { 817 SourceString className,
726 ClassElement lookupTarget = constructor.getEnclosingClass(); 818 SourceString constructorName) {
727 bool validTarget = true; 819 if (lookedupConstructor === null
728 FunctionElement result; 820 || !lookedupConstructor.isGenerativeConstructor()) {
729 if (isSuperCall) { 821 var fullConstructorName =
730 // Calculate correct lookup target and constructor name. 822 visitor.compiler.resolver.constructorNameForDiagnostics(className,
731 if (lookupTarget === visitor.compiler.objectClass) { 823 constructorName);
732 error(diagnosticNode, MessageKind.SUPER_INITIALIZER_IN_OBJECT);
733 } else {
734 lookupTarget = lookupTarget.supertype.element;
735 }
736 }
737
738 // Lookup constructor and try to match it to the selector.
739 ResolverTask resolver = visitor.compiler.resolver;
740 final SourceString className = lookupTarget.name;
741 result = lookupTarget.lookupConstructor(className, constructorName);
742 if (result === null || !result.isGenerativeConstructor()) {
743 String classNameString = className.slowToString();
744 String constructorNameString = constructorName.slowToString();
745 String name = (constructorName === const SourceString(''))
746 ? classNameString
747 : "$classNameString.$constructorNameString";
748 MessageKind kind = isImplicitSuperCall 824 MessageKind kind = isImplicitSuperCall
749 ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT 825 ? MessageKind.CANNOT_RESOLVE_CONSTRUCTOR_FOR_IMPLICIT
750 : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR; 826 : MessageKind.CANNOT_RESOLVE_CONSTRUCTOR;
751 error(diagnosticNode, kind, [name]); 827 error(diagnosticNode, kind, [fullConstructorName]);
752 } else { 828 } else {
753 if (!selector.applies(result, visitor.compiler)) { 829 if (!call.applies(lookedupConstructor, visitor.compiler)) {
754 MessageKind kind = isImplicitSuperCall 830 MessageKind kind = isImplicitSuperCall
755 ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT 831 ? MessageKind.NO_MATCHING_CONSTRUCTOR_FOR_IMPLICIT
756 : MessageKind.NO_MATCHING_CONSTRUCTOR; 832 : MessageKind.NO_MATCHING_CONSTRUCTOR;
757 error(diagnosticNode, kind); 833 error(diagnosticNode, kind);
758 } 834 }
759 } 835 }
760 return result;
761 } 836 }
762 837
763 FunctionElement resolveRedirection(FunctionElement constructor, 838 FunctionElement resolveRedirection(FunctionElement constructor,
764 FunctionExpression functionNode) { 839 FunctionExpression functionNode) {
765 if (functionNode.initializers === null) return null; 840 if (functionNode.initializers === null) return null;
766 Link<Node> link = functionNode.initializers.nodes; 841 Link<Node> link = functionNode.initializers.nodes;
767 if (!link.isEmpty() && Initializers.isConstructorRedirect(link.head)) { 842 if (!link.isEmpty() && Initializers.isConstructorRedirect(link.head)) {
768 return resolveSuperOrThisForSend(constructor, functionNode, link.head); 843 return resolveSuperOrThisForSend(constructor, functionNode, link.head);
769 } 844 }
770 return null; 845 return null;
(...skipping 2021 matching lines...) Expand 10 before | Expand all | Expand 10 after
2792 if (inConstContext) { 2867 if (inConstContext) {
2793 error(diagnosticNode, kind, arguments); 2868 error(diagnosticNode, kind, arguments);
2794 } else { 2869 } else {
2795 ResolutionWarning warning = new ResolutionWarning(kind, arguments); 2870 ResolutionWarning warning = new ResolutionWarning(kind, arguments);
2796 compiler.reportWarning(diagnosticNode, warning); 2871 compiler.reportWarning(diagnosticNode, warning);
2797 return new ErroneousFunctionElement(kind, arguments, targetName, 2872 return new ErroneousFunctionElement(kind, arguments, targetName,
2798 enclosing); 2873 enclosing);
2799 } 2874 }
2800 } 2875 }
2801 2876
2877 Selector createConstructorSelector(SourceString constructorName) {
2878 return constructorName == const SourceString('')
2879 ? new Selector.callDefaultConstructor(
2880 resolver.enclosingElement.getLibrary())
2881 : new Selector.callConstructor(
2882 constructorName,
2883 resolver.enclosingElement.getLibrary());
2884 }
2885
2802 // TODO(ngeoffray): method named lookup should not report errors. 2886 // TODO(ngeoffray): method named lookup should not report errors.
2803 FunctionElement lookupConstructor(ClassElement cls, 2887 FunctionElement lookupConstructor(ClassElement cls,
2804 Node diagnosticNode, 2888 Node diagnosticNode,
2805 SourceString constructorName) { 2889 SourceString constructorName) {
2806 cls.ensureResolved(compiler); 2890 cls.ensureResolved(compiler);
2807 Element result = cls.lookupConstructor(cls.name, constructorName); 2891 Selector selector = createConstructorSelector(constructorName);
2892 Element result = cls.lookupConstructor(selector);
2808 if (result === null) { 2893 if (result === null) {
2809 String fullConstructorName = cls.name.slowToString(); 2894 String fullConstructorName =
2810 if (constructorName !== const SourceString('')) { 2895 resolver.compiler.resolver.constructorNameForDiagnostics(
2811 fullConstructorName = '$fullConstructorName' 2896 cls.name,
2812 '.${constructorName.slowToString()}'; 2897 constructorName);
2813 } 2898 return failOrReturnErroneousElement(
2814 return failOrReturnErroneousElement(cls, diagnosticNode, 2899 cls,
2815 new SourceString(fullConstructorName), 2900 diagnosticNode,
2816 MessageKind.CANNOT_FIND_CONSTRUCTOR, 2901 new SourceString(fullConstructorName),
2817 [fullConstructorName]); 2902 MessageKind.CANNOT_FIND_CONSTRUCTOR,
2903 [fullConstructorName]);
2818 } else if (inConstContext && !result.modifiers.isConst()) { 2904 } else if (inConstContext && !result.modifiers.isConst()) {
2819 error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST); 2905 error(diagnosticNode, MessageKind.CONSTRUCTOR_IS_NOT_CONST);
2820 } 2906 }
2821 return result; 2907 return result;
2822 } 2908 }
2823 2909
2824 visitNewExpression(NewExpression node) { 2910 visitNewExpression(NewExpression node) {
2825 Node selector = node.send.selector; 2911 Node selector = node.send.selector;
2826 Element e = visit(selector); 2912 Element e = visit(selector);
2827 if (!Elements.isUnresolved(e) && e.kind === ElementKind.CLASS) { 2913 if (!Elements.isUnresolved(e) && e.kind === ElementKind.CLASS) {
(...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after
3142 return result; 3228 return result;
3143 } 3229 }
3144 Element lookup(SourceString name) => localLookup(name); 3230 Element lookup(SourceString name) => localLookup(name);
3145 Element lexicalLookup(SourceString name) => localLookup(name); 3231 Element lexicalLookup(SourceString name) => localLookup(name);
3146 3232
3147 Element add(Element newElement) { 3233 Element add(Element newElement) {
3148 throw "Cannot add an element in a patch library scope"; 3234 throw "Cannot add an element in a patch library scope";
3149 } 3235 }
3150 String toString() => 'PatchLibraryScope($origin,$patch)'; 3236 String toString() => 'PatchLibraryScope($origin,$patch)';
3151 } 3237 }
OLDNEW
« no previous file with comments | « lib/compiler/implementation/elements/elements.dart ('k') | lib/compiler/implementation/ssa/builder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698