Skip to content
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
7 changes: 0 additions & 7 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -7674,13 +7674,6 @@ ERROR(bitwise_copyable_outside_module,none,
(const ValueDecl *))

// -- older ones below --

ERROR(noncopyable_within_copyable, none,
"%kind0 cannot contain a noncopyable type without also being noncopyable",
(const ValueDecl *))
NOTE(noncopyable_within_copyable_location, none,
"contained noncopyable %0 '%1.%2'",
(DescriptiveDeclKind, StringRef, StringRef))
ERROR(noncopyable_cannot_conform_to_type, none,
"noncopyable %kind0 cannot conform to %1",
(const ValueDecl *, Type))
Expand Down
4 changes: 2 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10042,8 +10042,8 @@ parseDeclDeinit(ParseDeclOptions Flags, DeclAttributes &Attributes) {
// Reject 'destructor' functions outside of structs, enums, classes, or
// extensions that provide objc implementations.
//
// Later in the type checker, we validate that structs/enums only do this if
// they are move only and that @objcImplementations are main-body.
// Later in the type checker, we validate that structs/enums are noncopyable
// and that @objcImplementations are main-body.
auto rejectDestructor = [](DeclContext *dc) {
if (isa<StructDecl>(dc) || isa<EnumDecl>(dc) ||
isa<ClassDecl>(dc))
Expand Down
104 changes: 13 additions & 91 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "MiscDiagnostics.h"
#include "TypeCheckAvailability.h"
#include "TypeCheckConcurrency.h"
#include "TypeCheckInvertible.h"
#include "TypeChecker.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/DiagnosticsSema.h"
Expand Down Expand Up @@ -6313,103 +6314,24 @@ bool swift::diagnoseUnhandledThrowsInAsyncContext(DeclContext *dc,
return false;
}

//===----------------------------------------------------------------------===//
// Copyable Type Containing Move Only Type Visitor
//===----------------------------------------------------------------------===//
// If we haven't enabled the NoncopyableGenerics feature, force a
// Copyable conformance check now.
void swift::forceCopyableConformanceCheckIfNeeded(NominalTypeDecl *nom) {
auto &ctx = nom->getASTContext();

void swift::diagnoseCopyableTypeContainingMoveOnlyType(
NominalTypeDecl *copyableNominalType) {
auto &ctx = copyableNominalType->getASTContext();
if (ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics))
return; // taken care of in conformance checking

// If we already have a move only type, just bail, we have no further work to
// do.
if (!copyableNominalType->canBeCopyable())
return;

LLVM_DEBUG(llvm::dbgs() << "DiagnoseCopyableType for: "
<< copyableNominalType->getName() << '\n');

auto &DE = copyableNominalType->getASTContext().Diags;
auto emitError = [&copyableNominalType,
&DE](PointerUnion<EnumElementDecl *, VarDecl *>
topFieldToError,
DeclBaseName parentName, DescriptiveDeclKind fieldKind,
DeclBaseName fieldName) {
assert(!topFieldToError.isNull());
if (auto *eltDecl = topFieldToError.dyn_cast<EnumElementDecl *>()) {
DE.diagnoseWithNotes(
copyableNominalType->diagnose(
diag::noncopyable_within_copyable,
copyableNominalType),
[&]() {
eltDecl->diagnose(
diag::
noncopyable_within_copyable_location,
fieldKind, parentName.userFacingName(),
fieldName.userFacingName());
});
return;
}
auto *copyable = ctx.getProtocol(KnownProtocolKind::Copyable);
Type selfTy = nom->getDeclaredInterfaceType();

auto *varDecl = topFieldToError.get<VarDecl *>();
DE.diagnoseWithNotes(
copyableNominalType->diagnose(
diag::noncopyable_within_copyable,
copyableNominalType),
[&]() {
varDecl->diagnose(
diag::noncopyable_within_copyable_location,
fieldKind, parentName.userFacingName(),
fieldName.userFacingName());
});
};
auto conformanceRef = nom->getModuleContext()->lookupConformance(selfTy,
copyable,
/*allowMissing=*/false);

// If we have a struct decl...
if (auto *structDecl = dyn_cast<StructDecl>(copyableNominalType)) {
// Visit each of the stored property var decls of the struct decl...
for (auto *fieldDecl : structDecl->getStoredProperties()) {
LLVM_DEBUG(llvm::dbgs()
<< "Visiting struct field: " << fieldDecl->getName() << '\n');
if (!fieldDecl->getInterfaceType()->isNoncopyable())
continue;
emitError(fieldDecl, structDecl->getBaseName(),
fieldDecl->getDescriptiveKind(), fieldDecl->getBaseName());
}
// We completed our checking, just return.
// it's never copyable
if (conformanceRef.isInvalid())
return;
}

if (auto *enumDecl = dyn_cast<EnumDecl>(copyableNominalType)) {
// If we have an enum but we don't have any elements, just continue, we
// have nothing to check.
if (enumDecl->getAllElements().empty())
return;

// Otherwise for each element...
for (auto *enumEltDecl : enumDecl->getAllElements()) {
// If the element doesn't have any associated values, we have nothing to
// check, so continue.
if (!enumEltDecl->hasAssociatedValues())
continue;

LLVM_DEBUG(llvm::dbgs() << "Visiting enum elt decl: "
<< enumEltDecl->getName() << '\n');

// Otherwise, we have a case and need to check the types of the
// parameters of the case payload.
for (auto payloadParam : *enumEltDecl->getParameterList()) {
LLVM_DEBUG(llvm::dbgs() << "Visiting payload param: "
<< payloadParam->getName() << '\n');
if (payloadParam->getInterfaceType()->isNoncopyable()) {
emitError(enumEltDecl, enumDecl->getBaseName(),
enumEltDecl->getDescriptiveKind(),
enumEltDecl->getBaseName());
}
}
}
// We have finished processing this enum... so return.
return;
}
swift::checkCopyableConformance(nom, conformanceRef);
}
3 changes: 1 addition & 2 deletions lib/Sema/MiscDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ namespace swift {
static bool shouldWalkIntoDeclInClosureContext(Decl *D);
};

void diagnoseCopyableTypeContainingMoveOnlyType(
NominalTypeDecl *copyableNominalType);
void forceCopyableConformanceCheckIfNeeded(NominalTypeDecl *nominal);

} // namespace swift

Expand Down
17 changes: 5 additions & 12 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3055,7 +3055,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
ED->diagnose(diag::noncopyable_objc_enum);
}
// FIXME(kavon): see if these can be integrated into other parts of Sema
diagnoseCopyableTypeContainingMoveOnlyType(ED);
forceCopyableConformanceCheckIfNeeded(ED);
diagnoseIncompatibleProtocolsForMoveOnlyType(ED);

