| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | import 'package:_fe_analyzer_shared/src/field_promotability.dart'; | 
 | import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis_operations.dart'; | 
 | import 'package:checks/checks.dart'; | 
 | import 'package:test/scaffolding.dart'; | 
 |  | 
 | main() { | 
 |   test('final private field is promotable', () { | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason).equals(null); | 
 |   }); | 
 |  | 
 |   test('final public field is not promotable', () { | 
 |     var f = Field('f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     // Note that the map returned by `_TestFieldPromotability.run` is just the | 
 |     // map of *private* field names that are unpromotable, so even though `f` | 
 |     // is not promotable, the returned map is empty. | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotPrivate); | 
 |   }); | 
 |  | 
 |   test('non-final private field is not promotable', () { | 
 |     var f = Field('_f'); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |     check(nonPromotabilityInfo['_f']!.conflictingFields).unorderedEquals([f]); | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotFinal); | 
 |   }); | 
 |  | 
 |   test('public dominates non-final', () { | 
 |     // If a field is both public and non-final, the fact that it is public is | 
 |     // used as the non-promotability reason. | 
 |     var f = Field('f'); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo.keys).isEmpty; | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotPrivate); | 
 |   }); | 
 |  | 
 |   test('external private final field is not promotable', () { | 
 |     var f = Field('_f', isFinal: true, isExternal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isExternal); | 
 |   }); | 
 |  | 
 |   test('public dominates external', () { | 
 |     // If a field is both public and external, the fact that it is public is | 
 |     // used as the non-promotability reason. | 
 |     var f = Field('f', isFinal: true, isExternal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo.keys).isEmpty; | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotPrivate); | 
 |   }); | 
 |  | 
 |   test('external dominates non-final', () { | 
 |     // If a field is both external and non-final, the fact that it is external | 
 |     // is used as the non-promotability reason. | 
 |     var f = Field('_f', isFinal: false, isExternal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c]); | 
 |     check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isExternal); | 
 |   }); | 
 |  | 
 |   group('concrete getter renders a private field non-promotable:', () { | 
 |     test('in a concrete class', () { | 
 |       var c = Class(fields: [Field('_f', isFinal: true)]); | 
 |       var getter = Getter('_f'); | 
 |       var d = Class(getters: [getter]); | 
 |       var nonPromotabilityInfo = _TestFieldPromotability().run([c, d]); | 
 |       check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |       check(nonPromotabilityInfo['_f']!.conflictingGetters) | 
 |           .unorderedEquals([getter]); | 
 |       check(getter.nonPromotabilityReason) | 
 |           .equals(PropertyNonPromotabilityReason.isNotField); | 
 |     }); | 
 |  | 
 |     test('in an abstract class', () { | 
 |       var c = Class(fields: [Field('_f', isFinal: true)]); | 
 |       var getter = Getter('_f'); | 
 |       var d = Class(isAbstract: true, getters: [getter]); | 
 |       var nonPromotabilityInfo = _TestFieldPromotability().run([c, d]); | 
 |       check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |       check(nonPromotabilityInfo['_f']!.conflictingGetters) | 
 |           .unorderedEquals([getter]); | 
 |       check(getter.nonPromotabilityReason) | 
 |           .equals(PropertyNonPromotabilityReason.isNotField); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('abstract getter does not render a private field non-promotable', () { | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var getter = Getter('_f', isAbstract: true); | 
 |     var d = Class(isAbstract: true, getters: [getter]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason).equals(null); | 
 |     check(getter.nonPromotabilityReason).equals(null); | 
 |   }); | 
 |  | 
 |   test('public concrete getter is ignored', () { | 
 |     // Since public fields are never promotable, there's no need for the | 
 |     // algorithm to keep track of public concrete getters. | 
 |     var f = Field('f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var getter = Getter('f'); | 
 |     var d = Class(getters: [getter]); | 
 |     // Therefore the map returned by `_TestFieldPromotability.run` is empty. | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotPrivate); | 
 |     check(getter.nonPromotabilityReason) | 
 |         .equals(PropertyNonPromotabilityReason.isNotPrivate); | 
 |   }); | 
 |  | 
 |   group('unimplemented getter renders a field non-promotable:', () { | 
 |     test('induced by getter', () { | 
 |       var f = Field('_f', isFinal: true); | 
 |       var c = Class(fields: [f]); | 
 |       var getter = Getter('_f', isAbstract: true); | 
 |       var d = Class(isAbstract: true, getters: [getter]); | 
 |       var e = Class(implements: [d]); | 
 |       var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |       check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |       check(nonPromotabilityInfo['_f']!.conflictingNsmClasses) | 
 |           .unorderedEquals([e]); | 
 |       check(getter.nonPromotabilityReason).equals(null); | 
 |     }); | 
 |  | 
 |     test('induced by field', () { | 
 |       var f = Field('_f', isFinal: true); | 
 |       var c = Class(fields: [f]); | 
 |       var d = Class(isAbstract: true, fields: [Field('_f', isFinal: true)]); | 
 |       var e = Class(implements: [d]); | 
 |       var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |       check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |       check(nonPromotabilityInfo['_f']!.conflictingNsmClasses) | 
 |           .unorderedEquals([e]); | 
 |     }); | 
 |   }); | 
 |  | 
 |   test('unimplemented getter in an abstract class is ok', () { | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var getter = Getter('_f', isAbstract: true); | 
 |     var d = Class(isAbstract: true, getters: [getter]); | 
 |     var e = Class(isAbstract: true, implements: [d]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason).equals(null); | 
 |     check(getter.nonPromotabilityReason).equals(null); | 
 |   }); | 
 |  | 
 |   test('unimplemented abstract field renders a field non-promotable:', () { | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var d = Class( | 
 |         isAbstract: true, | 
 |         fields: [Field('_f', isAbstract: true, isFinal: true)]); | 
 |     var e = Class(extendsOrMixesIn: [d]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |     check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |     check(nonPromotabilityInfo['_f']!.conflictingNsmClasses) | 
 |         .unorderedEquals([e]); | 
 |   }); | 
 |  | 
 |   test('implementations are inherited transitively', () { | 
 |     // `e` inherits `f` from `c` via `d`, so no `noSuchMethod` forwarder is | 
 |     // needed, and therefore promotion is allowed. | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var d = Class(extendsOrMixesIn: [c]); | 
 |     var e = Class(extendsOrMixesIn: [d], implements: [c]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |     check(nonPromotabilityInfo).isEmpty(); | 
 |     check(f.nonPromotabilityReason).equals(null); | 
 |   }); | 
 |  | 
 |   test('interfaces are inherited transitively', () { | 
 |     // `e` inherits the interface for `f` from `c` via `d`, so a `noSuchMethod` | 
 |     // forwarder is needed, and therefore promotion is not allowed. | 
 |     var f = Field('_f', isFinal: true); | 
 |     var c = Class(fields: [f]); | 
 |     var d = Class(isAbstract: true, implements: [c]); | 
 |     var e = Class(implements: [d]); | 
 |     var nonPromotabilityInfo = _TestFieldPromotability().run([c, d, e]); | 
 |     check(nonPromotabilityInfo.keys).unorderedEquals({'_f'}); | 
 |     check(nonPromotabilityInfo['_f']!.conflictingNsmClasses) | 
 |         .unorderedEquals([e]); | 
 |   }); | 
 |  | 
 |   test('class hierarchy circularities are handled', () { | 
 |     // Since it's a compile error to have a circularity in the class hierarchy, | 
 |     // all we need to check is that the algorithm terminates; we don't check the | 
 |     // result. | 
 |     var c = Class(extendsOrMixesIn: []); | 
 |     var d = Class(extendsOrMixesIn: [c]); | 
 |     c.extendsOrMixesIn.add(d); | 
 |     var e = Class(extendsOrMixesIn: [d]); | 
 |     _TestFieldPromotability().run([c, d, e]); | 
 |   }); | 
 | } | 
 |  | 
 | class Class { | 
 |   final List<Class> extendsOrMixesIn; | 
 |   final List<Class> implements; | 
 |   final bool isAbstract; | 
 |   final List<Field> fields; | 
 |   final List<Getter> getters; | 
 |  | 
 |   Class( | 
 |       {this.extendsOrMixesIn = const [], | 
 |       this.implements = const [], | 
 |       this.isAbstract = false, | 
 |       this.fields = const [], | 
 |       this.getters = const []}); | 
 | } | 
 |  | 
 | class Field { | 
 |   final String name; | 
 |   final bool isFinal; | 
 |   final bool isAbstract; | 
 |   final bool isExternal; | 
 |   late final PropertyNonPromotabilityReason? nonPromotabilityReason; | 
 |  | 
 |   Field(this.name, | 
 |       {this.isFinal = false, this.isAbstract = false, this.isExternal = false}); | 
 | } | 
 |  | 
 | class Getter { | 
 |   final String name; | 
 |   final bool isAbstract; | 
 |   late final PropertyNonPromotabilityReason? nonPromotabilityReason; | 
 |  | 
 |   Getter(this.name, {this.isAbstract = false}); | 
 | } | 
 |  | 
 | class _TestFieldPromotability extends FieldPromotability<Class, Field, Getter> { | 
 |   @override | 
 |   Iterable<Class> getSuperclasses(Class class_, | 
 |       {required bool ignoreImplements}) { | 
 |     if (ignoreImplements) { | 
 |       return class_.extendsOrMixesIn; | 
 |     } else { | 
 |       return [...class_.extendsOrMixesIn, ...class_.implements]; | 
 |     } | 
 |   } | 
 |  | 
 |   Map<String, FieldNameNonPromotabilityInfo<Class, Field, Getter>> run( | 
 |       Iterable<Class> classes) { | 
 |     // Iterate through all the classes, enums, and mixins in the library, | 
 |     // recording the non-synthetic instance fields and getters of each. | 
 |     for (var class_ in classes) { | 
 |       var classInfo = addClass(class_, isAbstract: class_.isAbstract); | 
 |       for (var field in class_.fields) { | 
 |         field.nonPromotabilityReason = addField(classInfo, field, field.name, | 
 |             isFinal: field.isFinal, | 
 |             isAbstract: field.isAbstract, | 
 |             isExternal: field.isExternal); | 
 |       } | 
 |       for (var getter in class_.getters) { | 
 |         getter.nonPromotabilityReason = addGetter( | 
 |             classInfo, getter, getter.name, | 
 |             isAbstract: getter.isAbstract); | 
 |       } | 
 |     } | 
 |  | 
 |     // Compute field non-promotability info. | 
 |     return computeNonPromotabilityInfo(); | 
 |   } | 
 | } |