Skip to content
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

[SR-142] mutating function in protocol extension erroneously requires var declaration of class variables #42764

Closed
groue opened this issue Dec 8, 2015 · 9 comments

Comments

@groue
Copy link

@groue groue commented Dec 8, 2015

Previous ID SR-142
Radar None
Original Reporter @groue
Type Bug
Status Resolved
Resolution Done
Environment

Version 7.1.1 (7B1005)

Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug, LanguageFeatureRequest
Assignee None
Priority Medium

md5: c88df0cab57ceac17b5038fd1dff4b54

is duplicated by:

  • SR-2464 Reference types can't call mutating functions declared in a protocol extension.
  • SR-2693 Cannot assign to property of an instance of a class declared in a protocol extension
  • SR-4229 Cannot use mutating member on immutable value: 'self' is immutable
  • SR-7636 Reference type has struct mutability rules applied to it
  • SR-8805 Cannot set protocol extension property on adopting reference type which is immutable
  • SR-4227 Can't call mutating protocol extension method in class init
  • SR-6120 Bug with Class and Subtype existentials, when protocol doesn't have class requirement
  • SR-10775 Mutating method in protocol extension always requires variable to be var

relates to:

  • SR-2220 A protocol's mutating keyword is enforced on AnyObject when using generics
  • SR-4541 Class-Only protocol inheriting from normal protocol require to be var to allow assign to their properties.

Issue Description:

Hello,

The following code won't compile, when it should:

protocol P { }

extension P {
    mutating func m() { }
}

class C : P { }

let c = C()
// error: cannot use mutating member on immutable value: 'c' is a 'let' constant
c.m()

This error should not exist: the type of variable c is C. Mutating methods can not be added to C, because of the 'mutating' isn't valid on methods in classes or class-bound protocols error. Hence the mutating qualifier of P.m should not apply to c, and c should be able to be declared as a let variable.

@groue
Copy link
Author

@groue groue commented Dec 9, 2015

The issue is also there in Apple Swift version 2.2-dev (LLVM 46be9ff861, Clang 4deb154edc, Swift 778f829)

@groue
Copy link
Author

@groue groue commented Dec 9, 2015

See groue/GRDB.swift#12 (comment) for a practical problem raised by this issue: some APIs have to expose a base class instead of protocol as soon as they want to let adopting user types "override" default implementations. (updated to invalidate)

@groue
Copy link
Author

@groue groue commented Dec 9, 2015

For what it's worth, there is a (verbose) workaround, which is to redeclare the mutating method in the adopting class:

protocol P { }

extension P {
    mutating func m() { }
}

class C : P {
    // redeclare m() without the mutating qualifier
    func m() {
        // call protocol's default implementation
        var p: P = self // mandatory temp since (self as P).m() won't compile
        p.m()
    }
}

// OK, no error
let c = C()
c.m()

@gregomni
Copy link
Collaborator

@gregomni gregomni commented Dec 10, 2015

Looked into this briefly, and it's a lot harder than just changing the error checking, since m() has an implicit `inout P` first argument. Probably the solution is an automatic version of Gwendal's workaround: declaring a hidden `var p: P = c` and calling the method on p.

@groue
Copy link
Author

@groue groue commented Dec 10, 2015

Thanks for the investigation, Greg!

@lilyball
Copy link
Mannequin

@lilyball lilyball mannequin commented Dec 12, 2015

Automatically declaring a hidden var like that would cause surprising behavior when the mutating method actually assigns to self:

protocol P {
    init(name: String)
    mutating func foo()
}

extension P {
    mutating func foo() {
        self = Self.init(name: "foo")
    }
}

class C : P {
    var name: String
    required init(name: String) {
        self.name = name
    }
}

let c = C(name: "a")
c.foo() // assuming automatic creation of hidden variable
print(c.name)

With the automatic hidden variable, the c after c.foo() must still be the c from before the call (because it is let), but that means the mutating method didn't actually do anything.

@groue
Copy link
Author

@groue groue commented Dec 12, 2015

Thanks Kevin. I overlooked self assignment in default implementation, which requires the implementation described by Greg.

So it isn't a bug, but actually a feature, and anyone bothered by this behavior should discuss it on the swift-evolution mailing list, isn't it?

@swift-ci
Copy link
Collaborator

@swift-ci swift-ci commented Dec 14, 2015

Comment by Josh Avant (JIRA)

For posterity, here's a link to additional conversation about this on swift-evolution:
https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001575.html

@slavapestov
Copy link
Member

@slavapestov slavapestov commented Jun 3, 2017

As Kevin explained this is not a bug, because the method implementation may assign to self.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants