Skip to content

Commit 033638e

Browse files
committed
NFC: Split out TypeCheckAccessNotes.cpp
1 parent 6ef8f45 commit 033638e

File tree

3 files changed

+223
-190
lines changed

3 files changed

+223
-190
lines changed

lib/Sema/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ add_swift_host_library(swiftSema STATIC
5151
SyntacticElementTarget.cpp
5252
TypeOfReference.cpp
5353
TypeCheckAccess.cpp
54+
TypeCheckAccessNotes.cpp
5455
TypeCheckAttr.cpp
5556
TypeCheckAttrABI.cpp
5657
TypeCheckAvailability.cpp

lib/Sema/TypeCheckAccessNotes.cpp

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//===--- TypeCheckAccessNotes.cpp - Type Checking for Access Notes --------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file implements the implicit attribute transform caused by access notes.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "TypeCheckDecl.h"
18+
#include "TypeCheckObjC.h"
19+
#include "TypeChecker.h"
20+
#include "swift/AST/ASTContext.h"
21+
#include "swift/AST/ASTPrinter.h"
22+
#include "swift/AST/Decl.h"
23+
#include "swift/AST/Module.h"
24+
#include "swift/AST/SourceFile.h"
25+
#include "swift/AST/TypeCheckRequests.h"
26+
#include "swift/Basic/LLVM.h"
27+
28+
using namespace swift;
29+
30+
static StringRef prettyPrintAttrs(const ValueDecl *VD,
31+
ArrayRef<const DeclAttribute *> attrs,
32+
SmallVectorImpl<char> &out) {
33+
llvm::raw_svector_ostream os(out);
34+
StreamPrinter printer(os);
35+
36+
PrintOptions opts = PrintOptions::printDeclarations();
37+
VD->getAttrs().print(printer, opts, attrs, VD);
38+
return StringRef(out.begin(), out.size()).drop_back();
39+
}
40+
41+
static void diagnoseChangesByAccessNote(
42+
ValueDecl *VD, ArrayRef<const DeclAttribute *> attrs,
43+
Diag<StringRef, StringRef, const ValueDecl *> diagID,
44+
Diag<StringRef> fixItID,
45+
llvm::function_ref<void(InFlightDiagnostic, StringRef)> addFixIts) {
46+
if (!VD->getASTContext().LangOpts.shouldRemarkOnAccessNoteSuccess() ||
47+
attrs.empty())
48+
return;
49+
50+
// Generate string containing all attributes.
51+
SmallString<64> attrString;
52+
auto attrText = prettyPrintAttrs(VD, attrs, attrString);
53+
54+
SourceLoc fixItLoc;
55+
56+
auto reason = VD->getModuleContext()->getAccessNotes().Reason;
57+
auto diag = VD->diagnose(diagID, reason, attrText, VD);
58+
for (auto attr : attrs) {
59+
diag.highlight(attr->getRangeWithAt());
60+
if (fixItLoc.isInvalid())
61+
fixItLoc = attr->getRangeWithAt().Start;
62+
}
63+
64+
if (!fixItLoc)
65+
fixItLoc = VD->getAttributeInsertionLoc(true);
66+
67+
addFixIts(VD->getASTContext().Diags.diagnose(fixItLoc, fixItID, attrText),
68+
attrString);
69+
}
70+
71+
template <typename Attr>
72+
static void addOrRemoveAttr(ValueDecl *VD, const AccessNotesFile &notes,
73+
std::optional<bool> expected,
74+
SmallVectorImpl<DeclAttribute *> &removedAttrs,
75+
llvm::function_ref<Attr *()> willCreate) {
76+
if (!expected)
77+
return;
78+
79+
auto attr = VD->getAttrs().getAttribute<Attr>();
80+
if (*expected == (attr != nullptr))
81+
return;
82+
83+
if (*expected) {
84+
attr = willCreate();
85+
attr->setAddedByAccessNote();
86+
VD->getAttrs().add(attr);
87+
88+
// Arrange for us to emit a remark about this attribute after type checking
89+
// has ensured it's valid.
90+
if (auto SF = VD->getDeclContext()->getParentSourceFile())
91+
SF->AttrsAddedByAccessNotes[VD].push_back(attr);
92+
} else {
93+
removedAttrs.push_back(attr);
94+
VD->getAttrs().removeAttribute(attr);
95+
}
96+
}
97+
98+
InFlightDiagnostic swift::softenIfAccessNote(const Decl *D,
99+
const DeclAttribute *attr,
100+
InFlightDiagnostic &diag) {
101+
const ValueDecl *VD = dyn_cast<ValueDecl>(D);
102+
if (!VD || !attr || !attr->getAddedByAccessNote())
103+
return std::move(diag);
104+
105+
SmallString<32> attrString;
106+
auto attrText = prettyPrintAttrs(VD, llvm::ArrayRef(attr), attrString);
107+
108+
ASTContext &ctx = D->getASTContext();
109+
auto behavior = ctx.LangOpts.getAccessNoteFailureLimit();
110+
return std::move(diag.wrapIn(diag::wrap_invalid_attr_added_by_access_note,
111+
D->getModuleContext()->getAccessNotes().Reason,
112+
ctx.AllocateCopy(attrText), VD)
113+
.limitBehavior(behavior));
114+
}
115+
116+
static void applyAccessNote(ValueDecl *VD, const AccessNote &note,
117+
const AccessNotesFile &notes) {
118+
ASTContext &ctx = VD->getASTContext();
119+
SmallVector<DeclAttribute *, 2> removedAttrs;
120+
121+
addOrRemoveAttr<ObjCAttr>(VD, notes, note.ObjC, removedAttrs, [&] {
122+
return ObjCAttr::create(ctx, note.ObjCName, false);
123+
});
124+
125+
addOrRemoveAttr<DynamicAttr>(VD, notes, note.Dynamic, removedAttrs,
126+
[&] { return new (ctx) DynamicAttr(true); });
127+
128+
// FIXME: If we ever have more attributes, we'll need to sort removedAttrs by
129+
// SourceLoc. As it is, attrs are always before modifiers, so we're okay now.
130+
131+
diagnoseChangesByAccessNote(VD, removedAttrs,
132+
diag::attr_removed_by_access_note,
133+
diag::fixit_attr_removed_by_access_note,
134+
[&](InFlightDiagnostic diag, StringRef code) {
135+
for (auto attr : llvm::reverse(removedAttrs))
136+
diag.fixItRemove(attr->getRangeWithAt());
137+
});
138+
139+
if (note.ObjCName) {
140+
auto newName = note.ObjCName.value();
141+
142+
// addOrRemoveAttr above guarantees there's an ObjCAttr on this decl.
143+
auto attr = VD->getAttrs().getAttribute<ObjCAttr>();
144+
assert(attr && "ObjCName set, but ObjCAttr not true or did not apply???");
145+
146+
if (!attr->hasName()) {
147+
// There was already an @objc attribute with no selector. Set it.
148+
attr->setName(newName, true);
149+
150+
if (!ctx.LangOpts.shouldRemarkOnAccessNoteSuccess())
151+
return;
152+
153+
VD->diagnose(diag::attr_objc_name_changed_by_access_note, notes.Reason,
154+
VD, newName);
155+
156+
auto fixIt =
157+
VD->diagnose(diag::fixit_attr_objc_name_changed_by_access_note);
158+
fixDeclarationObjCName(fixIt, VD, ObjCSelector(), newName);
159+
} else if (attr->getName() != newName) {
160+
// There was already an @objc
161+
auto behavior = ctx.LangOpts.getAccessNoteFailureLimit();
162+
163+
VD->diagnose(diag::attr_objc_name_conflicts_with_access_note,
164+
notes.Reason, VD, attr->getName().value(), newName)
165+
.highlight(attr->getRangeWithAt())
166+
.limitBehavior(behavior);
167+
}
168+
}
169+
}
170+
171+
void TypeChecker::applyAccessNote(ValueDecl *VD) {
172+
(void)evaluateOrDefault(VD->getASTContext().evaluator,
173+
ApplyAccessNoteRequest{VD}, {});
174+
}
175+
176+
void swift::diagnoseAttrsAddedByAccessNote(SourceFile &SF) {
177+
if (!SF.getASTContext().LangOpts.shouldRemarkOnAccessNoteSuccess())
178+
return;
179+
180+
for (auto declAndAttrs : SF.AttrsAddedByAccessNotes) {
181+
auto D = declAndAttrs.getFirst();
182+
SmallVector<DeclAttribute *, 4> sortedAttrs;
183+
llvm::append_range(sortedAttrs, declAndAttrs.getSecond());
184+
185+
// Filter out invalid attributes.
186+
sortedAttrs.erase(llvm::remove_if(sortedAttrs,
187+
[](DeclAttribute *attr) {
188+
assert(attr->getAddedByAccessNote());
189+
return attr->isInvalid();
190+
}),
191+
sortedAttrs.end());
192+
if (sortedAttrs.empty())
193+
continue;
194+
195+
// Sort attributes by name.
196+
llvm::sort(sortedAttrs, [](DeclAttribute *first, DeclAttribute *second) {
197+
return first->getAttrName() < second->getAttrName();
198+
});
199+
sortedAttrs.erase(std::unique(sortedAttrs.begin(), sortedAttrs.end()),
200+
sortedAttrs.end());
201+
202+
diagnoseChangesByAccessNote(
203+
D, sortedAttrs, diag::attr_added_by_access_note,
204+
diag::fixit_attr_added_by_access_note,
205+
[=](InFlightDiagnostic diag, StringRef code) {
206+
diag.fixItInsert(D->getAttributeInsertionLoc(/*isModifier=*/true),
207+
code);
208+
});
209+
}
210+
}
211+
212+
evaluator::SideEffect ApplyAccessNoteRequest::evaluate(Evaluator &evaluator,
213+
ValueDecl *VD) const {
214+
// Access notes don't apply to ABI-only attributes.
215+
if (!ABIRoleInfo(VD).providesAPI())
216+
return {};
217+
218+
AccessNotesFile &notes = VD->getModuleContext()->getAccessNotes();
219+
if (auto note = notes.lookup(VD))
220+
applyAccessNote(VD, *note.get(), notes);
221+
return {};
222+
}

0 commit comments

Comments
 (0)