Skip to content

[5.9][Macros] Add missing macro validation. #67387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -7181,6 +7181,9 @@ ERROR(literal_type_in_macro_expansion,none,
ERROR(invalid_macro_introduced_name,none,
"declaration name %0 is not covered by macro %1",
(DeclName, DeclName))
ERROR(undocumented_conformance_in_expansion,none,
"conformance to %0 is not covered by macro %1",
(Type, DeclName))
ERROR(invalid_macro_role_for_macro_syntax,none,
"invalid macro role for %{a freestanding|an attached}0 macro",
(unsigned))
Expand All @@ -7203,6 +7206,9 @@ ERROR(local_extension_macro,none,
ERROR(extension_macro_invalid_conformance,none,
"invalid protocol conformance %0 in extension macro",
(Type))
ERROR(macro_attached_to_invalid_decl,none,
"'%0' macro cannot be attached to %1",
(StringRef, DescriptiveDeclKind))

ERROR(macro_resolve_circular_reference, none,
"circular reference resolving %select{freestanding|attached}0 macro %1",
Expand Down
11 changes: 9 additions & 2 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3659,10 +3659,17 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) {
Ctx.evaluator, CustomAttrNominalRequest{attr, dc}, nullptr);

if (!nominal) {
if (attr->isInvalid())
return;

// Try resolving an attached macro attribute.
auto *macro = D->getResolvedMacro(attr);
if (macro || !attr->isValid())
if (auto *macro = D->getResolvedMacro(attr)) {
for (auto *roleAttr : macro->getAttrs().getAttributes<MacroRoleAttr>()) {
diagnoseInvalidAttachedMacro(roleAttr->getMacroRole(), D);
}

return;
}

// Diagnose errors.

Expand Down
222 changes: 165 additions & 57 deletions lib/Sema/TypeCheckMacros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,112 @@ static Identifier makeIdentifier(ASTContext &ctx, std::nullptr_t) {
return Identifier();
}

bool swift::diagnoseInvalidAttachedMacro(MacroRole role,
Decl *attachedTo) {
switch (role) {
case MacroRole::Expression:
case MacroRole::Declaration:
case MacroRole::CodeItem:
llvm_unreachable("Invalid macro role for attached macro");

case MacroRole::Accessor:
// Only var decls and subscripts have accessors.
if (isa<AbstractStorageDecl>(attachedTo) && !isa<ParamDecl>(attachedTo))
return false;

break;

case MacroRole::MemberAttribute:
case MacroRole::Member:
// Nominal types and extensions can have members.
if (isa<NominalTypeDecl>(attachedTo) || isa<ExtensionDecl>(attachedTo))
return false;

break;

case MacroRole::Peer:
// Peer macros are allowed on everything except parameters.
if (!isa<ParamDecl>(attachedTo))
return false;

break;

case MacroRole::Conformance:
case MacroRole::Extension:
// Only primary declarations of nominal types
if (isa<NominalTypeDecl>(attachedTo))
return false;

break;
}

attachedTo->diagnose(diag::macro_attached_to_invalid_decl,
getMacroRoleString(role),
attachedTo->getDescriptiveKind());
return true;
}

static void diagnoseInvalidDecl(Decl *decl,
MacroDecl *macro,
llvm::function_ref<bool(DeclName)> coversName) {
auto &ctx = decl->getASTContext();

// Diagnose invalid declaration kinds.
if (isa<ImportDecl>(decl) ||
isa<OperatorDecl>(decl) ||
isa<PrecedenceGroupDecl>(decl) ||
isa<MacroDecl>(decl) ||
isa<ExtensionDecl>(decl)) {
decl->diagnose(diag::invalid_decl_in_macro_expansion,
decl->getDescriptiveKind());
decl->setInvalid();

if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
extension->setExtendedNominal(nullptr);
}

return;
}

// Diagnose `@main` types.
if (auto *mainAttr = decl->getAttrs().getAttribute<MainTypeAttr>()) {
ctx.Diags.diagnose(mainAttr->getLocation(),
diag::invalid_main_type_in_macro_expansion);
mainAttr->setInvalid();
}

// Diagnose default literal type overrides.
if (auto *typeAlias = dyn_cast<TypeAliasDecl>(decl)) {
auto name = typeAlias->getBaseIdentifier();
#define EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME(_, __, typeName, \
supportsOverride) \
if (supportsOverride && name == makeIdentifier(ctx, typeName)) { \
typeAlias->diagnose(diag::literal_type_in_macro_expansion, \
makeIdentifier(ctx, typeName)); \
typeAlias->setInvalid(); \
return; \
}
#include "swift/AST/KnownProtocols.def"
#undef EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME
}

// Diagnose value decls with names not covered by the macro
if (auto *value = dyn_cast<ValueDecl>(decl)) {
auto name = value->getName();

// Unique names are always permitted.
if (MacroDecl::isUniqueMacroName(name.getBaseName().userFacingName()))
return;

if (coversName(name)) {
return;
}

value->diagnose(diag::invalid_macro_introduced_name,
name, macro->getBaseName());
}
}

