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-3520] Generic function taking closure with inout parameter can result in a variety of compiler errors or EXC_BAD_ACCESS #46108

Closed
swift-ci opened this issue Jan 1, 2017 · 15 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Jan 1, 2017

Previous ID SR-3520
Radar None
Original Reporter goldsdad (JIRA User)
Type Bug
Status Resolved
Resolution Done

Attachment: Download

Environment

OS X 10.11.6
Xcode 8.2.1 (8C1002)

Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug
Assignee goldsdad (JIRA)
Priority Medium

md5: d07e7fa883571bf6529bbcdab6d6e651

is duplicated by:

  • SR-3632 Swift is unable to properly infer the type of closures with inout parameters without specific type information.

relates to:

  • SR-1976 Closure signature in Swift 3 required for inout params
  • SR-3479 Segmentation fault and other error for closure with inout parameter
  • SR-5628 inout argument of closure passed to generic function inferred as immutable

Issue Description:

There are several related bug reports, but the following example may possibly help toward a fix. The lines which provoke errors all have appended comments. Lines without comments are free of problems.

func with<T>(_ item: T, _ update: (inout T) -> Void) -> T {
    var this = item
    update(&this)
    return this
}


let i = Int()

_ = with(i) { $0 += 3 }                                   // Compiler error: Ambiguous use of operator '+='
_ = with(i) { $0 += 3; true }                             // Compiler error: Left side of mutating operator isn't mutable: '$0' is immutable
_ = with(i) { true; $0 += 3 }                             // Compiler error: Left side of mutating operator isn't mutable: '$0' is immutable
_ = with(i) { $0 = Int() }
_ = with(i) { $0 = Int(); true }                          // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(i) { true; $0 = Int() }                          // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(i) { (a: inout Int) in a += 3 }
_ = with(i) { (a: inout Int) in a += 3; true }
_ = with(i) { (a: inout Int) in true; a += 3 }


let s = String()

_ = with(s) { $0.append("Swift") }                        // Compiler error: Type of expression is ambiguous without more context
_ = with(s) { $0.append("Swift"); true }                  // Compiler error: Cannot use mutating member on immutable value: '$0' is immutable
_ = with(s) { true; $0.append("Swift") }                  // Compiler error: Cannot use mutating member on immutable value: '$0' is immutable
_ = with(s) { $0 = String() }
_ = with(s) { $0 = String(); true }                       // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(s) { true; $0 = String() }                       // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(s) { (a: inout String) in a.append("Swift") }
_ = with(s) { (a: inout String) in a.append("Swift"); true }
_ = with(s) { (a: inout String) in true; a.append("Swift") }


let a = [Int]()

_ = with(a) { $0.append(3) }                              // Compiler error: Type of expression is ambiguous without more context
_ = with(a) { $0.append(3); true }                        // Compiler error: Cannot use mutating member on immutable value: '$0' is immutable
_ = with(a) { true; $0.append(3) }                        // Compiler error: Cannot use mutating member on immutable value: '$0' is immutable
_ = with(a) { $0 = [Int]() }
_ = with(a) { $0 = [Int](); true }                        // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(a) { true; $0 = [Int]() }                        // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(a) { (a: inout [Int]) in a.append(3) }
_ = with(a) { (a: inout [Int]) in a.append(3); true }
_ = with(a) { (a: inout [Int]) in true; a.append(3) }


let sn = NSMutableString()

_ = with(sn) { $0.append("Swift") }                       // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(sn) { $0.append("Swift"); true }                 // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(sn) { true; $0.append("Swift") }                 // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(sn) { $0 = NSMutableString() }
_ = with(sn) { $0 = NSMutableString(); true }             // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(sn) { true; $0 = NSMutableString() }             // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(sn) { (a: inout NSMutableString) in a.append("Swift") }
_ = with(sn) { (a: inout NSMutableString) in a.append("Swift"); true }
_ = with(sn) { (a: inout NSMutableString) in true; a.append("Swift") }


let an = NSMutableArray()

_ = with(an) { $0.add(3) }                                // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(an) { $0.add(3); true }                          // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(an) { true; $0.add(3) }                          // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
_ = with(an) { $0 = NSMutableArray() }
_ = with(an) { $0 = NSMutableArray(); true }              // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(an) { true; $0 = NSMutableArray() }              // Compiler error: Cannot assign to value: '$0' is immutable
_ = with(an) { (a: inout NSMutableArray) in a.add(3) }
_ = with(an) { (a: inout NSMutableArray) in a.add(3); true }
_ = with(an) { (a: inout NSMutableArray) in true; a.add(3) }


var vi = Int()

vi = with(vi) { $0 += 3 }                                 // Compiler error: parameters may not have the 'var' specifier
vi = with(vi) { $0 = Int(0) }


var vs = String()

vs = with(vs) { $0.append("Swift") }                      // Compiler error: parameters may not have the 'var' specifier
vs = with(vs) { $0 = String() }


var va = [Int]()

va = with(va) { $0.append(3) }                            // Compiler error: parameters may not have the 'var' specifier
va = with(va) { $0 = [Int]() }


var vsn = NSMutableString()

vsn = with(vsn) { $0.append("Swift") }                    // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
vsn = with(vsn) { $0 = NSMutableString() }


var van = NSMutableArray()

van = with(van) { $0.add(3) }                             // EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
van = with(van) { $0 = NSMutableArray() }
@belkadan
Copy link
Contributor

belkadan commented Jan 4, 2017

I think we fixed the EXC_BAD_ACCESS on master. The various errors are still murky due to the compiler being reluctant to infer inout on a closure parameter.

@jtbandes
Copy link
Collaborator

jtbandes commented Jan 4, 2017

A handful of discussion about this from yesterday: https://twitter.com/slava_pestov/status/816196484244783104

@xedin
Copy link
Member

xedin commented Jan 4, 2017

@jtbandes In this example:

func f<T>(_ g: (inout T) -> Int){}
f{$0=1}

The problem is related to the way FailureDiagnose::visitApplyExpr is trying to diagnose call to f with argument { $0 = 1 }, here is the print out of the constraint debug

  (overload set choice binding $T0 := ((inout $T1) -> Int) -> ())
  (overload set choice binding $T4 := $T5)
(increasing score due to function conversion)
---Initial constraints for the given expression---
(call_expr type='()' location=<REPL Input>:1:1 range=[<REPL Input>:1:1 - line:1:7]  arg_labels=_:
  (declref_expr type='((inout $T1) -> Int) -> ()' location=<REPL Input>:1:1 range=[<REPL Input>:1:1 - line:1:1] decl=REPL.(file).f@<REPL Input>:1:6 function_ref=single specialized=no)
  (paren_expr type='(($T3) -> $T2)' location=<REPL Input>:1:2 range=[<REPL Input>:1:2 - line:1:7] trailing-closure
    (closure_expr type='($T3) -> $T2' location=<REPL Input>:1:2 range=[<REPL Input>:1:2 - line:1:7] discriminator=0 single-expression
      (parameter_list
        (parameter "$0" type='$T3' interface type='$T3'))
      (assign_expr
        (declref_expr type='$T4' location=<REPL Input>:1:3 range=[<REPL Input>:1:3 - line:1:3] decl=REPL.(file).top-level code.explicit closure discriminator=0.$0@<REPL Input>:1:2 function_ref=unapplied specialized=no)
        (integer_literal_expr type='$T6' location=<REPL Input>:1:6 range=[<REPL Input>:1:6 - line:1:6] value=1)))))
Score: 0 0 0 0 1 0 0 0 0 0
Type Variables:
  #&#8203;0 = $T0 [lvalue allowed] as ((inout $T1) -> Int) -> ()
  #&#8203;1 = $T1 [must be materializable]
  #&#8203;2 = $T2
  #&#8203;3 = $T3
  #&#8203;4 = $T4 [lvalue allowed] as @lvalue $T7
  #&#8203;5 = $T5 [lvalue allowed] equivalent to $T4
  #&#8203;6 = $T6
  #&#8203;7 = $T7 [must be materializable]

Active Constraints:
  $T3 bind param $T5 [[locator@0x7fc4d40204e0 [DeclRef@<REPL Input>:1:3]]];

Inactive Constraints:
  $T6 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
  $T6 conv $T7 [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
  () conv $T2 [[locator@0x7fc4d40203f0 [Closure@<REPL Input>:1:2 -> closure result]]];
  (inout $T1) subtype ($T3) [[locator@0x7fc4d4020810 [Call@<REPL Input>:1:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> function argument]]];
  $T2 subtype Int [[locator@0x7fc4d4020890 [Call@<REPL Input>:1:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> function result]]];
Resolved overloads:
  selected overload set choice $0: $T4 == $T5
  selected overload set choice f: $T0 == ((inout $T1) -> Int) -> ()


Opened types:
  locator@0x7fc4d4020200 [DeclRef@<REPL Input>:1:1] opens τ_0_0 -> $T1
---Constraint graph---
  $T0:
    Adjacencies: $T1 (fixed)

  $T1:
    Adjacencies: $T0 (fixed)
    Equivalence class: $T7

  $T2:
    Constraints:
      () conv $T2 [[locator@0x7fc4d40203f0 [Closure@<REPL Input>:1:2 -> closure result]]];
      $T2 subtype Int [[locator@0x7fc4d4020890 [Call@<REPL Input>:1:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> function result]]];

  $T3:
    Adjacencies: $T7 (fixed)

  $T4:
    Adjacencies: $T7 (fixed)
    Equivalence class: $T5

  $T5:

  $T6:
    Constraints:
      $T6 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
      $T6 conv $T7 [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T7

  $T7:
    Constraints:
      $T6 conv $T7 [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T3 (fixed) $T4 (fixed) $T6

---Connected components---
  0: $T0 $T1 $T3 $T4 $T5 $T6 $T7
  1: $T2
(solving component #&#8203;0
  ($T6 literal=3 involves_type_vars bindings=(subtypes of) (default from ExpressibleByIntegerLiteral) Int)
  Active bindings: $T6 := Int
  (trying $T6 := Int
    ($T1 bindings=(supertypes of) Int)
    Active bindings: $T1 := Int
    (trying $T1 := Int
      (found solution 0 0 0 0 1 0 0 0 0 0)
    )
  )
finished component #&#8203;0)
(solving component #&#8203;1
  ($T2 bindings=(supertypes of) () (subtypes of) Int)
  Active bindings: $T2 := () $T2 := Int
  (trying $T2 := ()
  )
  (trying $T2 := Int
  )
failed component #&#8203;1)
---Constraint graph---
  $T0:
    Adjacencies: $T1 (fixed)

  $T1:
    Adjacencies: $T0 (fixed)
    Equivalence class: $T7

  $T2:
    Constraints:
      () conv $T2 [[locator@0x7fc4d40203f0 [Closure@<REPL Input>:1:2 -> closure result]]];
      $T2 subtype Int [[locator@0x7fc4d4020890 [Call@<REPL Input>:1:1 -> apply argument -> comparing call argument #&#8203;0 to parameter #&#8203;0 -> function result]]];

  $T3:
    Adjacencies: $T7 (fixed)

  $T4:
    Adjacencies: $T7 (fixed)
    Equivalence class: $T5

  $T5:

  $T6:
    Constraints:
      $T6 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
      $T6 conv $T7 [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T7

  $T7:
    Constraints:
      $T6 conv $T7 [[locator@0x7fc4d40205f8 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T3 (fixed) $T4 (fixed) $T6

---Connected components---
  0: $T0 $T1 $T3 $T4 $T5 $T6 $T7
  1: $T2
(solving component #&#8203;0
  ($T6 literal=3 involves_type_vars bindings=(subtypes of) (default from ExpressibleByIntegerLiteral) Int)
  Active bindings: $T6 := Int
  (trying $T6 := Int
    ($T1 bindings=(supertypes of) Int)
    Active bindings: $T1 := Int
    (trying $T1 := Int
      (found solution 0 0 0 0 1 0 0 0 0 0)
    )
  )
finished component #&#8203;0)
(solving component #&#8203;1
  ($T2 bindings=(supertypes of) () (subtypes of) Int)
  Active bindings: $T2 := () $T2 := Int
  (trying $T2 := ()
  )
  (trying $T2 := Int
  )
failed component #&#8203;1)

^ At this point we know there are no possible solutions in this constraint system, so we go into salvage mode which creates FailureDiagnosis and via diagnoseFailureForExpr

FailureDiagnosis starts at visitApplyExpr, because CallExpr is the primary in the AST, and first tries to type-check function call without arguments, that is done to figure out
what is the possible set of callees (argument types) involved in this call...

  (overload set choice binding $T0 := ((inout $T1) -> Int) -> ())
---Initial constraints for the given expression---
(declref_expr type='((inout $T1) -> Int) -> ()' location=<REPL Input>:1:1 range=[<REPL Input>:1:1 - line:1:1] decl=REPL.(file).f@<REPL Input>:1:6 function_ref=single specialized=no)
Score: 0 0 0 0 0 0 0 0 0 0
Type Variables:
  #&#8203;0 = $T0 [lvalue allowed] as ((inout $T1) -> Int) -> ()
  #&#8203;1 = $T1 [must be materializable]

Active Constraints:

Inactive Constraints:
Resolved overloads:
  selected overload set choice f: $T0 == ((inout $T1) -> Int) -> ()


Opened types:
  locator@0x7fc4d301be00 [DeclRef@<REPL Input>:1:1] opens τ_0_0 -> $T1
(found solution 0 0 0 0 0 0 0 0 0 0)
---Solution---
Fixed score: 0 0 0 0 0 0 0 0 0 0
Type variables:
  $T1 as <<unresolvedtype>>
  $T0 as ((inout <<unresolvedtype>>) -> Int) -> ()

Overload choices:
  locator@0x7fc4d301be00 [DeclRef@<REPL Input>:1:1] with REPL.(file).f@<REPL Input>:1:6 as f: ((inout $T1) -> Int) -> ()


Constraint restrictions:

Disjunction choices:

Opened types:
  locator@0x7fc4d301be00 [DeclRef@<REPL Input>:1:1] opens τ_0_0 -> $T1
---Type-checked expression---
(declref_expr type='((inout <<unresolvedtype>>) -> Int) -> ()' location=<REPL Input>:1:1 range=[<REPL Input>:1:1 - line:1:1] decl=REPL.(file).f@<REPL Input>:1:6 [with <<unresolvedtype>>] function_ref=single specialized=no)

This attempt didn't give us anything expect that argument signature is now ((inout <<unresolvedtype>>) -> Int) -> () because type-check of the function allowed unresolved type variables.

Now we continue trying to type-check argument independently from function but using ((inout <<unresolvedtype>>) -> Int) -> () as contextual type (which is part of the problem).

  (overload set choice binding $T2 := $T3)
(increasing score due to function conversion)
---Initial constraints for the given expression---
(paren_expr type='(($T1) -> $T0)' location=<REPL Input>:1:2 range=[<REPL Input>:1:2 - line:1:7] trailing-closure
  (closure_expr type='($T1) -> $T0' location=<REPL Input>:1:2 range=[<REPL Input>:1:2 - line:1:7] discriminator=0 single-expression
    (parameter_list
      (parameter "$0" type='$T1' interface type='$T1'))
    (assign_expr
      (declref_expr type='$T2' location=<REPL Input>:1:3 range=[<REPL Input>:1:3 - line:1:3] decl=REPL.(file).top-level code.explicit closure discriminator=0.$0@<REPL Input>:1:2 function_ref=unapplied specialized=no)
      (integer_literal_expr type='$T4' location=<REPL Input>:1:6 range=[<REPL Input>:1:6 - line:1:6] value=1))))
Score: 0 0 0 0 1 0 0 0 0 0
Contextual Type: ((inout <<unresolvedtype>>) -> Int)
Type Variables:
  #&#8203;0 = $T0
  #&#8203;1 = $T1
  #&#8203;2 = $T2 [lvalue allowed] as @lvalue $T5
  #&#8203;3 = $T3 [lvalue allowed] equivalent to $T2
  #&#8203;4 = $T4
  #&#8203;5 = $T5 [must be materializable]
  #&#8203;6 = $T6

Active Constraints:
  $T1 bind param $T3 [[locator@0x7fc4d301bef0 [DeclRef@<REPL Input>:1:3]]];

Inactive Constraints:
  $T4 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
  $T4 conv $T5 [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
  () conv $T0 [[locator@0x7fc4d301be00 [Closure@<REPL Input>:1:2 -> closure result]]];
  (inout $T6) subtype ($T1) [[locator@0x7fc4d301c2b0 [Paren@<REPL Input>:1:2 -> function argument]]];
  $T0 subtype Int [[locator@0x7fc4d301c320 [Paren@<REPL Input>:1:2 -> function result]]];
Resolved overloads:
  selected overload set choice $0: $T2 == $T3

---Constraint graph---
  $T0:
    Constraints:
      () conv $T0 [[locator@0x7fc4d301be00 [Closure@<REPL Input>:1:2 -> closure result]]];
      $T0 subtype Int [[locator@0x7fc4d301c320 [Paren@<REPL Input>:1:2 -> function result]]];

  $T1:
    Adjacencies: $T5 (fixed)

  $T2:
    Adjacencies: $T5 (fixed)
    Equivalence class: $T3

  $T3:

  $T4:
    Constraints:
      $T4 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
      $T4 conv $T5 [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T5

  $T5:
    Constraints:
      $T4 conv $T5 [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T1 (fixed) $T2 (fixed) $T4
    Equivalence class: $T6

  $T6:

---Connected components---
  0: $T0
  1: $T1 $T2 $T3 $T4 $T5 $T6
(solving component #&#8203;0
  ($T0 bindings=(supertypes of) () (subtypes of) Int)
  Active bindings: $T0 := () $T0 := Int
  (trying $T0 := ()
  )
  (trying $T0 := Int
  )
failed component #&#8203;0)
---Constraint graph---
  $T0:
    Constraints:
      () conv $T0 [[locator@0x7fc4d301be00 [Closure@<REPL Input>:1:2 -> closure result]]];
      $T0 subtype Int [[locator@0x7fc4d301c320 [Paren@<REPL Input>:1:2 -> function result]]];

  $T1:
    Adjacencies: $T5 (fixed)

  $T2:
    Adjacencies: $T5 (fixed)
    Equivalence class: $T3

  $T3:

  $T4:
    Constraints:
      $T4 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
      $T4 conv $T5 [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T5

  $T5:
    Constraints:
      $T4 conv $T5 [[locator@0x7fc4d301c008 [IntegerLiteral@<REPL Input>:1:6]]];
    Adjacencies: $T1 (fixed) $T2 (fixed) $T4
    Equivalence class: $T6

  $T6:

---Connected components---
  0: $T0
  1: $T1 $T2 $T3 $T4 $T5 $T6
(solving component #&#8203;0
  ($T0 bindings=(supertypes of) () (subtypes of) Int)
  Active bindings: $T0 := () $T0 := Int
  (trying $T0 := ()
  )
  (trying $T0 := Int
  )
failed component #&#8203;0)
  (overload set choice binding $T1 := $T2)
(increasing score due to function conversion)

At this point argument type-check fails and it starts trying to diagnose argument problem itself (nested into other diagnostic), which means creating separate `FailureDiagnosis` instance and walking the tree
starting from ParenExpr, `FailureDiagnosis::visitIdentityExpr` is called which tries to set it's own contextual type and type-check it's sub-expression, in this case - ClosureExpr.

---Initial constraints for the given expression---
(closure_expr type='($T0) -> Int' location=<REPL Input>:1:2 range=[<REPL Input>:1:2 - line:1:7] discriminator=0 single-expression
  (parameter_list
    (parameter "$0" type='$T0' interface type='$T0'))
  (assign_expr
    (declref_expr type='$T1' location=<REPL Input>:1:3 range=[<REPL Input>:1:3 - line:1:3] decl=REPL.(file).top-level code.explicit closure discriminator=0.$0@<REPL Input>:1:2 function_ref=unapplied specialized=no)
    (integer_literal_expr type='$T3' location=<REPL Input>:1:6 range=[<REPL Input>:1:6 - line:1:6] value=1)))
Score: 0 0 0 0 1 0 0 0 0 0
Contextual Type: (inout <<unresolvedtype>>) -> Int
Type Variables:
  #&#8203;0 = $T0
  #&#8203;1 = $T1 [lvalue allowed] as @lvalue $T4
  #&#8203;2 = $T2 [lvalue allowed] equivalent to $T1
  #&#8203;3 = $T3
  #&#8203;4 = $T4 [must be materializable]

Active Constraints:
  $T0 bind param $T2 [[locator@0x7fc4d2818698 [DeclRef@<REPL Input>:1:3]]];

Inactive Constraints:
  $T3 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7fc4d28187b0 [IntegerLiteral@<REPL Input>:1:6]]];
  $T3 conv $T4 [[locator@0x7fc4d28187b0 [IntegerLiteral@<REPL Input>:1:6]]];
  (inout <<unresolvedtype>>) subtype ($T0) [[locator@0x7fc4d2818980 [Closure@<REPL Input>:1:2 -> function argument]]];
Resolved overloads:
  selected overload set choice $0: $T1 == $T2


Failed constraint:
  () conv Int [[locator@0x7fc4d2818908 [Closure@<REPL Input>:1:2 -> closure result]]];
  (overload set choice binding $T0 := @lvalue <<unresolvedtype>>)
l-value expression does not have l-value access kind set

At this point we see that inout <<unresolvedtype>> is supposed to be subtype of T0 (which is the parameters of the closure but the catch is that T0 is supposed to be bound to T2 and because of AssignExpr inside be the same as T1 (
@lvalue T4), such clashes with logic in solver which expects that bind param is @lvalue already, see CSSolver.cpp:1085 and ConstraintSystem.cpp:787

@xedin
Copy link
Member

xedin commented Jan 4, 2017

Looking into this further it looks like the cause of assert is related to the fact that diagnostics can't figure out () vs. Int closure return, and because no errors are diagnosed everything falls through to the verifier and fails there.

@jtbandes
Copy link
Collaborator

jtbandes commented Jan 4, 2017

Do you know about how member lookup works during constraint solving?

I tried fixing this original issue by passing TVO_CanBindToLValue when creating a type var for the implicit closure param, which successfully allows them to be mutable in multi-statement closures. But this has caused problems with single-expression closures in generic contexts:

struct X2 {
  func g() -> Float { return 0 }  
}
func f0<T, U>(_ t: T, _ f: (inout T) -> U) { }
f0(X2(), {$0.g()})

In the resulting constraint system there are two alternatives for $T5. When trying $T5 := X2, it directly fails when simplifying the (inout $T1) subtype ($T5) constraint. But when trying $T5 := inout X2, it fails because of a constraint inout X2 equal X2. I'm not sure where this constraint is coming from, I think it might be produced during member lookup.

I noticed that during simplifyMemberConstraint, it uses getRValueType. Yet this doesn't look through InOutType. I wonder if it should?

I also get an interesting error for this case:

  func sr3520<T>(_ item: T, _ update: (inout T) -> Void) {
    var x = item
    update(&x)
  }
  sr3520(i) { $0 += 3 } // error: passing value of type 'Int' to an inout parameter requires explicit '&'
              ^
              &

@jtbandes
Copy link
Collaborator

jtbandes commented Jan 4, 2017

The bad error message I mentioned at https://twitter.com/jtbandes/status/816196817461256194 is due to the failure diagnosis process like you mentioned above. However I believe it should be solvable in one go (the issues mentioned in my previous comment are what currently prevents it from solving on its first attempt). That's as far as I got this evening...

@xedin
Copy link
Member

xedin commented Jan 4, 2017

@jtbandes Parameters can't bind to lvalue, but they can be marked as inout because when you are passing argument to the parameter argument itself has to be lvalue but parameter is address-of that argument which makes argument inout, that's why diagnostics now ask you do add & before parameters to make it address-of explicitly. Param binding logic can be found at CSSolver.cpp:1085 and ConstraintSystem.cpp:787 when binding param to it's value it's implicitly converted from lvalue to inout to make it possible to write thinking like this $0 = 1...

I think this is only related to diagnostics because it's about how arguments are type-checked by FailureDiagnosis::visitApplyExpr, after looking at this for a bit tonight I think that we need to start accounting for hasTrailingClosure when using typeCheckArgumentChildIndependently and try to run diagnostics without contextual type provided by function of the application. The problem with diagnostics of the closures right now is it's impossible to properly type-check body of the closure which involves anonymous parameters because they are marked as unresolved by ExprRewriter...

@jtbandes
Copy link
Collaborator

jtbandes commented Jan 4, 2017

Why is unresolvedtype a problem when typechecking the closure body and how would typechecking without the contextual type fix it? I think the contextual type needs to be correct to properly typecheck the body, otherwise any mutating operations on $0 wouldn't work. Also, why should this only apply to trailing closures — shouldn't it be all closure params? (Although I haven't tried yet to see what happens with a non-trailing closure param)

@xedin
Copy link
Member

xedin commented Jan 4, 2017

@jtbandes Having unresolved type in the parameter is not the biggest problem actually for type-checking closure because it mostly uses (in this case) the return type which is Int, the bigger problem is that the body itself can't be properly type-checked without involving top closure expression itself (with parameters) if body involves anonymous parameters because all of the such parameters are going to be turned into UnresolvedType while solving for they would lack "bind param" constraint which is left at the top level. I think I have an idea how to solve this in the diagnostics and I also have similar ticket (https://bugs.swift.org/browse/SR-3493), going to try it out tonight and report back...

@rudkx
Copy link
Member

rudkx commented Jan 9, 2017

Fixed on master with this merge: c04c6c9

Please confirm with a build that has this fix in it and close the issue if you agree the issue is fixed.

@swift-ci
Copy link
Collaborator Author

Comment by David Friberg (JIRA)

Related: will this also fix the compiler crash described in the special case covered in SR-3450, where the inout parameter of the supplied closure is a closure itself?

@xedin
Copy link
Member

xedin commented Jan 16, 2017

dfrib (JIRA User) Unfortunately no, that has to do with limitation of SILGen which can't yet generate code for such case...

@werediver
Copy link

I suspect there is some related regression in Swift 4 bundled with Xcode 9 beta 4.

This sample performs well in Xcode 8.3.2:

func mut<T, U>(_ some: T, _ body: (inout T) -> U) -> U {
    var _some = some
    return body(&_some)
}

let a = 1
mut(a) { $0 += 1; print($0) }

But fails in Xcode 9 beta 4 with "error: left side of mutating operator isn't mutable: '$0' is immutable". Will work with explicit signature:

mut(a) { (b: inout Int) in b += 1; print(b) }

@belkadan
Copy link
Contributor

belkadan commented Aug 3, 2017

Raman, can you file that as a separate bug? Since it's a regression we might be able to get some more information about it.

@werediver
Copy link

Jordan, I've filed a separate ticket for this issue, see SR-5628.

@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
Projects
None yet
Development

No branches or pull requests

6 participants