Dependencies: start tracking whether a lookup is private to a file or not.

This is sort of two commits squashed into one: first, update
ReferencedNameTracker to record whether a name or type is non-private,
along with changing all clients to assume non-private; and second,
actually try to (conservatively) decide if a particular unqualified lookup can
be considered private.

What does "private" mean? That means that a dependency does not affect
"downstream" files. For example, if file A depends on B, and B depends on C,
then a change in C normally means A will get rebuilt. But if B's dependencies
on C are all private dependencies (e.g. lookups from within function bodies),
then A does not need to be rebuilt.

In practice there are several rules about when we can make this assumption,
and a few places where our current DeclContext model is not good enough to
distinguish private uses from non-private uses. In these cases we have to
be conservative and assume that the use is non-private (and thus that
downstream files will need to be rebuilt).

Part of rdar://problem/15353101

Swift SVN r23447
This commit is contained in:
Jordan Rose
2014-11-19 22:28:30 +00:00
parent a35c840e4a
commit c712c51d4a
8 changed files with 361 additions and 191 deletions

View File

@@ -14,28 +14,29 @@
#define SWIFT_REFERENCEDNAMETRACKER_H
#include "swift/AST/Identifier.h"
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/DenseMap.h"
namespace swift {
class NominalTypeDecl;
class ReferencedNameTracker {
SmallPtrSet<Identifier, 1> TopLevelNames;
SmallPtrSet<const NominalTypeDecl *, 1> UsedNominals;
llvm::DenseMap<Identifier, bool> TopLevelNames;
llvm::DenseMap<const NominalTypeDecl *, bool> UsedNominals;
public:
void addTopLevelName(Identifier name) {
TopLevelNames.insert(name);
void addTopLevelName(Identifier name, bool isNonPrivateUse) {
TopLevelNames[name] |= isNonPrivateUse;
}
const SmallPtrSetImpl<Identifier> &getTopLevelNames() const {
const llvm::DenseMap<Identifier, bool> &getTopLevelNames() const {
return TopLevelNames;
}
void addUsedNominal(const NominalTypeDecl *nominal) {
UsedNominals.insert(nominal);
void addUsedNominal(const NominalTypeDecl *nominal, bool isNonPrivateUse) {
UsedNominals[nominal] |= isNonPrivateUse;
}
const SmallPtrSetImpl<const NominalTypeDecl *> &getUsedNominals() const {
const llvm::DenseMap<const NominalTypeDecl *, bool> &getUsedNominals() const {
return UsedNominals;
}
};

View File

@@ -1134,8 +1134,9 @@ SourceFile::lookup##Kind##Operator(Identifier name, SourceLoc loc) { \
auto result = lookupOperatorDeclForName(*this, loc, name, true, \
&SourceFile::Kind##Operators); \
if (result.hasValue() && !result.getValue()) { \
/* FIXME: Track whether this is a private use or not. */ \
if (ReferencedNames) \
ReferencedNames->addTopLevelName(name); \
ReferencedNames->addTopLevelName(name, true); \
result = lookupOperatorDeclForName(getParentModule(), loc, name, \
&SourceFile::Kind##Operators); \
} \

View File

@@ -422,15 +422,64 @@ static void filterForDiscriminator(SmallVectorImpl<Result> &results,
results.push_back(lastMatch);
}
static bool isPrivateContextForLookup(const DeclContext *DC,
bool functionsArePrivate) {
switch (DC->getContextKind()) {
case DeclContextKind::AbstractClosureExpr:
break;
case DeclContextKind::Initializer:
// Default arguments still require a type.
if (isa<DefaultArgumentInitializer>(DC))
return true;
break;
case DeclContextKind::TopLevelCodeDecl:
// FIXME: Pattern initializers at top-level scope end up here.
return false;
case DeclContextKind::AbstractFunctionDecl:
if (functionsArePrivate)
return true;
break;
case DeclContextKind::Module:
case DeclContextKind::FileUnit:
return false;
case DeclContextKind::NominalTypeDecl: {
auto *nominal = cast<NominalTypeDecl>(DC);
if (nominal->hasAccessibility())
return nominal->getAccessibility() == Accessibility::Private;
break;
}
case DeclContextKind::ExtensionDecl: {
auto *extension = cast<ExtensionDecl>(DC);
if (extension->hasDefaultAccessibility())
return extension->getDefaultAccessibility() == Accessibility::Private;
// FIXME: duplicated from computeDefaultAccessibility in TypeCheckDecl.cpp.
if (auto *AA = extension->getAttrs().getAttribute<AccessibilityAttr>())
return AA->getAccess() == Accessibility::Private;
if (Type extendedTy = extension->getExtendedType())
return isPrivateContextForLookup(extendedTy->getAnyNominal(), true);
break;
}
}
return isPrivateContextForLookup(DC->getParent(), true);
}
static void recordLookupOfTopLevelName(DeclContext *topLevelContext,
DeclName name) {
DeclName name,
bool isNonPrivateUse) {
auto SF = dyn_cast<SourceFile>(topLevelContext);
if (!SF)
return;
auto *nameTracker = SF->getReferencedNameTracker();
if (!nameTracker)
return;
nameTracker->addTopLevelName(name.getBaseName());
nameTracker->addTopLevelName(name.getBaseName(), isNonPrivateUse);
}
UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
@@ -443,12 +492,16 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
const SourceManager &SM = Ctx.SourceMgr;
DebuggerClient *DebugClient = M.getDebugClient();
// Never perform local lookup for operators.
if (Name.isOperator())
DC = DC->getModuleScopeContext();
bool isPrivateUse = false;
// If we are inside of a method, check to see if there are any ivars in scope,
// and if so, whether this is a reference to one of them.
// Never perform local lookup for operators.
if (Name.isOperator()) {
isPrivateUse = isPrivateContextForLookup(DC, /*includeFunctions=*/true);
DC = DC->getModuleScopeContext();
} else {
// If we are inside of a method, check to see if there are any ivars in
// scope, and if so, whether this is a reference to one of them.
// FIXME: We should persist this information between lookups.
while (!DC->isModuleScopeContext()) {
ValueDecl *BaseDecl = 0;
ValueDecl *MetaBaseDecl = 0;
@@ -461,6 +514,8 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
// FIXME: when we can parse and typecheck the function body partially for
// code completion, AFD->getBody() check can be removed.
if (Loc.isValid() && AFD->getBody()) {
isPrivateUse =
SM.rangeContainsTokenLoc(AFD->getBodySourceRange(), Loc);
FindLocalVal localVal(SM, Loc, Name);
localVal.visit(AFD->getBody());
if (!localVal.MatchingValue) {
@@ -472,6 +527,8 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
return;
}
}
if (!isPrivateUse)
isPrivateUse = isPrivateContextForLookup(AFD, false);
if (AFD->getExtensionType()) {
ExtendedType = AFD->getExtensionType();
@@ -510,19 +567,26 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
}
}
}
isPrivateUse = isPrivateContextForLookup(ACE, false);
} else if (ExtensionDecl *ED = dyn_cast<ExtensionDecl>(DC)) {
ExtendedType = ED->getExtendedType();
BaseDecl = ExtendedType->getNominalOrBoundGenericNominal();
MetaBaseDecl = BaseDecl;
isPrivateUse = isPrivateContextForLookup(ED, false);
} else if (NominalTypeDecl *ND = dyn_cast<NominalTypeDecl>(DC)) {
ExtendedType = ND->getDeclaredType();
BaseDecl = ND;
MetaBaseDecl = BaseDecl;
isPrivateUse = isPrivateContextForLookup(ND, false);
} else if (auto I = dyn_cast<DefaultArgumentInitializer>(DC)) {
// In a default argument, skip immediately out of both the
// initializer and the function.
isPrivateUse = true;
DC = I->getParent()->getParent();
continue;
} else {
assert(isa<TopLevelCodeDecl>(DC) || isa<Initializer>(DC));
isPrivateUse = isPrivateContextForLookup(DC, false);
}
// Check the generic parameters for something with the given name.
@@ -623,6 +687,7 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
DC = DC->getParent();
}
}
if (auto SF = dyn_cast<SourceFile>(DC)) {
if (Loc.isValid()) {
@@ -644,7 +709,7 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
Loc, IsTypeLookup, Results))
return;
recordLookupOfTopLevelName(DC, Name);
recordLookupOfTopLevelName(DC, Name, !isPrivateUse);
// Add private imports to the extra search list.
SmallVector<Module::ImportedModule, 8> extraImports;
@@ -1121,7 +1186,10 @@ bool DeclContext::lookupQualified(Type type,
Module *module = moduleTy->getModule();
auto topLevelScope = getModuleScopeContext();
if (module == topLevelScope->getParentModule()) {
recordLookupOfTopLevelName(topLevelScope, member);
// FIXME: We should be able to tell if this is part of a function body
// or not.
recordLookupOfTopLevelName(topLevelScope, member,
isPrivateContextForLookup(this, false));
lookupInModule(module, /*accessPath=*/{}, member, decls,
NLKind::QualifiedLookup, ResolutionKind::Overloadable,
typeResolver, topLevelScope);
@@ -1262,8 +1330,10 @@ bool DeclContext::lookupQualified(Type type,
while (!stack.empty()) {
auto current = stack.back();
stack.pop_back();
// FIXME: We should be able to tell if this is a local or non-local use.
if (tracker)
tracker->addUsedNominal(current);
tracker->addUsedNominal(current, true);
// Make sure we've resolved implicit constructors, if we need them.
if (member.getBaseName() == ctx.Id_init && typeResolver)

View File

@@ -3580,8 +3580,9 @@ public:
D->getStartLoc(), D);
conformances.push_back(conformance);
// FIXME: We should be able to tell if this is a private use or not.
if (tracker)
tracker->addUsedNominal(proto);
tracker->addUsedNominal(proto, true);
}
D->setConformances(D->getASTContext().AllocateCopy(conformances));
@@ -4710,8 +4711,9 @@ public:
if (auto *SF = PD->getParentSourceFile()) {
if (auto *tracker = SF->getReferencedNameTracker()) {
// FIXME: Track whether this is a private use or not.
for (auto *parentProto : PD->getProtocols())
tracker->addUsedNominal(parentProto);
tracker->addUsedNominal(parentProto, true);
}
}
}

View File

@@ -2538,8 +2538,9 @@ bool TypeChecker::conformsToProtocol(Type T, ProtocolDecl *Proto,
const DeclContext *topLevelContext = DC->getModuleScopeContext();
if (auto *constSF = dyn_cast<SourceFile>(topLevelContext)) {
auto *SF = const_cast<SourceFile *>(constSF);
// FIXME: Track whether this is a private use or not.
if (auto *tracker = SF->getReferencedNameTracker())
tracker->addUsedNominal(nominal);
tracker->addUsedNominal(nominal, true);
}
Module *M = topLevelContext->getParentModule();

View File

@@ -33,3 +33,31 @@ protocol OtherFileProto2 {}
struct OtherFileProtoImplementor2 : OtherFileProto2 {}
func otherFileGetImpl2() -> OtherFileProtoImplementor2 {}
func otherFileUseGeneric<T: OtherFileProto2>(_: T) {}
func topLevel1() -> Int { return 2 }
func topLevel2() -> Int { return 2 }
func topLevel3() -> Int { return 2 }
func topLevel4() -> Int { return 2 }
func topLevel5() -> Int { return 2 }
func topLevel6() -> Int { return 2 }
func topLevel7() -> Int { return 2 }
func topLevel8() -> Int { return 2 }
func topLevel9() -> Int { return 2 }
typealias TopLevelTy1 = Int
typealias TopLevelTy2 = Int
typealias TopLevelTy3 = Int
func privateTopLevel1() -> Int { return 2 }
func privateTopLevel2() -> Int { return 2 }
func privateTopLevel3() -> Int { return 2 }
func privateTopLevel4() -> Int { return 2 }
func privateTopLevel5() -> Int { return 2 }
func privateTopLevel6() -> Int { return 2 }
func privateTopLevel7() -> Int { return 2 }
func privateTopLevel8() -> Int { return 2 }
func privateTopLevel9() -> Int { return 2 }
typealias PrivateTopLevelTy1 = Int
typealias PrivateTopLevelTy2 = Int
typealias PrivateTopLevelTy3 = Int

View File

@@ -1,4 +1,6 @@
// RUN: %swift -parse -primary-file %s %S/Inputs/reference-dependencies-helper.swift -emit-reference-dependencies-path - > %t.swiftdeps
// RUN: rm -rf %t && mkdir %t
// RUN: cp %s %t/main.swift
// RUN: %swift -parse -primary-file %t/main.swift %S/Inputs/reference-dependencies-helper.swift -emit-reference-dependencies-path - > %t.swiftdeps
// RUN: FileCheck %s < %t.swiftdeps
// RUN: FileCheck -check-prefix=NEGATIVE %s < %t.swiftdeps
@@ -13,7 +15,7 @@
// CHECK-NEXT: "someGlobal"
// CHECK-NEXT: "ExtraFloatLiteralConvertible"
// CHECK-NEXT: "lookUpManyTopLevelNames"
// CHECK-NEXT: "eof"
// CHECK: "eof"
// CHECK-LABEL: {{^nominals:$}}
// CHECK-NEXT: "V4main10IntWrapper"
@@ -74,22 +76,22 @@ func lookUpManyTopLevelNames() {
// CHECK-DAG: "Dictionary"
let _: Dictionary = [1:1]
// CHECK-DAG: "UInt"
// CHECK-DAG: "reduce"
// CHECK-DAG: "+"
// CHECK-DAG: !private "UInt"
// CHECK-DAG: !private "reduce"
// CHECK-DAG: !private "+"
let _: UInt = reduce([1,2], 0, +)
// CHECK-DAG: "AliasFromOtherFile"
// CHECK-DAG: !private "AliasFromOtherFile"
let _: AliasFromOtherFile = 1
// CHECK-DAG: "funcFromOtherFile"
// CHECK-DAG: !private "funcFromOtherFile"
funcFromOtherFile()
// "CInt" is not used as a top-level name here.
// CHECK-DAG: "StringLiteralType"
// NEGATIVE-NOT: "CInt"
let CInt = "abc"
// CHECK-DAG: "println"
// CHECK-DAG: !private "println"
println(CInt)
// NEGATIVE-NOT: "max"
@@ -98,24 +100,83 @@ func lookUpManyTopLevelNames() {
// NEGATIVE-NOT: "Stride"
let _: Int.Stride = 0
// CHECK-DAG: "OtherFileOuterType"
// CHECK-DAG: !private "OtherFileOuterType"
_ = OtherFileOuterType.InnerType.sharedConstant
// CHECK-DAG: "OtherFileAliasForSecret"
// CHECK-DAG: !private "OtherFileAliasForSecret"
_ = OtherFileAliasForSecret.constant
// CHECK-DAG: otherFileUse
// CHECK-DAG: otherFileGetImpl
// CHECK-DAG: !private "otherFileUse"
// CHECK-DAG: !private "otherFileGetImpl"
otherFileUse(otherFileGetImpl())
// CHECK-DAG: otherFileUse
// CHECK-DAG: otherFileGetImpl
// CHECK-DAG: !private "otherFileUseGeneric"
// CHECK-DAG: !private "otherFileGetImpl2"
otherFileUseGeneric(otherFileGetImpl2())
}
// NEGATIVE-NOT: "privateFunc"
private func privateFunc() {}
// CHECK-DAG: - "topLevel1"
var use1 = topLevel1()
// CHECK-DAG: - "topLevel2"
var use2 = { topLevel2() }
// CHECK-DAG: - "topLevel3"
var use3 = { ({ topLevel3() })() }
// CHECK-DAG: - "topLevel4"
struct Use4 {
var use4 = topLevel4()
}
// CHECK-DAG: - "*"
print(42 * 30)
// FIXME: Incorrectly marked non-private dependencies
// CHECK-DAG: - "topLevel6"
print(topLevel6())
// CHECK-DAG: - "topLevel7"
private var use7 = topLevel7()
// CHECK-DAG: - "topLevel8"
var use8: Int = topLevel8()
// CHECK-DAG: - "topLevel9"
var use9 = { () -> Int in return topLevel9() }
// CHECK-DAG: - "TopLevelTy1"
func useTy1(x: TopLevelTy1) {}
// CHECK-DAG: - "TopLevelTy2"
func useTy2() -> TopLevelTy2 {}
// CHECK-DAG: - "TopLevelTy3"
extension Use4 {
var useTy3: TopLevelTy3? { return nil }
}
// CHECK-DAG: !private "privateTopLevel1"
func private1(a: Int = privateTopLevel1()) {}
// CHECK-DAG: !private "privateTopLevel2"
private struct Private2 {
var private2 = privateTopLevel2()
}
// CHECK-DAG: !private "privateTopLevel3"
func outerPrivate3() {
let private3 = { privateTopLevel3() }
}
// CHECK-DAG: !private "PrivateTopLevelTy1"
private extension Use4 {
var privateTy1: PrivateTopLevelTy1? { return nil }
}
// CHECK-DAG: !private "PrivateTopLevelTy2"
extension Private2 {
var privateTy2: PrivateTopLevelTy2? { return nil }
}
// CHECK-DAG: !private "PrivateTopLevelTy3"
func outerPrivateTy3() {
func inner(a: PrivateTopLevelTy3?) {}
inner(nil)
}
// CHECK-LABEL: {{^member-access:$}}
// CHECK-DAG: "V4main10IntWrapper"
// CHECK-DAG: "PSs10Comparable"

View File

@@ -187,20 +187,26 @@ static bool emitReferenceDependencies(DiagnosticEngine &diags,
// FIXME: Sort these?
out << "top-level:\n";
for (Identifier name : tracker->getTopLevelNames()) {
out << "- \"" << name << "\"\n";
for (auto &entry : tracker->getTopLevelNames()) {
out << "- ";
if (!entry.second)
out << "!private ";
out << "\"" << entry.first << "\"\n";
}
// FIXME: Sort these?
out << "member-access:\n";
for (auto usedNominal : tracker->getUsedNominals()) {
if (usedNominal->hasAccessibility() &&
usedNominal->getAccessibility() == Accessibility::Private)
for (auto &entry : tracker->getUsedNominals()) {
if (entry.first->hasAccessibility() &&
entry.first->getAccessibility() == Accessibility::Private)
continue;
Mangle::Mangler mangler(out, /*debug style=*/false, /*Unicode=*/true);
out << "- \"";
mangler.mangleContext(usedNominal, Mangle::Mangler::BindGenerics::None);
out << "- ";
if (!entry.second)
out << "!private ";
out << "\"";
mangler.mangleContext(entry.first, Mangle::Mangler::BindGenerics::None);
out << "\"\n";
}