checkExplicitAvailability(ED);
Expand Down Expand Up @@ -3117,7 +3117,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {

// If this struct is not move only, check that all vardecls of nominal type
// are not move only.
diagnoseCopyableTypeContainingMoveOnlyType(SD);
forceCopyableConformanceCheckIfNeeded(SD);

diagnoseIncompatibleProtocolsForMoveOnlyType(SD);
}
Expand Down Expand Up @@ -4130,19 +4130,12 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
}

void visitDestructorDecl(DestructorDecl *DD) {
// Only check again for destructor decl outside of a class if our destructor
// is not marked as invalid.
// Only check again for destructor decl outside of a struct/enum/class
// if our destructor is not marked as invalid.
if (!DD->isInvalid()) {
auto *nom = dyn_cast<NominalTypeDecl>(
DD->getDeclContext()->getImplementedObjCContext());
if (!nom || isa<ProtocolDecl>(nom)) {
DD->diagnose(diag::destructor_decl_outside_class_or_noncopyable);

} else if (!Ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics)
&& !isa<ClassDecl>(nom)
&& nom->canBeCopyable()) {
// When we have NoncopyableGenerics, deinits get validated as part of
// Copyable-conformance checking.
if (!nom || !isa<ClassDecl, StructDecl, EnumDecl>(nom)) {
DD->diagnose(diag::destructor_decl_outside_class_or_noncopyable);
}

Expand Down
30 changes: 20 additions & 10 deletions lib/Sema/TypeCheckInvertible.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ static void tryEmitContainmentFixits(NominalTypeDecl *enclosingNom,

/// MARK: conformance checking
static void checkInvertibleConformanceCommon(DeclContext *dc,
ProtocolConformance *conformance,
ProtocolConformanceRef conformance,
InvertibleProtocolKind ip) {
assert(!conformance.isInvalid());

const auto kp = getKnownProtocolKind(ip);
auto *proto = conformance->getProtocol();
assert(proto->isSpecificProtocol(kp));
assert(conformance.getRequirement()->isSpecificProtocol(kp));

auto *nominalDecl = dc->getSelfNominalTypeDecl();
assert(isa<StructDecl>(nominalDecl) ||
Expand All @@ -133,17 +134,26 @@ static void checkInvertibleConformanceCommon(DeclContext *dc,

bool hasExplicitInverse = inverses.contains(ip);

bool hasUnconditionalConformance = false;
auto *normalConf = dyn_cast<NormalProtocolConformance>(conformance);
if (normalConf && normalConf->getConditionalRequirements().empty())
hasUnconditionalConformance = true;
bool hasUnconditionalConformance = conformance.isAbstract();
SourceLoc conformanceLoc = nominalDecl->getLoc();

if (conformance.isConcrete()) {
auto concrete = conformance.getConcrete();
if (auto *normalConf = dyn_cast<NormalProtocolConformance>(concrete)) {
hasUnconditionalConformance =
normalConf->getConditionalRequirements().empty();
conformanceLoc = normalConf->getLoc();
assert(conformanceLoc);
}
}
assert(!conformance.isPack() && "not handled");

if (!isa<ClassDecl>(nominalDecl) ||
ctx.LangOpts.hasFeature(Feature::MoveOnlyClasses)) {
// If the inheritance clause contains ~Copyable, reject an unconditional
// conformance to Copyable.
if (hasExplicitInverse && hasUnconditionalConformance) {
ctx.Diags.diagnose(normalConf->getLoc(),
ctx.Diags.diagnose(conformanceLoc,
diag::inverse_but_also_conforms,
nominalDecl, getProtocolName(kp));
}
Expand Down Expand Up @@ -224,13 +234,13 @@ static void checkInvertibleConformanceCommon(DeclContext *dc,
}

void swift::checkEscapableConformance(DeclContext *dc,
ProtocolConformance *conformance) {
ProtocolConformanceRef conformance) {
checkInvertibleConformanceCommon(dc, conformance,
InvertibleProtocolKind::Escapable);
}

void swift::checkCopyableConformance(DeclContext *dc,
ProtocolConformance *conformance) {
ProtocolConformanceRef conformance) {
checkInvertibleConformanceCommon(dc, conformance,
InvertibleProtocolKind::Copyable);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/Sema/TypeCheckInvertible.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ class StorageVisitor {

/// Checks that all stored properties or associated values are Copyable.
void checkCopyableConformance(DeclContext *dc,
ProtocolConformance *conformance);
ProtocolConformanceRef conformance);

/// Checks that all stored properties or associated values are Escapable.
void checkEscapableConformance(DeclContext *dc,
ProtocolConformance *conformance);
ProtocolConformanceRef conformance);
}


Expand Down
4 changes: 2 additions & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6119,10 +6119,10 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
tryDiagnoseExecutorConformance(Context, nominal, proto);
} else if (NoncopyableGenerics
&& proto->isSpecificProtocol(KnownProtocolKind::Copyable)) {
checkCopyableConformance(dc, conformance);
checkCopyableConformance(dc, ProtocolConformanceRef(conformance));
} else if (NoncopyableGenerics
&& proto->isSpecificProtocol(KnownProtocolKind::Escapable)) {
checkEscapableConformance(dc, conformance);
checkEscapableConformance(dc, ProtocolConformanceRef(conformance));
} else if (Context.LangOpts.hasFeature(Feature::BitwiseCopyable) &&
proto->isSpecificProtocol(KnownProtocolKind::BitwiseCopyable)) {
checkBitwiseCopyableConformance(
Expand Down
3 changes: 2 additions & 1 deletion test/ASTGen/types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ func testRepeatEach<each T>(_ t: repeat each T) -> (repeat each T) {
fatalError()
}

struct FileDescriptor: ~Copyable {
// FIXME: this error isn't correct to emit. the parsing might be ignoring the ~
struct FileDescriptor: ~Copyable { // expected-error {{struct 'FileDescriptor' required to be 'Copyable' but is marked with '~Copyable'}}
var fd = 1
}

Expand Down
8 changes: 3 additions & 5 deletions test/Parse/init_deinit.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// RUN: %target-typecheck-verify-swift

// XFAIL: noncopyable_generics

struct FooStructConstructorA {
init // expected-error {{expected '('}}
// expected-error@-1{{initializer requires a body}}
Expand Down Expand Up @@ -36,7 +34,7 @@ struct FooStructDeinitializerB {
}

struct FooStructDeinitializerC {
deinit {} // expected-error {{deinitializers may only be declared within a class, actor, or noncopyable type}}
deinit {} // expected-error {{deinitializer cannot be declared in struct 'FooStructDeinitializerC' that conforms to 'Copyable'}}
}

class FooClassDeinitializerA {
Expand All @@ -61,7 +59,7 @@ deinit {} // expected-error {{deinitializers may only be declared within a class

struct BarStruct {
init() {}
deinit {} // expected-error {{deinitializers may only be declared within a class, actor, or noncopyable type}}
deinit {} // NOTE: this doesn't get diagnosed with the other errors in this file for some reason.
}

extension BarStruct {
Expand All @@ -73,7 +71,7 @@ extension BarStruct {

enum BarUnion {
init() {}
deinit {} // expected-error {{deinitializers may only be declared within a class, actor, or noncopyable type}}
deinit {} // NOTE: this doesn't get diagnosed with the other errors in this file for some reason.
}

extension BarUnion {
Expand Down
1 change: 1 addition & 0 deletions test/Parse/inverses_legacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protocol Rope<Element>: ~Copyable { // expected-error {{cannot suppress conforma

extension S: ~Copyable {} // expected-error {{cannot suppress conformances here}}
// expected-error@-1 {{noncopyable struct 'S' cannot conform to 'Copyable'}}
// expected-error@-2 {{struct 'S' required to be 'Copyable' but is marked with '~Copyable'}}

func takeNoncopyableGeneric<T: ~Copyable>(_ t: T) {} // expected-error {{cannot suppress conformances here}}

Expand Down
1 change: 1 addition & 0 deletions test/Parse/inverses_legacy_ifdef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extension NC: Compare { // expected-error {{noncopyable struct 'NC' cannot confo

extension NC: Sortable { // expected-error {{noncopyable struct 'NC' cannot conform to 'Sortable'}}
// expected-error@-1 {{type 'NC' does not conform to protocol 'Sortable'}}
// expected-error@-2 {{struct 'NC' required to be 'Copyable' but is marked with '~Copyable'}}
typealias Element = NC // expected-note {{possibly intended match 'NC.Element' (aka 'NC') does not conform to 'Copyable'}}

mutating func sort() { }
Expand Down
4 changes: 3 additions & 1 deletion test/Sema/copyable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ typealias WhatIfIQualify = Swift.Copyable

class C: Copyable {}

@_moveOnly struct MOStruct: Copyable {} // expected-error {{noncopyable struct 'MOStruct' cannot conform to 'Copyable'}}
@_moveOnly struct MOStruct: Copyable {}
// expected-error@-1 {{noncopyable struct 'MOStruct' cannot conform to 'Copyable'}}
// expected-error@-2 {{struct 'MOStruct' required to be 'Copyable' but is marked with '~Copyable'}}


func whatever<T>(_ t: T) where T: Copyable {}
Expand Down
Loading