diff --git a/pkg/analyzer/lib/src/summary2/library_builder.dart b/pkg/analyzer/lib/src/summary2/library_builder.dart index c6c103394377..4e05c0c3127b 100644 --- a/pkg/analyzer/lib/src/summary2/library_builder.dart +++ b/pkg/analyzer/lib/src/summary2/library_builder.dart @@ -191,7 +191,7 @@ class LibraryBuilder { /// The fields that were speculatively created as [ConstFieldElementImpl], /// but we want to clear [ConstVariableElement.constantInitializer] for it /// if the class will not end up with a `const` constructor. We don't know - /// at the time when we create them, because of future augmentations, use + /// at the time when we create them, because of future augmentations, user /// written or macro generated. final Set finalInstanceFields = Set.identity(); @@ -555,6 +555,7 @@ class LibraryBuilder { augmentation: augmentation, ).perform(updateConstants: () { MacroUpdateConstantsForOptimizedCode( + libraryElement: element, unitNode: mergedUnit, codeEdits: optimizedCodeEdits, unitElement: unitElement, diff --git a/pkg/analyzer/lib/src/summary2/macro_merge.dart b/pkg/analyzer/lib/src/summary2/macro_merge.dart index 8550f626d11f..28063260d8c0 100644 --- a/pkg/analyzer/lib/src/summary2/macro_merge.dart +++ b/pkg/analyzer/lib/src/summary2/macro_merge.dart @@ -235,6 +235,9 @@ class MacroElementsMerger { } class MacroUpdateConstantsForOptimizedCode { + /// The container of [unitElement]. + final LibraryElementImpl libraryElement; + /// The parsed merged code. final ast.CompilationUnit unitNode; @@ -245,7 +248,11 @@ class MacroUpdateConstantsForOptimizedCode { /// The merged element, with elements in the same order as in [unitNode]. final CompilationUnitElementImpl unitElement; + /// The names of classes that have a `const` constructor. + final Set _namesOfConstClasses = {}; + MacroUpdateConstantsForOptimizedCode({ + required this.libraryElement, required this.unitNode, required this.codeEdits, required this.unitElement, @@ -263,6 +270,7 @@ class MacroUpdateConstantsForOptimizedCode { /// The same elements, in the same order. /// If not, we have a bug in [MacroElementsMerger]. void perform() { + _findConstClasses(); var nodeRecords = _orderedForNodes(); var elementRecords = _orderedForElement(); assert(nodeRecords.length == elementRecords.length); @@ -295,6 +303,21 @@ class MacroUpdateConstantsForOptimizedCode { } } + void _findConstClasses() { + for (var element in libraryElement.topLevelElements) { + if (element is! ClassElementImpl) continue; + if (element.isAugmentation) continue; + + var augmented = element.augmented; + if (augmented == null) continue; + + var hasConst = augmented.constructors.any((e) => e.isConst); + if (hasConst) { + _namesOfConstClasses.add(element.name); + } + } + } + List<(ElementImpl, ast.AstNodeImpl)> _orderedForElement() { var result = <(ElementImpl, ast.AstNodeImpl)>[]; @@ -390,11 +413,13 @@ class MacroUpdateConstantsForOptimizedCode { void addVariableList( ast.VariableDeclarationListImpl variableList, - List metadata, - ) { + List metadata, { + required bool withFinals, + }) { for (var variable in variableList.variables) { addMetadata(variable, metadata); - if (variableList.isConst) { + + if (variableList.isConst || variableList.isFinal && withFinals) { if (variable.initializer case var initializer?) { result.add((variable, initializer)); } @@ -402,10 +427,17 @@ class MacroUpdateConstantsForOptimizedCode { } } - void addInterfaceMembers(List members) { + void addInterfaceMembers( + List members, { + required bool hasConstConstructor, + }) { for (var field in members) { if (field is ast.FieldDeclarationImpl) { - addVariableList(field.fields, field.metadata); + addVariableList( + field.fields, + field.metadata, + withFinals: hasConstConstructor && !field.isStatic, + ); } } @@ -438,13 +470,22 @@ class MacroUpdateConstantsForOptimizedCode { for (var class_ in unitNode.declarations) { if (class_ is ast.ClassDeclarationImpl) { addAnnotatedNode(class_); - addInterfaceMembers(class_.members); + addInterfaceMembers( + class_.members, + hasConstConstructor: _namesOfConstClasses.contains( + class_.name.lexeme, + ), + ); } } for (var topVariable in unitNode.declarations) { if (topVariable is ast.TopLevelVariableDeclarationImpl) { - addVariableList(topVariable.variables, topVariable.metadata); + addVariableList( + topVariable.variables, + topVariable.metadata, + withFinals: false, + ); } } diff --git a/pkg/analyzer/test/src/summary/macro_test.dart b/pkg/analyzer/test/src/summary/macro_test.dart index d98e4d2cb76a..a1fe1cbfd068 100644 --- a/pkg/analyzer/test/src/summary/macro_test.dart +++ b/pkg/analyzer/test/src/summary/macro_test.dart @@ -2861,7 +2861,7 @@ augment class A { '''); } - test_codeOptimizer_constant_classField_constant() async { + test_codeOptimizer_constant_classField_const() async { newFile('$testPackageLibPath/a.dart', r''' const a = 0; '''); @@ -2913,6 +2913,107 @@ augment class B { '''); } + test_codeOptimizer_constant_classField_final_hasConstConstructor() async { + newFile('$testPackageLibPath/a.dart', r''' +const a = 0; +'''); + + var library = await buildLibrary(r''' +import 'append.dart'; +import 'a.dart'; + +@DeclareInType(' final x = {{package:test/a.dart@a}};') +class B { + const B(); +} +'''); + + configuration.forCodeOptimizer(); + checkElementText(library, r''' +library + imports + package:test/append.dart + package:test/a.dart + augmentationImports + package:test/test.macro.dart + macroGeneratedCode +--- +library augment 'test.dart'; + +import 'package:test/a.dart'; + +augment class B { + final x = a; +} +--- + imports + package:test/a.dart + definingUnit + classes + augment class B @75 + augmentationTarget: self::@class::B + fields + final x @87 + type: int + shouldUseTypeForInitializerInference: false + constantInitializer + SimpleIdentifier + token: a @91 + staticElement: package:test/a.dart::@getter::a + staticType: int + accessors + synthetic get x @-1 + returnType: int +'''); + } + + test_codeOptimizer_constant_classField_final_noConstConstructor() async { + newFile('$testPackageLibPath/a.dart', r''' +const a = 0; +'''); + + var library = await buildLibrary(r''' +import 'append.dart'; +import 'a.dart'; + +@DeclareInType(' final x = {{package:test/a.dart@a}};') +class B {} +'''); + + configuration.forCodeOptimizer(); + checkElementText(library, r''' +library + imports + package:test/append.dart + package:test/a.dart + augmentationImports + package:test/test.macro.dart + macroGeneratedCode +--- +library augment 'test.dart'; + +import 'package:test/a.dart'; + +augment class B { + final x = a; +} +--- + imports + package:test/a.dart + definingUnit + classes + augment class B @75 + augmentationTarget: self::@class::B + fields + final x @87 + type: int + shouldUseTypeForInitializerInference: false + accessors + synthetic get x @-1 + returnType: int +'''); + } + test_codeOptimizer_constant_classField_namedType() async { newFile('$testPackageLibPath/a.dart', r''' class A {}