Skip to content

Commit

Permalink
feat: add basic support for Readonly<T>
Browse files Browse the repository at this point in the history
[converter]

=== changelog ===
```ts
export interface TerminalOptions {
    prefix: string
}

export type ReadonlyTerminalOptions = Readonly<TerminalOptions>
```

```fs
[<AllowNullLiteral>]
[<Interface>]
type TerminalOptions =
    abstract member prefix: string with get, set

[<AllowNullLiteral>]
[<Interface>]
type ReadonlyTerminalOptions =
    abstract member prefix: string with get
```
=== changelog ===
  • Loading branch information
MangelMaxime committed Dec 3, 2024
1 parent b7e9030 commit 20dab77
Show file tree
Hide file tree
Showing 18 changed files with 506 additions and 35 deletions.
9 changes: 8 additions & 1 deletion src/Glutinum.Converter/GlueAST.fs
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,19 @@ type GlueRecord =
ValueType: GlueType
}

[<RequireQualifiedAccess>]
type GlueReadonly =
| Members of GlueMember list
| Union of GlueInterface list

[<RequireQualifiedAccess>]
type GlueUtilityType =
| Partial of GlueInterface
| Record of GlueRecord
| ReturnType of GlueType
| ThisParameterType of GlueType
| Omit of GlueMember list
| Readonly of GlueReadonly

type GlueMappedType =
{
Expand Down Expand Up @@ -359,11 +365,11 @@ type GlueType =
| Array arrayInfo -> $"ReadonlyArray<{arrayInfo.Name}>"
| _ -> info.Name
| ThisType thisTypeInfo -> thisTypeInfo.Name
| Union(GlueTypeUnion cases) -> cases |> List.map (fun x -> x.Name) |> String.concat (" | ")
| NamedTupleType _
| TypeLiteral _
| IntersectionType _
| IndexedAccessType _
| Union _
| FunctionType _
| TupleType _
| OptionalType _ // TODO: Should we take the name of the underlying type and add option to it?
Expand All @@ -375,6 +381,7 @@ type GlueType =
match utilityType with
| GlueUtilityType.Partial _
| GlueUtilityType.Omit _
| GlueUtilityType.Readonly _
| GlueUtilityType.Record _ -> "obj"
| GlueUtilityType.ReturnType returnType -> returnType.Name
| GlueUtilityType.ThisParameterType thisType -> thisType.Name
Expand Down
98 changes: 81 additions & 17 deletions src/Glutinum.Converter/Reader/TypeNode.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,34 @@ module UtilityType =

cases |> GlueTypeUnion |> GlueType.Union

/// <summary></summary>
/// <param name="reader"></param>
/// <param name="contextNode">Node used to report errors</param>
/// <param name="typ">Type to read the members from</param>
/// <returns></returns>
let private readMembers (reader: ITypeScriptReader) (contextNode: Ts.Node) (typ: Ts.Type) =

typ
|> reader.checker.getPropertiesOfType
|> Seq.toList
|> List.choose (fun property ->
match property.declarations with
| Some declarations -> declarations |> Seq.map reader.ReadDeclaration |> Some
| None ->
Report.readerError ("type node", "Missing declarations", contextNode)
|> reader.Warnings.Add

None
)
|> Seq.concat
|> Seq.distinct
|> Seq.toList

let readPartial (reader: ITypeScriptReader) (typeReferenceNode: Ts.TypeReferenceNode) =
let baseType =
typeReferenceNode.typeArguments.Value[0] |> reader.checker.getTypeFromTypeNode

let baseProperties =
let members =
match baseType.flags with
| HasTypeFlags Ts.TypeFlags.Any ->
Report.readerError (
Expand All @@ -116,23 +139,9 @@ module UtilityType =
)
|> reader.Warnings.Add

ResizeArray []
| _ -> baseType |> reader.checker.getPropertiesOfType

let members =
baseProperties
|> Seq.toList
|> List.choose (fun property ->
match property.declarations with
| Some declarations -> declarations |> Seq.map reader.ReadDeclaration |> Some
| None ->
Report.readerError ("type node", "Missing declarations", typeReferenceNode)
|> reader.Warnings.Add
[]

None
)
|> Seq.concat
|> Seq.toList
| _ -> baseType |> readMembers reader typeReferenceNode

({
FullName = getFullNameOrEmpty reader.checker typeReferenceNode
Expand Down Expand Up @@ -252,6 +261,60 @@ module UtilityType =

members |> GlueUtilityType.Omit |> GlueType.UtilityType

let readReadonly (reader: ITypeScriptReader) (typeReferenceNode: Ts.TypeReferenceNode) =

let typ = reader.checker.getTypeFromTypeNode typeReferenceNode

match typ.flags with
| HasTypeFlags Ts.TypeFlags.Object
| HasTypeFlags Ts.TypeFlags.Intersection ->
typ
|> readMembers reader typeReferenceNode
|> GlueReadonly.Members
|> GlueUtilityType.Readonly
|> GlueType.UtilityType
| HasTypeFlags Ts.TypeFlags.Union ->
let unionType = typ :?> Ts.UnionOrIntersectionType

try

let interfaces =
unionType.types
|> Seq.choose (fun innerType ->
if innerType.flags.HasFlag Ts.TypeFlags.Object then

({
Name = innerType.aliasTypeArguments.Value[0].symbol.name
FullName =
reader.checker.getFullyQualifiedName
innerType.aliasTypeArguments.Value[0].symbol
Members = readMembers reader typeReferenceNode innerType
TypeParameters = []
HeritageClauses = []
}
: GlueInterface)
|> Some
else
None
)
|> Seq.toList

interfaces
|> GlueReadonly.Union
|> GlueUtilityType.Readonly
|> GlueType.UtilityType
with _ ->
Report.readerError (
"Readonly",
"Unable to read the members of the union type",
typeReferenceNode
)
|> reader.Warnings.Add

GlueType.Primitive GluePrimitive.Any

| _ -> GlueType.Primitive GluePrimitive.Any

let readTypeNode (reader: ITypeScriptReader) (typeNode: Ts.TypeNode) : GlueType =
let checker = reader.checker

Expand Down Expand Up @@ -291,6 +354,7 @@ let readTypeNode (reader: ITypeScriptReader) (typeNode: Ts.TypeNode) : GlueType
| "ReturnType" -> UtilityType.readReturnType reader typeReferenceNode
| "ThisParameterType" -> UtilityType.readThisParameterType reader typeReferenceNode
| "Omit" -> UtilityType.readOmit reader typeReferenceNode
| "Readonly" -> UtilityType.readReadonly reader typeReferenceNode
| _ -> readTypeReference true
else
readTypeReference false
Expand Down
108 changes: 102 additions & 6 deletions src/Glutinum.Converter/Transform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,80 @@ module TypeLiteral =
makeIterableObj ()
)

module private UtilityType =
let transformReadOnly
(context: TransformContext)
(readonlyInfo: GlueReadonly)
(makeTypeAlias: (FSharpType -> FSharpType) option)
=
match readonlyInfo with
| GlueReadonly.Members members ->
let interfaceTyp =
{
Attributes = [ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Name = context.CurrentScopeName
OriginalName = context.CurrentScopeName
TypeParameters = []
Members =
TransformMembers.toFSharpMember context members
|> TransformMembers.forceReadonly
Inheritance = []
}
|> FSharpType.Interface

match makeTypeAlias with
| Some _ -> interfaceTyp
| None ->
context.ExposeType interfaceTyp

({
Name = context.FullName
TypeParameters = []
}
: FSharpMapped)
|> FSharpType.Mapped

| GlueReadonly.Union interfaces ->
let name, context = sanitizeNameAndPushScope $"U{interfaces.Length}" context

let cases =
interfaces
|> List.map (fun glueInterface ->
let context = context.PushScope $"ReadOnly{glueInterface.Name}"
let initialInterface = transformInterface context glueInterface

let adaptedInterface =
{ initialInterface with
Members = initialInterface.Members |> TransformMembers.forceReadonly
}

{ adaptedInterface with
Name = context.CurrentScopeName
}
|> FSharpType.Interface
|> context.ExposeType

{ adaptedInterface with
Name = context.FullName
}
|> FSharpType.Interface
|> FSharpUnionCase.Typed
)

let unionType =
({
Attributes = []
Name = name
Cases = cases
IsOptional = false
}
: FSharpUnion)
|> FSharpType.Union

match makeTypeAlias with
| Some makeTypeAlias -> makeTypeAlias unionType
| None -> unionType

let rec private transformType (context: TransformContext) (glueType: GlueType) : FSharpType =
match glueType with
| GlueType.ConstructorType
Expand Down Expand Up @@ -498,12 +572,16 @@ let rec private transformType (context: TransformContext) (glueType: GlueType) :
Name = mapTypeNameToFableCoreAwareName context typeReference
FullName = typeReference.FullName
TypeArguments = typeReference.TypeArguments |> List.map (transformType context)
Type = //transformType context typeReference.TypeRef
// TODO: This code looks suspicious
// Why would a typeReference always be a string? I think I added that here to make
// the compiler happy because we don't have a concrete type for the TypeReference
// this is because of the recursive types which creates infinite loops in the reader
FSharpType.Primitive FSharpPrimitive.String
Type =
context.TypeMemory
|> List.tryFind (fun glueType ->
match glueType with
| GlueType.Interface glueInterface ->
glueInterface.FullName = typeReference.FullName
| _ -> false
)
|> Option.map (transformType context)
|> Option.defaultValue FSharpType.Discard
}
: FSharpTypeReference)
|> FSharpType.TypeReference
Expand Down Expand Up @@ -789,6 +867,9 @@ let rec private transformType (context: TransformContext) (glueType: GlueType) :
: FSharpMapped)
|> FSharpType.Mapped

| GlueUtilityType.Readonly readonlyInfo ->
UtilityType.transformReadOnly context readonlyInfo None

