|
| 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 ¬es, |
| 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 ¬e, |
| 117 | + const AccessNotesFile ¬es) { |
| 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 ¬es = VD->getModuleContext()->getAccessNotes(); |
| 219 | + if (auto note = notes.lookup(VD)) |
| 220 | + applyAccessNote(VD, *note.get(), notes); |
| 221 | + return {}; |
| 222 | +} |
0 commit comments