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-2216] Confusing behavior related to closure types and tuples #44823

Closed
jepers opened this issue Jul 29, 2016 · 14 comments
Closed

[SR-2216] Confusing behavior related to closure types and tuples #44823

jepers opened this issue Jul 29, 2016 · 14 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@jepers
Copy link

jepers commented Jul 29, 2016

Previous ID SR-2216
Radar None
Original Reporter @jepers
Type Bug
Status Closed
Resolution Done
Environment

Xcode 8 beta 3, OS X 10.11.6

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: 462fe2809af80eb2c928975f227a8b51

relates to:

  • SR-1474 Feature Request: Single-label tuples
  • SR-296 Fix inconsistencies related to tuples, arg/param lists, type params, typealiases

Issue Description:

Shortest example:

let c0 : (Int, Int)   -> Void = { (x)             -> Void in print(x.0, x.1) }
let c1 : ((Int, Int)) -> Void = { (x)             -> Void in print(x.0, x.1) }
let c2                        = { (x: (Int, Int)) -> Void in print(x.0, x.1) }

c0(1, 2)   // Prints 1 2
c1((1, 2)) // Prints 1 2
c2((1, 2)) // Prints 1 2

// Note that c0 takes two parameters while c1 and c2 takes one (tuple) parameter.
// Note also that c0, c1, c2 all have the same type (see below), yet it seems like they
// can be "type casted" into different (but equivalent) types like that.

More detailed example:

typealias BinaryIntOp_v1 = (Int, Int) -> Int
typealias BinaryIntOp_v2 = ((Int, Int)) -> Int

print(BinaryIntOp_v1.self) // Prints ((Int, Int)) -> Int
print(BinaryIntOp_v2.self) // Prints ((Int, Int)) -> Int

let areRepresentingTheSameType = BinaryIntOp_v1.self == BinaryIntOp_v2.self // (alt-click the "==" and read doc.)
print(areRepresentingTheSameType) // Prints true

let add_v1: BinaryIntOp_v1 = (+)
let add_v2: BinaryIntOp_v2 = (+) // Or both could have been eg: { return $0 + $1 }

let ra = add_v1(1, 2)
let rb = add_v2((1, 2)) // NOTE: Needs these extra parens (otherwise error: "Extra argument in call")

let rc = (add_v1 as BinaryIntOp_v2)((1, 2)) // NOTE: I am type casting these to an identical type ...
let rd = (add_v2 as BinaryIntOp_v1)(1, 2)   // ... in order to swap which one of them need extra parens ...

And one more example:

let c0 : (Int, Int) -> Void = { (x) -> Void in print(x.0, x.1) }
let c1 : (Int, Int) -> Void = { (x: (Int, Int)) -> Void in print(x.0, x.1) }
let c2                      = { (x: (Int, Int)) -> Void in print(x.0, x.1) }
c0(1, 2)
c1(1, 2)
c2((1, 2)) // <-- NOTE: Only compiles with extra parens / single parameter of 2-tuple type.
// Also note that c1 and c2 are identical except that c1 has its type given
// explicitly while c2 lets the type checker infer it.
// And look at this:
print(c1.dynamicType) // Prints ((Int, Int)) -> ()
print(c2.dynamicType) // Prints ((Int, Int)) -> ()
// And this:
print(c1.dynamicType == c2.dynamicType) // Prints true
@belkadan
Copy link
Contributor

Yep, we are part-way through fixing tuples and closures. cc @DougGregor

@jepers
Copy link
Author

jepers commented Jul 29, 2016

Great! Can you say whether or not this will still compile after the fix:

let mul: (Int, Int) -> Int = (binary_int_operation: (mul: *))

let seven = (one: 1) + (this: mul((two: 2), (three: (yes: (three: 3)))))

_ = (lets_print_the_result: print((seven: (seven: seven)))) // Prints 7

?

@belkadan
Copy link
Contributor

Um. What syntax is that?

@jepers
Copy link
Author

jepers commented Jul 30, 2016

Well, the thing is that it's valid Swift (both 2.2 and 3).
That program compiles and runs in Xcode 7.3.1 and in Xcode 8 beta 3.
It will print "The result: 7".
Just copy, paste and see for yourself.

@belkadan
Copy link
Contributor

Ah. I'm not sure where we settled in terms of implicit conversions, but my guess is no change, since this only involves single-element-tuple-to-scalar conversions.

@jepers
Copy link
Author

jepers commented Jul 30, 2016

I thought there where no single-element tuples in Swift?

@belkadan
Copy link
Contributor

So did I, but that's the only explanation I have.

@belkadan
Copy link
Contributor

-dump-parse output:

(source_file
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_typed
          (pattern_named 'mul')
          (type_function
            (type_tuple
              (type_ident
                (component id='Int' bind=none))
              (type_ident
                (component id='Int' bind=none)))
            (type_ident
              (component id='Int' bind=none))))
        (tuple_expr type='<null>' names=binary_int_operation
          (tuple_expr type='<null>' names=mul
            (unresolved_decl_ref_expr type='<null>' name=* specialized=no) function_ref=unapplied)))
))
  (var_decl "mul" type='<null type>' let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named 'seven')
        (sequence_expr type='<null>'
          (tuple_expr type='<null>' names=one
            (integer_literal_expr type='<null>' value=1))
          (unresolved_decl_ref_expr type='<null>' name=+ specialized=no) function_ref=unapplied
          (tuple_expr type='<null>' names=this
            (call_expr type='<null>'  arg_labels=_:_:
              (unresolved_decl_ref_expr type='<null>' name=mul specialized=no) function_ref=unapplied
              (tuple_expr type='<null>' names='',''
                (tuple_expr type='<null>' names=two
                  (integer_literal_expr type='<null>' value=2))
                (tuple_expr type='<null>' names=three
                  (tuple_expr type='<null>' names=yes
                    (tuple_expr type='<null>' names=three
                      (integer_literal_expr type='<null>' value=3)))))))))
))
  (var_decl "seven" type='<null type>' let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (sequence_expr type='<null>'
        (discard_assignment_expr type='<null>')
        (assign_expr
          (**NULL EXPRESSION**)
          (**NULL EXPRESSION**))
        (tuple_expr type='<null>' names=lets_print_the_result
          (call_expr type='<null>'  arg_labels=_:
            (unresolved_decl_ref_expr type='<null>' name=print specialized=no) function_ref=unapplied
            (paren_expr type='<null>'
              (tuple_expr type='<null>' names=seven
                (tuple_expr type='<null>' names=seven
                  (unresolved_decl_ref_expr type='<null>' name=seven specialized=no) function_ref=unapplied)))))))))

@jepers
Copy link
Author

jepers commented Jul 30, 2016

A casual interpretation of the current behavior/design is: "I can wrap almost any expression in parenthesis and label it, sort of like comments." Like for example:

let (me: (declare: (the: (constant: result)))) = (one: 1) + (two: 2)
_ = (now_let_me: print((the_result: (namely_the_constant: result))))
// Prints 3

I get a feeling that it will take quite some time before all of this is sorted out. Perhaps the design (of parenthesized expressions, tuples, closure types, etc) needs to be reexamined/reevaluated from scratch?

Newcomers and experienced programmers alike are probably surprised and confused by trying to make sense of simple things like:

let a = (name: "Ada") // OK, (but why, given the error below?)
let b = (name: "Ada", age: 201) // OK
print(a.name) // Error
print(b.name, b.age)

@slavapestov
Copy link
Member

This was fixed as part of implementing SE-0110 in Swift 4. Please try a recent Swift 4 snapshot.

@jepers
Copy link
Author

jepers commented Jun 3, 2017

Some of the inconsistencies have been fixed, but definitely not all, as the following demonstrates.
This is with Swift Developer Snapshot 2017-06-02 (a), swift version 4:

typealias A = (Int, Int) -> Int
typealias B = ((Int, Int)) -> Int
// Type A takes two Ints
// Type B takes a Tuple of two Ints.
// So A and B are clearly two different (function) types.
// Yet:
print(A.self) // Prints (Int, Int) -> Int
print(B.self) // Prints (Int, Int) -> Int (No tuple?!)
// Does Swift think A and B are the same type?
// Perhaps it is only printing the types wrong, so let's
// check explicitly if A and B represent the same type:
let areRepresentingTheSameType = A.self == B.self
print(areRepresentingTheSameType) // Prints true (!)
// According to the std lib documentation of that == operator,
// the result is true only if they represent the same type.

// Here is another indication that they are the same type:
let add_a: A = (+)
let add_b: B = (+)

// Yet, they must be called differently:
let _ = add_a(1, 2)   // OK, but add_a((1, 2)) fails.
let _ = add_b((1, 2)) // OK, but add_b(1, 2) fails

// We can get the inverse behaviour by type casting (to the same type!):
let _ = (add_a as B)((1, 2))
let _ = (add_b as A)(1, 2)
// So the required calling syntax was changed by type casting to the same type, right? : P

@jepers
Copy link
Author

jepers commented Jun 3, 2017

Should I close this (broad) issue and file a new one that is more specifically about the remaining inconsistencies (demonstrated in my above comment)?

Or perhaps those inconsistencies are already sufficiently identified/reported?

@slavapestov
Copy link
Member

The remaining inconsistency is that the runtime type system still does not distinguish tuples from argument lists.

The "let add_b: B = ➕" bit is a bug unfortunately, it should not have been accepted.

@jepers
Copy link
Author

jepers commented Jun 5, 2017

Ok, thanks. Also, the following will not compile (dev snapshot 2017-06-02, Swift 4):

func foo(fun: (Int, Int) -> ()) { print("was given a function of type: (Int, Int) -> ()") }
func foo(fun: ((Int, Int)) -> ()) { print("was given a function of type: ((Int, Int)) -> ()") }
// error: invalid redeclaration of 'foo(fun:)'

I would expect it to compile since they take different function types.

To me, this doesn't look like it has anything to do with the runtime type system, so it is another bug?

Are all bugs like these sufficiently reported or should I file new ones?

@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

3 participants