Skip to content
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

[WIP] Fix importing generic types #312

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 3 additions & 2 deletions src/bridge.fs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ module internal Bridge =
| Bridge.Web _ -> "moduleName"
let private createProgram bridge =
let createDummy tsPaths (sourceFiles: SourceFile list) =
let allFiles = set [ yield! tsPaths; for f in sourceFiles -> f.fileName ]
let options = jsOptions<CompilerOptions>(fun o ->
o.target <- Some scriptTarget
o.``module`` <- Some ModuleKind.CommonJS
Expand All @@ -87,8 +88,8 @@ module internal Bridge =
o.getCanonicalFileName <- id
o.getCurrentDirectory <- fun _ -> ""
o.getNewLine <- fun _ -> "\r\n"
o.fileExists <- fun fileName -> List.contains fileName tsPaths
o.readFile <- fun _ -> Some ""
o.fileExists <- fun fileName -> allFiles |> Seq.contains fileName
o.readFile <- fun _fileName -> Some ""
o.directoryExists <- fun _ -> true
o.getDirectories <- fun _ -> ResizeArray []
)
Expand Down
11 changes: 4 additions & 7 deletions src/node/write.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,10 @@ let getFsFileOut (fsPath: string) (tsPaths: string list) (exports: string list)



let emitFsFileOutAsLines (fsPath: string) (fsFileOut: FsFileOut) =
let file = fs.createWriteStream (!^fsPath)
let lines = List []
for line in printFsFile Version.version fsFileOut do
lines.Add(line)
file.write(sprintf "%s%c" line '\n') |> ignore
file.``end``()
let emitFsFileOutAsLines (fsPath: string) (fsFileOut: FsFileOut) =
let lines = printFsFile Version.version fsFileOut
let fullText = lines |> String.concat "\n"
fs.writeFileSync(!! fsPath, !! (fullText + "\n"))
lines |> List.ofSeq

let emitFsFileOut fsPath (fsFileOut: FsFileOut) =
Expand Down
5 changes: 3 additions & 2 deletions src/print.fs
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,12 @@ let rec printModule (lines: ResizeArray<string>) (indent: string) (md: FsModule)
match imp with
| FsImport.Type imptp ->
let imsp = imptp.ImportSpecifier
let tpxs = printTypeParameters imsp.TypeParameters
match imsp.PropertyName with
| Some pn ->
sprintf "%stype %s = %s.%s" indent imsp.Name imptp.SpecifiedModule pn |> lines.Add
sprintf "%stype %s%s = %s.%s%s" indent imsp.Name tpxs imptp.SpecifiedModule pn tpxs |> lines.Add
| None ->
sprintf "%stype %s = %s.%s" indent imsp.Name imptp.SpecifiedModule imsp.Name |> lines.Add
sprintf "%stype %s%s = %s.%s%s" indent imsp.Name tpxs imptp.SpecifiedModule imsp.Name tpxs |> lines.Add
| _ -> ()
| _ -> ()

Expand Down
115 changes: 86 additions & 29 deletions src/read.fs
Original file line number Diff line number Diff line change
Expand Up @@ -618,42 +618,99 @@ let readExportAssignment(ea: ExportAssignment): FsType =
FsType.ExportAssignment path
| _ -> FsType.None

let readImportDeclaration(im: ImportDeclaration): FsType list =
let readImportDeclaration (checker: TypeChecker) (im: ImportDeclaration): FsType list =

// helper
// see https://stackoverflow.com/a/50528136/1872399
let tryGetTypeParametersFromImportLocation (node) =
let symbolOfImportNameO = checker.getSymbolAtLocation(node)
let metaTypeO = symbolOfImportNameO |> Option.map checker.getDeclaredTypeOfSymbol
let metaSymbolO =
metaTypeO
|> Option.map (fun t -> t.getSymbol())
|> Option.flatten
let declarationsO =
metaSymbolO
|> Option.map (fun s -> s.getDeclarations())
|> Option.flatten
let declarationO =
declarationsO
|> Option.map (Seq.tryHead)
|> Option.flatten

let typeParameters =
match declarationO with
| Some decl ->
match decl.kind with
| SyntaxKind.InterfaceDeclaration ->
let id = decl :?> InterfaceDeclaration
let tpx = readTypeParameters checker id.typeParameters
tpx
| SyntaxKind.ClassDeclaration ->
let cd = decl :?> ClassDeclaration
let tpx = readTypeParameters checker cd.typeParameters
tpx
// can other kinds of declarations also have type parameters? Probably yes
| _ -> []
| None -> []

typeParameters

let moduleSpecifier = im.moduleSpecifier.getText() |> removeQuotes
match im.importClause with
| None -> []
| Some cl ->
match cl.namedBindings with
| None -> []
| Some namedBindings ->
match namedBindings with
| U2.Case1 namespaceImport ->
if isNull namespaceImport.name = false then
// see https://github.com/microsoft/TypeScript/blob/9a62db2b5c5a305139d18f5f25c725f0779493cd/src/compiler/types.ts#L1807-L1817

// either or both of 'namedBindings' and 'name' can be set.

