Skip to content

[moveOnly] Add an @_noImplicitCopy decl attribute and allow it to be only attached to local lets. #39927

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
5 changes: 5 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,8 @@ within Swift 5 code that has adopted concurrency, but non-`@MainActor`

See the forum post on [Concurrency in Swift 5 and 6](https://forums.swift.org/t/concurrency-in-swift-5-and-6/49337)
for more details.

## `@_noImplicitCopy`

Marks a var decl as a variable that must be copied explicitly using the builtin
function Builtin.copy.
7 changes: 7 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,13 @@ DECL_ATTR(_nonSendable, NonSendable,
APIStableToAdd | APIBreakingToRemove,
121)

SIMPLE_DECL_ATTR(_noImplicitCopy, NoImplicitCopy,
UserInaccessible |
ABIStableToAdd | ABIBreakingToRemove |
APIStableToAdd | APIBreakingToRemove |
OnVar,
122)

// If you're adding a new underscored attribute here, please document it in
// docs/ReferenceGuides/UnderscoredAttributes.md.

Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6029,5 +6029,12 @@ ERROR(wrap_invalid_attr_added_by_access_note, none,

#undef WHICH_ACCESS_NOTE

// Move Only diagnostics

ERROR(noimplicitcopy_attr_valid_only_on_local_let,
none, "'@_noImplicitCopy' attribute can only be applied to local lets", ())
ERROR(noimplicitcopy_attr_invalid_in_generic_context,
none, "'@_noImplicitCopy' attribute cannot be applied to entities in generic contexts", ())

#define UNDEFINE_DIAGNOSTIC_MACROS
#include "DefineDiagnosticMacros.h"
36 changes: 36 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "TypeChecker.h"
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/ClangModuleLoader.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/Effects.h"
#include "swift/AST/GenericEnvironment.h"
Expand Down Expand Up @@ -262,10 +263,45 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

void visitReasyncAttr(ReasyncAttr *attr);
void visitNonisolatedAttr(NonisolatedAttr *attr);

void visitNoImplicitCopyAttr(NoImplicitCopyAttr *attr);
};

} // end anonymous namespace

void AttributeChecker::visitNoImplicitCopyAttr(NoImplicitCopyAttr *attr) {
auto *dc = D->getDeclContext();
auto *vd = dyn_cast<VarDecl>(D);
if (!vd) {
auto error = diag::noimplicitcopy_attr_valid_only_on_local_let;
diagnoseAndRemoveAttr(attr, error);
return;
}

// If we have a 'var' instead of a 'let', bail. We only support on local lets.
if (!vd->isLet()) {
auto error = diag::noimplicitcopy_attr_valid_only_on_local_let;
diagnoseAndRemoveAttr(attr, error);
return;
}

if (vd->hasStorage()) {
// We do not support fields of nominal types now.
if (isa<NominalTypeDecl>(dc)) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you want !dc->isLocalContext() here. It should be able to subsume this check and the one below as well. (You'll likely want a separate check for static).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I'll do that in a subsequent PR.

auto error = diag::noimplicitcopy_attr_valid_only_on_local_let;
diagnoseAndRemoveAttr(attr, error);
return;
}
}

// We do not support static or global vars either yet.
if (dc->isModuleScopeContext() || (dc->isTypeContext() && vd->isStatic())) {
auto error = diag::noimplicitcopy_attr_valid_only_on_local_let;
diagnoseAndRemoveAttr(attr, error);
return;
}
}

