Skip to content

Commit 769fc5d

Browse files
authored
Merge pull request #67383 from hborla/diagnose-undocumented-conformances
[Macros] Diagnose undocumented conformances in extension macro expansions.
2 parents 1cd3b19 + 3c24932 commit 769fc5d

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7249,6 +7249,9 @@ ERROR(literal_type_in_macro_expansion,none,
72497249
ERROR(invalid_macro_introduced_name,none,
72507250
"declaration name %0 is not covered by macro %1",
72517251
(DeclName, DeclName))
7252+
ERROR(undocumented_conformance_in_expansion,none,
7253+
"conformance to %0 is not covered by macro %1",
7254+
(Type, DeclName))
72527255
ERROR(invalid_macro_role_for_macro_syntax,none,
72537256
"invalid macro role for %{a freestanding|an attached}0 macro",
72547257
(unsigned))

lib/Sema/TypeCheckMacros.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,50 @@ swift::expandExtensions(CustomAttr *attr, MacroDecl *macro,
16341634
if (auto file = dyn_cast<FileUnit>(
16351635
decl->getDeclContext()->getModuleScopeContext()))
16361636
file->getOrCreateSynthesizedFile().addTopLevelDecl(extension);
1637+
1638+
// Don't validate documented conformances for the 'conformance' role.
1639+
if (role == MacroRole::Conformance)
1640+
continue;
1641+
1642+
// Extension macros can only add conformances that are documented by
1643+
// the `@attached(extension)` attribute.
1644+
for (auto inherited : extension->getInherited()) {
1645+
auto constraint =
1646+
TypeResolution::forInterface(
1647+
extension->getDeclContext(),
1648+
TypeResolverContext::GenericRequirement,
1649+
/*unboundTyOpener*/ nullptr,
1650+
/*placeholderHandler*/ nullptr,
1651+
/*packElementOpener*/ nullptr)
1652+
.resolveType(inherited.getTypeRepr());
1653+
1654+
// Already diagnosed or will be diagnosed later.
1655+
if (constraint->is<ErrorType>() || !constraint->isConstraintType())
1656+
continue;
1657+
1658+
std::function<bool(Type)> isUndocumentedConformance =
1659+
[&](Type constraint) -> bool {
1660+
if (auto *proto = constraint->getAs<ParameterizedProtocolType>())
1661+
return !llvm::is_contained(potentialConformances,
1662+
proto->getProtocol());
1663+
1664+
if (auto *proto = constraint->getAs<ProtocolType>())
1665+
return !llvm::is_contained(potentialConformances,
1666+
proto->getDecl());
1667+
1668+
return llvm::any_of(
1669+
constraint->castTo<ProtocolCompositionType>()->getMembers(),
1670+
isUndocumentedConformance);
1671+
};
1672+
1673+
if (isUndocumentedConformance(constraint)) {
1674+
extension->diagnose(
1675+
diag::undocumented_conformance_in_expansion,
1676+
constraint, macro->getBaseName());
1677+
1678+
extension->setInvalid();
1679+
}
1680+
}
16371681
}
16381682

16391683
return macroSourceFile->getBufferID();

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,52 @@ public struct DelegatedConformanceViaExtensionMacro: ExtensionMacro {
14371437
}
14381438
}
14391439

1440+
public struct AlwaysAddConformance: ExtensionMacro {
1441+
public static func expansion(
1442+
of node: AttributeSyntax,
1443+
attachedTo decl: some DeclGroupSyntax,
1444+
providingExtensionsOf type: some TypeSyntaxProtocol,
1445+
conformingTo protocols: [TypeSyntax],
1446+
in context: some MacroExpansionContext
1447+
) throws -> [ExtensionDeclSyntax] {
1448+
let decl: DeclSyntax =
1449+
"""
1450+
extension \(raw: type.trimmedDescription): P where Element: P {
1451+
static func requirement() {
1452+
Element.requirement()
1453+
}
1454+
}
1455+
1456+
"""
1457+
1458+
return [
1459+
decl.cast(ExtensionDeclSyntax.self)
1460+
]
1461+
}
1462+
}
1463+
1464+
public struct AlwaysAddCodable: ExtensionMacro {
1465+
public static func expansion(
1466+
of node: AttributeSyntax,
1467+
attachedTo decl: some DeclGroupSyntax,
1468+
providingExtensionsOf type: some TypeSyntaxProtocol,
1469+
conformingTo protocols: [TypeSyntax],
1470+
in context: some MacroExpansionContext
1471+
) throws -> [ExtensionDeclSyntax] {
1472+
let decl: DeclSyntax =
1473+
"""
1474+
extension \(raw: type.trimmedDescription): Codable {
1475+
}
1476+
1477+
"""
1478+
1479+
return [
1480+
decl.cast(ExtensionDeclSyntax.self)
1481+
]
1482+
}
1483+
}
1484+
1485+
14401486
public struct ExtendableEnum: MemberMacro {
14411487
public static func expansion(
14421488
of node: AttributeSyntax,

test/Macros/macro_expand_extensions.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,32 @@ struct S<Element> {}
127127
// expected-note@-1 {{in expansion of macro 'UndocumentedNamesInExtension' here}}
128128

129129
// CHECK-DIAGS: error: declaration name 'requirement()' is not covered by macro 'UndocumentedNamesInExtension'
130+
131+
@attached(extension, names: named(requirement))
132+
macro UndocumentedConformanceInExtension() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddConformance")
133+
134+
@UndocumentedConformanceInExtension
135+
struct InvalidConformance<Element> {}
136+
// expected-note@-1 {{in expansion of macro 'UndocumentedConformanceInExtension' here}}
137+
138+
// CHECK-DIAGS: error: conformance to 'P' is not covered by macro 'UndocumentedConformanceInExtension'
139+
140+
@attached(extension)
141+
macro UndocumentedCodable() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddCodable")
142+
143+
@UndocumentedCodable
144+
struct TestUndocumentedCodable {}
145+
// expected-note@-1 {{in expansion of macro 'UndocumentedCodable' here}}
146+
147+
// CHECK-DIAGS: error: conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedCodable'
148+
149+
@attached(extension, conformances: Decodable)
150+
macro UndocumentedEncodable() = #externalMacro(module: "MacroDefinition", type: "AlwaysAddCodable")
151+
152+
@UndocumentedEncodable
153+
struct TestUndocumentedEncodable {}
154+
// expected-note@-1 {{in expansion of macro 'UndocumentedEncodable' here}}
155+
156+
// CHECK-DIAGS: error: conformance to 'Codable' (aka 'Decodable & Encodable') is not covered by macro 'UndocumentedEncodable'
157+
130158
#endif

0 commit comments

Comments
 (0)