/// Diagnose macro expansions that produce any of the following declarations:
/// - Import declarations
/// - Operator and precedence group declarations
Expand All @@ -559,75 +665,33 @@ static void validateMacroExpansion(SourceFile *expansionBuffer,
llvm::SmallVector<DeclName, 2> introducedNames;
macro->getIntroducedNames(role, attachedTo, introducedNames);

llvm::SmallDenseSet<DeclName, 2> coversName(introducedNames.begin(),
introducedNames.end());
llvm::SmallDenseSet<DeclName, 2> introducedNameSet(
introducedNames.begin(), introducedNames.end());

for (auto *decl : expansionBuffer->getTopLevelDecls()) {
auto &ctx = decl->getASTContext();
auto coversName = [&](DeclName name) -> bool {
return (introducedNameSet.count(name) ||
introducedNameSet.count(name.getBaseName()) ||
introducedNameSet.count(MacroDecl::getArbitraryName()));
};

for (auto *decl : expansionBuffer->getTopLevelDecls()) {
// Certain macro roles can generate special declarations.
if ((isa<AccessorDecl>(decl) && role == MacroRole::Accessor) ||
(isa<ExtensionDecl>(decl) && role == MacroRole::Conformance) ||
(isa<ExtensionDecl>(decl) && role == MacroRole::Extension)) { // FIXME: Check extension for generated names.
(isa<ExtensionDecl>(decl) && role == MacroRole::Conformance)) {
continue;
}

// Diagnose invalid declaration kinds.
if (isa<ImportDecl>(decl) ||
isa<OperatorDecl>(decl) ||
isa<PrecedenceGroupDecl>(decl) ||
isa<MacroDecl>(decl) ||
isa<ExtensionDecl>(decl)) {
decl->diagnose(diag::invalid_decl_in_macro_expansion,
decl->getDescriptiveKind());
decl->setInvalid();

if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
extension->setExtendedNominal(nullptr);
if (role == MacroRole::Extension) {
auto *extension = dyn_cast<ExtensionDecl>(decl);

for (auto *member : extension->getMembers()) {
diagnoseInvalidDecl(member, macro, coversName);
}

continue;
}

// Diagnose `@main` types.
if (auto *mainAttr = decl->getAttrs().getAttribute<MainTypeAttr>()) {
ctx.Diags.diagnose(mainAttr->getLocation(),
diag::invalid_main_type_in_macro_expansion);
mainAttr->setInvalid();
}

// Diagnose default literal type overrides.
if (auto *typeAlias = dyn_cast<TypeAliasDecl>(decl)) {
auto name = typeAlias->getBaseIdentifier();
#define EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME(_, __, typeName, \
supportsOverride) \
if (supportsOverride && name == makeIdentifier(ctx, typeName)) { \
typeAlias->diagnose(diag::literal_type_in_macro_expansion, \
makeIdentifier(ctx, typeName)); \
typeAlias->setInvalid(); \
continue; \
}
#include "swift/AST/KnownProtocols.def"
#undef EXPRESSIBLE_BY_LITERAL_PROTOCOL_WITH_NAME
}

// Diagnose value decls with names not covered by the macro
if (auto *value = dyn_cast<ValueDecl>(decl)) {
auto name = value->getName();

// Unique names are always permitted.
if (MacroDecl::isUniqueMacroName(name.getBaseName().userFacingName()))
continue;

if (coversName.count(name) ||
coversName.count(name.getBaseName()) ||
coversName.count(MacroDecl::getArbitraryName())) {
continue;
}

value->diagnose(diag::invalid_macro_introduced_name,
name, macro->getBaseName());
}
diagnoseInvalidDecl(decl, macro, coversName);
}
}

Expand Down Expand Up @@ -1572,6 +1636,50 @@ llvm::Optional<unsigned> swift::expandExtensions(CustomAttr *attr,
if (auto file = dyn_cast<FileUnit>(
decl->getDeclContext()->getModuleScopeContext()))
file->getOrCreateSynthesizedFile().addTopLevelDecl(extension);

// Don't validate documented conformances for the 'conformance' role.
if (role == MacroRole::Conformance)
continue;

// Extension macros can only add conformances that are documented by
// the `@attached(extension)` attribute.
for (auto inherited : extension->getInherited()) {
auto constraint =
TypeResolution::forInterface(
extension->getDeclContext(),
TypeResolverContext::GenericRequirement,
/*unboundTyOpener*/ nullptr,
/*placeholderHandler*/ nullptr,
/*packElementOpener*/ nullptr)
.resolveType(inherited.getTypeRepr());

// Already diagnosed or will be diagnosed later.
if (constraint->is<ErrorType>() || !constraint->isConstraintType())
continue;

std::function<bool(Type)> isUndocumentedConformance =
[&](Type constraint) -> bool {
if (auto *proto = constraint->getAs<ParameterizedProtocolType>())
return !llvm::is_contained(potentialConformances,
proto->getProtocol());

if (auto *proto = constraint->getAs<ProtocolType>())
return !llvm::is_contained(potentialConformances,
proto->getDecl());

return llvm::any_of(
constraint->castTo<ProtocolCompositionType>()->getMembers(),
isUndocumentedConformance);
};

if (isUndocumentedConformance(constraint)) {
extension->diagnose(
diag::undocumented_conformance_in_expansion,
constraint, macro->getBaseName());

extension->setInvalid();
}
}
}

return macroSourceFile->getBufferID();
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/TypeCheckMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ bool accessorMacroOnlyIntroducesObservers(
bool accessorMacroIntroducesInitAccessor(
MacroDecl *macro, const MacroRoleAttr *attr);

/// Diagnose an error if the given macro role does not apply
/// to the declaration kind of \c attachedTo.
bool diagnoseInvalidAttachedMacro(MacroRole role,
Decl *attachedTo);

} // end namespace swift

#endif /* SWIFT_SEMA_TYPECHECKMACROS_H */
Expand Down
56 changes: 56 additions & 0 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1314,6 +1314,16 @@ public struct EmptyMacro: MemberMacro {
}
}

public struct EmptyPeerMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
return []
}
}

public struct EquatableMacro: ConformanceMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down Expand Up @@ -1427,6 +1437,52 @@ public struct DelegatedConformanceViaExtensionMacro: ExtensionMacro {
}
}

public struct AlwaysAddConformance: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let decl: DeclSyntax =
"""
extension \(raw: type.trimmedDescription): P where Element: P {
static func requirement() {
Element.requirement()
}
}

"""

return [
decl.cast(ExtensionDeclSyntax.self)
]
}
}

public struct AlwaysAddCodable: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let decl: DeclSyntax =
"""
extension \(raw: type.trimmedDescription): Codable {
}

"""

return [
decl.cast(ExtensionDeclSyntax.self)
]
}
}


public struct ExtendableEnum: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down
3 changes: 3 additions & 0 deletions test/Macros/accessor_macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,7 @@ struct MyBrokenStruct {
}
}

@myPropertyWrapper
struct CannotHaveAccessors {}
// CHECK-DIAGS: 'accessor' macro cannot be attached to struct
#endif
Loading