| GlueType.TypeAliasDeclaration typeAliasDeclaration ->
({
Name = typeAliasDeclaration.Name
Expand Down Expand Up @@ -1309,6 +1390,18 @@ module private TransformMembers =
|> Some
)

let forceReadonly (members: FSharpMember list) =
members
|> List.map (
function
| FSharpMember.Property property ->
{ property with
Accessor = Some FSharpAccessor.ReadOnly
}
|> FSharpMember.Property
| m -> m
)

let toFSharpParameters
(context: TransformContext)
(members: GlueMember list)
Expand Down Expand Up @@ -2112,6 +2205,9 @@ let private transformTypeAliasDeclaration
}
|> FSharpType.Interface

| GlueUtilityType.Readonly readonlyInfo ->
UtilityType.transformReadOnly context readonlyInfo (Some makeTypeAlias)

| GlueType.FunctionType functionType ->
{
Attributes = [ FSharpAttribute.AllowNullLiteral; FSharpAttribute.Interface ]
Expand Down
59 changes: 59 additions & 0 deletions src/Glutinum.Converter/TypeScript.Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,62 @@ type Ts.SyntaxKind with
| Ts.SyntaxKind.SyntheticReferenceExpression -> "SyntheticReferenceExpression"
| Ts.SyntaxKind.Count -> "Count"
| unknown -> $"Unknown {unknown}"

type Ts.TypeFlags with

member this.Debug() =

[
Ts.TypeFlags.Any, "Any"
Ts.TypeFlags.Unknown, "Unknown"
Ts.TypeFlags.String, "String"
Ts.TypeFlags.Number, "Number"
Ts.TypeFlags.Boolean, "Boolean"
Ts.TypeFlags.Enum, "Enum"
Ts.TypeFlags.BigInt, "BigInt"
Ts.TypeFlags.StringLiteral, "StringLiteral"
Ts.TypeFlags.NumberLiteral, "NumberLiteral"
Ts.TypeFlags.BooleanLiteral, "BooleanLiteral"
Ts.TypeFlags.EnumLiteral, "EnumLiteral"
Ts.TypeFlags.BigIntLiteral, "BigIntLiteral"
Ts.TypeFlags.ESSymbol, "ESSymbol"
Ts.TypeFlags.UniqueESSymbol, "UniqueESSymbol"
Ts.TypeFlags.Void, "Void"
Ts.TypeFlags.Undefined, "Undefined"
Ts.TypeFlags.Null, "Null"
Ts.TypeFlags.Never, "Never"
Ts.TypeFlags.TypeParameter, "TypeParameter"
Ts.TypeFlags.Object, "Object"
Ts.TypeFlags.Union, "Union"
Ts.TypeFlags.Intersection, "Intersection"
Ts.TypeFlags.Index, "Index"
Ts.TypeFlags.IndexedAccess, "IndexedAccess"
Ts.TypeFlags.Conditional, "Conditional"
Ts.TypeFlags.Substitution, "Substitution"
Ts.TypeFlags.NonPrimitive, "NonPrimitive"
Ts.TypeFlags.TemplateLiteral, "TemplateLiteral"
Ts.TypeFlags.StringMapping, "StringMapping"
Ts.TypeFlags.Literal, "Literal"
Ts.TypeFlags.Unit, "Unit"
Ts.TypeFlags.Freshable, "Freshable"
Ts.TypeFlags.StringOrNumberLiteral, "StringOrNumberLiteral"
Ts.TypeFlags.PossiblyFalsy, "PossiblyFalsy"
Ts.TypeFlags.StringLike, "StringLike"
Ts.TypeFlags.NumberLike, "NumberLike"
Ts.TypeFlags.BigIntLike, "BigIntLike"
Ts.TypeFlags.BooleanLike, "BooleanLike"
Ts.TypeFlags.EnumLike, "EnumLike"
Ts.TypeFlags.ESSymbolLike, "ESSymbolLike"
Ts.TypeFlags.VoidLike, "VoidLike"
Ts.TypeFlags.UnionOrIntersection, "UnionOrIntersection"
Ts.TypeFlags.StructuredType, "StructuredType"
Ts.TypeFlags.TypeVariable, "TypeVariable"
Ts.TypeFlags.InstantiableNonPrimitive, "InstantiableNonPrimitive"
Ts.TypeFlags.InstantiablePrimitive, "InstantiablePrimitive"
Ts.TypeFlags.Instantiable, "Instantiable"
Ts.TypeFlags.StructuredOrInstantiable, "StructuredOrInstantiable"
]
|> List.iter (fun (flag, name) ->
if this.HasFlag flag then
printfn "%s" name
)
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ type FSharpASTViewer =
FSharpASTViewer.Name typeReference.Name
FSharpASTViewer.FullName typeReference.FullName
FSharpASTViewer.TypeArguments typeReference.TypeArguments
FSharpASTViewer.Type typeReference.Type
]
context

Expand Down
Loading

0 comments on commit 20dab77

Please sign in to comment.