Skip to content
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

[SourceKit] Allow module refs to be indexed #19243

Merged
merged 21 commits into from
Sep 28, 2018

Conversation

rockbruno
Copy link
Contributor

@rockbruno rockbruno commented Sep 11, 2018

Pitch

This allows Swift to index explicit reference to modules, such as Swift.String.

Resolves SR-8677.

@rockbruno
Copy link
Contributor Author

@akyrtzi Here it is
I got the mangler to generate an USR to modules, but I'm not sure if that's the correct way

@akyrtzi akyrtzi self-requested a review September 24, 2018 17:48
Copy link
Contributor

@akyrtzi akyrtzi left a comment

Choose a reason for hiding this comment

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

Here are some directions:

  • For the module USR, update the clang checkout and use generateFullUSRForTopLevelModuleName(StringRef ModName, raw_ostream &OS) which comes from clang/Index/USRGeneration.h. This will allow us to use same USR and track module references across both Swift and ObjC.
  • For testing, primarily use swift-ide-test, and follow the examples from test/Index directory. Testing core functionality (e.g. how are symbols indexed) should occur via swift-ide-test, and SourceKit testing should be used mainly for verifying that the information goes through the SourceKit responses as expected. From your PR I think the modifications to existing SourceKit tests are sufficient to verify that the information goes through, you don't need to add additional SourceKit test cases.
  • For testing, I recommend to extend your test case with also:
    • Test that module references in import func type of imports also get handled
    • Test that module references in clang submodules (like import SomeClangModule.SubModule) are handled properly. You may find generateFullUSRForModule(const clang::Module *Mod, raw_ostream &OS) useful for this

@rockbruno
Copy link
Contributor Author

I had to add a few more methods to treat the clang modules - unit-pcm-dependency.swift is failing, but I wanted to iterate from here:

@@ -134,10 +134,18 @@ static SymbolKind getVarSymbolKind(const VarDecl *VD) {
return SymbolKind::Variable;
}

SymbolInfo index::getSymbolInfoForModule(ModuleEntity Mod) {
SymbolInfo info{ SymbolKind::Unknown, SymbolSubKind::None,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we give different parameters to clang modules?

Copy link
Contributor

@akyrtzi akyrtzi Sep 26, 2018

Choose a reason for hiding this comment

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

We can use the language kind to differentiate, SymbolLanguage::Swift for Swift modules and generic SymbolLanguage::C for clang modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@@ -13,6 +13,7 @@
#ifndef SWIFT_AST_USRGENERATION_H
#define SWIFT_AST_USRGENERATION_H

#include "swift/AST/Module.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally try to keep headers lean, meaning don't include other headers if you can just forward declare the symbols you need instead.
Here you included swift/AST/Module.h so you can reference ModuleEntity, but you can just forward declare it as class ModuleEntity;.
You'd need to include the header itself in the cases where the full definition of ModuleEntity is needed, for example if you use it inside an inline function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah forgive me, I didn't know you could do that in C++. Changes applied

@@ -13,6 +13,7 @@
#ifndef SWIFT_INDEX_INDEXSYMBOL_H
#define SWIFT_INDEX_INDEXSYMBOL_H

#include "swift/AST/Module.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Forward declare here as well.

@@ -365,6 +365,13 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx)
setAccess(AccessLevel::Public);
}

bool ModuleDecl::isClangModule() const {
if (!getFiles().empty())
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be using ModuleDecl::findUnderlyingClangModule() instead ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was the isClangModule logic at https://github.com/apple/swift/blob/c94bc1d7dbde329242cf68f856284095e07d9f1f/tools/swift-ide-test/swift-ide-test.cpp#L2436 duplicated? I took it from there, but changing it to use findUnderlyingClangModule works as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Was the isClangModule logic duplicated?

Quite possible, utility code in testing tools generally gets less scrutiny than additions in libAST.

lib/AST/Module.cpp Show resolved Hide resolved
@@ -205,6 +203,17 @@ swift::USRGenerationRequest::evaluate(Evaluator &evaluator, const ValueDecl* D)
llvm::SmallString<128> Buffer;
llvm::raw_svector_ostream OS(Buffer);

if (auto ModuleD = dyn_cast<ModuleDecl>(D)) {
StringRef moduleName = ModuleD->getName().str();
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit better to use ModuleD->getNameStr()

lib/AST/USRGeneration.cpp Show resolved Hide resolved
lib/Index/Index.cpp Show resolved Hide resolved
test/Index/index_module_refs.swift Show resolved Hide resolved
@@ -264,7 +264,6 @@ class ModuleDecl : public DeclContext, public TypeDecl {
return { Files.begin(), Files.size() };
}

bool isClangModule() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for being unclear, I actually liked that you added isClangModule() method, I was mainly suggesting to look into implementing this in terms of calling findUnderlyingModule()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries! I've misread it

bool ModuleDecl::isClangModule() const {
if (findUnderlyingClangModule())
return true;
return false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I mainly did this instead of return findUnderlyingClangModule() because I thought the latter might be confusing in terms of the actual return type)

Copy link
Contributor

Choose a reason for hiding this comment

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

In such situations I usually write it as:

return findUnderlyingClangModule() != nullptr;

@@ -1290,6 +1289,7 @@ class ModuleEntity {
bool isBuiltinModule() const;
const ModuleDecl *getAsSwiftModule() const;
const clang::Module *getAsClangModule() const;
void *getOpaqueValue() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend to make this an inline method (move the definition inline). The definition is simple and inlining it would be good for performance.

import ClangModuleB
// CHECK: [[@LINE-1]]:8 | module/C | ClangModuleB | c:@M@ClangModuleB | Ref | rel: 0
import ClangModuleC.Sub1
// CHECK: [[@LINE-1]]:8 | module/C | ClangModuleC | c:@M@ClangModuleC | Ref | rel: 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's a nice improvement you can make for how test checking works in this file:

  • Match the USR and add it as a FileCheck variable. So instead of having c:@M@ClangModuleC make it [[ClangModuleC_USR:c:@M@ClangModuleC]]
  • Later in the file, instead of repeating the same USR to match (c:@M@ClangModuleC) you match with the variable you defined earlier ([[ClangModuleC_USR]]).
  • Do the same for the other USRs that get repeated, like for SwiftModuleC.

The benefit of this is that it reduces the duplication of the USR string. Later on, if for some reason we decide to change how the USRs for modules are formed, there will be much fewer places in the test cases that would be necessary to correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Amazing feature. Thanks for the tip!

func myMethod() {
_ = SwiftModuleC.MyGeneric<SwiftModuleC.MyType, MyType>()
// CHECK: [[@LINE-1]]:9 | module/Swift | SwiftModuleC | c:@M@SwiftModuleC | Ref | rel: 0
// CHECK: [[@LINE-2]]:22 | struct/Swift | MyGeneric | s:12SwiftModuleC9MyGenericV | Ref,RelCont | rel: 1
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it will be a bit better to keep the test case focused on testing that module references are indexed. Meaning we don't need to check for MyGeneric and MyType here, other test cases should be responsible to make sure that we index these properly.
Essentially I'd recommend to remove the check lines for those.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since some of the references rely on getParentDecl(), I was concerned that a change to this feature could potentially break the reference that comes right after it - perhaps I should make a separate test file for this interaction?

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fair, it'd be fine to have the additional checks but at least reduce how strict the CHECK string needs to be, for example if we only want to check that the struct reference shows up we could have it like this:

// CHECK: [[@LINE-2]]:22 | struct/Swift | MyGeneric | {{.*}} | Ref

So essentially we care that MyGeneric reference shows up but we don't care to match its USR.

@@ -1288,6 +1289,12 @@ class ModuleEntity {
bool isSystemModule() const;
bool isBuiltinModule() const;
const ModuleDecl *getAsSwiftModule() const;
const clang::Module *getAsClangModule() const;

inline void *getOpaqueValue() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

inline is redundant, it's the fact that you have the definition present that makes it inlinable.

// CHECK: [[@LINE-1]]:11 | module/Swift | SwiftModuleC | [[SwiftModuleC_USR]] | Ref | rel: 0
func myMethod() {
_ = SwiftModuleC.MyGeneric<SwiftModuleC.MyType, MyType>()
// CHECK: [[@LINE-1]]:9 | module/Swift | SwiftModuleC | [[SwiftModuleC_USR]] | Ref | rel: 0
Copy link
Contributor

Choose a reason for hiding this comment

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

One I think I noticed is that while the other type references have | Ref,RelCont | rel: 1 (a 'RelCont' relation with the containing method), the module references do not have that.
I wouldn't say it is super important to have the containing relation, but could you take a look to see if it is simple to add, so that the module references do not diverge ?
If you see other test cases, we check for relations by using CHECK-NEXT to match the relations that are printed immediately after the symbol occurrence.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does this rule applies only to AbstractFunctionDecl? It seems that merely mimicking the generic Decl behaviour does the trick:

    auto Parent = getParentDecl();
    if (Parent && isa<AbstractFunctionDecl>(Parent))
      addRelation(Info, (unsigned)SymbolRole::RelationContainedBy, Parent);

module/Swift | SwiftModuleC | [[SwiftModuleC_USR]] | Ref,RelCont | rel: 1
RelCont | instance-method/Swift | myMethod() | [[myMethod_USR:.*]]

@@ -0,0 +1,29 @@
// RUN: %target-swift-ide-test -print-indexed-symbols -enable-source-import -source-filename %s -I %S/Store/Inputs | %FileCheck %s

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 shaping up great! One final (I think) suggestion about the test case; add a line like this:

import NonExistingModuleName // make sure there's no problem with invalid imports

as per comment this would be to guard that there's no issue occurring with invalid imports, like some crash/assertion hit while trying to index its module reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense! I added // CHECK-NOT: {{.*}} | NonExistingModuleName to it to guarantee it doesn't even show up, but if it has performance implications we can leave it out.

@akyrtzi
Copy link
Contributor

akyrtzi commented Sep 28, 2018

@swift-ci smoke test

@akyrtzi
Copy link
Contributor

akyrtzi commented Sep 28, 2018

Thanks for your patience and work on this! It will be critically useful for providing global rename functionality for frameworks at some point.

@rockbruno
Copy link
Contributor Author

rockbruno commented Sep 28, 2018

Thank -you- for the tips and assistance! I promise the next PRs will be far less painful...

@akyrtzi
Copy link
Contributor

akyrtzi commented Sep 28, 2018

I think test fails on linux because ClangModuleB.h uses @import ClangModuleA; and ObjC interop does not exist on linux.
Try changing that line to #import "ClangModuleA.h". Also I think this change would eliminate the need to have // REQUIRES: objc_interop in unit-pcm-dependency.swift

@akyrtzi
Copy link
Contributor

akyrtzi commented Sep 28, 2018

Also add // REQUIRES: objc_interop to SourceKit/Indexing/index_constructors.swift.

@rockbruno
Copy link
Contributor Author

Done

@akyrtzi
Copy link
Contributor

akyrtzi commented Sep 28, 2018

@swift-ci smoke test

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.

2 participants