Skip to content

Commit e8b3934

Browse files
Merge pull request #78459 from AnthonyLatsis/tuber-magnatum-2
TypeCheckType: Unconditionally warn about missing existential `any` until Swift 7
2 parents ac52024 + 3222053 commit e8b3934

19 files changed

+304
-244
lines changed

include/swift/AST/Decl.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5592,12 +5592,6 @@ class ProtocolDecl final : public NominalTypeDecl {
55925592
/// value requirement.
55935593
bool hasSelfOrAssociatedTypeRequirements() const;
55945594

5595-
/// Determine whether an existential type constrained by this protocol must
5596-
/// be written using `any` syntax.
5597-
///
5598-
/// \Note This method takes language feature state into account.
5599-
bool existentialRequiresAny() const;
5600-
56015595
/// Returns a list of protocol requirements that must be assessed to
56025596
/// determine a concrete's conformance effect polymorphism kind.
56035597
PolymorphicEffectRequirementList getPolymorphicEffectRequirements(

include/swift/AST/DiagnosticGroups.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ GROUP(Unsafe, "Unsafe.md")
2727
GROUP(UnknownWarningGroup, "UnknownWarningGroup.md")
2828
GROUP(PreconcurrencyImport, "PreconcurrencyImport.md")
2929
GROUP(StrictLanguageFeatures, "StrictLanguageFeatures.md")
30+
GROUP(ExistentialAny, "ExistentialAny.md")
3031

3132
#define UNDEFINE_DIAGNOSTIC_GROUPS_MACROS
3233
#include "swift/AST/DefineDiagnosticGroupsMacros.h"

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5843,11 +5843,12 @@ ERROR(any_not_existential,none,
58435843
ERROR(incorrect_optional_any,none,
58445844
"optional 'any' type must be written %0",
58455845
(Type))
5846-
ERROR(existential_requires_any,none,
5847-
"use of %select{protocol |}2%0 as a type must be written %1",
5848-
(Type, Type, bool))
5849-
ERROR(inverse_requires_any,none,
5850-
"constraint that suppresses conformance requires 'any'", ())
5846+
5847+
GROUPED_ERROR(existential_requires_any,ExistentialAny,none,
5848+
"use of %select{protocol |}2%0 as a type must be written %1",
5849+
(Type, Type, bool))
5850+
GROUPED_ERROR(inverse_requires_any,ExistentialAny,none,
5851+
"constraint that suppresses conformance requires 'any'", ())
58515852

58525853
ERROR(nonisolated_mutable_storage,none,
58535854
"'nonisolated' cannot be applied to mutable stored properties",

lib/AST/Decl.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6980,13 +6980,6 @@ bool ProtocolDecl::hasSelfOrAssociatedTypeRequirements() const {
69806980
resultForCycle);
69816981
}
69826982

6983-
bool ProtocolDecl::existentialRequiresAny() const {
6984-
if (getASTContext().LangOpts.hasFeature(Feature::ExistentialAny))
6985-
return true;
6986-
6987-
return hasSelfOrAssociatedTypeRequirements();
6988-
}
6989-
69906983
ArrayRef<AssociatedTypeDecl *>
69916984
ProtocolDecl::getPrimaryAssociatedTypes() const {
69926985
return evaluateOrDefault(getASTContext().evaluator,

lib/Sema/TypeCheckType.cpp

Lines changed: 112 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -909,11 +909,15 @@ static Type applyGenericArguments(Type type,
909909
auto parameterized =
910910
ParameterizedProtocolType::get(ctx, protoType, argTys);
911911

912+
// FIXME: Can this not be done in ExistentialTypeSyntaxChecker?
912913
if (resolution.getOptions().isConstraintImplicitExistential() &&
913914
!ctx.LangOpts.hasFeature(Feature::ImplicitSome)) {
914-
diags.diagnose(loc, diag::existential_requires_any, parameterized,
915-
ExistentialType::get(parameterized),
916-
/*isAlias=*/isa<TypeAliasType>(type.getPointer()));
915+
diags
916+
.diagnose(loc, diag::existential_requires_any, parameterized,
917+
ExistentialType::get(parameterized),
918+
/*isAlias=*/isa<TypeAliasType>(type.getPointer()))
919+
.warnUntilSwiftVersion(7);
920+
917921
return ErrorType::get(ctx);
918922
}
919923

@@ -6218,18 +6222,15 @@ namespace {
62186222
/// written syntax.
62196223
class ExistentialTypeSyntaxChecker : public ASTWalker {
62206224
ASTContext &Ctx;
6221-
bool checkStatements;
6225+
const bool checkStatements;
62226226
bool hitTopStmt;
6223-
bool warnUntilSwift7;
62246227

62256228
unsigned exprCount = 0;
62266229
llvm::SmallVector<TypeRepr *, 4> reprStack;
62276230

62286231
public:
6229-
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements,
6230-
bool warnUntilSwift7 = false)
6231-
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false),
6232-
warnUntilSwift7(warnUntilSwift7) {}
6232+
ExistentialTypeSyntaxChecker(ASTContext &ctx, bool checkStatements)
6233+
: Ctx(ctx), checkStatements(checkStatements), hitTopStmt(false) {}
62336234

62346235
MacroWalking getMacroWalkingBehavior() const override {
62356236
return MacroWalking::ArgumentsAndExpansion;
@@ -6262,7 +6263,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
62626263
}
62636264

62646265
PostWalkAction walkToTypeReprPost(TypeRepr *T) override {
6265-
assert(reprStack.back() == T);
6266+
ASSERT(reprStack.back() == T);
62666267
reprStack.pop_back();
62676268
return Action::Continue();
62686269
}
@@ -6386,14 +6387,13 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
63866387
diag.fixItReplace(replacementT->getSourceRange(), fix);
63876388
}
63886389

6389-
/// Returns a Boolean value indicating whether the type representation being
6390-
/// visited, assuming it is a constraint type demanding `any` or `some`, is
6391-
/// missing either keyword.
6392-
bool isAnyOrSomeMissing() const {
6393-
assert(isa<DeclRefTypeRepr>(reprStack.back()));
6390+
/// Returns a Boolean value indicating whether the currently visited
6391+
/// `DeclRefTypeRepr` node has a `some` or `any` keyword that applies to it.
6392+
bool currentNodeHasAnyOrSomeKeyword() const {
6393+
ASSERT(isa<DeclRefTypeRepr>(reprStack.back()));
63946394

63956395
if (reprStack.size() < 2) {
6396-
return true;
6396+
return false;
63976397
}
63986398

63996399
auto it = reprStack.end() - 1;
@@ -6403,7 +6403,7 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
64036403
break;
64046404
}
64056405

6406-
// Look through parens, inverses, metatypes, and compositions.
6406+
// Look through parens, inverses, `.Type` metatypes, and compositions.
64076407
if ((*it)->isParenType() || isa<InverseTypeRepr>(*it) ||
64086408
isa<CompositionTypeRepr>(*it) || isa<MetatypeTypeRepr>(*it)) {
64096409
continue;
@@ -6412,84 +6412,119 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
64126412
break;
64136413
}
64146414

6415-
return !(isa<OpaqueReturnTypeRepr>(*it) || isa<ExistentialTypeRepr>(*it));
6415+
return isa<OpaqueReturnTypeRepr>(*it) || isa<ExistentialTypeRepr>(*it);
64166416
}
64176417

6418-
void checkDeclRefTypeRepr(DeclRefTypeRepr *T) const {
6419-
assert(!T->isInvalid());
6418+
/// Returns the behavior with which to diagnose a missing `any` or `some`
6419+
/// keyword.
6420+
///
6421+
/// \param constraintTy The constraint type that is missing the keyword.
6422+
/// \param isInverted Whether the constraint type is an object of an `~`
6423+
/// inversion.
6424+
static bool shouldDiagnoseMissingAnyOrSomeKeyword(Type constraintTy,
6425+
bool isInverted,
6426+
ASTContext &ctx) {
6427+
// `Any` and `AnyObject` are always exempt from `any` syntax.
6428+
if (constraintTy->isAny() || constraintTy->isAnyObject()) {
6429+
return false;
6430+
}
64206431

6421-
if (Ctx.LangOpts.hasFeature(Feature::ImplicitSome)) {
6422-
return;
6432+
// A missing `any` or `some` is always diagnosed if this feature is enabled.
6433+
if (ctx.LangOpts.hasFeature(Feature::ExistentialAny)) {
6434+
return true;
64236435
}
64246436

6425-
// Backtrack the stack, looking just through parentheses and metatypes. If
6426-
// we find an inverse (which always requires `any`), diagnose it specially.
6427-
if (reprStack.size() > 1) {
6428-
auto it = reprStack.end() - 2;
6429-
while (it != reprStack.begin() &&
6430-
((*it)->isParenType() || isa<MetatypeTypeRepr>(*it))) {
6431-
--it;
6432-
continue;
6437+
// If the type is inverted, a missing `any` or `some` is always diagnosed.
6438+
if (isInverted) {
6439+
return true;
6440+
}
6441+
6442+
// If one of the protocols is inverted, a missing `any` or `some` is
6443+
// always diagnosed.
6444+
if (auto *PCT = constraintTy->getAs<ProtocolCompositionType>()) {
6445+
if (!PCT->getInverses().empty()) {
6446+
return true;
64336447
}
6448+
}
64346449

6435-
if (auto *inverse = dyn_cast<InverseTypeRepr>(*it);
6436-
inverse && isAnyOrSomeMissing()) {
6437-
auto diag = Ctx.Diags.diagnose(inverse->getTildeLoc(),
6438-
diag::inverse_requires_any);
6439-
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
6440-
emitInsertAnyFixit(diag, T);
6441-
return;
6450+
// If one of the protocols has "Self or associated type" requirements,
6451+
// a missing `any` or `some` is always diagnosed.
6452+
auto layout = constraintTy->getExistentialLayout();
6453+
for (auto *protoDecl : layout.getProtocols()) {
6454+
if (protoDecl->hasSelfOrAssociatedTypeRequirements()) {
6455+
return true;
64426456
}
64436457
}
64446458

6459+
return false;
6460+
}
6461+
6462+
void checkDeclRefTypeRepr(DeclRefTypeRepr *T) const {
6463+
if (Ctx.LangOpts.hasFeature(Feature::ImplicitSome)) {
6464+
return;
6465+
}
6466+
64456467
auto *decl = T->getBoundDecl();
64466468
if (!decl) {
64476469
return;
64486470
}
64496471

6450-
if (auto *proto = dyn_cast<ProtocolDecl>(decl)) {
6451-
if (proto->existentialRequiresAny() && isAnyOrSomeMissing()) {
6452-
auto diag =
6453-
Ctx.Diags.diagnose(T->getNameLoc(), diag::existential_requires_any,
6454-
proto->getDeclaredInterfaceType(),
6455-
proto->getDeclaredExistentialType(),
6456-
/*isAlias=*/false);
6457-
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
6458-
emitInsertAnyFixit(diag, T);
6472+
if (!isa<ProtocolDecl>(decl) && !isa<TypeAliasDecl>(decl)) {
6473+
return;
6474+
}
6475+
6476+
// If there is already an `any` or `some` that applies to this node,
6477+
// move on.
6478+
if (currentNodeHasAnyOrSomeKeyword()) {
6479+
return;
6480+
}
6481+
6482+
const auto type = decl->getDeclaredInterfaceType();
6483+
6484+
// A type alias may need to be prefixed with `any` only if it stands for a
6485+
// constraint type.
6486+
if (isa<TypeAliasDecl>(decl) && !type->isConstraintType()) {
6487+
return;
6488+
}
6489+
6490+
// First, consider the possibility of the current node being an object of
6491+
// an inversion, e.g. `~(Copyable)`.
6492+
// Climb up the stack, looking just through parentheses and `.Type`
6493+
// metatypes.
6494+
// If we find an inversion, we will diagnose it specially.
6495+
InverseTypeRepr *const outerInversion = [&] {
6496+
if (reprStack.size() < 2) {
6497+
return (InverseTypeRepr *)nullptr;
64596498
}
6460-
} else if (auto *alias = dyn_cast<TypeAliasDecl>(decl)) {
6461-
auto type = Type(alias->getDeclaredInterfaceType()->getDesugaredType());
6462-
6463-
// If this is a type alias to a constraint type, the type
6464-
// alias name must be prefixed with 'any' to be used as an
6465-
// existential type.
6466-
if (type->isConstraintType() && !type->isAny() && !type->isAnyObject()) {
6467-
bool diagnose = false;
6468-
6469-
// Look for protocol members that require 'any'.
6470-
auto layout = type->getExistentialLayout();
6471-
for (auto *protoDecl : layout.getProtocols()) {
6472-
if (protoDecl->existentialRequiresAny()) {
6473-
diagnose = true;
6474-
break;
6475-
}
6476-
}
64776499

6478-
// If inverses are present, require 'any' too.
6479-
if (auto *PCT = type->getAs<ProtocolCompositionType>())
6480-
diagnose |= !PCT->getInverses().empty();
6481-
6482-
if (diagnose && isAnyOrSomeMissing()) {
6483-
auto diag = Ctx.Diags.diagnose(
6484-
T->getNameLoc(), diag::existential_requires_any,
6485-
alias->getDeclaredInterfaceType(),
6486-
ExistentialType::get(alias->getDeclaredInterfaceType()),
6487-
/*isAlias=*/true);
6488-
diag.warnUntilSwiftVersionIf(warnUntilSwift7, 7);
6489-
emitInsertAnyFixit(diag, T);
6490-
}
6500+
auto it = reprStack.end() - 2;
6501+
while (it != reprStack.begin() &&
6502+
((*it)->isParenType() || isa<MetatypeTypeRepr>(*it))) {
6503+
--it;
6504+
continue;
64916505
}
6506+
6507+
return dyn_cast<InverseTypeRepr>(*it);
6508+
}();
6509+
6510+
if (!shouldDiagnoseMissingAnyOrSomeKeyword(
6511+
type, /*isInverted=*/outerInversion, this->Ctx)) {
6512+
return;
6513+
}
6514+
6515+
std::optional<InFlightDiagnostic> diag;
6516+
if (outerInversion) {
6517+
diag.emplace(Ctx.Diags.diagnose(outerInversion->getTildeLoc(),
6518+
diag::inverse_requires_any));
6519+
} else {
6520+
diag.emplace(Ctx.Diags.diagnose(T->getNameLoc(),
6521+
diag::existential_requires_any, type,
6522+
ExistentialType::get(type),
6523+
/*isAlias=*/isa<TypeAliasDecl>(decl)));
64926524
}
6525+
6526+
diag->warnUntilSwiftVersion(7);
6527+
emitInsertAnyFixit(*diag, T);
64936528
}
64946529

64956530
public:
@@ -6563,14 +6598,7 @@ void TypeChecker::checkExistentialTypes(ASTContext &ctx, Stmt *stmt,
65636598
if (DC->isInSwiftinterface())
65646599
return;
65656600

6566-
// Previously we missed this diagnostic on 'catch' statements, downgrade
6567-
// to a warning until Swift 7.
6568-
auto downgradeUntilSwift7 = false;
6569-
if (auto *CS = dyn_cast<CaseStmt>(stmt))
6570-
downgradeUntilSwift7 = CS->getParentKind() == CaseParentKind::DoCatch;
6571-
6572-
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true,
6573-
downgradeUntilSwift7);
6601+
ExistentialTypeSyntaxChecker checker(ctx, /*checkStatements=*/true);
65746602
stmt->walk(checker);
65756603
}
65766604

test/Generics/inverse_generics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,8 @@ func checkExistentials() {
475475

476476
typealias NotCopyable = ~Copyable
477477
typealias EmptyComposition = ~Copyable & ~Escapable
478-
func test(_ t: borrowing NotCopyable) {} // expected-error {{use of 'NotCopyable' (aka '~Copyable') as a type must be written 'any NotCopyable'}}
479-
func test(_ t: borrowing EmptyComposition) {} // expected-error {{use of 'EmptyComposition' (aka '~Copyable & ~Escapable') as a type must be written 'any EmptyComposition' (aka 'any ~Copyable & ~Escapable')}}
478+
func test(_ t: borrowing NotCopyable) {} // expected-warning {{use of 'NotCopyable' (aka '~Copyable') as a type must be written 'any NotCopyable'}}
479+
func test(_ t: borrowing EmptyComposition) {} // expected-warning {{use of 'EmptyComposition' (aka '~Copyable & ~Escapable') as a type must be written 'any EmptyComposition' (aka 'any ~Copyable & ~Escapable')}}
480480

481481
typealias Copy = Copyable
482482
func test(_ z1: Copy, _ z2: Copyable) {}

test/Macros/macros_diagnostics.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ struct MyStruct<T: MyProto> {
196196

197197
struct SomeType {
198198
#genericUnary<Equatable>(0 as Hashable)
199-
// expected-error@-1{{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
200-
// expected-error@-2{{use of protocol 'Hashable' as a type must be written 'any Hashable'}}
199+
// expected-warning@-1{{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
200+
// expected-warning@-2{{use of protocol 'Hashable' as a type must be written 'any Hashable'}}
201201
// expected-error@-3{{external macro implementation type}}
202202
}
203203

test/Macros/top_level_freestanding.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ func testGlobalVariable() {
107107

108108
// expected-note @+1 6 {{in expansion of macro 'anonymousTypes' here}}
109109
#anonymousTypes(causeErrors: true) { "foo" }
110-
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_{{.*}}: error: use of protocol 'Equatable' as a type must be written 'any Equatable'
111-
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser00142___swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_swift_DAIABdjIbfMX23_2_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll27introduceTypeCheckingErrorsfMf_{{.*}}: error: use of protocol 'Hashable' as a type must be written 'any Hashable'
110+
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_{{.*}}: warning: use of protocol 'Equatable' as a type must be written 'any Equatable'
111+
// DIAG_BUFFERS-DAG: @__swiftmacro_9MacroUser00142___swiftmacro_9MacroUser0033top_level_freestandingswift_DbGHjfMX108_0_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll14anonymousTypesfMf0_swift_DAIABdjIbfMX23_2_33_082AE7CFEFA6960C804A9FE7366EB5A0Ll27introduceTypeCheckingErrorsfMf_{{.*}}: warning: use of protocol 'Hashable' as a type must be written 'any Hashable'
112112

113113
// expected-note @+1 2 {{in expansion of macro 'anonymousTypes' here}}
114114
#anonymousTypes { () -> String in
115-
// expected-error @+1 {{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
115+
// expected-warning @+1 {{use of protocol 'Equatable' as a type must be written 'any Equatable'}}
116116
_ = 0 as Equatable
117117
return "foo"
118118
}

test/decl/nested/protocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ extension OuterProtocol {
237237

238238
struct ConformsToOuterProtocol : OuterProtocol {
239239
typealias Hen = Int
240-
func f() { let _ = InnerProtocol.self } // expected-error {{use of protocol 'InnerProtocol' as a type must be written 'any InnerProtocol'}}
240+
func f() { let _ = InnerProtocol.self } // expected-warning {{use of protocol 'InnerProtocol' as a type must be written 'any InnerProtocol'}}
241241
}
242242

243243
extension OuterProtocol {

0 commit comments

Comments
 (0)