OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 part of dart2js.js_emitter; | 5 part of dart2js.js_emitter; |
6 | 6 |
7 class ClassStubGenerator { | 7 class ClassStubGenerator { |
8 final Namer namer; | 8 final Namer namer; |
9 final Compiler compiler; | 9 final Compiler compiler; |
10 final JavaScriptBackend backend; | 10 final JavaScriptBackend backend; |
11 | 11 |
12 ClassStubGenerator(this.compiler, this.namer, this.backend); | 12 ClassStubGenerator(this.compiler, this.namer, this.backend); |
13 | 13 |
14 jsAst.Expression generateClassConstructor(ClassElement classElement, | 14 jsAst.Expression generateClassConstructor( |
15 Iterable<jsAst.Name> fields) { | 15 ClassElement classElement, Iterable<jsAst.Name> fields) { |
16 // TODO(sra): Implement placeholders in VariableDeclaration position: | 16 // TODO(sra): Implement placeholders in VariableDeclaration position: |
17 // | 17 // |
18 // String constructorName = namer.getNameOfClass(classElement); | 18 // String constructorName = namer.getNameOfClass(classElement); |
19 // return js.statement('function #(#) { #; }', | 19 // return js.statement('function #(#) { #; }', |
20 // [ constructorName, fields, | 20 // [ constructorName, fields, |
21 // fields.map( | 21 // fields.map( |
22 // (name) => js('this.# = #', [name, name]))])); | 22 // (name) => js('this.# = #', [name, name]))])); |
23 return js('function(#) { #; this.#();}', | 23 return js('function(#) { #; this.#();}', [ |
24 [fields, | 24 fields, |
25 fields.map((name) => js('this.# = #', [name, name])), | 25 fields.map((name) => js('this.# = #', [name, name])), |
26 namer.deferredAction]); | 26 namer.deferredAction |
| 27 ]); |
27 } | 28 } |
28 | 29 |
29 jsAst.Expression generateGetter(Element member, jsAst.Name fieldName) { | 30 jsAst.Expression generateGetter(Element member, jsAst.Name fieldName) { |
30 ClassElement cls = member.enclosingClass; | 31 ClassElement cls = member.enclosingClass; |
31 String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; | 32 String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; |
32 List<String> args = backend.isInterceptedMethod(member) ? ['receiver'] : []; | 33 List<String> args = backend.isInterceptedMethod(member) ? ['receiver'] : []; |
33 return js('function(#) { return #.# }', [args, receiver, fieldName]); | 34 return js('function(#) { return #.# }', [args, receiver, fieldName]); |
34 } | 35 } |
35 | 36 |
36 jsAst.Expression generateSetter(Element member, jsAst.Name fieldName) { | 37 jsAst.Expression generateSetter(Element member, jsAst.Name fieldName) { |
37 ClassElement cls = member.enclosingClass; | 38 ClassElement cls = member.enclosingClass; |
38 String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; | 39 String receiver = backend.isInterceptorClass(cls) ? 'receiver' : 'this'; |
39 List<String> args = backend.isInterceptedMethod(member) ? ['receiver'] : []; | 40 List<String> args = backend.isInterceptedMethod(member) ? ['receiver'] : []; |
40 // TODO(floitsch): remove 'return'? | 41 // TODO(floitsch): remove 'return'? |
41 return js('function(#, v) { return #.# = v; }', | 42 return js( |
42 [args, receiver, fieldName]); | 43 'function(#, v) { return #.# = v; }', [args, receiver, fieldName]); |
43 } | 44 } |
44 | 45 |
45 /** | 46 /** |
46 * Documentation wanted -- johnniwinther | 47 * Documentation wanted -- johnniwinther |
47 * | 48 * |
48 * Invariant: [member] must be a declaration element. | 49 * Invariant: [member] must be a declaration element. |
49 */ | 50 */ |
50 Map<jsAst.Name, jsAst.Expression> generateCallStubsForGetter( | 51 Map<jsAst.Name, jsAst.Expression> generateCallStubsForGetter( |
51 Element member, Map<Selector, SelectorConstraints> selectors) { | 52 Element member, Map<Selector, SelectorConstraints> selectors) { |
52 assert(invariant(member, member.isDeclaration)); | 53 assert(invariant(member, member.isDeclaration)); |
53 | 54 |
54 // If the method is intercepted, the stub gets the | 55 // If the method is intercepted, the stub gets the |
55 // receiver explicitely and we need to pass it to the getter call. | 56 // receiver explicitely and we need to pass it to the getter call. |
56 bool isInterceptedMethod = backend.isInterceptedMethod(member); | 57 bool isInterceptedMethod = backend.isInterceptedMethod(member); |
57 bool isInterceptorClass = | 58 bool isInterceptorClass = backend.isInterceptorClass(member.enclosingClass); |
58 backend.isInterceptorClass(member.enclosingClass); | |
59 | 59 |
60 const String receiverArgumentName = r'$receiver'; | 60 const String receiverArgumentName = r'$receiver'; |
61 | 61 |
62 jsAst.Expression buildGetter() { | 62 jsAst.Expression buildGetter() { |
63 jsAst.Expression receiver = | 63 jsAst.Expression receiver = |
64 js(isInterceptorClass ? receiverArgumentName : 'this'); | 64 js(isInterceptorClass ? receiverArgumentName : 'this'); |
65 if (member.isGetter) { | 65 if (member.isGetter) { |
66 jsAst.Name getterName = namer.getterForElement(member); | 66 jsAst.Name getterName = namer.getterForElement(member); |
67 if (isInterceptedMethod) { | 67 if (isInterceptedMethod) { |
68 return js('this.#(#)', [getterName, receiver]); | 68 return js('this.#(#)', [getterName, receiver]); |
69 } | 69 } |
70 return js('#.#()', [receiver, getterName]); | 70 return js('#.#()', [receiver, getterName]); |
71 } else { | 71 } else { |
72 jsAst.Name fieldName = namer.instanceFieldPropertyName(member); | 72 jsAst.Name fieldName = namer.instanceFieldPropertyName(member); |
73 return js('#.#', [receiver, fieldName]); | 73 return js('#.#', [receiver, fieldName]); |
74 } | 74 } |
75 } | 75 } |
76 | 76 |
77 Map<jsAst.Name, jsAst.Expression> generatedStubs = | 77 Map<jsAst.Name, jsAst.Expression> generatedStubs = |
78 <jsAst.Name, jsAst.Expression>{}; | 78 <jsAst.Name, jsAst.Expression>{}; |
79 | 79 |
80 // Two selectors may match but differ only in type. To avoid generating | 80 // Two selectors may match but differ only in type. To avoid generating |
81 // identical stubs for each we track untyped selectors which already have | 81 // identical stubs for each we track untyped selectors which already have |
82 // stubs. | 82 // stubs. |
83 Set<Selector> generatedSelectors = new Set<Selector>(); | 83 Set<Selector> generatedSelectors = new Set<Selector>(); |
84 for (Selector selector in selectors.keys) { | 84 for (Selector selector in selectors.keys) { |
85 if (generatedSelectors.contains(selector)) continue; | 85 if (generatedSelectors.contains(selector)) continue; |
86 if (!selector.appliesUnnamed(member, compiler.world)) continue; | 86 if (!selector.appliesUnnamed(member, compiler.world)) continue; |
87 if (selectors[selector].applies(member, selector, compiler.world)) { | 87 if (selectors[selector].applies(member, selector, compiler.world)) { |
88 generatedSelectors.add(selector); | 88 generatedSelectors.add(selector); |
89 | 89 |
90 jsAst.Name invocationName = namer.invocationName(selector); | 90 jsAst.Name invocationName = namer.invocationName(selector); |
91 Selector callSelector = new Selector.callClosureFrom(selector); | 91 Selector callSelector = new Selector.callClosureFrom(selector); |
92 jsAst.Name closureCallName = namer.invocationName(callSelector); | 92 jsAst.Name closureCallName = namer.invocationName(callSelector); |
93 | 93 |
94 List<jsAst.Parameter> parameters = <jsAst.Parameter>[]; | 94 List<jsAst.Parameter> parameters = <jsAst.Parameter>[]; |
95 List<jsAst.Expression> arguments = <jsAst.Expression>[]; | 95 List<jsAst.Expression> arguments = <jsAst.Expression>[]; |
96 if (isInterceptedMethod) { | 96 if (isInterceptedMethod) { |
97 parameters.add(new jsAst.Parameter(receiverArgumentName)); | 97 parameters.add(new jsAst.Parameter(receiverArgumentName)); |
98 } | 98 } |
99 | 99 |
100 for (int i = 0; i < selector.argumentCount; i++) { | 100 for (int i = 0; i < selector.argumentCount; i++) { |
101 String name = 'arg$i'; | 101 String name = 'arg$i'; |
102 parameters.add(new jsAst.Parameter(name)); | 102 parameters.add(new jsAst.Parameter(name)); |
103 arguments.add(js('#', name)); | 103 arguments.add(js('#', name)); |
104 } | 104 } |
105 | 105 |
106 jsAst.Fun function = js( | 106 jsAst.Fun function = js('function(#) { return #.#(#); }', |
107 'function(#) { return #.#(#); }', | 107 [parameters, buildGetter(), closureCallName, arguments]); |
108 [ parameters, buildGetter(), closureCallName, arguments]); | |
109 | 108 |
110 generatedStubs[invocationName] = function; | 109 generatedStubs[invocationName] = function; |
111 } | 110 } |
112 } | 111 } |
113 | 112 |
114 return generatedStubs; | 113 return generatedStubs; |
115 } | 114 } |
116 | 115 |
117 Map<jsAst.Name, Selector> computeSelectorsForNsmHandlers() { | 116 Map<jsAst.Name, Selector> computeSelectorsForNsmHandlers() { |
118 | |
119 Map<jsAst.Name, Selector> jsNames = <jsAst.Name, Selector>{}; | 117 Map<jsAst.Name, Selector> jsNames = <jsAst.Name, Selector>{}; |
120 | 118 |
121 // Do not generate no such method handlers if there is no class. | 119 // Do not generate no such method handlers if there is no class. |
122 if (compiler.codegenWorld.directlyInstantiatedClasses.isEmpty) { | 120 if (compiler.codegenWorld.directlyInstantiatedClasses.isEmpty) { |
123 return jsNames; | 121 return jsNames; |
124 } | 122 } |
125 | 123 |
126 void addNoSuchMethodHandlers(String ignore, | 124 void addNoSuchMethodHandlers( |
127 Map<Selector, SelectorConstraints> selectors) { | 125 String ignore, Map<Selector, SelectorConstraints> selectors) { |
128 for (Selector selector in selectors.keys) { | 126 for (Selector selector in selectors.keys) { |
129 SelectorConstraints maskSet = selectors[selector]; | 127 SelectorConstraints maskSet = selectors[selector]; |
130 if (maskSet.needsNoSuchMethodHandling(selector, compiler.world)) { | 128 if (maskSet.needsNoSuchMethodHandling(selector, compiler.world)) { |
131 jsAst.Name jsName = namer.invocationMirrorInternalName(selector); | 129 jsAst.Name jsName = namer.invocationMirrorInternalName(selector); |
132 jsNames[jsName] = selector; | 130 jsNames[jsName] = selector; |
133 } | 131 } |
134 } | 132 } |
135 } | 133 } |
136 | 134 |
137 compiler.codegenWorld.forEachInvokedName(addNoSuchMethodHandlers); | 135 compiler.codegenWorld.forEachInvokedName(addNoSuchMethodHandlers); |
138 compiler.codegenWorld.forEachInvokedGetter(addNoSuchMethodHandlers); | 136 compiler.codegenWorld.forEachInvokedGetter(addNoSuchMethodHandlers); |
139 compiler.codegenWorld.forEachInvokedSetter(addNoSuchMethodHandlers); | 137 compiler.codegenWorld.forEachInvokedSetter(addNoSuchMethodHandlers); |
140 return jsNames; | 138 return jsNames; |
141 } | 139 } |
142 | 140 |
143 StubMethod generateStubForNoSuchMethod(jsAst.Name name, | 141 StubMethod generateStubForNoSuchMethod(jsAst.Name name, Selector selector) { |
144 Selector selector) { | |
145 // Values match JSInvocationMirror in js-helper library. | 142 // Values match JSInvocationMirror in js-helper library. |
146 int type = selector.invocationMirrorKind; | 143 int type = selector.invocationMirrorKind; |
147 List<String> parameterNames = | 144 List<String> parameterNames = |
148 new List.generate(selector.argumentCount, (i) => '\$$i'); | 145 new List.generate(selector.argumentCount, (i) => '\$$i'); |
149 | 146 |
150 List<jsAst.Expression> argNames = | 147 List<jsAst.Expression> argNames = selector.callStructure |
151 selector.callStructure.getOrderedNamedArguments().map((String name) => | 148 .getOrderedNamedArguments() |
152 js.string(name)).toList(); | 149 .map((String name) => js.string(name)) |
| 150 .toList(); |
153 | 151 |
154 jsAst.Name methodName = namer.asName(selector.invocationMirrorMemberName); | 152 jsAst.Name methodName = namer.asName(selector.invocationMirrorMemberName); |
155 jsAst.Name internalName = namer.invocationMirrorInternalName(selector); | 153 jsAst.Name internalName = namer.invocationMirrorInternalName(selector); |
156 | 154 |
157 assert(backend.isInterceptedName(Identifiers.noSuchMethod_)); | 155 assert(backend.isInterceptedName(Identifiers.noSuchMethod_)); |
158 bool isIntercepted = backend.isInterceptedName(selector.name); | 156 bool isIntercepted = backend.isInterceptedName(selector.name); |
159 jsAst.Expression expression = | 157 jsAst.Expression expression = js( |
160 js('''this.#noSuchMethodName(#receiver, | 158 '''this.#noSuchMethodName(#receiver, |
161 #createInvocationMirror(#methodName, | 159 #createInvocationMirror(#methodName, |
162 #internalName, | 160 #internalName, |
163 #type, | 161 #type, |
164 #arguments, | 162 #arguments, |
165 #namedArguments))''', | 163 #namedArguments))''', |
166 {'receiver': isIntercepted ? r'$receiver' : 'this', | 164 { |
167 'noSuchMethodName': namer.noSuchMethodName, | 165 'receiver': isIntercepted ? r'$receiver' : 'this', |
168 'createInvocationMirror': | 166 'noSuchMethodName': namer.noSuchMethodName, |
169 backend.emitter.staticFunctionAccess( | 167 'createInvocationMirror': backend.emitter |
170 backend.helpers.createInvocationMirror), | 168 .staticFunctionAccess(backend.helpers.createInvocationMirror), |
171 'methodName': | 169 'methodName': js.quoteName( |
172 js.quoteName(compiler.options.enableMinification | 170 compiler.options.enableMinification ? internalName : methodName), |
173 ? internalName : methodName), | 171 'internalName': js.quoteName(internalName), |
174 'internalName': js.quoteName(internalName), | 172 'type': js.number(type), |
175 'type': js.number(type), | 173 'arguments': |
176 'arguments': | 174 new jsAst.ArrayInitializer(parameterNames.map(js).toList()), |
177 new jsAst.ArrayInitializer(parameterNames.map(js).toList()), | 175 'namedArguments': new jsAst.ArrayInitializer(argNames) |
178 'namedArguments': new jsAst.ArrayInitializer(argNames)}); | 176 }); |
179 | 177 |
180 jsAst.Expression function; | 178 jsAst.Expression function; |
181 if (isIntercepted) { | 179 if (isIntercepted) { |
182 function = js(r'function($receiver, #) { return # }', | 180 function = js( |
183 [parameterNames, expression]); | 181 r'function($receiver, #) { return # }', [parameterNames, expression]); |
184 } else { | 182 } else { |
185 function = js(r'function(#) { return # }', [parameterNames, expression]); | 183 function = js(r'function(#) { return # }', [parameterNames, expression]); |
186 } | 184 } |
187 return new StubMethod(name, function); | 185 return new StubMethod(name, function); |
188 } | 186 } |
189 } | 187 } |
190 | 188 |
191 /// Creates two JavaScript functions: `tearOffGetter` and `tearOff`. | 189 /// Creates two JavaScript functions: `tearOffGetter` and `tearOff`. |
192 /// | 190 /// |
193 /// `tearOffGetter` is internal and only used by `tearOff`. | 191 /// `tearOffGetter` is internal and only used by `tearOff`. |
(...skipping 18 matching lines...) Expand all Loading... |
212 jsAst.Expression tearOffGlobalObject; | 210 jsAst.Expression tearOffGlobalObject; |
213 if (closureFromTearOff != null) { | 211 if (closureFromTearOff != null) { |
214 tearOffAccessExpression = | 212 tearOffAccessExpression = |
215 backend.emitter.staticFunctionAccess(closureFromTearOff); | 213 backend.emitter.staticFunctionAccess(closureFromTearOff); |
216 tearOffGlobalObject = | 214 tearOffGlobalObject = |
217 js.stringPart(namer.globalObjectFor(closureFromTearOff)); | 215 js.stringPart(namer.globalObjectFor(closureFromTearOff)); |
218 tearOffGlobalObjectString = | 216 tearOffGlobalObjectString = |
219 js.string(namer.globalObjectFor(closureFromTearOff)); | 217 js.string(namer.globalObjectFor(closureFromTearOff)); |
220 } else { | 218 } else { |
221 // Default values for mocked-up test libraries. | 219 // Default values for mocked-up test libraries. |
222 tearOffAccessExpression = js( | 220 tearOffAccessExpression = |
223 r'''function() { throw "Helper 'closureFromTearOff' missing." }'''); | 221 js(r'''function() { throw "Helper 'closureFromTearOff' missing." }'''); |
224 tearOffGlobalObjectString = js.string('MissingHelperFunction'); | 222 tearOffGlobalObjectString = js.string('MissingHelperFunction'); |
225 tearOffGlobalObject = js( | 223 tearOffGlobalObject = js( |
226 r'''(function() { throw "Helper 'closureFromTearOff' missing." })()'''); | 224 r'''(function() { throw "Helper 'closureFromTearOff' missing." })()'''); |
227 } | 225 } |
228 | 226 |
229 jsAst.Statement tearOffGetter; | 227 jsAst.Statement tearOffGetter; |
230 if (!compiler.options.useContentSecurityPolicy) { | 228 if (!compiler.options.useContentSecurityPolicy) { |
231 jsAst.Expression tearOffAccessText = | 229 jsAst.Expression tearOffAccessText = |
232 new jsAst.UnparsedNode(tearOffAccessExpression, compiler, false); | 230 new jsAst.UnparsedNode(tearOffAccessExpression, compiler, false); |
233 tearOffGetter = js.statement(''' | 231 tearOffGetter = js.statement( |
| 232 ''' |
234 function tearOffGetter(funcs, reflectionInfo, name, isIntercepted) { | 233 function tearOffGetter(funcs, reflectionInfo, name, isIntercepted) { |
235 return isIntercepted | 234 return isIntercepted |
236 ? new Function("funcs", "reflectionInfo", "name", | 235 ? new Function("funcs", "reflectionInfo", "name", |
237 #tearOffGlobalObjectString, "c", | 236 #tearOffGlobalObjectString, "c", |
238 "return function tearOff_" + name + (functionCounter++) + "(x) {" + | 237 "return function tearOff_" + name + (functionCounter++) + "(x) {" + |
239 "if (c === null) c = " + #tearOffAccessText + "(" + | 238 "if (c === null) c = " + #tearOffAccessText + "(" + |
240 "this, funcs, reflectionInfo, false, [x], name);" + | 239 "this, funcs, reflectionInfo, false, [x], name);" + |
241 "return new c(this, funcs[0], x, name);" + | 240 "return new c(this, funcs[0], x, name);" + |
242 "}")(funcs, reflectionInfo, name, #tearOffGlobalObject, null) | 241 "}")(funcs, reflectionInfo, name, #tearOffGlobalObject, null) |
243 : new Function("funcs", "reflectionInfo", "name", | 242 : new Function("funcs", "reflectionInfo", "name", |
244 #tearOffGlobalObjectString, "c", | 243 #tearOffGlobalObjectString, "c", |
245 "return function tearOff_" + name + (functionCounter++)+ "() {" + | 244 "return function tearOff_" + name + (functionCounter++)+ "() {" + |
246 "if (c === null) c = " + #tearOffAccessText + "(" + | 245 "if (c === null) c = " + #tearOffAccessText + "(" + |
247 "this, funcs, reflectionInfo, false, [], name);" + | 246 "this, funcs, reflectionInfo, false, [], name);" + |
248 "return new c(this, funcs[0], null, name);" + | 247 "return new c(this, funcs[0], null, name);" + |
249 "}")(funcs, reflectionInfo, name, #tearOffGlobalObject, null); | 248 "}")(funcs, reflectionInfo, name, #tearOffGlobalObject, null); |
250 }''', {'tearOffAccessText': tearOffAccessText, | 249 }''', |
251 'tearOffGlobalObject': tearOffGlobalObject, | 250 { |
252 'tearOffGlobalObjectString': tearOffGlobalObjectString}); | 251 'tearOffAccessText': tearOffAccessText, |
| 252 'tearOffGlobalObject': tearOffGlobalObject, |
| 253 'tearOffGlobalObjectString': tearOffGlobalObjectString |
| 254 }); |
253 } else { | 255 } else { |
254 tearOffGetter = js.statement(''' | 256 tearOffGetter = js.statement( |
| 257 ''' |
255 function tearOffGetter(funcs, reflectionInfo, name, isIntercepted) { | 258 function tearOffGetter(funcs, reflectionInfo, name, isIntercepted) { |
256 var cache = null; | 259 var cache = null; |
257 return isIntercepted | 260 return isIntercepted |
258 ? function(x) { | 261 ? function(x) { |
259 if (cache === null) cache = #( | 262 if (cache === null) cache = #( |
260 this, funcs, reflectionInfo, false, [x], name); | 263 this, funcs, reflectionInfo, false, [x], name); |
261 return new cache(this, funcs[0], x, name); | 264 return new cache(this, funcs[0], x, name); |
262 } | 265 } |
263 : function() { | 266 : function() { |
264 if (cache === null) cache = #( | 267 if (cache === null) cache = #( |
265 this, funcs, reflectionInfo, false, [], name); | 268 this, funcs, reflectionInfo, false, [], name); |
266 return new cache(this, funcs[0], null, name); | 269 return new cache(this, funcs[0], null, name); |
267 }; | 270 }; |
268 }''', [tearOffAccessExpression, tearOffAccessExpression]); | 271 }''', |
| 272 [tearOffAccessExpression, tearOffAccessExpression]); |
269 } | 273 } |
270 | 274 |
271 jsAst.Statement tearOff = js.statement(''' | 275 jsAst.Statement tearOff = js.statement( |
| 276 ''' |
272 function tearOff(funcs, reflectionInfo, isStatic, name, isIntercepted) { | 277 function tearOff(funcs, reflectionInfo, isStatic, name, isIntercepted) { |
273 var cache; | 278 var cache; |
274 return isStatic | 279 return isStatic |
275 ? function() { | 280 ? function() { |
276 if (cache === void 0) cache = #tearOff( | 281 if (cache === void 0) cache = #tearOff( |
277 this, funcs, reflectionInfo, true, [], name).prototype; | 282 this, funcs, reflectionInfo, true, [], name).prototype; |
278 return cache; | 283 return cache; |
279 } | 284 } |
280 : tearOffGetter(funcs, reflectionInfo, name, isIntercepted); | 285 : tearOffGetter(funcs, reflectionInfo, name, isIntercepted); |
281 }''', {'tearOff': tearOffAccessExpression}); | 286 }''', |
| 287 {'tearOff': tearOffAccessExpression}); |
282 | 288 |
283 return <jsAst.Statement>[tearOffGetter, tearOff]; | 289 return <jsAst.Statement>[tearOffGetter, tearOff]; |
284 } | 290 } |
OLD | NEW |