Skip to content

FS-1006 struct tuples #30

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

Merged
merged 4 commits into from
Apr 5, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions spec/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ expr :=
expr .[ slice-ranges ] -- slice expression
expr <- expr -- assignment expression
expr , ... , expr -- tuple expression
struct (expr , ... , expr) -- struct tuple expression
new type expr -- simple object expression
{ new base-call object-members interface-impls } -- object expression
{ field-initializers } -- record expression
Expand Down Expand Up @@ -328,11 +329,20 @@ An expression of the form `expr1 , ..., exprn` is a _tuple expression_. For exam
let three = (1,2,"3")
let blastoff = (10,9,8,7,6,5,4,3,2,1,0)
```
The expression has the type `(ty1 * ... * tyn)` for fresh types `ty1 ... tyn` , and each individual
expression `expri` is checked using initial type `tyi`.

Tuple types and expressions are translated into applications of a family of F# library types named
System.Tuple. Tuple types `ty1 * ... * tyn` are translated as follows:
The expression has the type `S<ty1 * ... * tyn>` for fresh types `ty1 ... tyn` and fresh pseudo-type `S` that indicates the "structness" (i.e. reference tuple or struct tuple) of the tuple. Each individual
expression `expri` is checked using initial type `tyi`. The pseudo-type `S` participates in type checking similar to normal types until it is resolved to either reference or struct tuple, with a default of reference tuple.

An expression of the form `struct (expr1 , ..., exprn)` is a _struct tuple expression_. For example:

```fsharp
let pair = struct (1,2)
```

A _struct tuple expression_ is checked in the same way as a _tuple expression_, but the pseudo-type `S` is resolved to struct tuple.

Tuple types and expressions that have `S` resolved to reference tuple are translated into applications of a family of F# library types named
`System.Tuple`. Tuple types `ty1 * ... * tyn` are translated as follows:

- For `n <= 7` the elaborated form is `Tuple<ty1 ,... , tyn>`.
- For larger `n` , tuple types are shorthand for applications of the additional F# library type
Expand All @@ -355,6 +365,9 @@ example, `typeof<int * int>` is equivalent to `typeof<System.Tuple<int,int>>`, a
runtime type `System.Tuple<int,int>`. Likewise, `(1,2,3,4,5,6,7,8,9)` has the runtime type
`Tuple<int,int,int,int,int,int,int,Tuple<int,int>>`.

Tuple types and expressions that have `S` resolved to struct tuple are translated in the same way to `System.StructTuple`.


> Note: The above encoding is invertible and the substitution of types for type variables
preserves this inversion. This means, among other things, that the F# reflection library
can correctly report tuple types based on runtime System.Type values. The inversion is
Expand Down
3 changes: 2 additions & 1 deletion spec/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ pat :=
pat '&' pat -- conjunctive pattern
pat :: pat -- "cons" pattern
pat : type -- pattern with type constraint
pat ,..., pat -- tuple pattern
pat , ... , pat -- tuple pattern
struct (pat , ... , pat) -- struct tuple pattern
( pat ) -- parenthesized pattern
list-pat -- list pattern
array-pat -- array pattern
Expand Down
70 changes: 45 additions & 25 deletions spec/types-and-type-constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,45 @@ The following describes the syntactic forms of types as they appear in programs:
```fsgrammar
type :=
( type )
type - > type -- function type
type * ... * type -- tuple type
typar -- variable type
long-ident -- named type, such as int
long-ident <type-args> -- named type, such as list<int>
long-ident < > -- named type, such as IEnumerable< >
type long-ident -- named type, such as int list
type [ , ... , ] -- array type
type typar-defns -- type with constraints
typar :> type -- variable type with subtype constraint
# type -- anonymous type with subtype constraint
type - > type -- function type
type * ... * type -- tuple type
struct (type * ... * type) -- struct tuple type
typar -- variable type
long-ident -- named type, such as int
long-ident <type-args> -- named type, such as list<int>
long-ident < > -- named type, such as IEnumerable< >
type long-ident -- named type, such as int list
type [ , ... , ] -- array type
type typar-defns -- type with constraints
typar :> type -- variable type with subtype constraint
# type -- anonymous type with subtype constraint

type-args := type-arg , ..., type-arg

type-arg :=
type -- type argument
measure -- unit of measure argument
static-parameter -- static parameter
type -- type argument
measure -- unit of measure argument
static-parameter -- static parameter

atomic-type :=
type : one of
#type typar ( type ) long-ident long-ident <type-args>

typar :=
_ -- anonymous variable type
' ident -- type variable
^ ident -- static head-type type variable
_ -- anonymous variable type
' ident -- type variable
^ ident -- static head-type type variable

constraint :=
typar :> type -- coercion constraint
typar : null -- nullness constraint
typar :> type -- coercion constraint
typar : null -- nullness constraint
static-typars : ( member-sig ) -- member "trait" constraint
typar : (new : unit -> 'T) -- CLI default constructor constraint
typar : struct -- CLI non-Nullable struct
typar : not struct -- CLI reference type
typar : enum< type > -- enum decomposition constraint
typar : unmanaged -- unmanaged constraint
typar : delegate<type, type> -- delegate decomposition constraint
typar : (new : unit -> 'T) -- CLI default constructor constraint
typar : struct -- CLI non-Nullable struct
typar : not struct -- CLI reference type
typar : enum< type > -- enum decomposition constraint
typar : unmanaged -- unmanaged constraint
typar : delegate<type, type> -- delegate decomposition constraint
typar : equality
typar : comparison

Expand Down Expand Up @@ -188,6 +189,25 @@ When considered as static types, tuple types are distinct from their encoded for
encoded form of tuple types is visible in the F# type system through runtime types. For example,
`typeof<int * int>` is equivalent to `typeof<System.Tuple<int,int>>`.

#### Struct Tuple Types

A *struct tuple type* has the following form:

```fsgrammar
struct ( ty 1 * ... * tyn )
```

The elaborated form of a tuple type is shorthand for a use of the family of F# library types
`System.StructTuple<_, ..., _>`.

When considered as static types, tuple types are distinct from their encoded form. However, the
encoded form of tuple types is visible in the F# type system through runtime types. For example,
`typeof<int * int>` is equivalent to `typeof<System.StructTuple<int,int>>`.

Struct tuple types are value types (as opposed to tuple types which are reference types). Struct tuple types are primarily aimed at use in interop and performance tuning.

The "structness" (i.e. tuple type vs. struct tuple type) of tuple expressions and tuple patterns is inferred in the F# type inference process (unless they are explicitly tagged "struct"). However, code cannot be generic over structness, and there is no implicit conversion between struct tuples and reference tuples.

### Array Types

Array types have the following forms:
Expand Down