Skip to content

[Type checker] Finalize referenced members of class extensions. #18199

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
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
21 changes: 2 additions & 19 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -480,17 +480,13 @@ class alignas(1 << DeclAlignInBits) Decl {
IsDebuggerAlias : 1
);

SWIFT_INLINE_BITFIELD(NominalTypeDecl, GenericTypeDecl, 1+1+1,
SWIFT_INLINE_BITFIELD(NominalTypeDecl, GenericTypeDecl, 1+1,
/// Whether we have already added implicitly-defined initializers
/// to this declaration.
AddedImplicitInitializers : 1,

/// Whether there is are lazily-loaded conformances for this nominal type.
HasLazyConformances : 1,

/// Whether we have already validated all members of the type that
/// affect layout.
HasValidatedLayout : 1
HasLazyConformances : 1
);

SWIFT_INLINE_BITFIELD_FULL(ProtocolDecl, NominalTypeDecl, 1+1+1+1+1+1+1+1+2+8+16,
Expand Down Expand Up @@ -3036,7 +3032,6 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
Bits.NominalTypeDecl.AddedImplicitInitializers = false;
ExtensionGeneration = 0;
Bits.NominalTypeDecl.HasLazyConformances = false;
Bits.NominalTypeDecl.HasValidatedLayout = false;
}

friend class ProtocolType;
Expand Down Expand Up @@ -3074,18 +3069,6 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
Bits.NominalTypeDecl.AddedImplicitInitializers = true;
}

/// Determine whether we have already validated any members
/// which affect layout.
bool hasValidatedLayout() const {
return Bits.NominalTypeDecl.HasValidatedLayout;
}

/// Note that we have attempted to validate any members
/// which affect layout.
void setHasValidatedLayout() {
Bits.NominalTypeDecl.HasValidatedLayout = true;
}

/// Set the interface type of this nominal type to the metatype of the
/// declared interface type.
void computeType();
Expand Down
58 changes: 26 additions & 32 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,8 @@ static void validateAbstractStorageDecl(TypeChecker &TC,

static void finalizeAbstractStorageDecl(TypeChecker &TC,
AbstractStorageDecl *storage) {
TC.validateDecl(storage);

for (auto accessor : storage->getAllAccessors()) {
// Are there accessors we can safely ignore here, like maybe observers?
TC.validateDecl(accessor);
Expand All @@ -2330,8 +2332,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
if (auto VD = dyn_cast<ValueDecl>(decl)) {
checkRedeclaration(TC, VD);

(void)VD->isObjC();
(void)VD->isDynamic();
// Make sure we finalize this declaration.
TC.DeclsToFinalize.insert(VD);

// If this is a member of a nominal type, don't allow it to have a name of
// "Type" or "Protocol" since we reserve the X.Type and X.Protocol
Expand Down Expand Up @@ -2721,8 +2723,6 @@ class DeclChecker : public DeclVisitor<DeclChecker> {

checkUnsupportedNestedType(ED);
TC.validateDecl(ED);
TC.DeclsToFinalize.remove(ED);
ED->setHasValidatedLayout();

{
// Check for circular inheritance of the raw type.
Expand Down Expand Up @@ -2760,8 +2760,6 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
checkUnsupportedNestedType(SD);

TC.validateDecl(SD);
TC.DeclsToFinalize.remove(SD);
SD->setHasValidatedLayout();

TC.addImplicitConstructors(SD);

Expand Down Expand Up @@ -2891,8 +2889,6 @@ class DeclChecker : public DeclVisitor<DeclChecker> {

TC.validateDecl(CD);
TC.requestSuperclassLayout(CD);
TC.DeclsToFinalize.remove(CD);
CD->setHasValidatedLayout();

{
// Check for circular inheritance.
Expand Down Expand Up @@ -4625,6 +4621,14 @@ void TypeChecker::requestMemberLayout(ValueDecl *member) {
if (auto *protocolDecl = dyn_cast<ProtocolDecl>(dc))
requestNominalLayout(protocolDecl);

if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
if (ext->getAsClassOrClassExtensionContext()) {
// Finalize members of class extensions, to ensure we compute their
// @objc and dynamic state.
DeclsToFinalize.insert(member);
}
}

// If this represents (abstract) storage, form the appropriate accessors.
if (auto storage = dyn_cast<AbstractStorageDecl>(member)) {
validateAbstractStorageDecl(*this, storage);
Expand All @@ -4642,11 +4646,6 @@ void TypeChecker::requestMemberLayout(ValueDecl *member) {
}

void TypeChecker::requestNominalLayout(NominalTypeDecl *nominalDecl) {
if (nominalDecl->hasValidatedLayout())
return;

nominalDecl->setHasValidatedLayout();

if (isa<SourceFile>(nominalDecl->getModuleScopeContext()))
DeclsToFinalize.insert(nominalDecl);
}
Expand Down Expand Up @@ -4683,23 +4682,16 @@ static void finalizeType(TypeChecker &TC, NominalTypeDecl *nominal) {
if (!shouldValidateMemberDuringFinalization(nominal, VD))
continue;

TC.validateDecl(VD);

// Compute overrides.
(void)VD->getOverriddenDecls();

// Check whether the member is @objc or dynamic.
(void)VD->isObjC();
(void)VD->isDynamic();
TC.DeclsToFinalize.insert(VD);

// The only thing left to do is synthesize storage for lazy variables.
auto *prop = dyn_cast<VarDecl>(D);
if (!prop)
continue;

if (prop->getAttrs().hasAttribute<LazyAttr>() && !prop->isStatic()
&& prop->getGetter()) {
assert(!prop->getGetter()->hasBody());
if (prop->getAttrs().hasAttribute<LazyAttr>() && !prop->isStatic() &&
(!prop->getGetter() || !prop->getGetter()->hasBody())) {
finalizeAbstractStorageDecl(TC, prop);
TC.completeLazyVarImplementation(prop);
}
}
Expand Down Expand Up @@ -4739,18 +4731,20 @@ static void finalizeType(TypeChecker &TC, NominalTypeDecl *nominal) {
}

void TypeChecker::finalizeDecl(ValueDecl *decl) {
validateDecl(decl);

if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
finalizeType(*this, nominal);
} else if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
// We synthesize certain functions --- mostly accessors --- at
// times that can be inconvenient for immediate validation. We add
// them to the list of declarations to finalize so that we can
// fully validate them at a more opportune time.
validateDecl(func);
} else {
auto storage = cast<AbstractStorageDecl>(decl);
} else if (auto storage = dyn_cast<AbstractStorageDecl>(decl)) {
finalizeAbstractStorageDecl(*this, storage);
}

// Compute overrides.
(void)decl->getOverriddenDecls();

// Check whether the member is @objc or dynamic.
(void)decl->isObjC();
(void)decl->isDynamic();
}

bool swift::isPassThroughTypealias(TypeAliasDecl *typealias) {
Expand Down
8 changes: 4 additions & 4 deletions lib/Sema/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,9 @@ static void typeCheckFunctionsAndExternalDecls(SourceFile &SF, TypeChecker &TC)
// Note: if we ever start putting extension members in vtables, we'll need
// to validate those members too.
// FIXME: If we're not planning to run SILGen, this is wasted effort.
while (!TC.DeclsToFinalize.empty()) {
auto decl = TC.DeclsToFinalize.pop_back_val();
if (decl->isInvalid() || TC.Context.hadError())
while (TC.NextDeclToFinalize < TC.DeclsToFinalize.size()) {
auto decl = TC.DeclsToFinalize[TC.NextDeclToFinalize++];
if (decl->isInvalid())
continue;

TC.finalizeDecl(decl);
Expand Down Expand Up @@ -603,7 +603,7 @@ static void typeCheckFunctionsAndExternalDecls(SourceFile &SF, TypeChecker &TC)
currentExternalDef < TC.Context.ExternalDefinitions.size() ||
currentSynthesizedDecl < SF.SynthesizedDecls.size() ||
!TC.FunctionsToSynthesize.empty() ||
!TC.DeclsToFinalize.empty() ||
TC.NextDeclToFinalize < TC.DeclsToFinalize.size() ||
!TC.ConformanceContexts.empty() ||
!TC.DelayedRequirementSignatures.empty() ||
!TC.UsedConformances.empty() ||
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,10 @@ class TypeChecker final : public LazyResolver {
/// we can hand them off to SILGen etc.
llvm::SetVector<ValueDecl *> DeclsToFinalize;

/// Track the index of the next declaration that needs to be finalized,
/// from the \c DeclsToFinalize set.
unsigned NextDeclToFinalize = 0;

/// The list of functions that need to have their bodies synthesized.
llvm::MapVector<FuncDecl*, SynthesizedFunction> FunctionsToSynthesize;

Expand Down
44 changes: 26 additions & 18 deletions lib/Serialization/Serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4788,9 +4788,33 @@ static void collectInterestingNestedDeclarations(
const NominalTypeDecl *nominalParent = nullptr;

for (const Decl *member : members) {
// If there is a corresponding Objective-C method, record it.
auto recordObjCMethod = [&] {
if (isLocal)
return;

if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
if (func->isObjC()) {
if (auto owningClass =
func->getDeclContext()->getAsClassOrClassExtensionContext()) {
Mangle::ASTMangler mangler;
std::string ownerName = mangler.mangleNominalType(owningClass);
assert(!ownerName.empty() && "Mangled type came back empty!");

objcMethods[func->getObjCSelector()].push_back(
std::make_tuple(ownerName,
func->isObjCInstanceMethod(),
S.addDeclRef(func)));
}
}
}
};

if (auto memberValue = dyn_cast<ValueDecl>(member)) {
if (!memberValue->hasName())
if (!memberValue->hasName()) {
recordObjCMethod();
continue;
}

if (memberValue->isOperator()) {
// Add operator methods.
Expand Down Expand Up @@ -4826,23 +4850,7 @@ static void collectInterestingNestedDeclarations(
}

// Record Objective-C methods.
if (!isLocal) {
if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
if (func->isObjC()) {
if (auto owningClass =
func->getDeclContext()->getAsClassOrClassExtensionContext()) {
Mangle::ASTMangler mangler;
std::string ownerName = mangler.mangleNominalType(owningClass);
assert(!ownerName.empty() && "Mangled type came back empty!");

objcMethods[func->getObjCSelector()].push_back(
std::make_tuple(ownerName,
func->isObjCInstanceMethod(),
S.addDeclRef(func)));
}
}
}
}
recordObjCMethod();
}
}

Expand Down
9 changes: 0 additions & 9 deletions test/FixCode/fixits-apply-objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ import ObjectiveC

// REQUIRES: objc_interop

@objc class Selectors {
func takeSel(_: Selector) {}
func mySel() {}
func test() {
takeSel("mySel")
takeSel(Selector("mySel"))
}
}

func foo(an : Any) {
let a1 : AnyObject
a1 = an
Expand Down
9 changes: 0 additions & 9 deletions test/FixCode/fixits-apply-objc.swift.result
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ import ObjectiveC

// REQUIRES: objc_interop

@objc class Selectors {
func takeSel(_: Selector) {}
func mySel() {}
func test() {
takeSel(#selector(Selectors.mySel))
takeSel(#selector(Selectors.mySel))
}
}

func foo(an : Any) {
let a1 : AnyObject
a1 = an as AnyObject
Expand Down
1 change: 0 additions & 1 deletion test/IDE/complete_pound_keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,5 @@ func completeInKeyPath2() {
// CHECK-IN_KEYPATH: Decl[InstanceVar]/CurrNominal: prop1[#String#]; name=prop1
// CHECK-IN_KEYPATH: Decl[InstanceVar]/CurrNominal: prop2[#ObjCClass?#]; name=prop2
// CHECK-IN_KEYPATH: Decl[InstanceVar]/Super: hashValue[#Int#]; name=hashValue
// CHECK-IN_KEYPATH: Decl[InstanceVar]/Super: hash[#Int#]; name=hash


11 changes: 6 additions & 5 deletions test/Sema/Inputs/availability_multi_other.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,18 @@ class OtherIntroduced10_51 {
return OtherIntroduced10_52()
}

func takes10_52(o: OtherIntroduced10_52) {
func takes10_52(o: OtherIntroduced10_52) {
}

// expected-error@-2{{'OtherIntroduced10_52' is only available on OS X 10.52 or newer}}
// expected-note@-3{{add @available attribute to enclosing instance method}}

@available(OSX, introduced: 10.52)
func takes10_52Introduced10_52(o: OtherIntroduced10_52) {
}

// expected-error@+1{{'OtherIntroduced10_52' is only available on OS X 10.52 or newer}}
var propOf10_52: OtherIntroduced10_52 =
Copy link
Contributor

Choose a reason for hiding this comment

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

This would be a regression. Maybe we should skip finalizing declarations if any errors have been emitted?

Copy link
Member Author

Choose a reason for hiding this comment

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

We had that, and I removed it, because it suppresses a ton of unrelated errors.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, this way we're doing unnecessary work in other files.

Copy link
Member Author

Choose a reason for hiding this comment

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

We're finalizing those declarations because something needed them from the other file. In this case, they're methods of a class so we are computing the layout. Prior to my change we would skip a little work once we had produced an error, but "stop doing any work if there's any error anywhere" doesn't seem like the right default policy.

Copy link
Contributor

Choose a reason for hiding this comment

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

It might be more than a little work, though—it's all structs and enums you've used throughout the file, transitively. It seems like the same reasoning to me as "don't go on to SILGen if there are errors, even though you could still get some dataflow diagnostics".

Copy link
Member Author

@DougGregor DougGregor Jul 26, 2018

Choose a reason for hiding this comment

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

I have an idea. Once we've encountered an error, we'll stop finalizing declarations from other source files. That way, you'll still get diagnostics in your own source file, but we won't do unnecessary work elsewhere. PR is up at #18259.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes a lot of sense! I forgot that finalization is now the only way to get certain diagnostics from decls within the current file.



OtherIntroduced10_52() // We don't expect an error here because the initializer is not type checked (by design).
OtherIntroduced10_52()

@available(OSX, introduced: 10.52)
var propOf10_52Introduced10_52: OtherIntroduced10_52 = OtherIntroduced10_52()
Expand Down
2 changes: 1 addition & 1 deletion test/Sema/Inputs/circularity_multifile_error_helper.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
struct External {
var member: Something
var member: Something // expected-error{{use of undeclared type 'Something'}}
}

struct OtherExternal {}
2 changes: 1 addition & 1 deletion test/decl/protocol/conforms/near_miss_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class C1a : P1 {
@objc func doSomething(a: Int, c: Double) { }
// expected-warning@-1{{instance method 'doSomething(a:c:)' nearly matches optional requirement 'doSomething(a:b:)' of protocol 'P1'}}
// expected-note@-2{{rename to 'doSomething(a:b:)' to satisfy this requirement}}{{34-34=b }}{{none}}
// expected-note@-2{{rename to 'doSomething(a:b:)' to satisfy this requirement}}{{34-34=b }}{{8-8=(doSomethingWithA:b:)}} {{34-34=b }}
// expected-note@-3{{move 'doSomething(a:c:)' to an extension to silence this warning}}
// expected-note@-4{{make 'doSomething(a:c:)' private to silence this warning}}{{9-9=private }}
}
Expand Down
27 changes: 27 additions & 0 deletions test/expr/primary/selector/Inputs/fixits_helper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation

public class Bar : Foo {
@objc(method2WithValue:) public override func method2(_ value: Int) { }

@objc(overloadedWithInt:) public func overloaded(_ x: Int) { }
@objc(overloadedWithString:) public func overloaded(_ x: String) { }

@objc(staticOverloadedWithInt:) public static func staticOverloaded(_ x: Int) { }
@objc(staticOverloadedWithString:) public static func staticOverloaded(_ x: String) { }

@objc(staticOrNonStatic:) public func staticOrNonStatic(_ x: Int) { }
@objc(staticOrNonStatic:) public static func staticOrNonStatic(_ x: Int) { }

@objc(theInstanceOne:) public func staticOrNonStatic2(_ x: Int) { }
@objc(theStaticOne:) public static func staticOrNonStatic2(_ x: Int) { }
}

public class Foo {
@objc(methodWithValue:label:) public func method(_ value: Int, label: String) { }

@objc(method2WithValue:) public func method2(_ value: Int) { }

@objc public func method3() { }

@objc public var property: String = ""
}
Loading