let fromNamedBindings =
match cl.namedBindings with
| None -> []
| Some namedBindings ->
if ts.isNamedImports (!! namedBindings) then
let namedImport : NamedImports = !! namedBindings
namedImport.elements
|> Seq.toList
|> List.map (fun imp ->
let typeParameters = tryGetTypeParametersFromImportLocation imp.name

let import = {
ImportSpecifier = {
Name = imp.name.text
PropertyName = imp.propertyName |> Option.map (fun id -> id.text)
TypeParameters = typeParameters
}
SpecifiedModule = moduleSpecifier
ResolvedModule = None
}
import |> FsImport.Type |> FsType.Import
)
else
let namespaceImport : NamespaceImport = !! namedBindings
[
{ Module = namespaceImport.name.getText(); SpecifiedModule = moduleSpecifier; ResolvedModule = None }
|> FsImport.Module |> FsType.Import
]
else
namespaceImport.getChildren() |> List.ofSeq |> List.collect (fun ch ->
match ch.kind with
| SyntaxKind.SyntaxList ->
let sl = ch :?> SyntaxList
sl.getChildren() |> List.ofSeq |> List.choose (fun slch ->
match slch.kind with
| SyntaxKind.ImportSpecifier ->
let imp = slch :?> ImportSpecifier
{ ImportSpecifier =
{ Name = imp.name.text
PropertyName = imp.propertyName |> Option.map (fun id ->
id.text) }
SpecifiedModule = moduleSpecifier
ResolvedModule = None }
|> FsImport.Type |> FsType.Import |> Some
| _ -> None
)
| _ -> []
)
| U2.Case2 namedImports -> []

let fromName =
match cl.name with
| Some clName ->
let typeParameters = tryGetTypeParametersFromImportLocation clName

let import = {
ImportSpecifier = {
Name = clName.text
PropertyName = None
TypeParameters = typeParameters
}
SpecifiedModule = moduleSpecifier
ResolvedModule = None
}
import |> FsImport.Type |> FsType.Import |> List.singleton
| None -> []

fromNamedBindings @ fromName

let readStatement (checker: TypeChecker) (sd: Statement): FsType list =
match sd.kind with
Expand All @@ -674,7 +731,7 @@ let readStatement (checker: TypeChecker) (sd: Statement): FsType list =
| SyntaxKind.ExportAssignment ->
[readExportAssignment(sd :?> ExportAssignment)]
| SyntaxKind.ImportDeclaration ->
readImportDeclaration(sd :?> ImportDeclaration)
readImportDeclaration checker (sd :?> ImportDeclaration)
| SyntaxKind.NamespaceExportDeclaration ->
// let ns = sd :?> NamespaceExportDeclaration
[]
Expand Down
1 change: 1 addition & 0 deletions src/syntax.fs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ type FsImportSpecifier =
{
PropertyName: string option
Name: string
TypeParameters: FsType list
}

type FsTypeImport =
Expand Down
9 changes: 8 additions & 1 deletion src/transform.fs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,13 @@ let rec fixTypeEx (doFix:FsType->bool) (fix: FsType -> FsType) (tp: FsType): FsT
| FsType.TODO _ -> tp
| FsType.StringLiteral _ -> tp
| FsType.This -> tp
| FsType.Import _ -> tp
| FsType.Import (FsImport.Type timp) ->
let tpx = timp.ImportSpecifier.TypeParameters |> List.map (fixType fix)
{ timp with
ImportSpecifier = { timp.ImportSpecifier with TypeParameters = tpx }
}
|> FsImport.Type |> FsType.Import
| FsType.Import (FsImport.Module _) -> tp
| FsType.GenericParameterDefaults gpd ->
{ gpd with Default = fixType fix gpd.Default }
|> FsType.GenericParameterDefaults
Expand Down Expand Up @@ -576,6 +582,7 @@ let addTicForGenericTypes(f: FsFile): FsFile =
match tp with
| FsType.Interface it -> fixTic it.TypeParameters tp
| FsType.Alias al -> fixTic al.TypeParameters tp
| FsType.Import (FsImport.Type timp) -> fixTic timp.ImportSpecifier.TypeParameters tp
| _ -> tp
)

Expand Down
1 change: 1 addition & 0 deletions test/fragments/reactxp/multiple/ReactXP.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Fable.Import.JS
module ReactXP = __web_ReactXP

module __common_Accessibility =
type SubscribableEvent = Subscribableevent.SubscribableEvent

type [<AllowNullLiteral>] IExports =
abstract Accessibility: AccessibilityStatic
Expand Down
5 changes: 5 additions & 0 deletions test/fragments/regressions/#312-import-generics/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# include all
!*

# exclude actual
*.actual.fs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// ts2fable 0.0.0
module rec test312.actual
open System
open Fable.Core
open Fable.Import.JS

type IGenericInterface<'T> = __core_other.IGenericInterface<'T>

type [<AllowNullLiteral>] IExports =
abstract f: a: IGenericInterface<float> * b: IGenericInterfaceAlias<float> -> unit

type IGenericInterfaceAlias<'T> =
IGenericInterface<'T>

module __core_other =

type [<AllowNullLiteral>] IGenericInterface<'T> =
abstract value: 'T with get, set
15 changes: 15 additions & 0 deletions test/fsFileTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,21 @@ describe "transform tests" <| fun _ ->
it "regression #292 static props" <| fun _ ->
runRegressionTest "#292-static-props"

// https://github.com/fable-compiler/ts2fable/issues/312
it "regression #312 import generics" <| fun _ ->
// assume cwd is the repository root
let originalCwd = Node.``process``.cwd()
Node.``process``.chdir(Node.path.join(ResizeArray([originalCwd; "test/fragments/regressions/#312-import-generics"])))
try
let tsFile = "node_modules/test312/index.d.ts"
let fsFile = "test312.actual.fs"
ts2fable.node.Write.writeFile [tsFile] fsFile ["test312"]
let expectedFsFile = "test312.expected.fs"
fileLinesEqual (ts2fable.node.FileSystem.readLines expectedFsFile) (ts2fable.node.FileSystem.readLines fsFile)
finally
Node.``process``.chdir(originalCwd)

// https://github.com/fable-compiler/ts2fable/issues/314
it "regression #314 inline destruct" <| fun _ ->
runRegressionTest "#314-inline-destruct"