diff --git a/lib/src/model/field.dart b/lib/src/model/field.dart index 0232a2d577..a1b74409ff 100644 --- a/lib/src/model/field.dart +++ b/lib/src/model/field.dart @@ -92,6 +92,9 @@ class Field extends ModelElement return field.isFinal; } + @override + bool get isLate => isFinal && field.isLate; + @override bool get isInherited => _isInherited; diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index 3ef9b30a6b..671a1bd816 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -43,6 +43,7 @@ const Map featureOrder = { 'read / write': 1, 'covariant': 2, 'final': 2, + 'late': 2, 'inherited': 3, 'inherited-getter': 3, 'inherited-setter': 3, @@ -499,6 +500,7 @@ abstract class ModelElement extends Canonicalization // const and static are not needed here because const/static elements get // their own sections in the doc. if (isFinal) allFeatures.add('final'); + if (isLate) allFeatures.add('late'); return allFeatures; } @@ -897,6 +899,8 @@ abstract class ModelElement extends Canonicalization bool get isFinal => false; + bool get isLate => false; + bool get isLocalElement => element is LocalElement; bool get isPropertyAccessor => element is PropertyAccessorElement; diff --git a/lib/src/model/top_level_variable.dart b/lib/src/model/top_level_variable.dart index 2d93a8c174..3b53961799 100644 --- a/lib/src/model/top_level_variable.dart +++ b/lib/src/model/top_level_variable.dart @@ -65,6 +65,9 @@ class TopLevelVariable extends ModelElement return _variable.isFinal; } + @override + bool get isLate => isFinal && _variable.isLate; + @override String get kind => isConst ? 'top-level constant' : 'top-level property'; diff --git a/test/model_special_cases_test.dart b/test/model_special_cases_test.dart index c8575a70db..fdcd82792b 100644 --- a/test/model_special_cases_test.dart +++ b/test/model_special_cases_test.dart @@ -36,19 +36,42 @@ void main() { .firstWhere((lib) => lib.name == 'late_final_without_initializer'); }); - test('Late finals test', () { + test('Late final class member test', () { Class c = lateFinalWithoutInitializer.allClasses .firstWhere((c) => c.name == 'C'); Field a = c.allFields.firstWhere((f) => f.name == 'a'); Field b = c.allFields.firstWhere((f) => f.name == 'b'); Field cField = c.allFields.firstWhere((f) => f.name == 'cField'); Field dField = c.allFields.firstWhere((f) => f.name == 'dField'); - // If nnbd isn't enabled, fields named 'late' come back from the analyzer. + + // If nnbd isn't enabled, fields named 'late' come back from the analyzer + // instead of setting up 'isLate'. expect(c.allFields.any((f) => f.name == 'late'), isFalse); + expect(a.modelType.returnType.name, equals('dynamic')); + expect(a.isLate, isTrue); + expect(a.features, contains('late')); + expect(b.modelType.returnType.name, equals('int')); + expect(b.isLate, isTrue); + expect(b.features, contains('late')); + expect(cField.modelType.returnType.name, equals('dynamic')); + expect(cField.isLate, isTrue); + expect(cField.features, contains('late')); + expect(dField.modelType.returnType.name, equals('double')); + expect(dField.isLate, isTrue); + expect(dField.features, contains('late')); + }); + + test('Late final top level variables', () { + TopLevelVariable initializeMe = lateFinalWithoutInitializer + .publicProperties + .firstWhere((v) => v.name == 'initializeMe'); + expect(initializeMe.modelType.returnType.name, equals('String')); + expect(initializeMe.isLate, isTrue); + expect(initializeMe.features, contains('late')); }); }); diff --git a/test/model_test.dart b/test/model_test.dart index 1d74f7e909..c67bf45fff 100644 --- a/test/model_test.dart +++ b/test/model_test.dart @@ -2513,6 +2513,7 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, Field explicitGetterSetter; Field explicitNonDocumentedInBaseClassGetter; Field documentedPartialFieldInSubclassOnly; + Field finalProperty; Field ExtraSpecialListLength; Field aProperty; Field covariantField, covariantSetter; @@ -2539,6 +2540,8 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, (e) => e.name == 'explicitNonDocumentedInBaseClassGetter'); documentedPartialFieldInSubclassOnly = UnusualProperties.allModelElements .firstWhere((e) => e.name == 'documentedPartialFieldInSubclassOnly'); + finalProperty = UnusualProperties.allModelElements + .firstWhere((e) => e.name == 'finalProperty'); isEmpty = CatString.allInstanceFields.firstWhere((p) => p.name == 'isEmpty'); @@ -2774,8 +2777,18 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, expect(constField.isConst, isTrue); }); - test('is not final', () { + test('is final only when appropriate', () { expect(f1.isFinal, isFalse); + expect(finalProperty.isFinal, isTrue); + expect(finalProperty.isLate, isFalse); + expect(finalProperty.features, contains('final')); + expect(finalProperty.features, isNot(contains('late'))); + expect(onlySetter.isFinal, isFalse); + expect(onlySetter.features, isNot(contains('final'))); + expect(onlySetter.features, isNot(contains('late'))); + expect(dynamicGetter.isFinal, isFalse); + expect(dynamicGetter.features, isNot(contains('final'))); + expect(dynamicGetter.features, isNot(contains('late'))); }); test('is not static', () { @@ -2885,11 +2898,13 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, TopLevelVariable setAndGet, mapWithDynamicKeys; TopLevelVariable nodocGetter, nodocSetter; TopLevelVariable complicatedReturn; - TopLevelVariable importantComputations; + TopLevelVariable meaningOfLife, importantComputations; setUpAll(() { v = exLibrary.properties.firstWhere((p) => p.name == 'number'); v3 = exLibrary.properties.firstWhere((p) => p.name == 'y'); + meaningOfLife = + fakeLibrary.properties.firstWhere((v) => v.name == 'meaningOfLife'); importantComputations = fakeLibrary.properties .firstWhere((v) => v.name == 'importantComputations'); complicatedReturn = fakeLibrary.properties @@ -2908,6 +2923,19 @@ String topLevelFunction(int param1, bool param2, Cool coolBeans, .firstWhere((p) => p.name == 'mapWithDynamicKeys'); }); + test('Verify that final and late show up (or not) appropriately', () { + expect(meaningOfLife.isFinal, isTrue); + expect(meaningOfLife.isLate, isFalse); + expect(meaningOfLife.features, contains('final')); + expect(meaningOfLife.features, isNot(contains('late'))); + expect(justGetter.isFinal, isFalse); + expect(justGetter.features, isNot(contains('final'))); + expect(justGetter.features, isNot(contains('late'))); + expect(justSetter.isFinal, isFalse); + expect(justSetter.features, isNot(contains('final'))); + expect(justSetter.features, isNot(contains('late'))); + }); + test( 'Verify that a map containing anonymous functions as values works correctly', () { diff --git a/testing/test_package_experiments/lib/late_final_without_initializer.dart b/testing/test_package_experiments/lib/late_final_without_initializer.dart index 3cf903ccad..27765b26a2 100644 --- a/testing/test_package_experiments/lib/late_final_without_initializer.dart +++ b/testing/test_package_experiments/lib/late_final_without_initializer.dart @@ -4,6 +4,8 @@ import 'dart:math'; +late final String initializeMe; + class C { late final a; late final b = 0;