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

Side by Side Diff: pkg/dev_compiler/lib/src/compiler/property_model.dart

Issue 2803673007: fix #29233, final fields can be settable in a mock (Closed)
Patch Set: fix Created 3 years, 8 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
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, 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 import 'dart:collection' show HashMap, HashSet, Queue; 5 import 'dart:collection' show HashMap, HashSet, Queue;
6 6
7 import 'package:analyzer/dart/ast/ast.dart' show Identifier;
8 import 'package:analyzer/dart/element/element.dart'; 7 import 'package:analyzer/dart/element/element.dart';
8 import 'package:analyzer/dart/element/type.dart' show InterfaceType;
9 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; 9 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl;
10 10
11 import '../js_ast/js_ast.dart' as JS; 11 import '../js_ast/js_ast.dart' as JS;
12 import 'element_helpers.dart'; 12 import 'element_helpers.dart';
13 import 'extension_types.dart';
13 import 'js_names.dart' as JS; 14 import 'js_names.dart' as JS;
14 15
15 /// Dart allows all fields to be overridden. 16 /// Dart allows all fields to be overridden.
16 /// 17 ///
17 /// To prevent a performance/code size penalty for allowing this, we analyze 18 /// To prevent a performance/code size penalty for allowing this, we analyze
18 /// private classes within each library that is being compiled to determine 19 /// private classes within each library that is being compiled to determine
19 /// if those fields should be virtual or not. In effect, we devirtualize fields 20 /// if those fields should be virtual or not. In effect, we devirtualize fields
20 /// when possible by analyzing the class hierarchy and using knowledge of 21 /// when possible by analyzing the class hierarchy and using knowledge of
21 /// which members are private and thus, could not be overridden outside of the 22 /// which members are private and thus, could not be overridden outside of the
22 /// current library. 23 /// current library.
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
177 /// The set of inherited getters, used because JS getters/setters are paired, 178 /// The set of inherited getters, used because JS getters/setters are paired,
178 /// so if we're generating a setter we may need to emit a getter that calls 179 /// so if we're generating a setter we may need to emit a getter that calls
179 /// super. 180 /// super.
180 final inheritedGetters = new HashSet<String>(); 181 final inheritedGetters = new HashSet<String>();
181 182
182 /// The set of inherited setters, used because JS getters/setters are paired, 183 /// The set of inherited setters, used because JS getters/setters are paired,
183 /// so if we're generating a getter we may need to emit a setter that calls 184 /// so if we're generating a getter we may need to emit a setter that calls
184 /// super. 185 /// super.
185 final inheritedSetters = new HashSet<String>(); 186 final inheritedSetters = new HashSet<String>();
186 187
187 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem, 188 final mockMembers = <String, ExecutableElement>{};
188 Iterable<ExecutableElement> extensionMembers) { 189
190 final extensionMembers = new Set<ExecutableElement>();
191 final mixinExtensionMembers = new Set<ExecutableElement>();
192
193 ClassPropertyModel.build(ExtensionTypeSet extensionTypes,
194 VirtualFieldModel fieldModel, ClassElement classElem) {
189 // Visit superclasses to collect information about their fields/accessors. 195 // Visit superclasses to collect information about their fields/accessors.
190 // This is expensive so we try to collect everything in one pass. 196 // This is expensive so we try to collect everything in one pass.
191 for (var base in getSuperclasses(classElem)) { 197 for (var base in getSuperclasses(classElem)) {
192 for (var accessor in base.accessors) { 198 for (var accessor in base.accessors) {
193 // For getter/setter pairs only process them once. 199 // For getter/setter pairs only process them once.
194 if (accessor.correspondingGetter != null) continue; 200 if (accessor.correspondingGetter != null) continue;
195 201
196 var field = accessor.variable; 202 var field = accessor.variable;
197 var name = field.name;
198 // Ignore private names from other libraries. 203 // Ignore private names from other libraries.
199 if (Identifier.isPrivateName(name) && 204 if (field.isPrivate && accessor.library != classElem.library) {
200 accessor.library != classElem.library) {
201 continue; 205 continue;
202 } 206 }
203 207
204 if (field.getter?.isAbstract == false) inheritedGetters.add(name); 208 if (field.getter?.isAbstract == false) inheritedGetters.add(field.name);
205 if (field.setter?.isAbstract == false) inheritedSetters.add(name); 209 if (field.setter?.isAbstract == false) inheritedSetters.add(field.name);
206 } 210 }
207 } 211 }
208 212
209 var extensionNames = 213 _collectMockMembers(classElem.type);
210 new HashSet<String>.from(extensionMembers.map((e) => e.name)); 214 _collectExtensionMembers(extensionTypes, classElem);
215
216 var virtualAccessorNames = new HashSet<String>()
217 ..addAll(inheritedGetters)
218 ..addAll(inheritedSetters)
219 ..addAll(extensionMembers
220 .map((m) => m is PropertyAccessorElement ? m.variable.name : m.name))
221 ..addAll(mockMembers.values
222 .map((m) => m is PropertyAccessorElement ? m.variable.name : m.name));
211 223
212 // Visit accessors in the current class, and see if they need to be 224 // Visit accessors in the current class, and see if they need to be
213 // generated differently based on the inherited fields/accessors. 225 // generated differently based on the inherited fields/accessors.
214 for (var accessor in classElem.accessors) { 226 for (var accessor in classElem.accessors) {
215 // For getter/setter pairs only process them once. 227 // For getter/setter pairs only process them once.
216 if (accessor.correspondingGetter != null) continue; 228 if (accessor.correspondingGetter != null) continue;
217 // Also ignore abstract fields. 229 // Also ignore abstract fields.
218 if (accessor.isAbstract) continue; 230 if (accessor.isAbstract) continue;
219 231
220 var field = accessor.variable; 232 var field = accessor.variable;
221 var name = field.name; 233 var name = field.name;
222 // Is it a field? 234 // Is it a field?
223 if (!field.isSynthetic && field is FieldElementImpl) { 235 if (!field.isSynthetic && field is FieldElementImpl) {
224 if (inheritedGetters.contains(name) || 236 if (virtualAccessorNames.contains(name) ||
225 inheritedSetters.contains(name) ||
226 extensionNames.contains(name) ||
227 fieldModel.isVirtual(field)) { 237 fieldModel.isVirtual(field)) {
228 if (field.isStatic) { 238 if (field.isStatic) {
229 staticFieldOverrides.add(field); 239 staticFieldOverrides.add(field);
230 } else { 240 } else {
231 virtualFields[field] = new JS.TemporaryId(name); 241 virtualFields[field] = new JS.TemporaryId(name);
232 } 242 }
233 } 243 }
234 } 244 }
235 } 245 }
236 } 246 }
247
248 void _collectMockMembers(InterfaceType type) {
249 // TODO(jmesserly): every type with nSM will generate new stubs for all
250 // abstract members. For example:
251 //
252 // class C { m(); noSuchMethod(...) { ... } }
253 // class D extends C { m(); noSuchMethod(...) { ... } }
254 //
255 // We'll generate D.m even though it is not necessary.
256 //
257 // Doing better is a bit tricky, as our current codegen strategy for the
258 // mock methods encodes information about the number of arguments (and type
259 // arguments) that D expects.
260 var element = type.element;
261 if (!hasNoSuchMethod(element)) return;
262
263 // Collect all unimplemented members.
264 //
265 // Initially, we track abstract and concrete members separately, then
266 // remove concrete from the abstract set. This is done because abstract
267 // members are allowed to "override" concrete ones in Dart.
268 // (In that case, it will still be treated as a concrete member and can be
269 // called at runtime.)
270 var concreteMembers = new HashSet<String>();
271
272 void visit(InterfaceType type, bool isAbstract) {
273 if (type == null) return;
274 visit(type.superclass, isAbstract);
275 for (var m in type.mixins) visit(m, isAbstract);
276 for (var i in type.interfaces) visit(i, true);
277
278 for (var m in [type.methods, type.accessors].expand((m) => m)) {
279 if (isAbstract || m.isAbstract) {
280 mockMembers[m.name] = m;
281 } else if (!m.isStatic) {
282 concreteMembers.add(m.name);
283 }
284 }
285 }
286
287 visit(type, false);
288
289 concreteMembers.forEach(mockMembers.remove);
290 }
291
292 void _collectExtensionMembers(
293 ExtensionTypeSet extensionTypes, ClassElement element) {
294 if (extensionTypes.isNativeClass(element)) return;
295
296 // Collect all extension types we implement.
297 var type = element.type;
298 var types = extensionTypes.collectNativeInterfaces(element);
299 if (types.isEmpty) return;
300
301 // Collect all possible extension method names.
302 var possibleExtensions = new HashSet<String>();
303 for (var t in types) {
304 for (var m in [t.methods, t.accessors].expand((m) => m)) {
305 if (!m.isStatic && m.isPublic) possibleExtensions.add(m.name);
306 }
307 }
308
309 // Collect all of extension methods this type and its mixins implement.
310
311 void visitType(InterfaceType type, bool isMixin) {
312 for (var mixin in type.mixins) {
313 // If the mixin isn't native, make sure to visit it too, because those
314 // methods haven't been accounted for yet.
315 if (!extensionTypes.hasNativeSubtype(mixin)) visitType(mixin, true);
316 }
317 for (var m in [type.methods, type.accessors].expand((m) => m)) {
318 if (!m.isAbstract && possibleExtensions.contains(m.name)) {
319 (isMixin ? mixinExtensionMembers : extensionMembers).add(m);
320 }
321 }
322 }
323
324 visitType(type, false);
325
326 for (var m in mockMembers.values) {
327 if (possibleExtensions.contains(m.name)) extensionMembers.add(m);
328 }
329 }
237 } 330 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698