mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 20:51:50 +00:00
Macro. Build macro augmentation using the existing method.
The method is _buildAugmentationImport(), used for any other augmentation. To do this, we create a FileState instance that corresponds to the augmentation library. This way, we keep its content there, so that later we will able resolve it. For now, this allows reusing existing support for imports, and write a macro that references a type identifier from a library, that later is generated into a library import, and a prefixed referenced in code. Change-Id: I5affeaa07253f2464b1da255c6218c3d46a3d887 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326882 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
c562719814
commit
784b9d70e6
|
@ -35,7 +35,7 @@ void buildTests({required String packagePath}) {
|
|||
if (file_paths.isDart(pathContext, file.path)) {
|
||||
test(file.path, () {
|
||||
final content = file.readAsStringSync();
|
||||
if (content.contains('utf8.encode')) {
|
||||
if (content.contains('utf8.encode(')) {
|
||||
fail('Should not use `utf8.encode` before SDK 3.1');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@ import 'package:analyzer/src/utilities/uri_cache.dart';
|
|||
/// TODO(scheglov) Clean up the list of implicitly analyzed files.
|
||||
class AnalysisDriver implements AnalysisDriverGeneric {
|
||||
/// The version of data format, should be incremented on every format change.
|
||||
static const int DATA_VERSION = 306;
|
||||
static const int DATA_VERSION = 307;
|
||||
|
||||
/// The number of exception contexts allowed to write. Once this field is
|
||||
/// zero, we stop writing any new exception contexts in this process.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// 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 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart';
|
||||
|
@ -39,6 +40,7 @@ import 'package:analyzer/src/utilities/uri_cache.dart';
|
|||
import 'package:analyzer/src/workspace/workspace.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
@ -502,7 +504,14 @@ class FileState {
|
|||
FileStateRefreshResult refresh() {
|
||||
_invalidateCurrentUnresolvedData();
|
||||
|
||||
final rawFileState = _fsState.fileContentStrategy.get(path);
|
||||
final FileContent rawFileState;
|
||||
if (_fsState._macroFileContent case final macroFileContent?) {
|
||||
_fsState._macroFileContent = null;
|
||||
rawFileState = macroFileContent;
|
||||
} else {
|
||||
rawFileState = _fsState.fileContentStrategy.get(path);
|
||||
}
|
||||
|
||||
final contentChanged =
|
||||
_fileContent?.contentHash != rawFileState.contentHash;
|
||||
_fileContent = rawFileState;
|
||||
|
@ -1144,6 +1153,10 @@ class FileSystemState {
|
|||
|
||||
final FileSystemTestData? testData;
|
||||
|
||||
/// We set a value for this field when we are about to refresh the current
|
||||
/// macro [FileState]. During the refresh, this will is reset back to `null`.
|
||||
FileContent? _macroFileContent;
|
||||
|
||||
FileSystemState(
|
||||
this._logger,
|
||||
this._byteStore,
|
||||
|
@ -1263,6 +1276,10 @@ class FileSystemState {
|
|||
return _pathToFile[path];
|
||||
}
|
||||
|
||||
FileState? getExistingFromUri(Uri uri) {
|
||||
return _uriToFile[uri];
|
||||
}
|
||||
|
||||
/// Return the [FileState] for the given absolute [path]. The returned file
|
||||
/// has the last known state since if was last refreshed.
|
||||
/// TODO(scheglov) Merge with [getFileForPath2].
|
||||
|
@ -1657,6 +1674,12 @@ class LibraryFileKind extends LibraryOrAugmentationFileKind {
|
|||
|
||||
List<PartState>? _parts;
|
||||
|
||||
/// The synthetic augmentation import added to [augmentationImports] for
|
||||
/// the macro application result of this library. It is filled only if the
|
||||
/// library uses any macros, during linking or after loading the summary
|
||||
/// bundle.
|
||||
AugmentationImportWithFile? _macroImport;
|
||||
|
||||
LibraryCycle? _libraryCycle;
|
||||
|
||||
LibraryFileKind({
|
||||
|
@ -1749,6 +1772,60 @@ class LibraryFileKind extends LibraryOrAugmentationFileKind {
|
|||
}).toFixedList();
|
||||
}
|
||||
|
||||
AugmentationImportWithFile? addOrUpdateMacro(String code) {
|
||||
final pathContext = file._fsState.pathContext;
|
||||
final libraryFileName = pathContext.basename(file.path);
|
||||
final macroFileName =
|
||||
pathContext.setExtension(libraryFileName, '.macro.dart');
|
||||
|
||||
final augmentationContent = '''
|
||||
library augment '$libraryFileName';
|
||||
|
||||
$code
|
||||
''';
|
||||
|
||||
final contentBytes = utf8.encoder.convert(augmentationContent);
|
||||
final hashBytes = md5.convert(contentBytes).bytes;
|
||||
final hashStr = hex.encode(hashBytes);
|
||||
file._fsState._macroFileContent = StoredFileContent(
|
||||
content: augmentationContent,
|
||||
contentHash: hashStr,
|
||||
exists: true,
|
||||
);
|
||||
|
||||
final macroRelativeUri = uriCache.parse(macroFileName);
|
||||
final macroUri = uriCache.resolveRelative(file.uri, macroRelativeUri);
|
||||
|
||||
final macroFileResolution = file._fsState.getFileForUri(macroUri);
|
||||
if (macroFileResolution is! UriResolutionFile) return null;
|
||||
final macroFile = macroFileResolution.file;
|
||||
|
||||
final import = AugmentationImportWithFile(
|
||||
container: this,
|
||||
unlinked: UnlinkedAugmentationImportDirective(
|
||||
importKeywordOffset: -1,
|
||||
augmentKeywordOffset: -1,
|
||||
uri: macroFileName,
|
||||
),
|
||||
uri: DirectiveUriWithFile(
|
||||
relativeUriStr: macroFileName,
|
||||
relativeUri: macroRelativeUri,
|
||||
file: macroFile,
|
||||
),
|
||||
);
|
||||
_macroImport = import;
|
||||
|
||||
final lastImport = augmentationImports.lastOrNull;
|
||||
if (lastImport != null && lastImport.unlinked.uri == macroFileName) {
|
||||
augmentationImports[augmentationImports.length - 1] = import;
|
||||
// TODO(scheglov) refresh file
|
||||
} else {
|
||||
_augmentationImports = [...augmentationImports, import].toFixedList();
|
||||
}
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
@override
|
||||
void collectTransitive(Set<FileState> files) {
|
||||
super.collectTransitive(files);
|
||||
|
@ -1786,6 +1863,7 @@ class LibraryFileKind extends LibraryOrAugmentationFileKind {
|
|||
|
||||
void internal_setLibraryCycle(LibraryCycle? cycle) {
|
||||
_libraryCycle = cycle;
|
||||
_disposeMacroAugmentation();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1793,6 +1871,26 @@ class LibraryFileKind extends LibraryOrAugmentationFileKind {
|
|||
_libraryCycle?.invalidate();
|
||||
_libraryCycle = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LibraryFileKind($file)';
|
||||
}
|
||||
|
||||
/// When the library cycle that contains this library is invalidated, the
|
||||
/// macros might potentially generate different code, or no code at all. So,
|
||||
/// we discard the existing macro augmentation library, it will be rebuilt
|
||||
/// during linking.
|
||||
void _disposeMacroAugmentation() {
|
||||
if (_macroImport case final macroImport?) {
|
||||
_macroImport = null;
|
||||
_augmentationImports = augmentationImports.withoutLast.toFixedList();
|
||||
// Discard the file.
|
||||
final macroFile = macroImport.importedFile;
|
||||
file._fsState._pathToFile.remove(macroFile.path);
|
||||
file._fsState._uriToFile.remove(macroFile.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a single `import` directive.
|
||||
|
|
|
@ -20,7 +20,6 @@ import 'package:analyzer/src/summary2/constructor_initializer_resolver.dart';
|
|||
import 'package:analyzer/src/summary2/default_value_resolver.dart';
|
||||
import 'package:analyzer/src/summary2/element_builder.dart';
|
||||
import 'package:analyzer/src/summary2/export.dart';
|
||||
import 'package:analyzer/src/summary2/informative_data.dart';
|
||||
import 'package:analyzer/src/summary2/link.dart';
|
||||
import 'package:analyzer/src/summary2/macro_application.dart';
|
||||
import 'package:analyzer/src/summary2/metadata_resolver.dart';
|
||||
|
@ -489,79 +488,28 @@ class LibraryBuilder {
|
|||
return;
|
||||
}
|
||||
|
||||
final libraryFileName = uri.pathSegments.lastOrNull;
|
||||
if (libraryFileName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!libraryFileName.endsWith('.dart')) {
|
||||
return;
|
||||
}
|
||||
|
||||
final augmentationFileName =
|
||||
'${libraryFileName.substring(0, '.dart'.length - 1)}.macro.dart';
|
||||
final augmentationUri = uri.resolve(augmentationFileName);
|
||||
final augmentationUriStr = '$augmentationUri';
|
||||
final augmentationSource = _sourceFactory.forUri2(augmentationUri)!;
|
||||
|
||||
final parseResult = parseString(
|
||||
content: augmentationCode,
|
||||
featureSet: element.featureSet,
|
||||
throwIfDiagnostics: false,
|
||||
);
|
||||
final unitNode = parseResult.unit as ast.CompilationUnitImpl;
|
||||
|
||||
final unitElement = CompilationUnitElementImpl(
|
||||
source: augmentationSource,
|
||||
librarySource: augmentationSource,
|
||||
lineInfo: parseResult.lineInfo,
|
||||
);
|
||||
unitElement.uri = augmentationUriStr;
|
||||
|
||||
final augmentationReference =
|
||||
reference.getChild('@augmentation').getChild(augmentationUriStr);
|
||||
_bindReference(augmentationReference, unitElement);
|
||||
|
||||
final augmentation = LibraryAugmentationElementImpl(
|
||||
augmentationTarget: element,
|
||||
nameOffset: -1,
|
||||
);
|
||||
augmentation.definingCompilationUnit = unitElement;
|
||||
|
||||
augmentation.macroGenerated = MacroGenerationAugmentationLibrary(
|
||||
code: augmentationCode,
|
||||
informativeBytes: writeUnitInformative(unitNode),
|
||||
);
|
||||
|
||||
final directiveUri = DirectiveUriWithAugmentationImpl(
|
||||
relativeUriString: augmentationUriStr,
|
||||
relativeUri: augmentationUri,
|
||||
source: augmentationSource,
|
||||
augmentation: augmentation,
|
||||
);
|
||||
final macroState = kind.addOrUpdateMacro(augmentationCode);
|
||||
if (macroState == null) return;
|
||||
|
||||
final macroImport = _buildAugmentationImport(element, macroState);
|
||||
macroImport.isSynthetic = true;
|
||||
element.augmentationImports = [
|
||||
...element.augmentationImports,
|
||||
AugmentationImportElementImpl(
|
||||
importKeywordOffset: -1,
|
||||
uri: directiveUri,
|
||||
),
|
||||
];
|
||||
macroImport,
|
||||
].toFixedList();
|
||||
|
||||
final macroLinkingUnit = units.last;
|
||||
ElementBuilder(
|
||||
libraryBuilder: this,
|
||||
container: augmentation,
|
||||
unitReference: augmentationReference,
|
||||
unitElement: unitElement,
|
||||
).buildDeclarationElements(unitNode);
|
||||
container: macroLinkingUnit.container,
|
||||
unitReference: macroLinkingUnit.reference,
|
||||
unitElement: macroLinkingUnit.element,
|
||||
).buildDeclarationElements(macroLinkingUnit.node);
|
||||
|
||||
units.add(
|
||||
LinkingUnit(
|
||||
reference: augmentationReference,
|
||||
node: unitNode,
|
||||
element: unitElement,
|
||||
container: element,
|
||||
),
|
||||
macroImport.importedAugmentation!.macroGenerated =
|
||||
MacroGenerationAugmentationLibrary(
|
||||
code: macroState.importedFile.content,
|
||||
informativeBytes: macroState.importedFile.unlinked2.informativeBytes,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,8 @@ class LibraryMacroApplier {
|
|||
required this.libraryBuilder,
|
||||
});
|
||||
|
||||
bool get hasTargets => _targets.isNotEmpty;
|
||||
|
||||
Linker get _linker => libraryBuilder.linker;
|
||||
|
||||
/// Fill [_targets]s with macro applications.
|
||||
|
@ -347,6 +349,17 @@ class LibraryMacroApplier {
|
|||
}
|
||||
|
||||
macro.ResolvedIdentifier _resolveIdentifier(macro.Identifier identifier) {
|
||||
if (identifier is IdentifierImplFromElement) {
|
||||
// TODO(scheglov) other elements
|
||||
final element = identifier.element as ClassElementImpl;
|
||||
return macro.ResolvedIdentifier(
|
||||
// TODO(scheglov) other kinds
|
||||
kind: macro.IdentifierKind.topLevelMember,
|
||||
name: element.name,
|
||||
uri: element.source.uri,
|
||||
staticScope: null,
|
||||
);
|
||||
}
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,11 @@ extension IterableExtension<E> on Iterable<E> {
|
|||
}
|
||||
|
||||
extension ListExtension<E> on List<E> {
|
||||
Iterable<E> get withoutLast {
|
||||
final length = this.length;
|
||||
return length > 0 ? take(length - 1) : Iterable.empty();
|
||||
}
|
||||
|
||||
void addIfNotNull(E? element) {
|
||||
if (element != null) {
|
||||
add(element);
|
||||
|
|
|
@ -658,6 +658,15 @@ class _ElementWriter {
|
|||
}
|
||||
|
||||
void _writeLibraryOrAugmentationElement(LibraryOrAugmentationElementImpl e) {
|
||||
if (e is LibraryAugmentationElementImpl) {
|
||||
if (e.macroGenerated case final macroGenerated?) {
|
||||
_sink.writelnWithIndent('macroGeneratedCode');
|
||||
_sink.writeln('---');
|
||||
_sink.write(macroGenerated.code);
|
||||
_sink.writeln('---');
|
||||
}
|
||||
}
|
||||
|
||||
_writeDocumentation(e);
|
||||
_writeMetadata(e);
|
||||
_writeSinceSdkVersion(e);
|
||||
|
@ -671,15 +680,6 @@ class _ElementWriter {
|
|||
|
||||
_writeElements('exports', e.libraryExports, _writeExportElement);
|
||||
|
||||
if (e is LibraryAugmentationElementImpl) {
|
||||
if (e.macroGenerated case final macroGenerated?) {
|
||||
_sink.writelnWithIndent('macroGeneratedCode');
|
||||
_sink.writeln('---');
|
||||
_sink.writeln(macroGenerated.code);
|
||||
_sink.writeln('---');
|
||||
}
|
||||
}
|
||||
|
||||
_sink.writelnWithIndent('definingUnit');
|
||||
_sink.withIndent(() {
|
||||
_writeUnitElement(e.definingCompilationUnit);
|
||||
|
|
|
@ -1364,11 +1364,13 @@ library
|
|||
package:test/test.macro.dart
|
||||
macroGeneratedCode
|
||||
---
|
||||
library augment 'test.dart';
|
||||
|
||||
class MyClass {}
|
||||
---
|
||||
definingUnit
|
||||
classes
|
||||
class MyClass @6
|
||||
class MyClass @36
|
||||
constructors
|
||||
synthetic @-1
|
||||
''');
|
||||
|
@ -1412,11 +1414,13 @@ library
|
|||
package:test/test.macro.dart
|
||||
macroGeneratedCode
|
||||
---
|
||||
library augment 'test.dart';
|
||||
|
||||
class MyClass {}
|
||||
---
|
||||
definingUnit
|
||||
classes
|
||||
class MyClass @6
|
||||
class MyClass @36
|
||||
constructors
|
||||
synthetic @-1
|
||||
''');
|
||||
|
@ -1459,11 +1463,13 @@ library
|
|||
package:test/test.macro.dart
|
||||
macroGeneratedCode
|
||||
---
|
||||
library augment 'test.dart';
|
||||
|
||||
class MyClass {}
|
||||
---
|
||||
definingUnit
|
||||
classes
|
||||
class MyClass @6
|
||||
class MyClass @36
|
||||
constructors
|
||||
synthetic @-1
|
||||
''');
|
||||
|
@ -1506,13 +1512,83 @@ library
|
|||
package:test/test.macro.dart
|
||||
macroGeneratedCode
|
||||
---
|
||||
library augment 'test.dart';
|
||||
|
||||
class MyClass {}
|
||||
---
|
||||
definingUnit
|
||||
classes
|
||||
class MyClass @6
|
||||
class MyClass @36
|
||||
constructors
|
||||
synthetic @-1
|
||||
''');
|
||||
}
|
||||
|
||||
test_imports_class() async {
|
||||
newFile('$testPackageLibPath/a.dart', r'''
|
||||
import 'dart:async';
|
||||
import 'package:_fe_analyzer_shared/src/macros/api.dart';
|
||||
|
||||
macro class MyMacro implements ClassTypesMacro {
|
||||
const MyMacro();
|
||||
|
||||
FutureOr<void> buildTypesForClass(clazz, ClassTypeBuilder builder) async {
|
||||
final identifier = await builder.resolveIdentifier(
|
||||
Uri.parse('dart:math'),
|
||||
'Random',
|
||||
);
|
||||
builder.declareType(
|
||||
'MyClass',
|
||||
DeclarationCode.fromParts([
|
||||
'class MyClass {\n void foo(',
|
||||
identifier,
|
||||
' _) {}\n}',
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
var library = await buildLibrary(r'''
|
||||
import 'a.dart';
|
||||
|
||||
@MyMacro()
|
||||
class A {}
|
||||
''');
|
||||
|
||||
configuration
|
||||
..withConstructors = false
|
||||
..withMetadata = false;
|
||||
checkElementText(library, r'''
|
||||
library
|
||||
imports
|
||||
package:test/a.dart
|
||||
definingUnit
|
||||
classes
|
||||
class A @35
|
||||
augmentationImports
|
||||
package:test/test.macro.dart
|
||||
macroGeneratedCode
|
||||
---
|
||||
library augment 'test.dart';
|
||||
|
||||
import 'dart:math' as prefix0;
|
||||
|
||||
class MyClass {
|
||||
void foo(prefix0.Random _) {}
|
||||
}
|
||||
---
|
||||
imports
|
||||
dart:math as prefix0 @52
|
||||
definingUnit
|
||||
classes
|
||||
class MyClass @68
|
||||
methods
|
||||
foo @85
|
||||
parameters
|
||||
requiredPositional _ @104
|
||||
type: Random
|
||||
returnType: void
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,13 @@ class ListExtensionTest {
|
|||
[4, 2, 0, 5, 3, 1],
|
||||
);
|
||||
}
|
||||
|
||||
test_withoutLast() {
|
||||
expect([0, 1, 2].withoutLast, [0, 1]);
|
||||
expect([0, 1].withoutLast, [0]);
|
||||
expect([0].withoutLast, <int>[]);
|
||||
expect(<int>[].withoutLast, <int>[]);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
|
|
Loading…
Reference in a new issue