Skip to content

Turn on the Copyable as an inferred generic constraint by default #64059

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 5 commits into from
Mar 5, 2023
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
19 changes: 16 additions & 3 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,16 @@ LookupConformanceInModuleRequest::evaluate(
return getBuiltinBuiltinTypeConformance(type, builtinType, protocol);
}

// Specific handling of Copyable for pack expansions.
if (auto packExpansion = type->getAs<PackExpansionType>()) {
if (protocol->isSpecificProtocol(KnownProtocolKind::Copyable)) {
auto patternType = packExpansion->getPatternType();
return (patternType->isTypeParameter()
? ProtocolConformanceRef(protocol)
: mod->lookupConformance(patternType, protocol));
}
}

auto nominal = type->getAnyNominal();

// If we don't have a nominal type, there are no conformances.
Expand Down Expand Up @@ -1768,9 +1778,12 @@ LookupConformanceInModuleRequest::evaluate(
if (nominal->isMoveOnly()) {
return ProtocolConformanceRef::forInvalid();
} else {
// FIXME: this should probably follow the Sendable case in that
// we should synthesize and append a ProtocolConformance to the `conformances` list.
return ProtocolConformanceRef(protocol);
// Specifically do not create a concrete conformance to Copyable. At
// this stage, we don't even want Copyable to appear in swiftinterface
// files, which will happen for a marker protocol that's registered
// in a nominal type's conformance table. We can reconsider this
// decision later once there's a clearer picture of noncopyable generics
return ProtocolConformanceRef(protocol);
}
} else {
// Was unable to infer the missing conformance.
Expand Down
5 changes: 0 additions & 5 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1241,11 +1241,6 @@ static SmallVector<ProtocolConformance *, 2> findSynthesizedConformances(
// Concrete types may synthesize some conformances
if (!isa<ProtocolDecl>(nominal)) {
trySynthesize(KnownProtocolKind::Sendable);

// FIXME(kavon): make sure this conformance doesn't show up in swiftinterfaces
// before do this synthesis unconditionally.
if (dc->getASTContext().LangOpts.hasFeature(Feature::MoveOnly))
trySynthesize(KnownProtocolKind::Copyable);
}

/// Distributed actors can synthesize Encodable/Decodable, so look for those
Expand Down
14 changes: 13 additions & 1 deletion lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,19 @@ void BindingSet::finalize(

if (TransitiveProtocols.has_value()) {
for (auto *constraint : *TransitiveProtocols) {
auto protocolTy = constraint->getSecondType();
Type protocolTy = constraint->getSecondType();

// The Copyable protocol can't have members, yet will be a
// constraint of basically all type variables, so don't suggest it.
//
// NOTE: worth considering for all marker protocols, but keep in
// mind that you're allowed to extend them with members!
if (auto p = protocolTy->getAs<ProtocolType>()) {
if (ProtocolDecl *decl = p->getDecl())
if (decl->isSpecificProtocol(KnownProtocolKind::Copyable))
continue;
}

addBinding({protocolTy, AllowedBindingKind::Exact, constraint});
}
}
Expand Down
17 changes: 6 additions & 11 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1828,17 +1828,12 @@ TypeVariableType *ConstraintSystem::openGenericParameter(
assert(result.second);
(void)result;

// When move-only types are available, add a constraint to force generic
// parameters to conform to a "Copyable" protocol.
if (getASTContext().LangOpts.hasFeature(Feature::MoveOnly)) {
ProtocolDecl *copyable = TypeChecker::getProtocol(
getASTContext(), SourceLoc(), KnownProtocolKind::Copyable);

// FIXME(kavon): there's a dependency ordering issues here with the
// protocol being defined in the stdlib, because when trying to build
// the stdlib itself, or a Swift program with -parse-stdlib, we can't
// load the protocol to add this constraint. (rdar://104898230)
assert(copyable && "stdlib is missing _Copyable protocol!");
// Add a constraint that generic parameters conform to Copyable.
// This lookup only can fail if the stdlib (i.e. the Swift module) has not
// been loaded because you've passed `-parse-stdlib` and are not building the
// stdlib itself (which would have `-module-name Swift` too).
if (auto *copyable = TypeChecker::getProtocol(getASTContext(), SourceLoc(),
KnownProtocolKind::Copyable)) {
addConstraint(
ConstraintKind::ConformsTo, typeVar,
copyable->getDeclaredInterfaceType(),
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4618,7 +4618,7 @@ swift::checkTypeWitness(Type type, AssociatedTypeDecl *assocType,
if (type->isPureMoveOnly()) {
// describe the failure reason as it not conforming to Copyable
auto *copyable = ctx.getProtocol(KnownProtocolKind::Copyable);
assert(copyable && "missing _Copyable from stdlib!");
assert(copyable && "missing _Copyable protocol!");
return CheckTypeWitnessResult(copyable->getDeclaredInterfaceType());
}

Expand Down
2 changes: 0 additions & 2 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2301,7 +2301,6 @@ bool TypeResolver::diagnoseMoveOnlyMissingOwnership(
diagnose(repr->getLoc(),
diag::moveonly_parameter_missing_ownership);

// FIXME: this should be 'borrowing'
diagnose(repr->getLoc(), diag::moveonly_parameter_ownership_suggestion,
"borrowing", "for an immutable reference")
.fixItInsert(repr->getStartLoc(), "borrowing ");
Expand All @@ -2310,7 +2309,6 @@ bool TypeResolver::diagnoseMoveOnlyMissingOwnership(
"inout", "for a mutable reference")
.fixItInsert(repr->getStartLoc(), "inout ");

// FIXME: this should be 'consuming'
diagnose(repr->getLoc(), diag::moveonly_parameter_ownership_suggestion,
"consuming", "to take the value from the caller")
.fixItInsert(repr->getStartLoc(), "consuming ");
Expand Down
10 changes: 6 additions & 4 deletions stdlib/public/core/Misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ public func _unsafePerformance<T>(_ c: () -> T) -> T {
return c()
}


/// This is not a protocol you can explicitly use in your programs.
/// It exists for the compiler and type checker for diagnostic purposes.
@_marker public protocol _Copyable { }
/// This marker protocol represents types that support copying.
/// This type is not yet available for use to express explicit
/// constraints on generics in your programs. It is currently
/// only used internally by the compiler.
@available(*, unavailable)
@_marker public protocol _Copyable {}
1 change: 1 addition & 0 deletions test/Concurrency/async_overload_filtering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func filter_async(_: String) -> Void {}
var a: String? = nil

// CHECK: attempting disjunction choice $T0 bound to decl async_overload_filtering.(file).filter_async(fn2:)
// CHECK-NEXT: added constraint: {{.*}} conforms to _Copyable
// CHECK-NEXT: overload set choice binding $T0 := {{.*}}
// CHECK-NEXT: (considering -> ({{.*}}) -> {{.*}} applicable fn {{.*}}
// CHECK: increasing 'sync-in-asynchronous' score by 1
Expand Down
5 changes: 2 additions & 3 deletions test/Constraints/moveonly_constraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ func checkCasting(_ b: any Box, _ mo: __shared MO, _ a: Any) {

let _: Sendable = (MO(), MO()) // expected-error {{move-only type '(MO, MO)' cannot be used with generics yet}}
let _: Sendable = MO() // expected-error {{move-only type 'MO' cannot be used with generics yet}}
let _: _Copyable = mo // expected-error {{move-only type 'MO' cannot be used with generics yet}}
let _: _Copyable = mo // expected-error {{'_Copyable' is unavailable}}
// expected-error@-1 {{move-only type 'MO' cannot be used with generics yet}}
let _: AnyObject = MO() // expected-error {{move-only type 'MO' cannot be used with generics yet}}
let _: Any = mo // expected-error {{move-only type 'MO' cannot be used with generics yet}}

Expand All @@ -163,8 +164,6 @@ func checkCasting(_ b: any Box, _ mo: __shared MO, _ a: Any) {
_ = a as MO // expected-error {{cannot convert value of type 'Any' to type 'MO' in coercion}}
_ = b as MO // expected-error {{cannot convert value of type 'any Box' to type 'MO' in coercion}}

// FIXME(kavon): make sure at runtime these casts actually fail, or just make them errors? (rdar://104900293)

_ = MO() is AnyHashable // expected-warning {{cast from 'MO' to unrelated type 'AnyHashable' always fails}}
// expected-error@-1 {{move-only types cannot be conditionally cast}}
_ = MO() is AnyObject // expected-warning {{cast from 'MO' to unrelated type 'AnyObject' always fails}}
Expand Down
6 changes: 4 additions & 2 deletions test/Constraints/rdar68155466.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Foundation
}
}

struct Loop< // expected-note {{required by generic struct 'Loop' where 'ID' = '() -> Int'}}
// FIXME: the diagnostic below ideally should have been emitted (rdar://106241733)
struct Loop< // note {{required by generic struct 'Loop' where 'ID' = '() -> Int'}}
Data : RandomAccessCollection,
ID : Hashable,
Content
Expand All @@ -25,4 +26,5 @@ func data() -> [A] {
}

_ = Loop(data(), id: \.uniqueID) { $0 } // expected-error {{key path cannot refer to instance method 'uniqueID()'}}
// expected-error@-1 {{type '() -> Int' cannot conform to 'Hashable'}} expected-note@-1 {{only concrete types such as structs, enums and classes can conform to protocols}}
// FIXME: the diagnostics below ideally should have been emitted (rdar://106241733)
// error@-1 {{type '() -> Int' cannot conform to 'Hashable'}} note@-1 {{only concrete types such as structs, enums and classes can conform to protocols}}
61 changes: 61 additions & 0 deletions test/ModuleInterface/copyable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// RUN: %empty-directory(%t)

// This test checks that conformances to _Copyable do not appear in swiftinterface files

// Generate the parseable interface of the current file via the merge-modules step
// RUN: %target-build-swift -emit-module -o %t/Test.swiftmodule -emit-module-interface-path %t/TestMerge.swiftinterface -module-name Test %s -enable-library-evolution -swift-version 5

// Generate the parseable interface of the current file via a single frontend invocation
// RUN: %target-swift-frontend -enable-library-evolution -typecheck -emit-module-interface-path %t/TestSingle.swiftinterface -module-name Test %s -enable-library-evolution -swift-version 5

// Make sure Copyable doesn't appear anywhere in these files!
// RUN: %FileCheck --implicit-check-not Copyable %s < %t/TestSingle.swiftinterface
// RUN: %FileCheck --implicit-check-not Copyable %s < %t/TestMerge.swiftinterface


// CHECK: forceGenericSubst
public func forceGenericSubst<T>(_ t: T) {
print(t)
}

public protocol ProtocolWithAssocType {
associatedtype SomeType
func get() -> SomeType
}

public class BestClass: ProtocolWithAssocType {
public typealias SomeType = BestStruct
public func get() -> SomeType { return BestStruct() }
}

public struct BestStruct { let c = BestClass() }

public enum BestEnum<T> {
case nothing
case something(T)
}

public func caller(_ c: BestClass, _ s: BestStruct, _ e: BestEnum<BestStruct>) {
forceGenericSubst(c)
forceGenericSubst(s)
forceGenericSubst(e)
}

public typealias TheTop = (Int, String)

public struct S<T> {
let t: T
init(_ t: T) { self.t = t }
}

public typealias Handler = () -> ()

public func genericFn<T>(_ t: T) -> S<T> {
return S(t)
}

public func maker(_ top: TheTop, withCompletion comp: @escaping Handler) -> S<TheTop> {
_ = genericFn(top)
_ = genericFn(comp)
return S(top)
}
2 changes: 2 additions & 0 deletions test/SILGen/unmanaged_ownership.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

// RUN: %target-swift-emit-silgen -parse-stdlib -module-name Swift %s | %FileCheck %s

@_marker protocol _Copyable {}

class C {}

enum Optional<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// RUN: %target-swift-frontend -emit-sil -parse-stdlib -module-name Swift %s
// RUN: %target-swift-frontend -emit-sil -O -parse-stdlib -module-name Swift %s

@_marker protocol _Copyable {}
precedencegroup CastingPrecedence {}
precedencegroup AssignmentPrecedence {}

Expand Down
12 changes: 6 additions & 6 deletions test/SILOptimizer/opaque_values_Onone_stdlib.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend -parse-stdlib -module-name Swift -enable-sil-opaque-values -parse-as-library -emit-sil -Onone %s | %FileCheck %s
// RUN: %target-swift-frontend -enable-experimental-move-only -parse-stdlib -module-name Swift -enable-sil-opaque-values -parse-as-library -emit-sil -Onone %s | %FileCheck %s

// Like opaque_values_Onone.swift but for code that needs to be compiled with
// -parse-stdlib.
Expand All @@ -10,6 +10,7 @@ precedencegroup AssignmentPrecedence { assignment: true }
precedencegroup CastingPrecedence {}

public protocol _ObjectiveCBridgeable {}
@_marker public protocol _Copyable {}

public protocol _ExpressibleByBuiltinBooleanLiteral {
init(_builtinBooleanLiteral value: Builtin.Int1)
Expand All @@ -32,11 +33,10 @@ public func type<T, Metatype>(of value: T) -> Metatype
class X {}
func consume(_ x : __owned X) {}

// FIXME: disabled temporarily until rdar://104898230 is resolved
//func foo(@_noImplicitCopy _ x: __owned X) {
// consume(_copy(x))
// consume(x)
//}
func foo(@_noImplicitCopy _ x: __owned X) {
consume(_copy(x))
consume(x)
}

// CHECK-LABEL: sil [transparent] [_semantics "lifetimemanagement.copy"] @_copy : {{.*}} {
// CHECK: {{bb[0-9]+}}([[OUT_ADDR:%[^,]+]] : $*T, [[IN_ADDR:%[^,]+]] : $*T):
Expand Down
28 changes: 25 additions & 3 deletions test/Sema/copyable.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
// RUN: %target-typecheck-verify-swift -enable-experimental-move-only

protocol P: _Copyable {}
protocol P: _Copyable {} // expected-error {{'_Copyable' is unavailable}}
struct S: P {}

class C: _Copyable {}
typealias PleaseLetMeDoIt = _Copyable // expected-error {{'_Copyable' is unavailable}}
typealias WhatIfIQualify = Swift._Copyable // expected-error {{'_Copyable' is unavailable}}

@_moveOnly struct MOStruct: _Copyable {} // expected-error {{move-only struct 'MOStruct' cannot conform to '_Copyable'}}
class C: _Copyable {} // expected-error {{'_Copyable' is unavailable}}

@_moveOnly struct MOStruct: _Copyable {}
// expected-error@-1 {{move-only struct 'MOStruct' cannot conform to '_Copyable'}}
// expected-error@-2 {{'_Copyable' is unavailable}}


func whatever<T>(_ t: T) where T: _Copyable {} // expected-error {{'_Copyable' is unavailable}}
func vatever<T: _Copyable>(_ t: T) {} // expected-error {{'_Copyable' is unavailable}}
func buttever(_ t: any _Copyable) {} // expected-error {{'_Copyable' is unavailable}}
func zuttever(_ t: some _Copyable) {} // expected-error 2{{'_Copyable' is unavailable}}

enum RockNRoll<T: _Copyable> { // expected-error {{'_Copyable' is unavailable}}
case isNoisePollution(_Copyable) // expected-error {{'_Copyable' is unavailable}}
case isMusic(T)
}

enum namespace {
typealias _Copyable = Int

func _Copyable() -> _Copyable { return 0 }
}
14 changes: 14 additions & 0 deletions test/Sema/copyable_constraint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

// >> First verify that when building the stdlib, we do have the copyable constraint. Note the module-name!!
// RUN: %target-swift-frontend -enable-experimental-move-only -typecheck -verify -parse-stdlib -module-name Swift %s

// FIXME: Now demonstrate that plain -parse-stdlib, such as in some arbitrary test, doesn't get the Copyable constraint :(
// RUN: not %target-swift-frontend -enable-experimental-move-only -typecheck -verify -parse-stdlib %s

@_marker public protocol _Copyable {}

func nextTime<T>(_ t: T) {}

@_moveOnly struct MO {}

nextTime(MO()) // expected-error {{move-only type 'MO' cannot be used with generics yet}}
6 changes: 6 additions & 0 deletions test/type/pack_expansion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ func invalidPackRef(_: each Int) {}

// expected-error@+1 {{pack reference 'T' can only appear in pack expansion or generic requirement}}
func packRefOutsideExpansion<each T>(_: each T) {}

// coverage to ensure a 'repeat each' type is considered Copyable
func golden<Z>(_ z: Z) {}
func hour<each T>(_ t: repeat each T) {
_ = repeat golden(each t)
}