Skip to content

[WIP] Teach SourceKit name translation request to translate Swift names to ObjC ones (by using PrintAsObjC). #7449

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 7 commits into from
Feb 14, 2017
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
9 changes: 6 additions & 3 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4116,11 +4116,13 @@ class AbstractStorageDecl : public ValueDecl {

/// Given that this is an Objective-C property or subscript declaration,
/// produce its getter selector.
ObjCSelector getObjCGetterSelector(LazyResolver *resolver = nullptr) const;
ObjCSelector getObjCGetterSelector(LazyResolver *resolver = nullptr,
Identifier preferredName = Identifier()) const;

/// Given that this is an Objective-C property or subscript declaration,
/// produce its setter selector.
ObjCSelector getObjCSetterSelector(LazyResolver *resolver = nullptr) const;
ObjCSelector getObjCSetterSelector(LazyResolver *resolver = nullptr,
Identifier preferredName = Identifier()) const;

AbstractStorageDecl *getOverriddenDecl() const {
return OverriddenDecl;
Expand Down Expand Up @@ -4844,7 +4846,8 @@ class AbstractFunctionDecl : public ValueDecl, public DeclContext {
const CaptureInfo &getCaptureInfo() const { return Captures; }

/// Retrieve the Objective-C selector that names this method.
ObjCSelector getObjCSelector(LazyResolver *resolver = nullptr) const;
ObjCSelector getObjCSelector(LazyResolver *resolver = nullptr,
DeclName preferredName = DeclName()) const;

/// Determine whether the given method would produce an Objective-C
/// instance method.
Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/Identifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ class ObjCSelector {
/// pieces.
ObjCSelector(ASTContext &ctx, unsigned numArgs, ArrayRef<Identifier> pieces);

/// Construct an invalid ObjCSelector.
ObjCSelector() : Storage() {};
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: trailing semicolon. :-)


/// Convert to true if the decl name is valid.
explicit operator bool() const { return (bool)Storage; }

/// Determine the number of arguments in the selector.
///
/// When this is zero, the number of selector pieces will be one. Otherwise,
Expand Down
10 changes: 10 additions & 0 deletions include/swift/PrintAsObjC/PrintAsObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,26 @@

#include "swift/Basic/LLVM.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/Identifier.h"

namespace swift {
class ModuleDecl;
class ValueDecl;

/// Print the Objective-C-compatible declarations in a module as a Clang
/// header.
///
/// Returns true on error.
bool printAsObjC(raw_ostream &out, ModuleDecl *M, StringRef bridgingHeader,
Accessibility minRequiredAccess);

/// Get the name for a value decl D if D is exported to ObjC, PreferredName is
/// specified to perform what-if analysis, shadowing D's original name during
/// computation.
///
/// Returns a pair of Identifier and ObjCSelector, only one of which is valid.
std::pair<Identifier, ObjCSelector> getObjCNameForSwiftDecl(const ValueDecl *VD,
DeclName PreferredName);
}

#endif
42 changes: 30 additions & 12 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3406,7 +3406,7 @@ void AbstractStorageDecl::setInvalidBracesRange(SourceRange BracesRange) {
}

ObjCSelector AbstractStorageDecl::getObjCGetterSelector(
LazyResolver *resolver) const {
LazyResolver *resolver, Identifier preferredName) const {
// If the getter has an @objc attribute with a name, use that.
if (auto getter = getGetter()) {
if (auto objcAttr = getter->getAttrs().getAttribute<ObjCAttr>()) {
Expand All @@ -3430,11 +3430,16 @@ ObjCSelector AbstractStorageDecl::getObjCGetterSelector(

// The getter selector is the property name itself.
auto var = cast<VarDecl>(this);
return VarDecl::getDefaultObjCGetterSelector(ctx, var->getObjCPropertyName());
auto name = var->getObjCPropertyName();

// Use preferred name is specified.
if (!preferredName.empty())
name = preferredName;
return VarDecl::getDefaultObjCGetterSelector(ctx, name);
}

ObjCSelector AbstractStorageDecl::getObjCSetterSelector(
LazyResolver *resolver) const {
LazyResolver *resolver, Identifier preferredName) const {
// If the setter has an @objc attribute with a name, use that.
auto setter = getSetter();
auto objcAttr = setter ? setter->getAttrs().getAttribute<ObjCAttr>()
Expand Down Expand Up @@ -3464,9 +3469,10 @@ ObjCSelector AbstractStorageDecl::getObjCSetterSelector(
// The setter selector for, e.g., 'fooBar' is 'setFooBar:', with the
// property name capitalized and preceded by 'set'.
auto var = cast<VarDecl>(this);
auto result = VarDecl::getDefaultObjCSetterSelector(
ctx,
var->getObjCPropertyName());
Identifier Name = var->getObjCPropertyName();
if (!preferredName.empty())
Name = preferredName;
auto result = VarDecl::getDefaultObjCSetterSelector(ctx, Name);

// Cache the result, so we don't perform string manipulation again.
if (objcAttr)
Expand Down Expand Up @@ -4320,7 +4326,7 @@ SourceRange AbstractFunctionDecl::getSignatureSourceRange() const {
}

ObjCSelector AbstractFunctionDecl::getObjCSelector(
LazyResolver *resolver) const {
LazyResolver *resolver, DeclName preferredName) const {
// If there is an @objc attribute with a name, use that name.
auto objc = getAttrs().getAttribute<ObjCAttr>();
if (objc) {
Expand All @@ -4329,15 +4335,27 @@ ObjCSelector AbstractFunctionDecl::getObjCSelector(
}

auto &ctx = getASTContext();
auto baseName = getName();
auto argNames = getFullName().getArgumentNames();

// Use the preferred name if specified
if (preferredName) {
// Return invalid selector if argument count doesn't match.
if (argNames.size() != preferredName.getArgumentNames().size()) {
return ObjCSelector();
}
baseName = preferredName.getBaseName();
argNames = preferredName.getArgumentNames();
}

auto func = dyn_cast<FuncDecl>(this);
if (func) {
// For a getter or setter, go through the variable or subscript decl.
if (func->isGetterOrSetter()) {
auto asd = cast<AbstractStorageDecl>(func->getAccessorStorageDecl());
return func->isGetter() ? asd->getObjCGetterSelector(resolver)
: asd->getObjCSetterSelector(resolver);
return func->isGetter() ?
asd->getObjCGetterSelector(resolver, baseName) :
asd->getObjCSetterSelector(resolver, baseName);
}
}

Expand Down Expand Up @@ -4373,12 +4391,12 @@ ObjCSelector AbstractFunctionDecl::getObjCSelector(

// If we have no arguments, it's a nullary selector.
if (numSelectorPieces == 0) {
return ObjCSelector(ctx, 0, getName());
return ObjCSelector(ctx, 0, baseName);
}

// If it's a unary selector with no name for the first argument, we're done.
if (numSelectorPieces == 1 && argNames.size() == 1 && argNames[0].empty()) {
return ObjCSelector(ctx, 1, getName());
return ObjCSelector(ctx, 1, baseName);
}

/// Collect the selector pieces.
Expand All @@ -4403,7 +4421,7 @@ ObjCSelector AbstractFunctionDecl::getObjCSelector(

// For the first selector piece, attach either the first parameter
// or "AndReturnError" to the base name, if appropriate.
auto firstPiece = getName();
auto firstPiece = baseName;
llvm::SmallString<32> scratch;
scratch += firstPiece.str();
if (errorConvention && piece == errorConvention->getErrorParameterIndex()) {
Expand Down
31 changes: 31 additions & 0 deletions lib/PrintAsObjC/PrintAsObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2633,3 +2633,34 @@ bool swift::printAsObjC(llvm::raw_ostream &os, ModuleDecl *M,
llvm::PrettyStackTraceString trace("While generating Objective-C header");
return ModuleWriter(*M, bridgingHeader, minRequiredAccess).writeToStream(os);
}

std::pair<Identifier, ObjCSelector> swift::
getObjCNameForSwiftDecl(const ValueDecl *VD, DeclName PreferredName){
ASTContext &Ctx = VD->getASTContext();
LazyResolver *Resolver = Ctx.getLazyResolver();
if (auto *FD = dyn_cast<AbstractFunctionDecl>(VD)) {
return {Identifier(), FD->getObjCSelector(Resolver, PreferredName)};
} else if (auto *VAD = dyn_cast<VarDecl>(VD)) {
if (PreferredName)
return {PreferredName.getBaseName(), ObjCSelector()};
return {VAD->getObjCPropertyName(), ObjCSelector()};
} else if (auto *SD = dyn_cast<SubscriptDecl>(VD)) {
return getObjCNameForSwiftDecl(SD->getGetter(), PreferredName);
} else if (auto *EL = dyn_cast<EnumElementDecl>(VD)) {
EnumDecl* ED = EL->getDeclContext()->getAsEnumOrEnumExtensionContext();
SmallString<64> Buffer;
{
llvm::raw_svector_ostream OS(Buffer);
OS << getNameForObjC(ED).str();
SmallString<64> Scratch;
OS << camel_case::toSentencecase(PreferredName.getBaseName().str(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you abstract this out so that the actual printing code can use this too?

Scratch);
}
return {Ctx.getIdentifier(Buffer.str()), ObjCSelector()};
} else {
auto Name = getNameForObjC(VD, CustomNamesOnly);
if (!Name.empty())
return {Ctx.getIdentifier(Name), ObjCSelector()};
return {PreferredName.getBaseName(), ObjCSelector()};
Copy link
Contributor

Choose a reason for hiding this comment

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

Hang on, what if PreferredName is also null? (Classes and protocols would currently fall into this case.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We get an empty identifier then. I think it is fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't it supposed to return the current name in that case?

}
}
83 changes: 83 additions & 0 deletions test/SourceKit/NameTranslation/swiftnames.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
class C1 : NSObject {
func foo(a : Int, b: Int, c : Int) {}
func foo1(a : Int, _: Int, c : Int) {}
func foo2(_ : Int, _: Int, c : Int) {}
func foo3() {}
init(a : Int, b :Int) {}
init(_ : Int) {}
}
func takeC1(a : Int, b: Int, c :Int) -> C1 {
let C1Ins = C1(a: a, b: b)
C1Ins.foo(a: a, b: b, c: c)
C1Ins.foo1(a: a, b, c: c)
C1Ins.foo2(a, b, c: c)
C1Ins.foo3()
let C1Ins2 = C1(a)
return C1Ins2
}

@objc public enum CommonFix : Int {
case A1
case B1
case C1
}

func takeCommonFix(_ f: CommonFix) -> Int {
switch(f) {
case .A1: break
case .B1: break
case .C1: break
return 1
}
return 0
}

@objc public enum CustomError: Int, Error {
case a1, b1
}

func takeError(_ e : CustomError) {
switch(e) {
case .a1: break
case .b1: break
return
}
}

// REQUIRES: objc_interop
// RUN: %sourcekitd-test -req=translate -swift-name "foo(a:b:c:)" -pos=11:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK1 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo(a1:b1:c1:)" -pos=11:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK2 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo(_:b1:c1:)" -pos=11:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK3 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo1(_:_:c2:)" -pos=11:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK4 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo1(_:_:_:)" -pos=11:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK5 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo2(a:b:c:)" -pos=12:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK6 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo2(_:_:_:)" -pos=12:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK7 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo1()" -pos=14:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK8 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo1(a:b:c:)" -pos=14:11 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK-NONE %s
// RUN: %sourcekitd-test -req=translate -swift-name "C11" -pos=1:8 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK9 %s

// RUN: %sourcekitd-test -req=translate -swift-name "init(a1:b2:)" -pos=10:16 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK10 %s
// RUN: %sourcekitd-test -req=translate -swift-name "init(_:_:)" -pos=10:16 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK11 %s
// RUN: %sourcekitd-test -req=translate -swift-name "foo(a1:_:)" -pos=10:16 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK12 %s

// RUN: %sourcekitd-test -req=translate -swift-name "A2" -pos=27:10 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK13 %s
// RUN: %sourcekitd-test -req=translate -swift-name "a2" -pos=27:10 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK13 %s
// RUN: %sourcekitd-test -req=translate -swift-name "a2" -pos=41:10 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK14 %s
// RUN: %sourcekitd-test -req=translate -swift-name "A2" -pos=41:10 %s -- -F %S/Inputs/mock-sdk -I %t.tmp %mcp_opt %s | %FileCheck -check-prefix=CHECK14 %s

// CHECK-NONE: <empty name translation info>
// CHECK1: fooWithA:b:c:
// CHECK2: fooWithA1:b1:c1:
// CHECK3: foo:b1:c1:
// CHECK4: foo1::c2:
// CHECK5: foo1:::
// CHECK6: foo2WithA:b:c:
// CHECK7: foo2:::
// CHECK8: foo1
// CHECK9: C11

// CHECK10: initWithA1:b2:
// CHECK11: init::
// CHECK12: fooWithA1::
// CHECK13: CommonFixA2
// CHECK14: CustomErrorA2
2 changes: 1 addition & 1 deletion tools/SourceKit/lib/SwiftLang/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ add_sourcekit_library(SourceKitSwiftLang
SwiftSourceDocInfo.cpp
DEPENDS SourceKitCore swiftDriver swiftFrontend swiftClangImporter swiftIndex swiftIDE
swiftAST swiftMarkup swiftParse swiftSIL swiftSILGen swiftSILOptimizer
swiftIRGen swiftSema swiftBasic swiftSerialization
swiftIRGen swiftSema swiftBasic swiftSerialization swiftPrintAsObjC
swiftOption libcmark_static
# Clang dependencies.
clangIndex
Expand Down
40 changes: 37 additions & 3 deletions tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "swift/IDE/ModuleInterfacePrinting.h"
#include "swift/IDE/Utils.h"
#include "swift/Markup/XMLUtils.h"
#include "swift/PrintAsObjC/PrintAsObjC.h"
#include "swift/Sema/IDETypeChecking.h"

#include "clang/AST/ASTContext.h"
Expand Down Expand Up @@ -877,14 +878,47 @@ getClangDeclarationName(clang::ASTContext &Ctx, NameTranslatingInfo &Info) {
}
}

static DeclName
getSwiftDeclName(ASTContext &Ctx, NameTranslatingInfo &Info) {
assert(SwiftLangSupport::getNameKindForUID(Info.NameKind) == NameKind::Swift);
std::vector<Identifier> Args(Info.ArgNames.size(), Identifier());
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Why not SmallVector?

std::transform(Info.ArgNames.begin(), Info.ArgNames.end(), Args.begin(),
[&](StringRef T) { return Ctx.getIdentifier(T); });
return DeclName(Ctx, Ctx.getIdentifier(Info.BaseName),
llvm::makeArrayRef(Args));
}

/// Returns true for failure to resolve.
static bool passNameInfoForDecl(const ValueDecl *VD, NameTranslatingInfo &Info,
std::function<void(const NameTranslatingInfo &)> Receiver) {
switch (SwiftLangSupport::getNameKindForUID(Info.NameKind)) {
case NameKind::Swift: {

// FIXME: Implement the swift to objc name translation.
return true;
NameTranslatingInfo Result;
auto &Ctx = VD->getDeclContext()->getASTContext();
auto ResultPair = getObjCNameForSwiftDecl(VD, getSwiftDeclName(Ctx, Info));
Identifier Name = ResultPair.first;
if (!Name.empty()) {
Result.NameKind = SwiftLangSupport::getUIDForNameKind(NameKind::ObjC);
Result.BaseName = Name.str();
} else if (ObjCSelector Selector = ResultPair.second) {
Result.NameKind = SwiftLangSupport::getUIDForNameKind(NameKind::ObjC);
SmallString<64> Buffer;
StringRef Total = Selector.getString(Buffer);
SmallVector<StringRef, 4> Pieces;
Total.split(Pieces, ":");
if (Selector.getNumArgs()) {
assert(Pieces.back().empty());
Pieces.pop_back();
std::transform(Pieces.begin(), Pieces.end(), Pieces.begin(),
[](StringRef P) { return StringRef(P.data(), P.size() + 1); });
}
Result.ArgNames = llvm::makeArrayRef(Pieces);
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks suspicious, even though I know ArgNames isn't an ArrayRef itself. Can you go through a different API just to make it not look like you're storing the ArrayRef?

} else {
Receiver(Result);
return true;
}
Receiver(Result);
return false;
}
case NameKind::ObjC: {
ClangImporter *Importer = static_cast<ClangImporter *>(VD->getDeclContext()->
Expand Down
5 changes: 1 addition & 4 deletions tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ static int handleTestInvocation(ArrayRef<const char *> Args,
} else {
BaseName = Text.substr(0, ArgStart);
auto ArgEnd = Text.find_last_of(')');
if (ArgEnd != StringRef::npos) {
if (ArgEnd == StringRef::npos) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Heh, oops.

llvm::errs() << "Swift name is malformed.\n";
return 1;
}
Expand Down Expand Up @@ -1160,9 +1160,6 @@ static void printNameTranslationInfo(sourcekitd_variant_t Info,
}
for (auto S : Selectors) {
OS << S;
if (S != Selectors.back()) {
OS << ":";
}
}
OS << '\n';
}
Expand Down
5 changes: 4 additions & 1 deletion tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,10 @@ handleSemanticRequest(RequestDict Req,
}
Input.ArgNames.resize(ArgParts.size() + Selectors.size());
std::transform(ArgParts.begin(), ArgParts.end(), Input.ArgNames.begin(),
[](const char *C) { return StringRef(C); });
[](const char *C) {
StringRef Original(C);
return Original == "_" ? StringRef() : Original;
});
std::transform(Selectors.begin(), Selectors.end(), Input.ArgNames.begin(),
[](const char *C) { return StringRef(C); });
return Lang.getNameInfo(*SourceFile, Offset, Input, Args,
Expand Down