Skip to content

Conversation

@nathawes
Copy link
Contributor

We have an issue where the Swift symbols that are exported to ObjC via the generated ObjC header that the Swift compiler emits, have different USRs when indexed from swift & clang.
The swift side uses Swift-specific USRs while the clang side uses it’s own ObjC USRs, making these symbols and their references seem as if they are different symbols (e.g. for jump-to-definition, callers, etc).

This patch changes the USR generation for @objc swift symbols to use clang-style USRs, so there is a single USR cross-language.

This is a step towards resolving rdar://problem/16271632, but it doesn't yet 'namespace' the generated clang-style USRs with the swift module name (waiting on some clang changes). This is needed to avoid USR conflicts with symbols from different Swift frameworks.

@nathawes
Copy link
Contributor Author

@swift-ci please smoke test

lib/AST/Decl.cpp Outdated
return true;
} else if (checkParent) {
if (auto ctor = dyn_cast<ConstructorDecl>(VD)) {
// Check if we're overriding an initializer that is visible to obj-c
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this the only case that checks the parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the logic PrintAsObjC was using to decide whether to include a decl in the generated header – I just moved the function here to make sure USR generation and the header printer use the same logic. I assume 'checkParent' was added specifically for the case of still writing out < minRequiredAccess initializers that override a >= minRequiredAccess initializer.


auto Parent = D->getDeclContext()->getInnermostDeclarationDeclContext();
if (Parent && (!ShouldUseObjCName(Parent) || // parent should be visible too
!D->getDeclContext()->isTypeContext() || // no local decls
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation is off.


if (const ValueDecl *VD = dyn_cast<ValueDecl>(D)) {
if (isa<EnumElementDecl>(VD))
return Parent;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does an EnumElementDecl ever not have a parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wasn't sure about invalid code, but it doesn't look like it – I'll change it to true ;-)

}

// CHECK: [[@LINE+1]]:14 c:objc(cs)ObjCClass1(cpy)typeComputed{{$}}
static var typeComputed: Int {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this actually show up as a class property in ObjC? I would have expected you'd need to say "class" instead of "static" (they're not the same).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does at the moment, yeah:
SWIFT_CLASS_PROPERTY(@Property (nonatomic, class) NSInteger typeComputed;)

  • (NSInteger)typeComputed SWIFT_WARN_UNUSED_RESULT;
  • (void)setTypeComputed:(NSInteger)newValue;

/// Returns true if the given value decl D is visible to ObjC of its
/// own accord (i.e. without considering its context)
bool isVisibleToObjC(const ValueDecl *VD, Accessibility minRequiredAccess,
bool checkParent = true);
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: bool should align with const in the previous line.

lib/AST/Decl.cpp Outdated
} else if (checkParent) {
if (auto ctor = dyn_cast<ConstructorDecl>(VD)) {
// Check if we're overriding an initializer that is visible to obj-c
if (auto parent = ctor->getOverriddenDecl())
Copy link
Contributor

Choose a reason for hiding this comment

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

do we care about getSatisfiedProtocolRequirements() here?

OS << "@" << ObjCName;
} else {
llvm_unreachable("Unexpected value decl");
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: redundant return here

OS << clang::index::getUSRSpacePrefix();

if (D->getDeclContext()->isTypeContext()) {
auto *Parent = D->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext();
Copy link
Contributor

Choose a reason for hiding this comment

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

these above two lines can be combined as:
if (auto *Parent = D->getDeclContext()->getAsNominalTypeOrNominalTypeExtensionContext())


if (!Ident.empty())
return printObjCNameFragment(D, Ident.str(), OS);
if (Selector) {
Copy link
Contributor

Choose a reason for hiding this comment

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

assert(Selector) here. one and only one of Ident and Selector is valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are there no cases (e.g. invalid code) where they'll both come back invalid?

Copy link
Contributor

Choose a reason for hiding this comment

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

For invalid code, we may get nameless value decls. Can we have an early exist in this function like if (VD->getName().empty) return false like the one in ide::printDeclUSR so that we know what's going on without ignoring the cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

Copy link
Contributor

Choose a reason for hiding this comment

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

I realize we do have similar forgiving check in SwiftSourceDocInfo.cpp. The reason there is that we may have preferred names that are non-translatable to ObjC names, for instance, when the argument counts do not match. However, these cases are unlikely to happen here because we don't have preferred names.

lib/AST/Decl.cpp Outdated
}

bool swift::objc_translation::
isVisibleToObjC(const ValueDecl *VD, Accessibility minRequiredAccess,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a better place for this than Decl.cpp?

Copy link
Contributor

Choose a reason for hiding this comment

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

We want the utilities to be inside libAST. Which file do you suggest?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest either moving it to some other file, in lib/IDE/, or making it a method on ValueDecl instead of a top-level function. Just feels weird to see 'swift::objc_translation' in this file.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also how about 'isVisibleInBridgingHeader' instead of 'isVisibleToObjC'?

Copy link
Contributor

Choose a reason for hiding this comment

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

Bridging header is the other direction (ObjC code visible in Swift).

Copy link
Contributor

Choose a reason for hiding this comment

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

It's generally preferable for headers to have an associated implementation file, so SwiftNameTranslation.h can have SwiftNameTranslation.cpp for its code, it doesn't have to go to Decl.cpp

Copy link
Contributor

Choose a reason for hiding this comment

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

I like @akyrtzi's suggest the best.

… own file.

Also fix code formatting issues and simplify the code in USRGeneration.cpp based on review comments in PR swiftlang#7670.
@nathawes
Copy link
Contributor Author

@swift-ci please smoke test

@nathawes
Copy link
Contributor Author

@swift-ci please smoke test

bool checkParent) {
if (!(VD->isObjC() || VD->getAttrs().hasAttribute<CDeclAttr>()))
return false;
if (VD->getFormalAccess() >= minRequiredAccess) {
Copy link
Contributor

@nkcsgexi nkcsgexi Feb 21, 2017

Choose a reason for hiding this comment

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

can we check VD->hasAccessibility() before getting it? invalid code usually has this trap.

Copy link
Contributor

Choose a reason for hiding this comment

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

You mean hasAccessibility()?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right

@nathawes
Copy link
Contributor Author

@swift-ci please smoke test

@nathawes nathawes merged commit 444775f into swiftlang:master Feb 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants