Skip to content

Commit

Permalink
Partial Fix of #401: Variables & Properties with generic type paramet…
Browse files Browse the repository at this point in the history
…er (#412)

* Fix brackets for variables with generic types

Variables are now transformed into Properties:
Variables were already emitted as F# Properties, but their handling was
different
from real Properties. Now Variables are converted into Properties in
`transform.fs`
and can use the same print code as real Properties.
Exception: Top level variables. These are printed as `let` binding,
not abstract members in an interface.

* Add Generic Type Constraints for Variables and Properties

* Add tests for interface

(Properties instead of variables -- but same issues for function
properties)

* Add tests for class

* Add more tests

Add (more) tests for multiple parameter:
Strange behaviour in F#:
`abstract v: string -> string -> string` is ok,
`abstract v: string -> string -> string` isn't, but required brackets
Further with generyc type parameter:
`abstract v: 'T -> 'T -> 'T` is ok, but (unlike with just `string`)
`abstract v: ('T -> 'T -> 'T)` is not (type parameter 'T is not defined)

Add tests for optional property:
Again strange behaviour in F#:
`abstract o: 'T option` isn't allowed, but
`abstract o: 'T option with get,set` is.

Most of these cases most likely don't occur in real code.
In the source for this issue and its tests it's not a general `'T`,
but a limitation (subset) of a string Enum

* Revert: Don't handle Variable in `printType`

There might be cases a Variable wasn't transformed into a property.
These would now fail. To prevent this Variables are now again handled in
`printType` as a fallback. Additional a log message is printed to
console.

* Add tests for static members
  • Loading branch information
Booksbaum authored Apr 25, 2021
1 parent 08a7717 commit 5c2d3f8
Show file tree
Hide file tree
Showing 5 changed files with 1,106 additions and 20 deletions.
80 changes: 63 additions & 17 deletions src/print.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,14 @@ let printType (tp: FsType): string =
| FsType.Generic g ->
printGeneric g
| FsType.Function ft ->
let line = ResizeArray()
let typs =
if ft.Params.Length = 0 then
[ simpleType "unit"; ft.ReturnType ]
else
(ft.Params |> List.map (fun p -> p.Type)) @ [ ft.ReturnType ]
"(" |> line.Add
typs |> List.map printType |> String.concat " -> " |> line.Add
")"|> line.Add
line |> String.concat ""
printFunctionType ft
|> sprintf "(%s)"
| FsType.Tuple tp ->
let line = ResizeArray()
tp.Types |> List.map printType |> String.concat " * " |> line.Add
line |> String.concat ""
| FsType.Variable vb ->
printfn "Variable in printType that should have been converted into property: %s" (vb.Name)
let vtp = vb.Type |> printType
sprintf "abstract %s: %s%s" vb.Name vtp (if vb.IsConst then "" else " with get, set")
| FsType.StringLiteral _ -> "string"
Expand Down Expand Up @@ -65,6 +58,36 @@ let printType (tp: FsType): string =
printfn "unsupported printType %s: %s" (getTypeName tp) (getName tp)
"obj"

/// unlike `printType`, don't surround Function with brackets:
/// * `printType`: `(string -> string)`
/// * `printRootType`: `string -> string`
/// Necessary for generic properties `'T -> 'T`: not valid in brackets
let printRootType (tp: FsType): string =
match tp with
| FsType.Function f -> printFunctionType f
| _ -> printType tp

let printFunctionType (f: FsFunction) =
match f.Params with
| [] -> sprintf "unit -> %s" (printType f.ReturnType)
| ps ->
let ps =
ps
|> List.map (fun p ->
let t = printType p.Type
if p.Optional then
// this bracket isn't always necessary for example:
// `const f: (value?: string) => void`
// in F#: `abstract f: (string) option -> unit`
// but it's easier to always put into brackets, than to discrimate
// when it's needed (tuple, function) and when not (simple type).
sprintf "(%s) option" t
else
t
)
ps @ [ printType f.ReturnType ]
|> String.concat " -> "

let printGeneric (g: FsGenericType): string =
let line = ResizeArray()
sprintf "%s" (printType g.Type) |> line.Add
Expand Down Expand Up @@ -124,7 +147,7 @@ let printFunction (fn: FsFunction): string =
line |> String.concat ""

let printProperty (pr: FsProperty): string =
sprintf "%sabstract %s: %s%s%s%s"
sprintf "%sabstract %s: %s%s%s%s%s"
( match pr.Kind with
| FsPropertyKind.Regular -> ""
| FsPropertyKind.Index -> "[<EmitIndexer>] "
Expand All @@ -135,14 +158,37 @@ let printProperty (pr: FsProperty): string =
| Some idx -> sprintf "%s: %s -> " idx.Name (printType idx.Type)
)
(
let t = printType pr.Type
// if `Option<A * B>`, surround tuple with brackets (`(A * B) option`), otherwise it's emitted as `A * Option<B>` (`A * B option`)
match pr.Option, pr.Type |> FsType.asTuple with
| true, (Some { Kind = FsTupleKind.Tuple; Types = tys }) when (tys |> List.length) > 1 ->
sprintf "(%s)" t
| _ -> t
let t = printRootType pr.Type
let optionInBrackets ty =
if pr.Option then
match ty with
// if `Option<A * B>`, surround tuple with brackets (`(A * B) option`), otherwise it's emitted as `A * Option<B>` (`A * B option`)
| FsType.Tuple { Kind = FsTupleKind.Tuple; Types = tys } when (tys |> List.length) > 1 ->
sprintf "(%s)" t
// brackets required for function type like `(string -> string) option`
| FsType.Function _ ->
sprintf "(%s)" t
| _ -> t
else
t

match pr.Accessor with
| ReadOnly ->
optionInBrackets pr.Type
| WriteOnly | ReadWrite ->
match pr.Type with
| FsType.Function _ ->
sprintf "(%s)" t
| _ -> optionInBrackets pr.Type
)
(if pr.Option then " option" else "")
(
// generic type parameter
match pr.Type with
| FsType.Function f when f.TypeParameters.Length > 0 ->
printGenericTypeConstraints f.TypeParameters
| _ -> ""
)
(
match pr.Accessor with
| ReadOnly -> ""
Expand Down
26 changes: 24 additions & 2 deletions src/transform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,22 @@ let rec createIExportsModule (ns: string list) (md: FsModule): FsModule * FsVari
| _ -> ()
)

// convert variables in interfaces (after transformation) into properties
// -> no extra handling of Variables in print
let variableToProperty (v: FsVariable): FsProperty =
{
Attributes = v.Attributes
Comments = v.Comments
Kind = FsPropertyKind.Regular
Index = None
Name = v.Name
Option = false
Type = v.Type
Accessor = if v.IsConst then ReadOnly else ReadWrite
IsStatic = v.IsStatic
Accessibility = v.Accessibility
}

md.Types |> List.iter(fun tp ->
match tp with
| FsType.Module smd ->
Expand Down Expand Up @@ -286,9 +302,15 @@ let rec createIExportsModule (ns: string list) (md: FsModule): FsModule * FsVari
if vb.IsGlobal then
typesGlobal.Add tp
else
typesInIExports.Add tp
vb
|> variableToProperty
|> FsType.Property
|> typesInIExports.Add
else
typesInIExports.Add tp
vb
|> variableToProperty
|> FsType.Property
|> typesInIExports.Add
| FsType.Function _ -> typesInIExports.Add tp
| FsType.Interface it ->
if it.IsStatic then
Expand Down
Loading

0 comments on commit 5c2d3f8

Please sign in to comment.