Skip to content

Commit 91ee109

Browse files
committed
[Macros] Start expanding accessor macros attached to a storage declaration.
Accessor macros are attached macros (written with attribute syntax) that can generate accessors for a property or subscript. Recognize custom attributes that are accessor macros when written on a storage declaration, and expand those macros. This is very much a work in progress, and the result of the expansion isn't yet parsed or wired into the AST.
1 parent 0988e54 commit 91ee109

File tree

8 files changed

+455
-0
lines changed

8 files changed

+455
-0
lines changed

include/swift/Basic/SourceManager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
namespace swift {
2525

26+
class CustomAttr;
2627
class DeclContext;
2728

2829
/// Augments a buffer that was created specifically to hold generated source
@@ -59,6 +60,9 @@ class GeneratedSourceInfo {
5960

6061
/// The declaration context in which this buffer logically resides.
6162
DeclContext *declContext;
63+
64+
/// The custom attribute for an attached macro.
65+
CustomAttr *attachedMacroCustomAttr = nullptr;
6266
};
6367

6468
/// This class manages and owns source buffers.

lib/ASTGen/Sources/ASTGen/Macros.swift

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,138 @@ func evaluateMacro(
215215

216216
return 0
217217
}
218+
219+
/// Retrieve a syntax node in the given source file, with the given type.
220+
private func findSyntaxNodeInSourceFile<Node: SyntaxProtocol>(
221+
sourceFilePtr: UnsafeRawPointer,
222+
sourceLocationPtr: UnsafePointer<UInt8>?,
223+
type: Node.Type
224+
) -> Node? {
225+
guard let sourceLocationPtr = sourceLocationPtr else {
226+
return nil
227+
}
228+
229+
let sourceFilePtr = sourceFilePtr.bindMemory(
230+
to: ExportedSourceFile.self, capacity: 1
231+
)
232+
233+
// Find the offset.
234+
let buffer = sourceFilePtr.pointee.buffer
235+
let offset = sourceLocationPtr - buffer.baseAddress!
236+
if offset < 0 || offset >= buffer.count {
237+
print("source location isn't inside this buffer")
238+
return nil
239+
}
240+
241+
// Find the token at that offset.
242+
let sf = sourceFilePtr.pointee.syntax
243+
guard let token = sf.token(at: AbsolutePosition(utf8Offset: offset)) else {
244+
print("couldn't find token at offset \(offset)")
245+
return nil
246+
}
247+
248+
// Dig out its parent.
249+
guard let parentSyntax = token.parent else {
250+
print("not on a macro expansion node: \(token.recursiveDescription)")
251+
return nil
252+
}
253+
254+
return parentSyntax.as(type)
255+
}
256+
257+
@_cdecl("swift_ASTGen_expandAttachedMacro")
258+
@usableFromInline
259+
func expandAttachedMacro(
260+
diagEnginePtr: UnsafeMutablePointer<UInt8>,
261+
macroPtr: UnsafeRawPointer,
262+
customAttrSourceFilePtr: UnsafeRawPointer,
263+
customAttrSourceLocPointer: UnsafePointer<UInt8>?,
264+
declarationSourceFilePtr: UnsafeRawPointer,
265+
attachedTo declarationSourceLocPointer: UnsafePointer<UInt8>?,
266+
expandedSourcePointer: UnsafeMutablePointer<UnsafePointer<UInt8>?>,
267+
expandedSourceLength: UnsafeMutablePointer<Int>
268+
) -> Int {
269+
// We didn't expand anything so far.
270+
expandedSourcePointer.pointee = nil
271+
expandedSourceLength.pointee = 0
272+
273+
// Dig out the custom attribute for the attached macro declarations.
274+
guard let customAttrNode = findSyntaxNodeInSourceFile(
275+
sourceFilePtr: customAttrSourceFilePtr,
276+
sourceLocationPtr: customAttrSourceLocPointer,
277+
type: AttributeSyntax.self
278+
) else {
279+
return 1
280+
}
281+
282+
// Dig out the node for the declaration to which the custom attribute is
283+
// attached.
284+
guard let declarationNode = findSyntaxNodeInSourceFile(
285+
sourceFilePtr: declarationSourceFilePtr,
286+
sourceLocationPtr: declarationSourceLocPointer,
287+
type: DeclSyntax.self
288+
) else {
289+
return 1
290+
}
291+
292+
// Get the macro.
293+
let macroPtr = macroPtr.bindMemory(to: ExportedMacro.self, capacity: 1)
294+
let macro = macroPtr.pointee.macro
295+
296+
// FIXME: Which source file? I don't know! This should go.
297+
let declarationSourceFilePtr = declarationSourceFilePtr.bindMemory(
298+
to: ExportedSourceFile.self, capacity: 1
299+
)
300+
301+
var context = MacroExpansionContext(
302+
moduleName: declarationSourceFilePtr.pointee.moduleName,
303+
fileName: declarationSourceFilePtr.pointee.fileName.withoutPath()
304+
)
305+
306+
var evaluatedSyntaxStr: String
307+
do {
308+
switch macro {
309+
case let attachedMacro as AccessorDeclarationMacro.Type:
310+
let accessors = try attachedMacro.expansion(
311+
of: customAttrNode, attachedTo: declarationNode, in: &context
312+
)
313+
314+
// Form a buffer of accessor declarations to return to the caller.
315+
evaluatedSyntaxStr = accessors.map {
316+
$0.withoutTrivia().description
317+
}.joined(separator: "\n\n")
318+
319+
default:
320+
print("\(macroPtr) does not conform to any known attached macro protocol")
321+
return 1
322+
}
323+
} catch {
324+
// Record the error
325+
// FIXME: Need to decide where to diagnose the error:
326+
context.diagnose(
327+
Diagnostic(
328+
node: Syntax(declarationNode),
329+
message: ThrownErrorDiagnostic(message: String(describing: error))
330+
)
331+
)
332+
333+
return 1
334+
}
335+
336+
// FIXME: Emit diagnostics, but how do we figure out which source file to
337+
// use?
338+
339+
// Form the result buffer for our caller.
340+
evaluatedSyntaxStr.withUTF8 { utf8 in
341+
let evaluatedResultPtr = UnsafeMutablePointer<UInt8>.allocate(capacity: utf8.count + 1)
342+
if let baseAddress = utf8.baseAddress {
343+
evaluatedResultPtr.initialize(from: baseAddress, count: utf8.count)
344+
}
345+
evaluatedResultPtr[utf8.count] = 0
346+
347+
expandedSourcePointer.pointee = UnsafePointer(evaluatedResultPtr)
348+
expandedSourceLength.pointee = utf8.count
349+
}
350+
351+
return 0
352+
}

lib/Sema/TypeCheckMacros.cpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ extern "C" ptrdiff_t swift_ASTGen_evaluateMacro(
4242
const void *sourceLocation,
4343
const char **evaluatedSource, ptrdiff_t *evaluatedSourceLength);
4444

45+
extern "C" ptrdiff_t swift_ASTGen_expandAttachedMacro(
46+
void *diagEngine, void *macro,
47+
void *customAttrSourceFile,
48+
const void *customAttrSourceLocation,
49+
void *declarationSourceFile,
50+
const void *declarationSourceLocation,
51+
const char **evaluatedSource,
52+
ptrdiff_t *evaluatedSourceLength
53+
);
54+
4555
/// Produce the mangled name for the nominal type descriptor of a type
4656
/// referenced by its module and type name.
4757
static std::string mangledNameForTypeMetadataAccessor(
@@ -662,3 +672,193 @@ bool swift::expandFreestandingDeclarationMacro(
662672
}
663673
return true;
664674
}
675+
676+
void swift::expandAccessors(
677+
AbstractStorageDecl *storage, CustomAttr *attr, MacroDecl *macro
678+
) {
679+
auto *dc = storage->getInnermostDeclContext();
680+
ASTContext &ctx = dc->getASTContext();
681+
SourceManager &sourceMgr = ctx.SourceMgr;
682+
683+
auto moduleDecl = dc->getParentModule();
684+
685+
auto attrSourceFile =
686+
moduleDecl->getSourceFileContainingLocation(attr->AtLoc);
687+
if (!attrSourceFile)
688+
return;
689+
690+
auto declSourceFile =
691+
moduleDecl->getSourceFileContainingLocation(storage->getStartLoc());
692+
if (!declSourceFile)
693+
return;
694+
695+
// Evaluate the macro.
696+
NullTerminatedStringRef evaluatedSource;
697+
698+
if (isFromExpansionOfMacro(attrSourceFile, macro) ||
699+
isFromExpansionOfMacro(declSourceFile, macro)) {
700+
storage->diagnose(diag::macro_recursive, macro->getName());
701+
return;
702+
}
703+
704+
auto macroDef = macro->getDefinition();
705+
switch (macroDef.kind) {
706+
case MacroDefinition::Kind::Undefined:
707+
case MacroDefinition::Kind::Invalid:
708+
// Already diagnosed as an error elsewhere.
709+
return;
710+
711+
case MacroDefinition::Kind::Builtin: {
712+
switch (macroDef.getBuiltinKind()) {
713+
case BuiltinMacroKind::ExternalMacro:
714+
// FIXME: Error here.
715+
return;
716+
}
717+
}
718+
719+
case MacroDefinition::Kind::External: {
720+
// Retrieve the external definition of the macro.
721+
auto external = macroDef.getExternalMacro();
722+
ExternalMacroDefinitionRequest request{
723+
&ctx, external.moduleName, external.macroTypeName
724+
};
725+
auto externalDef = evaluateOrDefault(
726+
ctx.evaluator, request, ExternalMacroDefinition()
727+
);
728+
if (!externalDef.opaqueHandle) {
729+
storage->diagnose(diag::external_macro_not_found,
730+
external.moduleName.str(),
731+
external.macroTypeName.str(),
732+
macro->getName()
733+
);
734+
macro->diagnose(diag::decl_declared_here, macro->getName());
735+
return;
736+
}
737+
738+
// Make sure macros are enabled before we expand.
739+
if (!ctx.LangOpts.hasFeature(Feature::Macros)) {
740+
storage->diagnose(diag::macro_experimental);
741+
return;
742+
}
743+
744+
#if SWIFT_SWIFT_PARSER
745+
PrettyStackTraceDecl debugStack("expanding accessor macro", storage);
746+
747+
auto astGenAttrSourceFile = attrSourceFile->exportedSourceFile;
748+
if (!astGenAttrSourceFile)
749+
return;
750+
751+
auto astGenDeclSourceFile = declSourceFile->exportedSourceFile;
752+
if (!astGenDeclSourceFile)
753+
return;
754+
755+
Decl *searchDecl = storage;
756+
if (auto var = dyn_cast<VarDecl>(storage))
757+
searchDecl = var->getParentPatternBinding();
758+
759+
const char *evaluatedSourceAddress;
760+
ptrdiff_t evaluatedSourceLength;
761+
swift_ASTGen_expandAttachedMacro(
762+
&ctx.Diags,
763+
externalDef.opaqueHandle,
764+
astGenAttrSourceFile, attr->AtLoc.getOpaquePointerValue(),
765+
astGenDeclSourceFile, searchDecl->getStartLoc().getOpaquePointerValue(),
766+
&evaluatedSourceAddress, &evaluatedSourceLength);
767+
if (!evaluatedSourceAddress)
768+
return;
769+
evaluatedSource = NullTerminatedStringRef(evaluatedSourceAddress,
770+
(size_t)evaluatedSourceLength);
771+
break;
772+
#else
773+
med->diagnose(diag::macro_unsupported);
774+
return false;
775+
#endif
776+
}
777+
}
778+
779+
// Figure out a reasonable name for the macro expansion buffer.
780+
std::string bufferName;
781+
{
782+
llvm::raw_string_ostream out(bufferName);
783+
784+
out << "macro:" << storage->getName()
785+
<< "@" << macro->getName().getBaseName();
786+
if (auto bufferID = declSourceFile->getBufferID()) {
787+
unsigned startLine, startColumn;
788+
std::tie(startLine, startColumn) =
789+
sourceMgr.getLineAndColumnInBuffer(storage->getStartLoc(), *bufferID);
790+
791+
SourceLoc endLoc =
792+
Lexer::getLocForEndOfToken(sourceMgr, storage->getEndLoc());
793+
unsigned endLine, endColumn;
794+
std::tie(endLine, endColumn) =
795+
sourceMgr.getLineAndColumnInBuffer(endLoc, *bufferID);
796+
797+
out << ":" << sourceMgr.getIdentifierForBuffer(*bufferID) << ":"
798+
<< startLine << ":" << startColumn
799+
<< "-" << endLine << ":" << endColumn;
800+
}
801+
}
802+
803+
// Dump macro expansions to standard output, if requested.
804+
if (ctx.LangOpts.DumpMacroExpansions) {
805+
llvm::errs() << bufferName
806+
<< "\n------------------------------\n"
807+
<< evaluatedSource
808+
<< "\n------------------------------\n";
809+
}
810+
811+
// Create a new source buffer with the contents of the expanded macro.
812+
auto macroBuffer =
813+
llvm::MemoryBuffer::getMemBufferCopy(evaluatedSource, bufferName);
814+
unsigned macroBufferID = sourceMgr.addNewSourceBuffer(std::move(macroBuffer));
815+
auto macroBufferRange = sourceMgr.getRangeForBuffer(macroBufferID);
816+
GeneratedSourceInfo sourceInfo{
817+
GeneratedSourceInfo::MacroExpansion,
818+
storage->getEndLoc(),
819+
SourceRange(macroBufferRange.getStart(), macroBufferRange.getEnd()),
820+
ASTNode(storage).getOpaqueValue(),
821+
dc,
822+
attr
823+
};
824+
sourceMgr.setGeneratedSourceInfo(macroBufferID, sourceInfo);
825+
free((void*)evaluatedSource.data());
826+
827+
// Create a source file to hold the macro buffer. This is automatically
828+
// registered with the enclosing module.
829+
auto macroSourceFile = new (ctx) SourceFile(
830+
*dc->getParentModule(), SourceFileKind::MacroExpansion, macroBufferID,
831+
/*parsingOpts=*/{}, /*isPrimary=*/false);
832+
833+
PrettyStackTraceDecl debugStack(
834+
"type checking expanded declaration macro", storage);
835+
836+
// FIXME: getTopLevelItems() is going to have to figure out how to parse
837+
// a sequence of accessor declarations. Specifically, we will have to
838+
// encode enough information in the GeneratedSourceInfo to know that
839+
// the "top level" here is really a set of accessor declarations, so we
840+
// can parse those. Both parsers will need to do it this way. Alternatively,
841+
// we can put some extra braces around things and use parseGetSet, but that
842+
// doesn't feel quite right, because we might eventually allow additional
843+
// accessors to be inserted for properties that already have accesssors.
844+
// parseTopLevelItems is where things get interesting... hmmm...
845+
846+
847+
#if false
848+
// Retrieve the parsed declarations from the list of top-level items.
849+
auto topLevelItems = macroSourceFile->getTopLevelItems();
850+
for (auto item : topLevelItems) {
851+
auto *decl = item.dyn_cast<Decl *>();
852+
if (!decl) {
853+
ctx.Diags.diagnose(
854+
macroBufferRange.getStart(), diag::expected_macro_expansion_decls);
855+
return;
856+
}
857+
decl->setDeclContext(dc);
858+
TypeChecker::typeCheckDecl(decl);
859+
results.push_back(decl);
860+
}
861+
862+
#endif
863+
return;
864+
}

lib/Sema/TypeCheckMacros.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
namespace swift {
2323

24+
class CustomAttr;
2425
class Expr;
26+
class MacroDecl;
2527
class MacroExpansionDecl;
2628
class TypeRepr;
2729

@@ -40,6 +42,12 @@ Expr *expandMacroExpr(
4042
bool expandFreestandingDeclarationMacro(
4143
MacroExpansionDecl *med, SmallVectorImpl<Decl *> &results);
4244

45+
/// Expand the accessors for the given storage declaration based on the
46+
/// custom attribute that references the given macro.
47+
void expandAccessors(
48+
AbstractStorageDecl *storage, CustomAttr *attr, MacroDecl *macro
49+
);
50+
4351
} // end namespace swift
4452

4553
#endif /* SWIFT_SEMA_TYPECHECKMACROS_H */

0 commit comments

Comments
 (0)