Skip to content

Commit

Permalink
Merge pull request fable-compiler#3746 from nojaf/extract-type-checking
Browse files Browse the repository at this point in the history
Extract type-checking from compilation
  • Loading branch information
nojaf authored Feb 10, 2024
2 parents e09608f + d6277a5 commit 6705b20
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 167 deletions.
1 change: 1 addition & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

* [GH-3742](https://github.com/fable-compiler/Fable/pull/3742) Return diagnostics in compile response (by @nojaf)
* [GH-3746](https://github.com/fable-compiler/Fable/pull/3746) Extract type-checking from compilation (by @nojaf)

## 4.0.0-alpha-004 - 2024-01-30

Expand Down
327 changes: 180 additions & 147 deletions src/Fable.Compiler/Library.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Fable.Compiler.CodeServices
namespace Fable.Compiler

open System
open System.IO
Expand All @@ -11,6 +11,12 @@ open Fable.Transforms
open Fable.Compiler.ProjectCracker
open Fable.Compiler.Util

type TypeCheckProjectResult =
{
Assemblies: FSharp.Compiler.Symbols.FSharpAssembly list
ProjectCheckResults: FSharpCheckProjectResults
}

type CompileResult =
{
/// A map of absolute file path to transpiled JavaScript code
Expand Down Expand Up @@ -88,156 +94,183 @@ type BabelWriter
// let sourcePath = defaultArg file sourcePath |> Path.getRelativeFileOrDirPath false targetPath false
// mapGenerator.Force().AddMapping(generated, original, source=sourcePath, ?name=displayName)

let compileFileToJs (com: Compiler) (pathResolver: PathResolver) (outPath: string) (fileExt: string) : Async<string> =
async {
let babel =
FSharp2Fable.Compiler.transformFile com
|> FableTransforms.transformFile com
|> Fable2Babel.Compiler.transformFile com
module CodeServices =

use writer =
new BabelWriter(com, pathResolver, com.ProjectFile, com.CurrentFile, outPath, fileExt = fileExt)
let compileFileToJs
(com: Compiler)
(pathResolver: PathResolver)
(outPath: string)
(fileExt: string)
: Async<string>
=
async {
let babel =
FSharp2Fable.Compiler.transformFile com
|> FableTransforms.transformFile com
|> Fable2Babel.Compiler.transformFile com

do! BabelPrinter.run writer babel
let! output = writer.ReadContentAsString()
return output
}
use writer =
new BabelWriter(com, pathResolver, com.ProjectFile, com.CurrentFile, outPath, fileExt = fileExt)

let compileProjectToJavaScript
(sourceReader: SourceReader)
(checker: InteractiveChecker)
(pathResolver: PathResolver)
(cliArgs: CliArgs)
(crackerResponse: CrackerResponse)
: Async<CompileResult>
=
async {
let! assemblies = checker.GetImportedAssemblies()

let! checkProjectResult =
checker.ParseAndCheckProject(cliArgs.ProjectFile, crackerResponse.ProjectOptions.SourceFiles, sourceReader)

ignore checkProjectResult.Diagnostics

let fableProj =
Project.From(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
checkProjectResult.AssemblyContents.ImplementationFiles,
assemblies,
Log.log,
getPlugin = Reflection.loadType cliArgs
// ?precompiledInfo =
// (projCracked.PrecompiledInfo |> Option.map (fun i -> i :> _)),
)

let opts = cliArgs.CompilerOptions

let! compiledFiles =
crackerResponse.ProjectOptions.SourceFiles
|> Array.filter (fun filePath -> not (filePath.EndsWith(".fsi", StringComparison.Ordinal)))
|> Array.map (fun currentFile ->
async {
let fableLibDir = Path.getRelativePath currentFile crackerResponse.FableLibDir

let compiler: Compiler =
CompilerImpl(
currentFile,
fableProj,
opts,
fableLibDir,
crackerResponse.OutputType,
?outDir = cliArgs.OutDir
)

let outputPath =
Path.ChangeExtension(currentFile, cliArgs.CompilerOptions.FileExtension)

let! js = compileFileToJs compiler pathResolver outputPath cliArgs.CompilerOptions.FileExtension

return currentFile, js
do! BabelPrinter.run writer babel
let! output = writer.ReadContentAsString()
return output
}


let typeCheckProject
(sourceReader: SourceReader)
(checker: InteractiveChecker)
(cliArgs: CliArgs)
(crackerResponse: CrackerResponse)
: Async<TypeCheckProjectResult>
=
async {
let! assemblies = checker.GetImportedAssemblies()

let! checkProjectResult =
checker.ParseAndCheckProject(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
sourceReader
)

return
{
Assemblies = assemblies
ProjectCheckResults = checkProjectResult
}
)
|> Async.Parallel

return
{
CompiledFiles = Map.ofArray compiledFiles
Diagnostics = checkProjectResult.Diagnostics
}
}
}

let compileFileToJavaScript
(sourceReader: SourceReader)
(checker: InteractiveChecker)
(pathResolver: PathResolver)
(cliArgs: CliArgs)
(crackerResponse: CrackerResponse)
(currentFile: string)
: Async<CompileResult>
=
async {
let! dependentFiles =
checker.GetDependentFiles(currentFile, crackerResponse.ProjectOptions.SourceFiles, sourceReader)
let compileMultipleFilesToJavaScript
(pathResolver: PathResolver)
(cliArgs: CliArgs)
(crackerResponse: CrackerResponse)
(typeCheckProjectResult: TypeCheckProjectResult)
(inputFiles: string seq)
: Async<CompileResult>
=
async {
let fableProj =
Project.From(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
typeCheckProjectResult.ProjectCheckResults.AssemblyContents.ImplementationFiles,
typeCheckProjectResult.Assemblies,
Log.log,
getPlugin = Reflection.loadType cliArgs
// ?precompiledInfo =
// (projCracked.PrecompiledInfo |> Option.map (fun i -> i :> _)),
)

let opts = cliArgs.CompilerOptions

let! compiledFiles =
inputFiles
|> Seq.map (fun currentFile ->
async {
let fableLibDir = Path.getRelativePath currentFile crackerResponse.FableLibDir

let compiler: Compiler =
CompilerImpl(
currentFile,
fableProj,
opts,
fableLibDir,
crackerResponse.OutputType,
?outDir = cliArgs.OutDir
)

let outputPath =
Path.ChangeExtension(currentFile, cliArgs.CompilerOptions.FileExtension)

let! js =
compileFileToJs compiler pathResolver outputPath cliArgs.CompilerOptions.FileExtension

return currentFile, js
}
)
|> Async.Parallel

return
{
CompiledFiles = Map.ofArray compiledFiles
Diagnostics = typeCheckProjectResult.ProjectCheckResults.Diagnostics
}
}

let lastFile =
if Array.isEmpty dependentFiles then
currentFile
else
Array.last dependentFiles

let! assemblies = checker.GetImportedAssemblies()

// Type-check the project up until the last dependent file.
let! checkProjectResult =
checker.ParseAndCheckProject(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
sourceReader,
lastFile = lastFile
)

let fableProj =
Project.From(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
checkProjectResult.AssemblyContents.ImplementationFiles,
assemblies,
Log.log,
getPlugin = Reflection.loadType cliArgs
)

let opts = cliArgs.CompilerOptions

let! compiledFiles =
dependentFiles
|> Array.filter (fun filePath -> not (filePath.EndsWith(".fsi", StringComparison.Ordinal)))
|> Array.map (fun currentFile ->
async {
let fableLibDir = Path.getRelativePath currentFile crackerResponse.FableLibDir

let compiler: Compiler =
CompilerImpl(
currentFile,
fableProj,
opts,
fableLibDir,
crackerResponse.OutputType,
?outDir = cliArgs.OutDir
)

let outputPath = Path.ChangeExtension(currentFile, ".js")

let! js = compileFileToJs compiler pathResolver outputPath cliArgs.CompilerOptions.FileExtension

return currentFile, js
let compileFileToJavaScript
(sourceReader: SourceReader)
(checker: InteractiveChecker)
(pathResolver: PathResolver)
(cliArgs: CliArgs)
(crackerResponse: CrackerResponse)
(currentFile: string)
: Async<CompileResult>
=
async {
let! dependentFiles =
checker.GetDependentFiles(currentFile, crackerResponse.ProjectOptions.SourceFiles, sourceReader)

let lastFile =
if Array.isEmpty dependentFiles then
currentFile
else
Array.last dependentFiles

let! assemblies = checker.GetImportedAssemblies()

// Type-check the project up until the last dependent file.
let! checkProjectResult =
checker.ParseAndCheckProject(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
sourceReader,
lastFile = lastFile
)

let fableProj =
Project.From(
cliArgs.ProjectFile,
crackerResponse.ProjectOptions.SourceFiles,
checkProjectResult.AssemblyContents.ImplementationFiles,
assemblies,
Log.log,
getPlugin = Reflection.loadType cliArgs
)

let opts = cliArgs.CompilerOptions

let! compiledFiles =
dependentFiles
|> Array.filter (fun filePath -> not (filePath.EndsWith(".fsi", StringComparison.Ordinal)))
|> Array.map (fun currentFile ->
async {
let fableLibDir = Path.getRelativePath currentFile crackerResponse.FableLibDir

let compiler: Compiler =
CompilerImpl(
currentFile,
fableProj,
opts,
fableLibDir,
crackerResponse.OutputType,
?outDir = cliArgs.OutDir
)

let outputPath = Path.ChangeExtension(currentFile, ".js")

let! js =
compileFileToJs compiler pathResolver outputPath cliArgs.CompilerOptions.FileExtension

return currentFile, js
}
)
|> Async.Parallel

return
{
CompiledFiles = Map.ofArray compiledFiles
Diagnostics = checkProjectResult.Diagnostics
}
)
|> Async.Parallel

return
{
CompiledFiles = Map.ofArray compiledFiles
Diagnostics = checkProjectResult.Diagnostics
}
}
}
Loading

0 comments on commit 6705b20

Please sign in to comment.