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-11654] Optional wrapped property can't be passed to generic function #54065

Closed
swift-ci opened this issue Oct 23, 2019 · 17 comments
Closed
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself property wrappers Feature: property wrappers type checker Area → compiler: Semantic analysis

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-11654
Radar rdar://problem/56543018
Original Reporter mozeryansky (JIRA User)
Type Bug
Status Closed
Resolution Done
Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, PropertyWrappers, TypeChecker
Assignee @theblixguy
Priority Medium

md5: af43c2e4854ca43112ad7636efacc0ca

is duplicated by:

  • SR-13381 Cannot pass a property wrapper value to a generic function as an optional
  • SR-12233 Type inference not working with property wrapper?

relates to:

Issue Description:

When I pass a property which is declared as `@Published` to a generic function, I get an error referring to the published value. It's fine anywhere else where I use the variable, just not when passing into the generic function.

Error:

error: cannot convert value 'user' of type 'User?' to expected type 'Published<User?>.Publisher?', use wrapper instead

Here's the playground code:

struct User {}

class Test {
    @Published var user: User?
}

func normalFunc(_ argument: User?) -> User? {
    return argument
}

func genericFunc<T>(_ argument: T?) -> T? {
    return argument
}

let test = Test()
normalFunc(test.user) // Ok
genericFunc(test.user) // Error

The best I have been able to do is wrap the value in parenthesis, like so:

genericFunc((test.user)) // Ok!

Stackoverflow Question

@belkadan
Copy link
Contributor

Very weird. Thanks for the reduced test case!

@swift-ci create

@theblixguy
Copy link
Collaborator

Definitely weird! Another workaround is to define a method or a computed property on Test that returns User?.

@theblixguy
Copy link
Collaborator

Test case that doesn't include Combine and throws the same error:

struct User {}

@propertyWrapper struct Wrapper<T> {
  var wrappedValue: T
}

class Test {
  @Wrapper var user: User?
}

func genericFunc<T>(_ argument: T?) -> T? {
  return argument
}

let test = Test()
genericFunc(test.user) // fail

Looking into this a bit more, we're adding an additional constraint:

Wrapper<User?> subtype $T1 [[locator@0x7fa4470076b8 [Call@/Users/suyashsrijan/Desktop/test.swift:15:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> optional payload]]];

which is causing the solver to reject the first choice:

(attempting disjunction choice User? bind $T1? [deep equality] [[locator@0x7fa447007658 [Call@/Users/suyashsrijan/Desktop/test.swift:15:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0]]];
    (failed constraint Wrapper<User?> subtype $T1 [[locator@0x7fa4470076b8 [Call@/Users/suyashsrijan/Desktop/test.swift:15:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> optional payload]]];)
  )

So for some reason, we're inferring the type of the generic parameter T to be Wrapper<User?> and failing. This isn't the case when the parameter type is non-optional for example, or when we force-unwrap test.user.

Seems like it has something to do with potential bindings we are generating, because I can see that in the working case, we have:

$T1 potentially_incomplete involves_type_vars bindings={} @ locator@0x7fa447007250 [DeclRef@/Users/suyashsrijan/Desktop/test.swift:15:1 -> generic parameter 'T']

but in this case, we have:

$T1 potentially_incomplete involves_type_vars bindings={(supertypes of) Wrapper<User?>} @ locator@0x7fa447007250 [DeclRef@/Users/suyashsrijan/Desktop/test.swift:15:1 -> generic parameter 'T']

cc @xedin who might know what's going on


Also, there's another bug - the diagnostic we're suggesting is not correct i.e. we're saying to use the backing variable (_user) but it's always private and it's not accessible outside the type in which the property wrapper is used. So, the fix-it will not work. We should check the declaration context before offering this specialised diagnostic and fall back to the normal one in case the backing var is not accessible.

@swift-ci
Copy link
Collaborator Author

Comment by Michael Ozeryansky (JIRA)

@theblixguy, very nice explanation. Why do you think wrapping the variable in parenthesis worked fine? i.e. `genericFunc((test.user))`. I would have assumed the compiler would remove that since, I believe, it's a parenthesized-expression and shouldn't change its type.