void AttributeChecker::visitTransparentAttr(TransparentAttr *attr) {
DeclContext *dc = D->getDeclContext();
// Protocol declarations cannot be transparent.
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,7 @@ namespace {
UNINTERESTING_ATTR(ImplicitSelfCapture)
UNINTERESTING_ATTR(InheritActorContext)
UNINTERESTING_ATTR(Isolated)
UNINTERESTING_ATTR(NoImplicitCopy)
#undef UNINTERESTING_ATTR

void visitAvailableAttr(AvailableAttr *attr) {
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
/// describe what change you made. The content of this comment isn't important;
/// it just ensures a conflict if two people change the module format.
/// Don't worry about adhering to the 80-column limit for this line.
const uint16_t SWIFTMODULE_VERSION_MINOR = 636; // 'isolated' in param decls
const uint16_t SWIFTMODULE_VERSION_MINOR = 637; // @_noImplicitCopy

/// A standard hash seed used for all string hashes in a serialized module.
///
Expand Down
31 changes: 31 additions & 0 deletions test/Parse/noimplicitcopy_attr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
f// RUN: %target-typecheck-verify-swift -parse -parse-stdlib -disable-availability-checking -verify-syntax-tree

import Swift

class Klass {}

func argumentsAndReturns(@_noImplicitCopy _ x: Klass) -> Klass {
return x
}

func letDecls(@_noImplicitCopy _ x: Klass) -> () {
@_noImplicitCopy let y: Klass = x
print(y)
}

func varDecls(@_noImplicitCopy _ x: Klass, @_noImplicitCopy _ x2: Klass) -> () {
@_noImplicitCopy var y: Klass = x
y = x2
print(y)
}

func getKlass() -> Builtin.NativeObject {
let k = Klass()
let b = Builtin.unsafeCastToNativeObject(k)
return Builtin.move(b)
}

@_noImplicitCopy var g: Builtin.NativeObject = getKlass()
@_noImplicitCopy let g2: Builtin.NativeObject = getKlass()


141 changes: 141 additions & 0 deletions test/Sema/noimplicitcopy_attr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// RUN: %target-typecheck-verify-swift -parse-stdlib -disable-availability-checking -verify-syntax-tree

import Swift

class Klass {}

func argumentsAndReturns(@_noImplicitCopy _ x: Klass) -> Klass { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
return x
}
func letDecls(_ x: Klass) -> () {
@_noImplicitCopy let y: Klass = x
print(y)
}

func varDecls(_ x: Klass, _ x2: Klass) -> () {
@_noImplicitCopy var y: Klass = x // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
y = x2
print(y)
}

func getKlass() -> Builtin.NativeObject {
let k = Klass()
let b = Builtin.unsafeCastToNativeObject(k)
return Builtin.move(b)
}

@_noImplicitCopy var g: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
@_noImplicitCopy let g2: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
@_noImplicitCopy var g3: Builtin.NativeObject { getKlass() } // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}

struct MyStruct {
// Error if @_noImplicitCopy on struct fields. We do not have move only types and
// these are part of MyStruct.
//
// TODO: Make error specific for move only on struct/enum.
@_noImplicitCopy var x: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
@_noImplicitCopy let y: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}

@_noImplicitCopy var myMoveOnly: Builtin.NativeObject { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return getKlass()
}

func foo<T>(@_noImplicitCopy _ t: T) { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
}
}

struct MyGenericStruct<T> {
func foo(@_noImplicitCopy _ t: T) { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
}
}

protocol P {
@_noImplicitCopy var x: Builtin.NativeObject { get } // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
}

func foo<T>(@_noImplicitCopy _ t: T) { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
}

// Do not error on class fields. The noImplicitCopy field is separate from the
// underlying class itself so the fact the class is not move only does not
// suggest that the binding inside the class can be.
class MyClass {
@_noImplicitCopy var x: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
@_noImplicitCopy let y: Builtin.NativeObject = getKlass() // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}

@_noImplicitCopy var myMoveOnly: Builtin.NativeObject { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return getKlass()
}

func foo<T>(@_noImplicitCopy _ t: T) { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
}
}

class MyGenericClass<T> {
@_noImplicitCopy var x: T? = nil // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
@_noImplicitCopy let y: T? = nil // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}

@_noImplicitCopy var myMoveOnly: T? { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return nil
}

@_noImplicitCopy var myMoveOnly2: Builtin.NativeObject? { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return nil
}

func foo(@_noImplicitCopy _ t: T) { // expected-error {{@_noImplicitCopy may only be used on 'var' declarations}}
}
}

// We need to error on Enums since the case is part of the value and we do not
// support move only types.
enum MyEnum {
case none
case noImplicitCopyCase(Klass)

// We suport doing it on computed properties though.
@_noImplicitCopy var myMoveOnly: Builtin.NativeObject { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return getKlass()
}
}

// We need to error on Enums since the case is part of the value and we do not
// support move only types.
enum MyGenericEnum<T> {
case none
case noImplicitCopyCase(Klass)

// We suport doing it on computed properties though.
@_noImplicitCopy var myMoveOnly: Builtin.NativeObject { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return getKlass()
}

// We suport doing it on computed properties though.
@_noImplicitCopy var myMoveOnly2: T? { // expected-error {{'@_noImplicitCopy' attribute can only be applied to local lets}}
return nil
}
}

struct UnsafePointerWithOwner<T> {
var owner: AnyObject? = nil
var data: UnsafePointer<T>? = nil

func doNothing() {}
}

func useUnsafePointerWithOwner<T>(_ x: UnsafePointerWithOwner<T>) {
// We allow for this here (even without opaque values, since we check this
// at the SIL level in SILGen).
@_noImplicitCopy let y = x
y.doNothing()
let z = y
print(z)
}

func useGeneric<T>(_ x: T) {
// We allow for this here (even without opaque values, since we check this
// at the SIL level in SILGen).
@_noImplicitCopy let y = x
let z = y
print(z)
}