Skip to content

[6.0] Fix parsing issues related to suppressed conformances / noncopyable generics #72775

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
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
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5694,6 +5694,8 @@ ERROR(incorrect_optional_any,none,
ERROR(existential_requires_any,none,
"use of %select{protocol |}2%0 as a type must be written %1",
(Type, Type, bool))
ERROR(inverse_requires_any,none,
"constraint that suppresses conformance requires 'any'", ())

ERROR(nonisolated_let,none,
"'nonisolated' is meaningless on 'let' declarations because "
Expand Down
19 changes: 13 additions & 6 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1239,12 +1239,19 @@ class Parser {
// the following token is something that can introduce a type. Thankfully
// none of these tokens overlap with the set of tokens that can follow an
// identifier in a type production.
return (Tok.is(tok::identifier) &&
peekToken().isAny(tok::at_sign, tok::kw_inout, tok::l_paren,
tok::identifier, tok::l_square, tok::kw_Any,
tok::kw_Self, tok::kw__, tok::kw_var,
tok::kw_let)) ||
isLifetimeDependenceToken();
if (Tok.is(tok::identifier)) {
auto next = peekToken();
if (next.isAny(tok::at_sign, tok::kw_inout, tok::l_paren,
tok::identifier, tok::l_square, tok::kw_Any,
tok::kw_Self, tok::kw__, tok::kw_var,
tok::kw_let))
return true;

if (next.is(tok::oper_prefix) && next.getText() == "~")
return true;
}

return isLifetimeDependenceToken();
}

struct ParsedTypeAttributeList {
Expand Down
3 changes: 2 additions & 1 deletion lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,8 @@ ParserResult<Expr> Parser::parseExprSequenceElement(Diag<> message,

// 'any' followed by another identifier is an existential type.
if (Tok.isContextualKeyword("any") &&
peekToken().is(tok::identifier) &&
(peekToken().is(tok::identifier) ||
peekToken().isContextualPunctuator("~")) &&
!peekToken().isAtStartOfLine()) {
ParserResult<TypeRepr> ty = parseType();
auto *typeExpr = new (Context) TypeExpr(ty.get());
Expand Down
9 changes: 9 additions & 0 deletions lib/Parse/ParseType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,15 @@ bool Parser::canParseType() {
if (!canParseTypeIdentifier())
return false;
break;
case tok::oper_prefix:
if (Tok.getText() != "~") {
return false;
}

consumeToken();
if (!canParseTypeIdentifier())
return false;
break;
case tok::kw_protocol:
return canParseOldStyleProtocolComposition();
case tok::l_paren: {
Expand Down
38 changes: 38 additions & 0 deletions lib/Sema/PreCheckExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1826,10 +1826,37 @@ VarDecl *PreCheckExpression::getImplicitSelfDeclForSuperContext(SourceLoc Loc) {
return methodSelf;
}

/// Check whether this expression refers to the ~ operator.
static bool isTildeOperator(Expr *expr) {
auto nameMatches = [&](DeclName name) {
return name.isOperator() && name.getBaseName().getIdentifier().is("~");
};

if (auto overload = dyn_cast<OverloadedDeclRefExpr>(expr)) {
return llvm::any_of(overload->getDecls(), [=](auto *decl) -> bool {
return nameMatches(decl->getName());
});
}

if (auto unresolved = dyn_cast<UnresolvedDeclRefExpr>(expr)) {
return nameMatches(unresolved->getName().getFullName());
}

if (auto declRef = dyn_cast<DeclRefExpr>(expr)) {
return nameMatches(declRef->getDecl()->getName());
}

return false;
}

/// Simplify expressions which are type sugar productions that got parsed
/// as expressions due to the parser not knowing which identifiers are
/// type names.
TypeExpr *PreCheckExpression::simplifyTypeExpr(Expr *E) {
// If it's already a type expression, return it.
if (auto typeExpr = dyn_cast<TypeExpr>(E))
return typeExpr;

// Fold member types.
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(E)) {
return simplifyNestedTypeExpr(UDE);
Expand Down Expand Up @@ -2081,6 +2108,17 @@ TypeExpr *PreCheckExpression::simplifyTypeExpr(Expr *E) {
return new (Ctx) TypeExpr(NewTypeRepr);
}

// Fold '~P' into a composition type.
if (auto *unaryExpr = dyn_cast<PrefixUnaryExpr>(E)) {
if (isTildeOperator(unaryExpr->getFn())) {
if (auto operand = simplifyTypeExpr(unaryExpr->getOperand())) {
auto inverseTypeRepr = new (Ctx) InverseTypeRepr(
unaryExpr->getLoc(), operand->getTypeRepr());
return new (Ctx) TypeExpr(inverseTypeRepr);
}
}
}

// Fold 'P & Q' into a composition type
if (auto *binaryExpr = getCompositionExpr(E)) {
// The protocols we are composing
Expand Down
48 changes: 45 additions & 3 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ static unsigned getGenericRequirementKind(TypeResolutionOptions options) {
case TypeResolverContext::ImmediateOptionalTypeArgument:
case TypeResolverContext::AbstractFunctionDecl:
case TypeResolverContext::CustomAttr:
case TypeResolverContext::Inverted:
break;
}

Expand Down Expand Up @@ -5150,6 +5151,7 @@ NeverNullType TypeResolver::resolveImplicitlyUnwrappedOptionalType(
case TypeResolverContext::GenericParameterInherited:
case TypeResolverContext::AssociatedTypeInherited:
case TypeResolverContext::CustomAttr:
case TypeResolverContext::Inverted:
doDiag = true;
break;
}
Expand Down Expand Up @@ -5740,10 +5742,30 @@ NeverNullType TypeResolver::buildMetatypeType(

NeverNullType TypeResolver::resolveInverseType(InverseTypeRepr *repr,
TypeResolutionOptions options) {
auto ty = resolveType(repr->getConstraint(), options);
auto subOptions = options.withoutContext(true)
.withContext(TypeResolverContext::Inverted);
auto ty = resolveType(repr->getConstraint(), subOptions);
if (ty->hasError())
return ErrorType::get(getASTContext());

// If the inverted type is an existential metatype, unwrap the existential
// metatype so we can look at the instance type. We'll re-wrap at the end.
ExistentialMetatypeType *existentialTy =
dyn_cast<ExistentialMetatypeType>(ty.get().getPointer());
if (existentialTy) {
ty = existentialTy->getInstanceType();
}

auto wrapInExistential = [existentialTy](Type type) -> Type {
if (!existentialTy)
return type;

std::optional<MetatypeRepresentation> repr;
if (existentialTy->hasRepresentation())
repr = existentialTy->getRepresentation();
return ExistentialMetatypeType::get(type, repr);
};

if (auto kp = ty->getKnownProtocol()) {
if (auto kind = getInvertibleProtocolKind(*kp)) {

Expand All @@ -5752,13 +5774,16 @@ NeverNullType TypeResolver::resolveInverseType(InverseTypeRepr *repr,
!getASTContext().LangOpts.hasFeature(Feature::NonescapableTypes)) {
diagnoseInvalid(repr, repr->getLoc(),
diag::escapable_requires_feature_flag);
return ErrorType::get(getASTContext());
return wrapInExistential(ErrorType::get(getASTContext()));
}

return ProtocolCompositionType::getInverseOf(getASTContext(), *kind);
return wrapInExistential(
ProtocolCompositionType::getInverseOf(getASTContext(), *kind));
}
}

// Rewrap for diagnostic purposes.
ty = wrapInExistential(ty);
diagnoseInvalid(repr, repr->getLoc(), diag::inverse_type_not_invertible, ty);
return ErrorType::get(getASTContext());
}
Expand Down Expand Up @@ -5917,6 +5942,23 @@ class ExistentialTypeSyntaxChecker : public ASTWalker {
if (isa<ExistentialTypeRepr>(T))
return Action::SkipNode();

// Suppressed conformance needs to be within any/some.
if (auto inverse = dyn_cast<InverseTypeRepr>(T)) {
// Find an enclosing protocol composition, if there is one, so we
// can insert 'any' before that.
SourceLoc anyLoc = inverse->getTildeLoc();
if (!reprStack.empty()) {
if (isa<CompositionTypeRepr>(reprStack.back())) {
anyLoc = reprStack.back()->getStartLoc();
}
}

Ctx.Diags.diagnose(inverse->getTildeLoc(), diag::inverse_requires_any)
.highlight(inverse->getConstraint()->getSourceRange())
.fixItInsert(anyLoc, "any ");
return Action::SkipNode();
}

reprStack.push_back(T);

auto *declRefTR = dyn_cast<DeclRefTypeRepr>(T);
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/TypeCheckType.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ enum class TypeResolverContext : uint8_t {

/// Whether this is a custom attribute.
CustomAttr,

/// Whether this is the argument of an inverted constraint (~).
Inverted,
};

/// Options that determine how type resolution should work.
Expand Down Expand Up @@ -298,6 +301,7 @@ class TypeResolutionOptions {
case Context::GenericParameterInherited:
case Context::AssociatedTypeInherited:
case Context::CustomAttr:
case Context::Inverted:
return false;
}
llvm_unreachable("unhandled kind");
Expand All @@ -316,6 +320,7 @@ class TypeResolutionOptions {
case Context::GenericRequirement:
case Context::ExistentialConstraint:
case Context::MetatypeBase:
case Context::Inverted:
return false;
case Context::None:
case Context::ScalarGenericArgument:
Expand Down Expand Up @@ -353,6 +358,7 @@ class TypeResolutionOptions {
case Context::PackElement:
case Context::TupleElement:
case Context::VariadicGenericArgument:
case Context::Inverted:
return true;
case Context::None:
case Context::PatternBindingDecl:
Expand Down Expand Up @@ -425,6 +431,7 @@ class TypeResolutionOptions {
case Context::ImmediateOptionalTypeArgument:
case Context::AbstractFunctionDecl:
case Context::CustomAttr:
case Context::Inverted:
return false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/IRGen/existential_shape_metadata_noncopyable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public protocol QNC<A>: ~Copyable {
public struct NCStruct: ~Copyable { }


public func testNoncopyableConcrete() -> (Any & ~Copyable).Type {
public func testNoncopyableConcrete() -> any ~Copyable.Type {
return (any QNC<NCStruct>).self
}
36 changes: 31 additions & 5 deletions test/Parse/inverses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,18 @@ func ownership2(_ t: ~ borrowing Int) {} // expected-error {{cannot find type 'b

func ownership3(_ t: consuming some ~Clone) {}

func what(one: ~Copyable..., // expected-error {{type 'any Copyable' cannot be suppressed}}
func what(one: ~Copyable..., // expected-error {{noncopyable type '~Copyable' cannot be used within a variadic type yet}}
two: ~(Copyable...) // expected-error {{variadic parameter cannot appear outside of a function parameter list}}
// expected-error@-1 {{type 'any Copyable' cannot be suppressed}}
// expected-error@-1 {{parameter of noncopyable type '~Copyable' must specify ownership}}
// expected-note@-2{{add 'borrowing' for an immutable reference}}
// expected-note@-3{{add 'inout' for a mutable reference}}
// expected-note@-4{{add 'consuming' to take the value from the caller}}
) {}

struct A { struct B { struct C {} } }

typealias Z1 = (~Copyable).Type // FIXME: should be an error
typealias Z1 = ~Copyable.Type // expected-error {{type 'any Copyable.Type' cannot be suppressed}}
typealias Z0 = (~Copyable).Type // expected-error{{constraint that suppresses conformance requires 'any'}}{{17-17=any }}
typealias Z1 = ~Copyable.Type // expected-error{{constraint that suppresses conformance requires 'any'}}{{16-16=any }}
typealias Z2 = ~A.B.C // expected-error {{type 'A.B.C' cannot be suppressed}}
typealias Z3 = ~A? // expected-error {{type 'A?' cannot be suppressed}}
typealias Z4 = ~Rope<Int> // expected-error {{type 'Rope<Int>' cannot be suppressed}}
Expand All @@ -101,4 +104,27 @@ typealias Z8 = ~Copyable & Hashable // expected-error {{composition cannot conta

struct NotAProtocol {}

struct Bad: ~NotAProtocol {} // expected-error {{type 'NotAProtocol' cannot be suppressed}}
struct Bad: ~NotAProtocol {} // expected-error {{type 'NotAProtocol' cannot be suppressed}}

struct X<T: ~Copyable>: ~Copyable { }

func typeInExpression() {
_ = [~Copyable]() // expected-error{{type '~Copyable' does not conform to protocol 'Copyable'}}
_ = X<any ~Copyable>()

_ = X<any ~Copyable & Foo>()
_ = X<any Foo & ~Copyable>()

_ = X<(borrowing any ~Copyable) -> Void>()

_ = ~Copyable.self // expected-error{{unary operator '~' cannot be applied to an operand of type '(any Copyable).Type'}}
_ = (~Copyable).self // expected-error{{constraint that suppresses conformance requires 'any'}}{{8-8=any }}
_ = (any ~Copyable).self
}

func param1(_ t: borrowing ~Copyable) {} // expected-error{{constraint that suppresses conformance requires 'any'}}{{28-28=any }}
func param2(_ t: ~Copyable.Type) {} // expected-error{{constraint that suppresses conformance requires 'any'}}{{18-18=any }}
func param3(_ t: borrowing any ~Copyable) {}
func param4(_ t: any ~Copyable.Type) {}

func param3(_ t: borrowing ExtraNoncopyProto & ~Copyable) {} // expected-error{{constraint that suppresses conformance requires 'any'}}{{28-28=any }}