Skip to content

Signature generation drops parentheses around nested tuple argument when a sibling argument is named #20397

Description

@nojaf

Generated signatures (GetValSignatureText, GenerateSignature, fsc --sig) drop the parentheses around a tuple-typed unnamed argument when another argument in the same curried group is named, so (int * float) * string comes out as the flat 3-tuple int * float * string. The printed type is a different type.

Repro

module X

let a ((x: int, y: float), z: string) = 1uy
let d ((x, y): int * float, z: string) = 1uy
let e ((p: int * float), z: string) = 1uy
let b (p: (int * float) * string) = 1uy

fsc --sig:X.fsi -a X.fs on current main (7ea5992) and on FCS 43.11.303 through 43.12.400:

val a: int * float * z: string -> byte       // wrong, was (int * float) * z: string in 43.9.303
val d: int * float * z: string -> byte       // wrong
val e: p: (int * float) * z: string -> byte  // ok, named
val b: (int * float) * string -> byte        // ok, whole group unnamed

FSharpMemberOrFunctionOrValue.CurriedParameterGroups is identical for a and b apart from the name z, so only the printing differs.

Cause

#18842 changed the unnamed-argument branch of PrintTypes.layoutArgInfo in src/Compiler/Checking/NicePrint.fs to

| None, _, _ ->
    let prec =
        match ty with
        | TType_tuple _ -> 2
        | _ -> 4
    layoutTypeWithInfoAndPrec denv env prec ty

The match is on the raw ty. For a destructured or pattern-annotated tuple parameter the argument's type is an inference variable solved to a tuple, so it falls into the _ -> 4 arm and the tuple case's bracketIfL (prec <= 2) never fires. b is fine because a fully unnamed group is laid out as a single type, and e is fine because the named branch still passes precedence 2 unconditionally.

Fix

match stripTyEqns g ty with

(g is already bound at the top of layoutArgInfo.) With that one-line change, fsc --sig on main prints

val a: (int * float) * z: string -> byte
val d: (int * float) * z: string -> byte
val e: p: (int * float) * z: string -> byte
val b: (int * float) * string -> byte

Happy to open a PR with the change and a --sig roundtrip test.

Found while upgrading Telplin to FCS 43.12.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions