Skip to content

Support raw identifiers (backtick-delimited identifiers containing non-identifier characters). #76636

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 9 commits into from
Mar 12, 2025
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
4 changes: 4 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,10 @@ class ASTContext final {
/// are the real (physical) module names on disk.
void setModuleAliases(const llvm::StringMap<StringRef> &aliasMap);

/// Adds a given alias to the map of Identifiers between module aliases and
/// their actual names.
void addModuleAlias(StringRef moduleAlias, StringRef realName);

/// Look up option used in \c getRealModuleName when module aliasing is applied.
enum class ModuleAliasLookupOption {
alwaysRealName,
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/ASTDemangler.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ class ASTBuilder {

static GenericTypeDecl *getAcceptableTypeDeclCandidate(ValueDecl *decl,
Demangle::Node::Kind kind);

/// Returns an identifier with the given name, automatically removing any
/// surrounding backticks that are present for raw identifiers.
Identifier getIdentifier(StringRef name);
};

SWIFT_END_INLINE_NAMESPACE
Expand Down
10 changes: 6 additions & 4 deletions include/swift/AST/ASTPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ class ASTPrinter {
void printEscapedStringLiteral(StringRef str);

void printName(Identifier Name,
PrintNameContext Context = PrintNameContext::Normal);
PrintNameContext Context = PrintNameContext::Normal,
bool IsSpecializedCxxType = false);

void setIndent(unsigned NumSpaces) {
CurrentIndentation = NumSpaces;
Expand Down Expand Up @@ -444,9 +445,10 @@ void printWithCompatibilityFeatureChecks(ASTPrinter &printer,
Decl *decl,
llvm::function_ref<void()> printBody);

/// Determine whether we need to escape the given keyword within the
/// given context, by wrapping it in backticks.
bool escapeKeywordInContext(StringRef keyword, PrintNameContext context);
/// Determine whether we need to escape the given name within the given
/// context, by wrapping it in backticks.
bool escapeIdentifierInContext(Identifier name, PrintNameContext context,
bool isSpecializedCxxType = false);

} // namespace swift

Expand Down
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6417,6 +6417,14 @@ ERROR(objc_enum_case_multi,none,
ERROR(objc_extension_not_class,none,
"'@objc' can only be applied to an extension of a class", ())

ERROR(objc_name_not_valid_identifier,none,
"'@objc' %0 name is not a valid Objective-C identifier",
(DescriptiveDeclKind))
ERROR(objc_cannot_infer_name_raw_identifier,none,
"cannot infer '@objc' %0 name because the Swift name is not a valid "
"Objective-C identifier; specify the name in '@objc' explicitly",
(DescriptiveDeclKind))

// If you change this, also change enum ObjCReason
#define OBJC_ATTR_SELECT "select{marked @_cdecl|marked dynamic|marked @objc|marked @objcMembers|marked @IBOutlet|marked @IBAction|marked @IBSegueAction|marked @NSManaged|a member of an @objc protocol|implicitly @objc|an @objc override|an implementation of an @objc requirement|marked @IBInspectable|marked @GKInspectable|in an @objc extension of a class (without @nonobjc)|in an @objc @implementation extension of a class (without final or @nonobjc)|marked @objc by an access note}"

Expand Down
22 changes: 18 additions & 4 deletions include/swift/AST/Identifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,19 +101,22 @@ class Identifier {

/// isOperator - Return true if this identifier is an operator, false if it is
/// a normal identifier.
/// FIXME: We should maybe cache this.
bool isOperator() const {
if (empty())
return false;
if (isEditorPlaceholder())
return false;
if ((unsigned char)Pointer[0] < 0x80)
return isOperatorStartCodePoint((unsigned char)Pointer[0]);

// Handle the high unicode case out of line.
return isOperatorSlow();
}

/// Returns true if this identifier contains non-identifier characters and
/// must always be escaped with backticks, even in contexts were other
/// escaped identifiers could omit backticks (like keywords as argument
/// labels).
bool mustAlwaysBeEscaped() const;
Comment on lines +114 to +118
Copy link
Member

@rintaro rintaro Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some test cases for code completion checking mustAlwaysBeEscaped() items are actually escaped in code completion suggestions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Those did need to be fixed.


bool isArithmeticOperator() const {
return is("+") || is("-") || is("*") || is("/") || is("%");
}
Expand Down Expand Up @@ -350,6 +353,10 @@ class DeclBaseName {
return !isSpecial() && getIdentifier().isOperator();
}

bool mustAlwaysBeEscaped() const {
return !isSpecial() && getIdentifier().mustAlwaysBeEscaped();
}

bool isEditorPlaceholder() const {
return !isSpecial() && getIdentifier().isEditorPlaceholder();
}
Expand Down Expand Up @@ -571,7 +578,12 @@ class DeclName {
bool isOperator() const {
return getBaseName().isOperator();
}


/// True if this name is an escaped identifier.
bool mustAlwaysBeEscaped() const {
return getBaseName().mustAlwaysBeEscaped();
}

/// True if this name should be found by a decl ref or member ref under the
/// name specified by 'refName'.
///
Expand Down Expand Up @@ -728,6 +740,8 @@ class DeclNameRef {
return FullName.isOperator();
}

bool mustAlwaysBeEscaped() const { return FullName.mustAlwaysBeEscaped(); }

bool isCompoundName() const {
return FullName.isCompoundName();
}
Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ LANGUAGE_FEATURE(NonescapableTypes, 446, "Nonescapable types")
LANGUAGE_FEATURE(BuiltinEmplaceTypedThrows, 0, "Builtin.emplace typed throws")
SUPPRESSIBLE_LANGUAGE_FEATURE(MemorySafetyAttributes, 458, "@unsafe attribute")
LANGUAGE_FEATURE(ValueGenerics, 452, "Value generics feature (integer generics)")
LANGUAGE_FEATURE(RawIdentifiers, 451, "Raw identifiers")

// Swift 6
UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
Expand Down
9 changes: 8 additions & 1 deletion include/swift/Basic/Mangler.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ class Mangler {
print(llvm::dbgs());
}

/// Appends the given raw identifier to the buffer in the form required to
/// mangle it. This handles the transformations needed for such identifiers
/// to retain compatibility with older runtimes.
static void
appendRawIdentifierForRuntime(StringRef ident,
llvm::SmallVectorImpl<char> &buffer);

protected:
/// Removes the last characters of the buffer by setting it's size to a
/// smaller value.
Expand Down Expand Up @@ -143,7 +150,7 @@ class Mangler {
SWIFT_DEBUG_DUMP;

/// Appends a mangled identifier string.
void appendIdentifier(StringRef ident);
void appendIdentifier(StringRef ident, bool allowRawIdentifiers = true);

// NOTE: the addSubstitution functions perform the value computation before
// the assignment because there is no sequence point synchronising the
Expand Down
10 changes: 8 additions & 2 deletions include/swift/Demangling/ManglingUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,16 @@ inline bool isWordEnd(char ch, char prevCh) {
return false;
}

/// Returns true if \p ch is a valid character which may appear at the start
/// of a symbol mangling.
inline bool isValidSymbolStart(char ch) {
return isLetter(ch) || ch == '_' || ch == '$';
}

/// Returns true if \p ch is a valid character which may appear in a symbol
/// mangling.
/// mangling anywhere other than the first character.
inline bool isValidSymbolChar(char ch) {
return isLetter(ch) || isDigit(ch) || ch == '_' || ch == '$';
return isValidSymbolStart(ch) || isDigit(ch);
}

/// Returns true if \p str contains any character which may not appear in a
Expand Down
28 changes: 19 additions & 9 deletions include/swift/Frontend/ModuleInterfaceLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,10 +318,11 @@ class ExplicitModuleMapParser {
public:
ExplicitModuleMapParser(llvm::BumpPtrAllocator &Allocator) : Saver(Allocator) {}

std::error_code
parseSwiftExplicitModuleMap(llvm::MemoryBufferRef BufferRef,
llvm::StringMap<ExplicitSwiftModuleInputInfo> &swiftModuleMap,
llvm::StringMap<ExplicitClangModuleInputInfo> &clangModuleMap) {
std::error_code parseSwiftExplicitModuleMap(
llvm::MemoryBufferRef BufferRef,
llvm::StringMap<ExplicitSwiftModuleInputInfo> &swiftModuleMap,
llvm::StringMap<ExplicitClangModuleInputInfo> &clangModuleMap,
llvm::StringMap<std::string> &moduleAliases) {
using namespace llvm::yaml;
// Use a new source manager instead of the one from ASTContext because we
// don't want the JSON file to be persistent.
Expand All @@ -331,7 +332,8 @@ class ExplicitModuleMapParser {
assert(DI != Stream.end() && "Failed to read a document");
if (auto *MN = dyn_cast_or_null<SequenceNode>(DI->getRoot())) {
for (auto &entry : *MN) {
if (parseSingleModuleEntry(entry, swiftModuleMap, clangModuleMap)) {
if (parseSingleModuleEntry(entry, swiftModuleMap, clangModuleMap,
moduleAliases)) {
return std::make_error_code(std::errc::invalid_argument);
}
}
Expand Down Expand Up @@ -359,16 +361,19 @@ class ExplicitModuleMapParser {
llvm_unreachable("Unexpected JSON value for isFramework");
}

bool parseSingleModuleEntry(llvm::yaml::Node &node,
llvm::StringMap<ExplicitSwiftModuleInputInfo> &swiftModuleMap,
llvm::StringMap<ExplicitClangModuleInputInfo> &clangModuleMap) {
bool parseSingleModuleEntry(
llvm::yaml::Node &node,
llvm::StringMap<ExplicitSwiftModuleInputInfo> &swiftModuleMap,
llvm::StringMap<ExplicitClangModuleInputInfo> &clangModuleMap,
llvm::StringMap<std::string> &moduleAliases) {
using namespace llvm::yaml;
auto *mapNode = dyn_cast<MappingNode>(&node);
if (!mapNode)
return true;
StringRef moduleName;
std::optional<std::string> swiftModulePath, swiftModuleDocPath,
swiftModuleSourceInfoPath, swiftModuleCacheKey, clangModuleCacheKey;
swiftModuleSourceInfoPath, swiftModuleCacheKey, clangModuleCacheKey,
moduleAlias;
std::optional<std::vector<std::string>> headerDependencyPaths;
std::string clangModuleMapPath = "", clangModulePath = "";
bool isFramework = false, isSystem = false,
Expand Down Expand Up @@ -405,6 +410,8 @@ class ExplicitModuleMapParser {
clangModuleCacheKey = val.str();
} else if (key == "isBridgingHeaderDependency") {
isBridgingHeaderDependency = parseBoolValue(val);
} else if (key == "moduleAlias") {
moduleAlias = val.str();
} else {
// Being forgiving for future fields.
continue;
Expand Down Expand Up @@ -439,6 +446,9 @@ class ExplicitModuleMapParser {
clangModuleCacheKey);
didInsert = clangModuleMap.try_emplace(moduleName, std::move(entry)).second;
}
if (didInsert && moduleAlias.has_value()) {
moduleAliases[*moduleAlias] = moduleName;
}
// Prevent duplicate module names.
return !didInsert;
}
Expand Down
2 changes: 2 additions & 0 deletions include/swift/IDE/CompletionLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
void addValueBaseName(CodeCompletionResultBuilder &Builder,
DeclBaseName Name);

void addIdentifier(CodeCompletionResultBuilder &Builder, Identifier Name);

void addLeadingDot(CodeCompletionResultBuilder &Builder);

void addTypeAnnotation(CodeCompletionResultBuilder &Builder, Type T,
Expand Down
9 changes: 9 additions & 0 deletions include/swift/Parse/Lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@ class Lexer {
/// identifier, without escaping characters.
static bool isIdentifier(StringRef identifier);

// Returns true if the given string is a raw identifier that must always
// be escaped by backticks when printing it back in source form or writing
// its name into runtime metadata.
static bool identifierMustAlwaysBeEscaped(StringRef str);

/// Determines if the given string is a valid non-operator
/// identifier if it were surrounded by backticks.
static bool isValidAsEscapedIdentifier(StringRef identifier);

/// Determine the token kind of the string, given that it is a valid
/// non-operator identifier. Return tok::identifier if the string is not a
/// reserved word.
Expand Down
16 changes: 10 additions & 6 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2136,16 +2136,20 @@ void ASTContext::setModuleAliases(const llvm::StringMap<StringRef> &aliasMap) {
for (auto k: aliasMap.keys()) {
auto v = aliasMap.lookup(k);
if (!v.empty()) {
auto key = getIdentifier(k);
auto val = getIdentifier(v);
// key is a module alias, val is its corresponding real name
ModuleAliasMap[key] = std::make_pair(val, true);
// add an entry with an alias as key for an easier lookup later
ModuleAliasMap[val] = std::make_pair(key, false);
addModuleAlias(k, v);
}
}
}

void ASTContext::addModuleAlias(StringRef moduleAlias, StringRef realName) {
auto key = getIdentifier(moduleAlias);
auto val = getIdentifier(realName);
// key is a module alias, val is its corresponding real name
ModuleAliasMap[key] = std::make_pair(val, true);
// add an entry with an alias as key for an easier lookup later
ModuleAliasMap[val] = std::make_pair(key, false);
}

Identifier ASTContext::getRealModuleName(Identifier key, ModuleAliasLookupOption option) const {
auto found = ModuleAliasMap.find(key);
if (found == ModuleAliasMap.end())
Expand Down
Loading