From 22e9bff0023ad877adc20f537a88d9cfbb34e260 Mon Sep 17 00:00:00 2001 From: Kevin Barabash Date: Sun, 22 Sep 2024 13:34:43 -0400 Subject: [PATCH] skip private properties in TypeScript classes, handle getters/setters in TypeScript classes --- src/Escalier.Compiler/Prelude.fs | 65 ++++++- src/Escalier.Data/Library.fs | 9 +- src/Escalier.Data/Visitor.fs | 1 + src/Escalier.Interop/MergeLib.fs | 4 +- src/Escalier.Interop/Migrate.fs | 159 ++++++++++++------ src/Escalier.Interop/Parser.fs | 134 +++++++++++---- .../snapshots/Tests.ParseEnum.verified.txt | 2 + .../Tests.ParseGenericEnum.verified.txt | 3 + .../Tests.ParseOptionEnum.verified.txt | 2 + src/Escalier.Parser/Parser.fs | 1 + src/Escalier.TypeChecker/Infer.fs | 6 + 11 files changed, 292 insertions(+), 94 deletions(-) diff --git a/src/Escalier.Compiler/Prelude.fs b/src/Escalier.Compiler/Prelude.fs index a0dd3c0..2fe133c 100644 --- a/src/Escalier.Compiler/Prelude.fs +++ b/src/Escalier.Compiler/Prelude.fs @@ -489,7 +489,8 @@ module Prelude = for KeyValue(key, value) in value.Namespaces do ns <- ns.AddNamespace key value | None -> failwith $"Couldn't find namespace: '{name}'" - | NamedExport { Src = src; Specifiers = specifiers } -> + | NamedExport { Src = Some src + Specifiers = specifiers } -> let mutable resolvedPath = resolvePath projectRoot env.Filename src if resolvedPath.EndsWith(".js") then @@ -530,13 +531,63 @@ module Prelude = printfn $"resolvedPath = {resolvedPath}" failwith "TODO: getLibExports - NamedExport" - for specifier in specifiers do - match specifier with - | Named { Name = name; Alias = alias } -> - // TODO: look for specifer in exportNs and add it to ns - printfn $"name = {name}, alias = {alias}" + for Named { Name = name; Alias = alias } in specifiers do + let mutable found = false - failwith "TODO: getExports - NamedExports" + let exportName = + match alias with + | Some a -> a + | None -> name + + match exportNs.Values.TryFind name with + | Some binding -> + ns <- ns.AddBinding exportName binding + found <- true + | None -> () + + match exportNs.Schemes.TryFind name with + | Some scheme -> + ns <- ns.AddScheme exportName scheme + found <- true + | None -> () + + match exportNs.Namespaces.TryFind name with + | Some value -> + ns <- ns.AddNamespace exportName value + found <- true + | None -> () + + if found then + failwith $"Couldn't find export '{name}' in {resolvedPath}" + | NamedExport { Src = None; Specifiers = specifiers } -> + for Named { Name = name; Alias = alias } in specifiers do + let mutable found = false + + let exportName = + match alias with + | Some a -> a + | None -> name + + match env.TryFindValue name with + | Some binding -> + ns <- ns.AddBinding exportName binding + found <- true + | None -> () + + match env.TryFindScheme name with + | Some scheme -> + ns <- ns.AddScheme exportName scheme + found <- true + | None -> () + + match env.Namespace.Namespaces.TryFind name with + | Some value -> + ns <- ns.AddNamespace exportName value + found <- true + | None -> () + + if found then + failwith $"Couldn't find '{name}' to export" | ExportDefault expr -> match expr.Kind with | ExprKind.Identifier { Name = name } -> diff --git a/src/Escalier.Data/Library.fs b/src/Escalier.Data/Library.fs index 223c69f..d09f7fc 100644 --- a/src/Escalier.Data/Library.fs +++ b/src/Escalier.Data/Library.fs @@ -500,6 +500,7 @@ module Syntax = type EnumVariant = { Name: string TypeAnn: option // Must only be an object or tuple type + Init: option // Can only be a numer literal or string literal Span: Span } type EnumDecl = @@ -619,6 +620,11 @@ module Syntax = Op: string // TODO: Use an enum for this Right: TypeAnn } + type ImportType = + { Src: string + Qualifier: option + TypeArgs: option> } + type TypeAnnKind = | Literal of Common.Literal | Keyword of KeywordTypeAnn @@ -641,6 +647,7 @@ module Syntax = | Binary of BinaryType | TemplateLiteral of Common.TemplateLiteral | Intrinsic + | ImportType of ImportType [] type TypeAnn = @@ -669,7 +676,7 @@ module Syntax = type ExportSpecifier = Named of Named type NamedExport = - { Src: string + { Src: option Specifiers: list } type ExportAll = { Src: string } diff --git a/src/Escalier.Data/Visitor.fs b/src/Escalier.Data/Visitor.fs index 38da59b..2deb650 100644 --- a/src/Escalier.Data/Visitor.fs +++ b/src/Escalier.Data/Visitor.fs @@ -340,6 +340,7 @@ module rec ExprVisitor = walk max | TypeAnnKind.TemplateLiteral { Exprs = expr } -> List.iter walk expr | TypeAnnKind.Intrinsic -> () + | TypeAnnKind.ImportType _ -> () let walkTypeAnnObjElem (visitor: SyntaxVisitor<'S>) diff --git a/src/Escalier.Interop/MergeLib.fs b/src/Escalier.Interop/MergeLib.fs index 18689a6..a2fcdfc 100644 --- a/src/Escalier.Interop/MergeLib.fs +++ b/src/Escalier.Interop/MergeLib.fs @@ -143,6 +143,8 @@ module MergeLib = if List.contains resolvedPath visitedPaths then return () else + printfn $"resolvedPath = {resolvedPath}" + visitedPaths <- resolvedPath :: visitedPaths let contents = File.ReadAllText(resolvedPath) @@ -159,7 +161,7 @@ module MergeLib = for item in ast.Items do match item with | Import { Path = path } -> do! traverse resolvedPath path - | Export(Export.NamedExport { Src = src }) -> + | Export(Export.NamedExport { Src = Some src }) -> do! traverse resolvedPath src | Export(Export.ExportAll { Src = src }) -> do! traverse resolvedPath src diff --git a/src/Escalier.Interop/Migrate.fs b/src/Escalier.Interop/Migrate.fs index 385b500..3b447b0 100644 --- a/src/Escalier.Interop/Migrate.fs +++ b/src/Escalier.Interop/Migrate.fs @@ -84,12 +84,12 @@ module rec Migrate = Span = DUMMY_SPAN InferredType = None } - let kind = + let kind: TypeAnnKind = { TypeRef.Ident = Common.QualifiedIdent.Ident "Self" TypeRef.TypeArgs = None } - |> TypeRef + |> TypeAnnKind.TypeRef - let selfTypeAnn = + let selfTypeAnn: TypeAnn = { Kind = kind Span = DUMMY_SPAN InferredType = None } @@ -112,10 +112,10 @@ module rec Migrate = Span = DUMMY_SPAN InferredType = None } - let kind = + let kind: TypeAnnKind = { TypeRef.Ident = Common.QualifiedIdent.Ident "Self" TypeRef.TypeArgs = None } - |> TypeRef + |> TypeAnnKind.TypeRef let selfTypeAnn = { Kind = kind @@ -321,7 +321,7 @@ module rec Migrate = | TsType.TsThisType _ -> { TypeRef.Ident = Common.QualifiedIdent.Ident "Self" TypeRef.TypeArgs = None } - |> TypeRef + |> TypeAnnKind.TypeRef | TsType.TsFnOrConstructorType tsFnOrConstructorType -> // Assumes the entire object is mutable let isMutable = true @@ -375,7 +375,7 @@ module rec Migrate = { TypeRef.Ident = entityNameToQualifiedIdent typeName TypeRef.TypeArgs = typeArgs } - |> TypeRef + |> TypeAnnKind.TypeRef | TsType.TsTypeQuery { ExprName = exprName TypeArgs = _typeArgs } -> let name = @@ -500,7 +500,15 @@ module rec Migrate = | TsType.TsTypePredicate _ -> // TODO: add proper support for type predicates TypeAnnKind.Keyword KeywordTypeAnn.Boolean - | TsType.TsImportType _ -> failwith "TODO: migrateType - TsImportType" + | TsType.TsImportType { Arg = arg + Qualifier = qualifier + TypeArgs = typeArgs } -> + let qualifier = Option.map entityNameToQualifiedIdent qualifier + + TypeAnnKind.ImportType + { Src = arg.Value + Qualifier = qualifier + TypeArgs = None } { Kind = kind Span = DUMMY_SPAN @@ -646,8 +654,8 @@ module rec Migrate = props PatternKind.Object { Elems = elems; Immutable = false } - | Pat.Assign(_) -> failwith "TODO: handle assignment patterns" - | Pat.Invalid(_) -> failwith "TODO: handle invalid patterns" + | Pat.Assign _ -> failwith "TODO: handle assignment patterns" + | Pat.Invalid _ -> failwith "TODO: handle invalid patterns" { Kind = kind Span = DUMMY_SPAN @@ -716,18 +724,12 @@ module rec Migrate = | None -> Syntax.ExportSpecifier.Named { Name = name; Alias = None }) - match namedExport.Src with - | Some src -> - [ Export( - NamedExport - { Specifiers = specifiers - Src = src.Value } - ) ] - | None -> - // TODO: handle renaming named exports, e.g. - // export { setVerbosity as setLogVerbosity }; - printfn "TODO: migrate named export without src" - [] + let src = + match namedExport.Src with + | Some src -> Some src.Value + | None -> None + + [ Export(NamedExport { Specifiers = specifiers; Src = src }) ] | ModuleDecl.ExportDefaultDecl _ -> failwith "TODO: migrateModuleDecl - exportDefaultDecl" | ModuleDecl.ExportDefaultExpr _ -> @@ -796,8 +798,8 @@ module rec Migrate = Body = body } } = decl - let elems: list = - List.map + let elems: list = + List.collect (fun (elem: ClassMember) -> match elem with | ClassMember.Constructor { Params = paramList @@ -836,7 +838,7 @@ module rec Migrate = Throws = None IsAsync = false } - ClassElem.Constructor { Sig = fnSig; Body = None } + [ ClassElem.Constructor { Sig = fnSig; Body = None } ] | ClassMember.Method { Key = key Function = f Kind = methodKind @@ -881,20 +883,34 @@ module rec Migrate = match methodKind with | Method -> - ClassElem.Method - { Name = name - Sig = fnSig - Body = None - Static = isStatic } - | Getter -> failwith "TODO: migrateClassDecl - Getter" - | Setter -> failwith "TODO: migrateClassDecl - Setter" - | ClassMember.PrivateMethod(_) -> + [ ClassElem.Method + { Name = name + Sig = fnSig + Body = None + Static = isStatic } ] + | Getter -> + [ ClassElem.Getter + { Name = name + Self = self + Body = None // TODO + ReturnType = Some retType + Throws = None + Static = isStatic } ] + | Setter -> + [ ClassElem.Setter + { Name = name + Self = self + Param = migrateParam f.Params[0] + Body = None // TODO + Throws = None + Static = isStatic } ] + | ClassMember.PrivateMethod _ -> failwith "TODO: migrateClassDecl - PrivateMethod" | ClassMember.ClassProp { Key = key Value = _ TypeAnn = typeAnn IsStatic = isStatic - Accessibility = _ + Accessibility = accessMod IsAbstract = _ IsOptional = optional IsOverride = _ @@ -910,26 +926,34 @@ module rec Migrate = // TODO: update `key` to handle `unique symbol`s as well failwith "TODO: computed property name" - let typeAnn = - match typeAnn with - | Some t -> migrateTypeAnn t - | None -> - failwith "all class properties must have a type annotation" - - ClassElem.Property - { Name = name - TypeAnn = typeAnn - Optional = optional - Readonly = readonly - Static = isStatic } - | ClassMember.PrivateProp(_) -> + match typeAnn with + | Some t -> + let typeAnn = migrateTypeAnn t + + [ ClassElem.Property + { Name = name + TypeAnn = typeAnn + Optional = optional + Readonly = readonly + Static = isStatic } ] + | None -> + match accessMod with + | Some Accessibility.Private -> [] + | _ -> + printfn $"failed to migrate property '{name}'" + printfn $"elem = %A{elem}" + + failwith + "all public class properties must have a type annotation" + + | ClassMember.PrivateProp _ -> failwith "TODO: migrateClassDecl - PrivateProp" - | ClassMember.TsIndexSignature(_) -> + | ClassMember.TsIndexSignature _ -> failwith "TODO: migrateClassDecl - TsIndexSignature" - | ClassMember.Empty(_) -> failwith "TODO: migrateClassDecl - Empty" - | ClassMember.StaticBlock(_) -> + | ClassMember.Empty _ -> failwith "TODO: migrateClassDecl - Empty" + | ClassMember.StaticBlock _ -> failwith "TODO: migrateClassDecl - StaticBlock" - | ClassMember.AutoAccessor(_) -> + | ClassMember.AutoAccessor _ -> failwith "TODO: migrateClassDecl - AutoAccessor") body @@ -1076,7 +1100,35 @@ module rec Migrate = let kind = DeclKind.TypeDecl decl [ { Kind = kind; Span = DUMMY_SPAN } ] - | Decl.TsEnum _ -> failwith "TODO: migrate enum" + | Decl.TsEnum { Export = export + Declare = declare + IsConst = _ + Id = ident + Members = variants } -> + let variants: list = + variants + |> List.mapi (fun i var -> + let { TsEnumMember.Id = varId; Init = init } = var + + let name = + match varId with + | TsEnumMemberId.Ident ident -> ident.Name + | TsEnumMemberId.Str str -> str.Value + + let init = + match init with + | Some expr -> migrateExpr expr + | None -> + { Kind = ExprKind.Literal(Common.Literal.Number(Common.Int i)) + Span = DUMMY_SPAN + InferredType = None } + + { Name = ident.Name + TypeAnn = None + Init = Some init + Span = DUMMY_SPAN }) + + failwith "TODO: migrate enum" | Decl.TsModule { Export = export Declare = declare Global = _global @@ -1151,7 +1203,8 @@ module rec Migrate = with ex -> // TODO: fix all of the error messages printfn $"Error migrating module item: {ex.Message}" - []) + [] + ) m.Body { Items = items } diff --git a/src/Escalier.Interop/Parser.fs b/src/Escalier.Interop/Parser.fs index 398d627..8b0fc85 100644 --- a/src/Escalier.Interop/Parser.fs +++ b/src/Escalier.Interop/Parser.fs @@ -479,10 +479,10 @@ module Parser = [ callSigDecl attempt constructorSigDecl attempt indexSig // can conflict with propSig when parsing computed properties - attempt propSig attempt getterSig attempt setterSig - attempt methodSig ] + attempt methodSig + attempt propSig ] let typeLit: Parser = between @@ -870,16 +870,18 @@ module Parser = Loc = None } |> Decl.TsEnum - let constructor: Parser = + let constructor: Parser -> ClassMember, unit> = keyword "constructor" >>. fnParams |>> fun ps -> - { Params = List.map ParamOrTsParamProp.Param ps - Body = None - Accessibility = None - IsOptional = false - Loc = None } + fun accessMod -> + { Params = List.map ParamOrTsParamProp.Param ps + Body = None + Accessibility = accessMod + IsOptional = false + Loc = None } + |> ClassMember.Constructor - let classMethod: Parser = + let classMethod: Parser -> ClassMember, unit> = pipe5 (tuple2 (opt (strWs "static")) (opt (strWs "async"))) ident @@ -896,33 +898,42 @@ module Parser = ReturnType = typeAnn Loc = None } - { Key = PropName.Ident id - Function = fn - Kind = MethodKind.Method // TODO: handle getters and setters - IsStatic = isStatic.IsSome - Accessibility = None - IsAbstract = false - IsOptional = false - IsOverride = false - Loc = None } + fun accessMod -> + let method: ClassMethod = + { Key = PropName.Ident id + Function = fn + Kind = MethodKind.Method // TODO: handle getters and setters + IsStatic = isStatic.IsSome + Accessibility = accessMod + IsAbstract = false + IsOptional = false + IsOverride = false + Loc = None } - let classProp: Parser = - pipe5 + ClassMember.Method method + + let accessMod: Parser = + choice + [ keyword "private" |>> fun _ -> Accessibility.Private + keyword "protected" |>> fun _ -> Accessibility.Protected + keyword "public" |>> fun _ -> Accessibility.Public ] + + let classProp: Parser -> ClassMember, unit> = + pipe4 ((opt (keyword "static")) .>>. (opt (keyword "readonly"))) - ident - (opt (pstring "?")) + (ident .>>. (opt (pstring "?"))) (opt (strWs ":" >>. tsTypeAnn)) (opt (strWs "=" >>. expr)) - <| fun (isStatic, isReadonly) name optional typeAnn value -> + <| fun (isStatic, isReadonly) (name, optional) typeAnn value -> - let prop: ClassProp = + fun accessMod -> { Key = PropName.Ident name Value = value TypeAnn = typeAnn IsStatic = isStatic.IsSome // TODO=Decorators // decorators=list - Accessibility = None + Accessibility = accessMod IsAbstract = false IsOptional = optional.IsSome IsOverride = false @@ -930,16 +941,75 @@ module Parser = Declare = false // what is this? Definite = false // what is this? Loc = None } + |> ClassMember.ClassProp + + let classGetter: Parser -> ClassMember, unit> = + pipe3 + (keyword "get" >>. ident) + (opt typeParams) + (strWs "(" >>. strWs ")" >>. (opt (strWs ":" >>. tsTypeAnn))) + <| fun id typeParams typeAnn -> + let fn: Function = + { Params = [] + Body = None + IsGenerator = false // TODO + IsAsync = false // TODO + TypeParams = typeParams + ReturnType = typeAnn + Loc = None } + + fun accessMod -> + { ClassMethod.Key = PropName.Ident id + Function = fn + Kind = MethodKind.Getter + IsStatic = false // TODO + Accessibility = accessMod + IsAbstract = false // TODO + IsOptional = false + IsOverride = false + Loc = None } + |> ClassMember.Method + + let classSetter: Parser -> ClassMember, unit> = + pipe4 + (keyword "set" >>. ident) + (opt typeParams) + (between (strWs "(") (strWs ")") param) + (opt (strWs ":" >>. tsTypeAnn)) + <| fun id typeParams param typeAnn -> + let fn: Function = + { Params = [ param ] + Body = None + IsGenerator = false // TODO + IsAsync = false // TODO + TypeParams = typeParams + ReturnType = typeAnn + Loc = None } - prop + fun accessMod -> + { ClassMethod.Key = PropName.Ident id + Function = fn + Kind = MethodKind.Getter + IsStatic = false // TODO + Accessibility = accessMod + IsAbstract = false // TODO + IsOptional = false + IsOverride = false + Loc = None } + |> ClassMember.Method let classMember: Parser = - choice - [ constructor |>> ClassMember.Constructor - attempt classMethod |>> ClassMember.Method - attempt classProp |>> ClassMember.ClassProp ] - .>> (opt (pstring ";")) - .>> ws + pipe2 + (opt accessMod) + (choice + [ constructor + attempt classMethod + attempt classGetter + attempt classSetter + attempt classProp ] + .>> (opt (pstring ";")) + .>> ws) + <| fun accessMod mem -> mem accessMod let classDecl: Parser Decl, unit> = pipe5 diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseEnum.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseEnum.verified.txt index 56cbc2d..7abde41 100644 --- a/src/Escalier.Parser.Tests/snapshots/Tests.ParseEnum.verified.txt +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseEnum.verified.txt @@ -39,6 +39,7 @@ output: Ok Span = { Start = (Ln: 3, Col: 10) Stop = (Ln: 3, Col: 35) } InferredType = None } + Init = None Span = { Start = (Ln: 3, Col: 7) Stop = (Ln: 4, Col: 7) } }; { Name = "Bar" @@ -72,6 +73,7 @@ output: Ok Span = { Start = (Ln: 4, Col: 11) Stop = (Ln: 4, Col: 33) } InferredType = None } + Init = None Span = { Start = (Ln: 4, Col: 7) Stop = (Ln: 5, Col: 5) } }] } Span = { Start = (Ln: 2, Col: 5) diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseGenericEnum.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseGenericEnum.verified.txt index 089df6d..9b2492d 100644 --- a/src/Escalier.Parser.Tests/snapshots/Tests.ParseGenericEnum.verified.txt +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseGenericEnum.verified.txt @@ -46,6 +46,7 @@ output: Ok Span = { Start = (Ln: 3, Col: 10) Stop = (Ln: 3, Col: 13) } InferredType = None } + Init = None Span = { Start = (Ln: 3, Col: 7) Stop = (Ln: 4, Col: 7) } }; { Name = "Bar" @@ -62,6 +63,7 @@ output: Ok Span = { Start = (Ln: 4, Col: 10) Stop = (Ln: 4, Col: 13) } InferredType = None } + Init = None Span = { Start = (Ln: 4, Col: 7) Stop = (Ln: 5, Col: 7) } }; { Name = "Baz" @@ -78,6 +80,7 @@ output: Ok Span = { Start = (Ln: 5, Col: 10) Stop = (Ln: 5, Col: 13) } InferredType = None } + Init = None Span = { Start = (Ln: 5, Col: 7) Stop = (Ln: 6, Col: 5) } }] } Span = { Start = (Ln: 2, Col: 5) diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionEnum.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionEnum.verified.txt index 133d1f0..a34634c 100644 --- a/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionEnum.verified.txt +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionEnum.verified.txt @@ -23,6 +23,7 @@ output: Ok Variants = [{ Name = "None" TypeAnn = None + Init = None Span = { Start = (Ln: 3, Col: 7) Stop = (Ln: 4, Col: 7) } }; { Name = "Some" @@ -39,6 +40,7 @@ output: Ok Span = { Start = (Ln: 4, Col: 11) Stop = (Ln: 4, Col: 14) } InferredType = None } + Init = None Span = { Start = (Ln: 4, Col: 7) Stop = (Ln: 5, Col: 5) } }] } Span = { Start = (Ln: 2, Col: 5) diff --git a/src/Escalier.Parser/Parser.fs b/src/Escalier.Parser/Parser.fs index 1d0e0fb..3242234 100644 --- a/src/Escalier.Parser/Parser.fs +++ b/src/Escalier.Parser/Parser.fs @@ -1642,6 +1642,7 @@ module Parser = { Name = name TypeAnn = kind + Init = None Span = { Start = start; Stop = stop } } let private enumDecl: Parser = diff --git a/src/Escalier.TypeChecker/Infer.fs b/src/Escalier.TypeChecker/Infer.fs index 7112a2c..6d87258 100644 --- a/src/Escalier.TypeChecker/Infer.fs +++ b/src/Escalier.TypeChecker/Infer.fs @@ -2160,6 +2160,9 @@ module rec Infer = let! exprs = List.traverseResultM (inferTypeAnn ctx env) exprs return TypeKind.TemplateLiteral { Parts = parts; Exprs = exprs } | TypeAnnKind.Intrinsic -> return TypeKind.Intrinsic + | TypeAnnKind.ImportType _ -> + return! + Error(TypeError.NotImplemented "TODO: inferTypeAnn - ImportType") } let t: Result = @@ -3672,6 +3675,9 @@ module rec Infer = let mutable variantTags = [] for variant in variants do + if variant.Init.IsSome then + failwith "TODO: enum variants where .Init.IsSome is true" + let tag = ctx.FreshSymbol() variantTags <-