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

Default arguments cannot be used if they would define another generic parameter's type. #65035

Open
JessyCatterwaul opened this issue Apr 9, 2023 · 7 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself default arguments Feature: default arguments for value parameters generics Feature: generic declarations and types swift 5.9 type checker Area → compiler: Semantic analysis type inference Feature: type inference unexpected error Bug: Unexpected error

Comments

@JessyCatterwaul
Copy link

JessyCatterwaul commented Apr 9, 2023

Given

protocol Protocol {
  var property: Never { get }
}

This should be expressible as:

func ƒ<Instance: Protocol, Property>(
  instance: Instance,
  property: (Instance) -> Property = \.property // Cannot use default expression for inference of '(Instance) -> Property' because it is inferrable from parameters #0, #1
) {
  _ = property(instance)
}

…but that error occurs instead.


Workaround 1—Explicit Overloads:

func ƒ<Instance: Protocol, Property>(
  instance: Instance,
  property: (Instance) -> Property
) {
  _ = property(instance)
}

func ƒ<Instance: Protocol>(instance: Instance) {
  _ = instance.property
}

(Somewhat inequivalent) Workaround 2—Existential:

func ƒ<Instance: Protocol, Property>(
  instance: Instance,
  property: (any Protocol) -> Property = \.property
) {
  _ = property(instance)
}

Note: it's not necessary for a closure to be involved.

func ƒ<T>(
  _: T,
  _: T = () // Cannot use default expression for inference of 'T' because it is inferrable from parameters #0, #1
) { }
@JessyCatterwaul JessyCatterwaul added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Apr 9, 2023
@JessyCatterwaul JessyCatterwaul changed the title Default closure arguments cannot be used if they would define another parameter's type. Default arguments cannot be used if they would define another generic parameter's type. Apr 9, 2023
@AnthonyLatsis AnthonyLatsis added compiler The Swift compiler in itself type checker Area → compiler: Semantic analysis generics Feature: generic declarations and types default arguments Feature: default arguments for value parameters unexpected error Bug: Unexpected error type inference Feature: type inference swift 5.9 and removed triage needed This issue needs more specific labels labels Apr 10, 2023
@AnthonyLatsis
Copy link
Collaborator

I think this is intended behavior. Consider your last and most basic example:

func ƒ<T>(
  _: T,
  _: T = () // Cannot use default expression for inference of 'T' because it is inferrable from parameters #0, #1
) { }

Given the default argument will not participate in the inference of T (because explicit arguments are a priority type inference source and there is another non-defaulted parameter dependent on T), I assume you expect the default argument to be conditional, that is, defined only when its standalone type is convertible to whatever T gets inferred as via the first argument, whereas SE-0347 merely proposes that default arguments be a fallback type inference source.

cc @xedin

@tbkka
Copy link
Contributor

tbkka commented Apr 10, 2023

For the last case, the default expression makes the generic type on the second parameter entirely redundant. That's not true for the original example:

func ƒ<Instance: Protocol, Property>(
  instance: Instance,
  property: (Instance) -> Property = \.property
) {
  _ = property(instance)
}

The first parameter specifies the type Instance, but it does not specify the Property type, and therefore does not fully specify the function type (Instance) -> Property. The Property type can only be inferred from the type of the property itself. So in this case, I suspect the message is in fact incorrect.

@JessyCatterwaul
Copy link
Author

JessyCatterwaul commented Apr 10, 2023

I assume you expect the default argument to be conditional, that is, defined only when its standalone type is convertible to whatever T gets inferred as via the first argument

For the last case, the default expression makes the generic type on the second parameter entirely redundant.

No, if it compiled, it would replace these two overloads:

func ƒ<T>(_: T, _: T) { }
func ƒ(_: Void) { }

à la

func ƒ<T, U>(_: T = (),  _: U = ()) { }

@xedin
Copy link
Member

xedin commented Apr 10, 2023

This is intentional, these cases are outlined in the proposal, also note that \.property is type-checked in isolation, it cannot rely on any sort of directionality of argument inference.

@JessyCatterwaul
Copy link
Author

JessyCatterwaul commented Apr 10, 2023

This is intentional, these cases are outlined in the proposal

Given the partial implementation in the compiler for the below code, I don't believe in the "intentionality", but if it exists, the bug is that it needs to be better advertised so that nobody else wastes your time about it in the future. I don't see it.

\.property is type-checked in isolation, it cannot rely on any sort of directionality of argument inference.

This is partially broken, in that it can compile in this form, but cannot be used:

extension Protocol {
  func ƒ<Property>(
    property: (Self) -> Property = \Self.property // Will not compile without explicit `Self`
  ) {
    _ = property(self)
  }
}

struct Struct: Protocol {
  var property: Never { fatalError() }
}


Struct().ƒ() // Generic parameter 'Property' could not be inferred

@xedin
Copy link
Member

xedin commented Apr 10, 2023

I don't believe in the "intentionality", but if it exists, the bug is that it needs to be better advertised so that nobody else wastes your time about it in the future. I don't see it.

I'm not sure what you mean, can you elaborate on how in your opinion this advertisement should work?

property: (Self) -> Property = \Self.property

\Self.property and \.property behave differently. The former has a concrete type base - Self, the latter is going to attempt to infer the base type from value type of the subscript which is represented by a generic parameter Property so type-checking \.property in isolation cannot be done.

@AnthonyLatsis
Copy link
Collaborator

AnthonyLatsis commented Apr 11, 2023

Regardless of how confusing these rules may appear — I will probably fail to name all the nooks and crannies myself — I sympathize with Jessy in that we ought to have the book set the rules and clarify the behavior, which AFAIU is supposed to be the primary source of truth regarding language semantics. From what I gather the expected behavior is:

  • Default arguments are type-checked in the context of the corresponding function or subscript:

    func test<A, B>(a: A, b: B = String(describing: A.self)) {} // Silly, but yes
  • Default argument evaluation cannot depend on other arguments.

    func test(a: Int, b: Int = a) {} // No
  • A default argument cannot establish type relationships between type parameters.

    struct Context<A> {
      func test<B>(b: B = Array<A>()) {} // No
    }
  • Type inference prioritizes explicit arguments over default arguments. This means that a default argument cannot be used to infer a given type parameter if there is another non-defaulted value parameter whose type is also dependent on said type parameter.

    func test1<T>(_: T, _: Array<T> = Array<T>()) // Yes (default arg not used to infer 'T'
    
    func test2<T>(_: T, _: T = 0) {} // No
    func test3<T>(_: T = 0, _: T) {} // No

    But (@xedin should I report these?):

    func test4<T>(_: T = 0, _: T = 1) {} // No?
    
    struct Context<T> {
      func test5   (_: T, _: T = 0) {} // Yes?
      func test6<U>(_: U, _: U = 0) {} // Yes?
    }

There should be better ways to phrase some of these, of course.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself default arguments Feature: default arguments for value parameters generics Feature: generic declarations and types swift 5.9 type checker Area → compiler: Semantic analysis type inference Feature: type inference unexpected error Bug: Unexpected error
Projects
None yet
Development

No branches or pull requests

4 participants