Files
swift-mirror/lib/AST/AccessNotes.cpp
Tony Allevato 0924981eab Handle raw identifiers correctly in renamed APIs.
This change adds support for raw identifiers when renaming APIs in
Swift. This includes APIs defined in Swift (using the
`@availability(..., renamed: ...)` attribute), as well as C/Objective-C
(using either the `swift_name` attribute or APINotes). All of these take
decl names as strings, and so the parsing for these requires that any
decl name components (the base name or the argument labels) must have
surrounding backticks in order to be treated correctly as a raw
identifier.

Getting this working required some refactoring of `ParsedDeclName`
since it previously assumed that it could just split around delimiters
like `.`, `(`, and `:`. That's no longer guaranteed to work, so I've
introduced backtick-aware split helpers to deal with this. Likewise,
`ParsedDeclName` now keeps track of special decl base names (like
`init` and `subscript`) to ensure that they're handled properly and
not incorrectly escaped under the new logic.
2026-05-17 21:03:18 -04:00

308 lines
9.0 KiB
C++

//===--- AccessNotes.cpp - Access Notes -------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Implements access notes, which allow certain modifiers or attributes to be
// added to the declarations in a module.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/AccessNotes.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/Module.h" // DeclContext::isModuleScopeContext()
#include "swift/Basic/Assertions.h"
#include "swift/Parse/ParseDeclName.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/YAMLTraits.h"
namespace swift {
AccessNoteDeclName::AccessNoteDeclName()
: parentNames(), name(), accessorKind(std::nullopt) {}
AccessNoteDeclName::AccessNoteDeclName(ASTContext &ctx, StringRef str) {
auto parsedName = parseDeclName(str);
for (auto component : parsedName.ContextNames) {
parentNames.push_back(ctx.getIdentifier(component));
}
if (parsedName.IsGetter)
accessorKind = AccessorKind::Get;
else if (parsedName.IsSetter)
accessorKind = AccessorKind::Set;
else
accessorKind = std::nullopt;
name = parsedName.formDeclName(ctx);
}
bool AccessNoteDeclName::matches(ValueDecl *VD) const {
// The declaration whose name and context we ought to match. Usually `VD`, but
// when `VD` is an accessor, it becomes the accessor's storage instead.
auto lookupVD = VD;
// First, we check if the accessor-ness of `VD` matches the accessor-ness of
// the name, and update `lookupVD` if necessary.
if (auto accessor = dyn_cast<AccessorDecl>(VD)) {
if (!accessorKind || *accessorKind != accessor->getAccessorKind())
return false;
lookupVD = accessor->getStorage();
}
else if (accessorKind.has_value())
return false;
// Check that `name` matches `lookupVD`.
if (!lookupVD->getName().matchesRef(name))
return false;
// The rest of this checks `parentNames` against the parents of `lookupVD`.
ArrayRef<Identifier> remainingContextNames = parentNames;
DeclContext *nextContext = lookupVD->getDeclContext();
while (!nextContext->isModuleScopeContext()) {
// If we've run out of names without reaching module scope, we've failed.
if (remainingContextNames.empty())
return false;
Identifier contextName = remainingContextNames.back();
// If the context is not a type (or extension), we can't name VD in an
// access note and the match fails; if the name doesn't match, the match
// fails too.
auto contextType = nextContext->getSelfNominalTypeDecl();
if (!contextType || contextType->getName() != contextName)
return false;
// Still checking. Move to the parent.
remainingContextNames = remainingContextNames.drop_back();
nextContext = contextType->getParent();
}
// If the context is module-scoped, we've succeeded if we're out of names, or
// failed if we still have some names to go.
return remainingContextNames.empty();
}
bool AccessNoteDeclName::empty() const {
return !name;
}
void AccessNoteDeclName::print(llvm::raw_ostream &os) const {
if (accessorKind)
os << getAccessorLabel(*accessorKind) << "ter:";
for (auto parentName : parentNames)
os << parentName << '.';
name.print(os, /*skipEmptyArgumentNames=*/false);
}
void AccessNoteDeclName::dump() const {
print(llvm::errs());
llvm::errs() << '\n';
}
NullablePtr<const AccessNote> AccessNotesFile::lookup(ValueDecl *VD) const {
assert(VD != nullptr);
auto iter = llvm::find_if(Notes, [&](const AccessNote &note) -> bool {
return note.Name.matches(VD);
});
return NullablePtr<const AccessNote>(iter == Notes.end() ? nullptr : &*iter);
}
void AccessNotesFile::dump() const {
dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
void AccessNote::dump() const {
dump(llvm::dbgs());
llvm::dbgs() << "\n";
}
void AccessNotesFile::dump(llvm::raw_ostream &os) const {
os << "(access_notes reason='" << Reason << "'";
for (const auto &note : Notes) {
os << "\n";
note.dump(os, /*indent=*/2);
}
os << ")";
}
void AccessNote::dump(llvm::raw_ostream &os, int indent) const {
os.indent(indent) << "(note name='";
Name.print(os);
os << "'";
if (Name.name.getBaseName().isSpecial())
os << " is_special_name";
if (ObjC)
os << " objc=" << *ObjC;
if (ObjCName)
os << " objc_name='" << *ObjCName << "'";
if (Dynamic)
os << " dynamic=" << *Dynamic;
os << ")";
}
}
LLVM_YAML_IS_SEQUENCE_VECTOR(swift::AccessNote)
// Not using macro to avoid dllimport/dllexport issues on Windows.
template <> struct llvm::yaml::ScalarTraits<swift::AccessNoteDeclName> {
static void output(const swift::AccessNoteDeclName &Value, void *ctx,
raw_ostream &Out);
static StringRef input(StringRef Scalar, void *ctxt,
swift::AccessNoteDeclName &Value);
static QuotingType mustQuote(StringRef);
};
// Not using macro to avoid dllimport/dllexport issues on Windows.
template <> struct llvm::yaml::ScalarTraits<swift::ObjCSelector> {
static void output(const swift::ObjCSelector &Value, void *ctx,
raw_ostream &Out);
static StringRef input(StringRef Scalar, void *ctxt,
swift::ObjCSelector &Value);
static QuotingType mustQuote(StringRef);
};
// Not using macro to avoid validation issues.
template <> struct llvm::yaml::MappingTraits<swift::AccessNote> {
static void mapping(IO &IO, swift::AccessNote &Obj);
static std::string validate(IO &IO, swift::AccessNote &Obj);
};
// Not using macro to avoid dllimport/dllexport issues on Windows.
template <> struct llvm::yaml::MappingTraits<swift::AccessNotesFile> {
static void mapping(IO &IO, swift::AccessNotesFile &Obj);
};
namespace swift {
static void
convertToErrorAndJoin(const llvm::SMDiagnostic &diag, void *ctxPtr) {
ASTContext &ctx = *(ASTContext*)ctxPtr;
auto loc = SourceLoc::getFromPointer(diag.getLoc().getPointer());
assert(ctx.SourceMgr.isOwning(loc));
switch (diag.getKind()) {
case llvm::SourceMgr::DK_Error:
ctx.Diags.diagnose(loc, diag::error_in_access_notes_file,
diag.getMessage());
break;
default:
ctx.Diags.diagnose(loc, diag::warning_in_access_notes_file,
diag.getMessage());
break;
}
}
std::optional<AccessNotesFile>
AccessNotesFile::load(ASTContext &ctx, const llvm::MemoryBuffer *buffer) {
llvm::yaml::Input yamlIn(llvm::MemoryBufferRef(*buffer), (void *)&ctx,
convertToErrorAndJoin, &ctx);
yamlIn.setAllowUnknownKeys(true);
AccessNotesFile notes;
yamlIn >> notes;
if (yamlIn.error())
return std::nullopt;
return notes;
}
}
namespace llvm {
namespace yaml {
using AccessNote = swift::AccessNote;
using AccessNotesFile = swift::AccessNotesFile;
using ASTContext = swift::ASTContext;
using AccessNoteDeclName = swift::AccessNoteDeclName;
using ObjCSelector = swift::ObjCSelector;
void ScalarTraits<AccessNoteDeclName>::
output(const AccessNoteDeclName &name, void *ctxPtr, raw_ostream &os) {
name.print(os);
}
StringRef ScalarTraits<AccessNoteDeclName>::
input(StringRef str, void *ctxPtr, AccessNoteDeclName &name) {
auto &ctx = *static_cast<swift::ASTContext *>(ctxPtr);
name = AccessNoteDeclName(ctx, str);
return name.empty() ? "invalid declaration name" : "";
}
QuotingType ScalarTraits<AccessNoteDeclName>::mustQuote(StringRef) {
return QuotingType::Single;
}
void ScalarTraits<ObjCSelector>::output(const ObjCSelector &selector,
void *ctxPtr, raw_ostream &os) {
os << selector;
}
StringRef ScalarTraits<ObjCSelector>::input(StringRef str, void *ctxPtr,
ObjCSelector &selector) {
auto &ctx = *static_cast<swift::ASTContext *>(ctxPtr);
if (auto sel = ObjCSelector::parse(ctx, str)) {
selector = *sel;
return "";
}
return "invalid selector";
}
QuotingType ScalarTraits<ObjCSelector>::mustQuote(StringRef) {
return QuotingType::Single;
}
void MappingTraits<AccessNote>::mapping(IO &io, AccessNote &note) {
io.mapRequired("Name", note.Name);
io.mapOptional("ObjC", note.ObjC);
io.mapOptional("Dynamic", note.Dynamic);
io.mapOptional("ObjCName", note.ObjCName);
}
std::string MappingTraits<AccessNote>::validate(IO &io, AccessNote &note) {
if (note.ObjCName.has_value()) {
if (!note.ObjC)
note.ObjC = true;
else if (!*note.ObjC)
return "cannot have an 'ObjCName' if 'ObjC' is false";
}
return "";
}
void MappingTraits<AccessNotesFile>::mapping(IO &io, AccessNotesFile &notes) {
io.mapRequired("Reason", notes.Reason);
io.mapRequired("Notes", notes.Notes);
}
}
}