@xedin
Copy link
Member

xedin commented Oct 25, 2019

The reason why it works with parenthesis is related to how constraint solver handles optionals - it would just go through a different path when for this conversion between argument is going to be `ParenType(User?)` instead of just `User?`. @theblixguy After taking a quick look at this - it seems like the reason why it's happening is related to how `simplifyRestrictedConstraintImpl` handles `ValueToOptional` vs. `OptionalToOptional`. It looks like both need to be taught how to handle existence of property wrappers.

@theblixguy
Copy link
Collaborator

I can take a stab at fixing it, although I’m not sure how to go about fixing it correctly. Do we need to call matchTypes with the property wrapper type?

@xedin
Copy link
Member

xedin commented Oct 25, 2019

@theblixguy I think first step would be to figure out how it happens that solver ends up adding `Wrapper<User?> subtype $T1`, that should give you an idea how to go about it. To make it somehow easier we know that wrapping type in parens doesn't result in that constraint.

@theblixguy
Copy link
Collaborator

yeah, I am trying to figure that out. I actually put a check in addConstraintImpl to detect when a subtype constraint is added where the locator's last element is OptionalPayload but it doesn't seem to get hit. Will investigate more...

@xedin
Copy link
Member

xedin commented Oct 25, 2019

Sounds good! It might be easier to put a break point to each place where `OptionalPayload` is added to the locator builder, there should be just a couple of places in CSSimplify.cpp

@theblixguy
Copy link
Collaborator

Sure, the only place it seems to be used is simplifyRestrictedConstraintImpl when kind is ConversionRestrictionKind::ValueToOptional. We do locator.withPathElement(ConstraintLocator::OptionalPayload) when calling matchTypes.

@xedin
Copy link
Member

xedin commented Oct 26, 2019

That might be too far. I'd suggest you start in `matchCallArguments`, here - https://github.com/apple/swift/blob/master/lib/Sema/CSSimplify.cpp#L1098 that's the place where argument type is going to be matched to parameter - I think a that point we'd still have `User?` vs. `T?` but what happens next and how we get to `Wrapper<User?>` I have no idea.

@theblixguy
Copy link
Collaborator

@xedin Okay, matchCallArguments didn't really help because most of the types still have a type variable somewhere. But, I think I know what's going on - matchTypes is calling into repairFailures, which attempts to create three different "property wrapper" fixes, of which the UsePropertyWrapper one succeeds. If I don't create the fix, this error goes away. The output of -debug-constraints then shows:

(attempting disjunction choice User? bind $T1? [deep equality] [[locator@0x7fcbc5039588 [Call@/Users/suyashsrijan/Desktop/test.swift:17:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0]]];
    (found solution 0 0 0 0 0 0 0 0 0 0 0 0)
 )

Previously, it failed at that disjunction choice due to failed constraint Wrapper<User?> subtype $T1. This is because attempting the fix calls matchTypes with subtype constraint which fails because the backing property type is not a subtype of T1?.

@theblixguy
Copy link
Collaborator

You previously said "figure out how it happens that solver ends up adding `Wrapper<User?> subtype $T1`, that should give you an idea how to go about it.", so I think we now know how this is being added. Now, the next step I guess is to figure out how do we fix this.

@xedin
Copy link
Member

xedin commented Feb 28, 2020

@theblixguy That's something I suspected, we shouldn't really be generating any constraints in `repairFailures` but we have no choice at the moment sometimes... I think we should try to move this check up before `fixPropertyWrapperFailure` or move `fixPropertyWrapperFailure` after that check.

@xedin
Copy link
Member

xedin commented Feb 28, 2020

Also verify that neither side is a type variable before trying to fix property wrappers.

@theblixguy
Copy link
Collaborator

Fixed by #30129. Thank you @xedin for your help!

mozeryansky (JIRA User) could you please verify using the next available snapshot and close this?

@swift-ci
Copy link
Collaborator Author

swift-ci commented Mar 2, 2020

Comment by Michael Ozeryansky (JIRA)

Thank you!

@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
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself property wrappers Feature: property wrappers type checker Area → compiler: Semantic analysis
Projects
None yet
Development

No branches or pull requests

4 participants