OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 /** A dynamic member stub. */ | |
6 class VarMember { | |
7 final String name; | |
8 bool isGenerated = false; | |
9 | |
10 VarMember(this.name); | |
11 | |
12 abstract void generate(CodeWriter code); | |
13 | |
14 String get body() => null; | |
15 | |
16 Type get returnType() => world.varType; | |
17 | |
18 Value invoke(CallingContext context, Node node, Value target, Arguments args)
{ | |
19 return new Value(returnType, | |
20 '${target.code}.$name(${args.getCode()})', node.span); | |
21 } | |
22 } | |
23 | |
24 /** | |
25 * This function generates a dynamic call stub for functions. It's part of a | |
26 * series of steps described below. Most of the code is generated by | |
27 * gen.dart, with some helpers in core.js | |
28 * | |
29 * Given a call site in Dart like: | |
30 * f(1, 2, capture:true); | |
31 * | |
32 * We compile to JS like: | |
33 * f.call$2$capture(1, 2, true); | |
34 * | |
35 * And then generate this function: | |
36 * Function.prototype.call$2$capture = function($0, $1, capture) { | |
37 * this.call$2$capture = this.$genStub(3, ['capture']); | |
38 * return this.call$2$capture($0, $1, capture); | |
39 * } | |
40 * | |
41 * Or for a fixed-arity function, generate this: | |
42 * Function.prototype.call$2 = function($0, $1) { | |
43 * return this.to$call$2()($0, $1); | |
44 * } | |
45 * Function.prototype.to$call$2 = function() { | |
46 * this.call$2 = this.$genStub(2); | |
47 * this.to$call$2 = function() { return this.call$2; } | |
48 * return this.to$call$2(); | |
49 * } | |
50 * We use .to$call$2 to convert to a typed function. | |
51 * | |
52 * For each method that can be passed as a function, such as a "get" on the | |
53 * method or is a lambda, generate optional argument info. Given a function | |
54 * like: | |
55 * class SomeType { | |
56 * void add(x, y, [bubbles = true, capture = false]) { ... } | |
57 * ... } | |
58 * | |
59 * The generated argument info looks like: | |
60 * SomeType.prototype.add.$optional = ['bubbles','capture', 'true','false']; | |
61 */ | |
62 // TODO(jmesserly): we don't currently put $optional on lambdas. | |
63 // Also, maybe a string encoding would perform better? | |
64 // TODO(jmesserly): _genStub is a hole in the run-time type checker. | |
65 // It bypasses the checks we would do at the callsite for methods. | |
66 // Also, it won't work properly for native JS functions (those don't have | |
67 // an accurate .length) | |
68 class VarFunctionStub extends VarMember { | |
69 final Arguments args; | |
70 | |
71 VarFunctionStub(String name, Arguments callArgs) | |
72 : super(name), args = callArgs.toCallStubArgs() { | |
73 // Ensure dependency is generated | |
74 world.functionImplType.markUsed(); | |
75 world.gen.genMethod(world.functionImplType.getMember('_genStub')); | |
76 } | |
77 | |
78 Value invoke(CallingContext context, Node node, Value target, | |
79 Arguments args) { | |
80 return super.invoke(context, node, target, args); | |
81 } | |
82 | |
83 void generate(CodeWriter code) { | |
84 isGenerated = true; | |
85 if (args.hasNames) { | |
86 generateNamed(code); | |
87 } else { | |
88 generatePositional(code); | |
89 } | |
90 } | |
91 | |
92 void generatePositional(CodeWriter w) { | |
93 // Positional arg functions can be converted from "var" to a fixed arity | |
94 // function type. So emit a to$N stub as well as the call$N stub. | |
95 int arity = args.length; | |
96 w.enterBlock('Function.prototype.to\$$name = function() {'); | |
97 w.writeln('this.$name = this._genStub($arity);'); | |
98 w.writeln('this.to\$$name = function() { return this.$name; };'); | |
99 w.writeln('return this.$name;'); | |
100 w.exitBlock('};'); | |
101 var argsCode = args.getCode(); | |
102 w.enterBlock('Function.prototype.$name = function(${argsCode}) {'); | |
103 w.writeln('return this.to\$$name()($argsCode);'); | |
104 w.exitBlock('};'); | |
105 | |
106 // TODO(jmesserly): HACK, we couldn't allocate temps from Value, so we | |
107 // needed this stub to check for null. | |
108 w.writeln('function to\$$name(f) { return f && f.to\$$name(); }'); | |
109 } | |
110 | |
111 void generateNamed(CodeWriter w) { | |
112 // Named functions use simpler stubs, because we never convert to a named | |
113 // stub type. | |
114 var named = Strings.join(args.getNames(), '", "'); | |
115 var argsCode = args.getCode(); | |
116 w.enterBlock('Function.prototype.$name = function(${argsCode}) {'); | |
117 w.writeln('this.$name = this._genStub(${args.length}, ["$named"]);'); | |
118 w.writeln('return this.$name($argsCode);'); | |
119 w.exitBlock('}'); | |
120 } | |
121 } | |
122 | |
123 class VarMethodStub extends VarMember { | |
124 final Member member; | |
125 final Arguments args; | |
126 final String body; | |
127 | |
128 VarMethodStub(String name, this.member, this.args, this.body): super(name); | |
129 | |
130 bool get isHidden() => | |
131 member != null ? member.declaringType.isHiddenNativeType : false; | |
132 | |
133 Type get returnType() => | |
134 member != null ? member.returnType : world.varType; | |
135 | |
136 Type get declaringType() => | |
137 member != null ? member.declaringType : world.objectType; | |
138 | |
139 void generate(CodeWriter code) { | |
140 isGenerated = true; | |
141 if (!isHidden && _useDirectCall(args)) { | |
142 world.gen._writePrototypePatch(declaringType, name, | |
143 world.gen._prototypeOf(declaringType, member.jsname), code); | |
144 } else { | |
145 String suffix = world.gen._writePrototypePatch(declaringType, name, | |
146 'function(${args.getCode()}) {', code, false); | |
147 if (!suffix.endsWith(';')) { | |
148 suffix += ';'; | |
149 } | |
150 if (_needsExactTypeCheck()) { | |
151 code.enterBlock( | |
152 'if (Object.getPrototypeOf(this).hasOwnProperty("$name")) {'); | |
153 code.writeln('$body;'); | |
154 code.exitBlock('}'); | |
155 String argsCode = args.getCode(); | |
156 if (argsCode != '') argsCode = ', ' + argsCode; | |
157 code.writeln('return Object.prototype.$name.call(this$argsCode);'); | |
158 code.exitBlock(suffix); | |
159 } else { | |
160 code.writeln('$body;'); | |
161 code.exitBlock(suffix); | |
162 } | |
163 } | |
164 } | |
165 | |
166 /** | |
167 * If we have a native method overridden by a hidden native method, we need to | |
168 * make sure the base one has an exact type test. Otherwise we don't need | |
169 * this. | |
170 */ | |
171 bool _needsExactTypeCheck() { | |
172 if (member == null || member.declaringType.isObject) return false; | |
173 | |
174 var members = member.potentialMemberSet.members; | |
175 return members.filter((m) => m != member | |
176 && m.declaringType.isHiddenNativeType).length >= 1; | |
177 } | |
178 | |
179 bool _useDirectCall(Arguments args) { | |
180 // Create direct stubs when we can. We don't do this in some cases, such as | |
181 // types that have native subtypes (like Object), otherwise things like | |
182 // Object.prototype.toString$0 end up calling the toString on Object instead | |
183 // of on the derived type. | |
184 if (member is MethodMember && !member.declaringType.hasNativeSubtypes) { | |
185 MethodMember method = member; | |
186 if (method.needsArgumentConversion(args)) { | |
187 return false; | |
188 } | |
189 | |
190 // If we have the right number of parameters, or all defaults would be | |
191 // filled in as "undefined" anyway, we can just call the method directly. | |
192 for (int i = args.length; i < method.parameters.length; i++) { | |
193 if (method.parameters[i].value.code != 'null') { | |
194 return false; | |
195 } | |
196 } | |
197 return method.namesInHomePositions(args); | |
198 } else { | |
199 return false; | |
200 } | |
201 } | |
202 } | |
203 | |
204 /** | |
205 * A special member with a mangled name that represents a dynamic call | |
206 * (i.e. a call with multiple valid targets). We generate this if we have | |
207 * a dynamic call that needs different implementation methods for different | |
208 * members. | |
209 */ | |
210 class VarMethodSet extends VarMember { | |
211 final String baseName; | |
212 final List<Member> members; | |
213 final Type returnType; | |
214 final Arguments args; | |
215 | |
216 bool invoked = false; | |
217 | |
218 VarMethodSet(this.baseName, String name, this.members, Arguments callArgs, | |
219 this.returnType) | |
220 : super(name), args = callArgs.toCallStubArgs() { | |
221 } | |
222 | |
223 Value invoke(CallingContext context, Node node, Value target, | |
224 Arguments args) { | |
225 _invokeMembers(context, node); | |
226 return super.invoke(context, node, target, args); | |
227 } | |
228 | |
229 /** Invokes members to ensure they're generated. */ | |
230 _invokeMembers(CallingContext context, Node node) { | |
231 if (invoked) return; | |
232 invoked = true; | |
233 | |
234 bool hasObjectType = false; | |
235 for (var member in members) { | |
236 // Invoke the member with the stub args (this gives us the method body), | |
237 // then create the stub method. | |
238 final type = member.declaringType; | |
239 final target = new Value(type, 'this', node.span); | |
240 var result = member.invoke(context, node, target, args); | |
241 var stub = new VarMethodStub(name, member, args, 'return ${result.code}'); | |
242 type.varStubs[stub.name] = stub; | |
243 if (type.isObject) hasObjectType = true; | |
244 } | |
245 | |
246 // Create a noSuchMethod fallback on Object if needed. | |
247 // Some methods, like toString and == already have a fallback on Object. | |
248 if (!hasObjectType) { | |
249 final target = new Value(world.objectType, 'this', node.span); | |
250 var result = target.invokeNoSuchMethod(context, baseName, node, args); | |
251 var stub = new VarMethodStub(name, null, args, 'return ${result.code}'); | |
252 world.objectType.varStubs[stub.name] = stub; | |
253 } | |
254 } | |
255 | |
256 // TODO(jmesserly): get rid of this as it's unused now | |
257 void generate(CodeWriter code) {} | |
258 } | |
259 | |
260 String _getCallStubName(String name, Arguments args) { | |
261 // The stub name should not collide with any user declared method name since | |
262 // the '$'s in the stub name are always alone and world.toJsIdentifier doubles | |
263 // up those in a user declared method name. | |
264 final nameBuilder = new StringBuffer('${name}\$${args.bareCount}'); | |
265 for (int i = args.bareCount; i < args.length; i++) { | |
266 var argName = args.getName(i); | |
267 nameBuilder.add('\$'); | |
268 if (argName.contains('\$')) { | |
269 // Disambiguate "a:b:" from "a$b:". Prefixing the length works because | |
270 // parameter names can't start with digits. | |
271 nameBuilder.add('${argName.length}'); | |
272 } | |
273 nameBuilder.add(argName); | |
274 } | |
275 return nameBuilder.toString(); | |
276 } | |
OLD | NEW |