From 81f3b83dfd40702f1f3b09f1065627388d24d2c4 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Thu, 15 Oct 2026 22:00:02 +0200 Subject: [PATCH] fix(cli): recompile dependents on signature change in watch --- src/Fable.Cli/Main.fs | 194 ++++++++++++++---- src/Fable.Transforms/Babel/Fable2Babel.fs | 1 + src/Fable.Transforms/Beam/Fable2Beam.fs | 1 + src/Fable.Transforms/Dart/Fable2Dart.fs | 1 + src/Fable.Transforms/FSharp2Fable.Util.fs | 3 + src/Fable.Transforms/FSharp2Fable.fs | 16 +- src/Fable.Transforms/Global/Compiler.fs | 1 + src/Fable.Transforms/Php/Fable2Php.fs | 1 + src/Fable.Transforms/Python/PythonCompiler.fs | 1 + src/Fable.Transforms/Rust/Fable2Rust.fs | 1 + src/Fable.Transforms/State.fs | 151 ++++++++++++++ 11 files changed, 331 insertions(+), 40 deletions(-) diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index a616e2528f..351d9ec0a3 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -220,6 +220,7 @@ module private Util = Logs = com.Logs InlineExprs = Array.empty WatchDependencies = com.WatchDependencies + SignatureDependencies = com.SignatureDependencies |} with e -> return @@ -375,11 +376,11 @@ type ProjectCracked(cliArgs: CliArgs, crackerResponse: CrackerResponse, sourceFi let fableLibDir = Path.getRelativePath currentFile crackerResponse.FableLibDir - let watchDependencies = + let watchDependencies, signatureDependencies = if cliArgs.IsWatch then - Some(HashSet()) + Some(HashSet()), Some(HashSet()) else - None + None, None CompilerImpl( currentFile, @@ -388,7 +389,8 @@ type ProjectCracked(cliArgs: CliArgs, crackerResponse: CrackerResponse, sourceFi fableLibDir, crackerResponse.OutputType, ?outDir = cliArgs.OutDir, - ?watchDependencies = watchDependencies + ?watchDependencies = watchDependencies, + ?signatureDependencies = signatureDependencies ) member _.MapSourceFiles(f) = @@ -440,6 +442,7 @@ type FableCompileResult = Logs: LogEntry[] InlineExprs: (string * InlineExpr)[] WatchDependencies: string[] + SignatureDependencies: string[] |}, {| File: string @@ -457,6 +460,7 @@ type FableCompilerMsg = pathResolver: PathResolver * isSilent: bool * isTriggeredByDependency: (string -> bool) * + getSignatureDependents: (string list * string list -> string[]) * ReplyChannel | FSharpFileTypeChecked of FSharpImplementationFileContents | FSharpCompilationFinished of FSharpCheckProjectResults @@ -469,8 +473,14 @@ type FableCompilerState = PathResolver: PathResolver IsSilent: bool TriggeredByDependency: string -> bool + SourceFiles: Fable.Compiler.File[] + GetSignatureDependents: string list * string list -> string[] + /// Files whose public signature changed in this cycle, and the subset whose literal values changed + SignatureChanges: string list + LiteralChanges: string list FilesToCompile: string[] FilesToCompileSet: Set + FilesCompiledInCycle: Set FilesCheckedButNotCompiled: Set FableFilesToCompileExpectedCount: int FableFilesCompiledCount: int @@ -484,15 +494,29 @@ type FableCompilerState = } static member Create - (fableProj, filesToCompile: string[], ?pathResolver, ?isSilent, ?triggeredByDependency, ?replyChannel) + ( + fableProj, + filesToCompile: string[], + ?sourceFiles, + ?pathResolver, + ?isSilent, + ?triggeredByDependency, + ?getSignatureDependents, + ?replyChannel + ) = { FableProj = fableProj PathResolver = defaultArg pathResolver PathResolver.Dummy IsSilent = defaultArg isSilent false TriggeredByDependency = defaultArg triggeredByDependency (fun _ -> false) + SourceFiles = defaultArg sourceFiles [||] + GetSignatureDependents = defaultArg getSignatureDependents (fun _ -> [||]) + SignatureChanges = [] + LiteralChanges = [] FilesToCompile = filesToCompile FilesToCompileSet = set filesToCompile + FilesCompiledInCycle = set filesToCompile FilesCheckedButNotCompiled = Set.empty FableFilesToCompileExpectedCount = filesToCompile |> Array.filter isImplementationFile |> Array.length FableFilesCompiledCount = 0 @@ -517,6 +541,54 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl let startInThreadPool toMsg work = postTo toMsg work |> Async.Start + let subscriber = + if projCracked.CliArgs.NoParallelTypeCheck then + None + else + Some(FSharpFileTypeChecked >> agent.Post) + + let typeCheck (sourceFiles: Fable.Compiler.File[]) (lastFile: string) = + startInThreadPool + FSharpCompilationFinished + (fun () -> + let filePaths, sourceReader = Fable.Compiler.File.MakeSourceReader sourceFiles + + checker.ParseAndCheckProject( + projCracked.ProjectFile, + filePaths, + sourceReader, + lastFile, + ?subscriber = subscriber + ) + ) + + let updateProject (state: FableCompilerState) (files: FSharpImplementationFileContents list) = + let fableProj = state.FableProj.Update(files) + + let signatureChanges, literalChanges = + ((state.SignatureChanges, state.LiteralChanges), files) + ||> List.fold (fun (signatureChanges, literalChanges) file -> + let fileName = Path.normalizePathAndEnsureFsExtension file.FileName + + match + Map.tryFind fileName state.FableProj.ImplementationFiles, + Map.tryFind fileName fableProj.ImplementationFiles + with + | Some before, Some after when FileSignature.Changed(before.Signature, after.Signature) -> + fileName :: signatureChanges, + (if FileSignature.LiteralsChanged(before.Signature, after.Signature) then + fileName :: literalChanges + else + literalChanges) + | _ -> signatureChanges, literalChanges + ) + + { state with + FableProj = fableProj + SignatureChanges = signatureChanges + LiteralChanges = literalChanges + } + let fableCompile state fileName = let fableProj = state.FableProj @@ -562,37 +634,21 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl pathResolver, isSilent, isTriggeredByDependency, + getSignatureDependents, replyChannel) -> let state = FableCompilerState.Create( state.FableProj, filesToCompile, + sourceFiles, pathResolver, isSilent, isTriggeredByDependency, + getSignatureDependents, replyChannel ) - startInThreadPool - FSharpCompilationFinished - (fun () -> - let filePaths, sourceReader = Fable.Compiler.File.MakeSourceReader sourceFiles - - let subscriber = - if projCracked.CliArgs.NoParallelTypeCheck then - None - else - Some(FSharpFileTypeChecked >> agent.Post) - - checker.ParseAndCheckProject( - projCracked.ProjectFile, - filePaths, - sourceReader, - Array.last filesToCompile, - ?subscriber = subscriber - ) - ) - + typeCheck sourceFiles (Array.last filesToCompile) return! loop state | FSharpFileTypeChecked file -> @@ -613,7 +669,7 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl if not (state.FilesToCompileSet.Contains(fileName)) then state else - let state = { state with FableProj = state.FableProj.Update([ file ]) } + let state = updateProject state [ file ] // For Rust the last file has to compile after every other // one: it re-exports the namespaces the other files @@ -644,7 +700,10 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl let state = { state with - FSharpLogs = CodeServices.getFSharpDiagnostics results.Diagnostics + FSharpLogs = + Array.append + state.FSharpLogs + (CodeServices.getFSharpDiagnostics results.Diagnostics) HasFSharpCompilationFinished = true } @@ -656,7 +715,7 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl else results.AssemblyContents.ImplementationFiles - let state = { state with FableProj = state.FableProj.Update(implFiles) } + let state = updateProject state (List.ofSeq implFiles) let filesToCompile = state.FilesToCompile @@ -669,8 +728,37 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl else state - FableCompiler.CheckIfCompilationIsFinished(state) - return! loop state + let dependents = + if List.isEmpty state.SignatureChanges then + [||] + else + state.GetSignatureDependents(state.SignatureChanges, state.LiteralChanges) + |> Array.filter (fun file -> not (state.FilesCompiledInCycle.Contains file)) + + if Array.isEmpty dependents then + FableCompiler.CheckIfCompilationIsFinished(state) + return! loop state + else + Log.verbose ( + lazy + $"""Signature changed in {String.concat ", " (state.SignatureChanges |> List.map File.relPathToCurDir)}, recompiling {dependents.Length} dependent files""" + ) + + let state = + { state with + SignatureChanges = [] + LiteralChanges = [] + FilesToCompile = dependents + FilesToCompileSet = set dependents + FilesCompiledInCycle = Set.union state.FilesCompiledInCycle (set dependents) + FableFilesToCompileExpectedCount = + state.FableFilesToCompileExpectedCount + + (dependents |> Array.filter isImplementationFile |> Array.length) + HasFSharpCompilationFinished = false + } + + typeCheck state.SourceFiles (Array.last dependents) + return! loop state | FableFileCompiled(fileName, result) -> let state = @@ -747,7 +835,9 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl member _.GetFableProject() = agent.PostAndAsyncReply(GetFableProject) - member _.StartCompilation(sourceFiles, filesToCompile, pathResolver, isSilent, isTriggeredByDependency) = + member _.StartCompilation + (sourceFiles, filesToCompile, pathResolver, isSilent, isTriggeredByDependency, getSignatureDependents) + = async { if Array.isEmpty filesToCompile then return [||], [] @@ -765,6 +855,7 @@ and FableCompiler(checker: InteractiveChecker, projCracked: ProjectCracked, fabl pathResolver, isSilent, isTriggeredByDependency, + getSignatureDependents, channel ) ) @@ -865,6 +956,7 @@ type State = CliArgs: CliArgs ProjectCrackedAndFableCompiler: (ProjectCracked * FableCompiler) option WatchDependencies: Map + SignatureDependencies: Map PendingFiles: string[] DeduplicateDic: ConcurrentDictionary Watcher: Watcher option @@ -900,6 +992,7 @@ type State = CliArgs = cliArgs ProjectCrackedAndFableCompiler = None WatchDependencies = Map.empty + SignatureDependencies = Map.empty Watcher = watchDelay |> Option.map Watcher.Create DeduplicateDic = ConcurrentDictionary() PendingFiles = [||] @@ -1449,23 +1542,49 @@ let private compilationCycle (state: State) (changes: ISet) = | None -> FableCompiler.Init(projCracked) | Some fableCompiler -> async.Return fableCompiler + // Literal values are inlined by the F# compiler, so a changed literal file is followed by every later file + let getSignatureDependents (changedFiles: string list, literalFiles: string list) = + let changedFiles = set changedFiles + + let firstLiteralFile = + literalFiles + |> List.choose (fun file -> Array.tryFindIndex ((=) file) projCracked.SourceFilePaths) + |> List.sort + |> List.tryHead + + projCracked.SourceFilePaths + |> Array.indexed + |> Array.filter (fun (index, path) -> + (match Map.tryFind path state.SignatureDependencies with + | Some dependencies -> dependencies |> Array.exists changedFiles.Contains + | None -> false) + || ( + match firstLiteralFile with + | Some literalIndex -> index > literalIndex + | None -> false + ) + ) + |> Array.map snd + let! fsharpLogs, fableResults = fableCompiler.StartCompilation( projCracked.SourceFiles, // Make sure to pass the up-to-date source files (with cleared hashes for changed files) filesToCompile, state.GetPathResolver(?precompiledInfo = projCracked.PrecompiledInfo), state.SilentCompilation, - fun f -> state.TriggeredByDependency(f, changes) + (fun f -> state.TriggeredByDependency(f, changes)), + getSignatureDependents ) - let logs, watchDependencies = - ((fsharpLogs, state.WatchDependencies), fableResults) - ||> List.fold (fun (logs, deps) -> + let logs, watchDependencies, signatureDependencies = + ((fsharpLogs, state.WatchDependencies, state.SignatureDependencies), fableResults) + ||> List.fold (fun (logs, deps, sigDeps) -> function | Ok res -> let logs = Array.append logs res.Logs let deps = Map.add res.File res.WatchDependencies deps - logs, deps + let sigDeps = Map.add res.File res.SignatureDependencies sigDeps + logs, deps, sigDeps | Error e -> let log = match e.Exception with @@ -1475,13 +1594,14 @@ let private compilationCycle (state: State) (changes: ISet) = LogEntry.MakeError(msg, fileName = e.File, tag = "EXCEPTION") - Array.append logs [| log |], deps + Array.append logs [| log |], deps, sigDeps ) let state = { state with PendingFiles = [||] WatchDependencies = watchDependencies + SignatureDependencies = signatureDependencies SilentCompilation = false } diff --git a/src/Fable.Transforms/Babel/Fable2Babel.fs b/src/Fable.Transforms/Babel/Fable2Babel.fs index f72d32e3a7..f3a630f375 100644 --- a/src/Fable.Transforms/Babel/Fable2Babel.fs +++ b/src/Fable.Transforms/Babel/Fable2Babel.fs @@ -5059,6 +5059,7 @@ module Compiler = member _.GetInlineExpr(fullName) = com.GetInlineExpr(fullName) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddSignatureDependency(fileName) = com.AddSignatureDependency(fileName) member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/Beam/Fable2Beam.fs b/src/Fable.Transforms/Beam/Fable2Beam.fs index 9ee0cf6453..73a6506d27 100644 --- a/src/Fable.Transforms/Beam/Fable2Beam.fs +++ b/src/Fable.Transforms/Beam/Fable2Beam.fs @@ -3842,6 +3842,7 @@ let transformFile (com: Fable.Compiler) (file: File) : Beam.ErlModule = member _.TryGetEntity(ref) = com.TryGetEntity(ref) member _.GetInlineExpr(key) = com.GetInlineExpr(key) member _.AddWatchDependency(file) = com.AddWatchDependency(file) + member _.AddSignatureDependency(file) = com.AddSignatureDependency(file) member _.AddLog(msg, severity, ?range, ?fileName, ?tag) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/Dart/Fable2Dart.fs b/src/Fable.Transforms/Dart/Fable2Dart.fs index f5e9570c08..d590902545 100644 --- a/src/Fable.Transforms/Dart/Fable2Dart.fs +++ b/src/Fable.Transforms/Dart/Fable2Dart.fs @@ -3208,6 +3208,7 @@ module Compiler = member _.GetInlineExpr(fullName) = com.GetInlineExpr(fullName) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddSignatureDependency(fileName) = com.AddSignatureDependency(fileName) member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index cd84edd7dd..c4de960e19 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -2432,6 +2432,7 @@ module Util = if not com.IsPrecompilingInlineFunction && file = com.CurrentFile then makeIdentExpr entityName else + com.AddSignatureDependency(file) makeInternalClassImport com ent entityName file let entityIdent (com: Compiler) (ent: Fable.EntityRef) = entityIdentWithSuffix com ent "" @@ -2514,6 +2515,8 @@ module Util = if hasOverloadSuffix then com.AddWatchDependency(file) + com.AddSignatureDependency(file) + // Private values are not exported so they can't be imported by call sites in other files. // We need to handle it manually because Fable handle resolve inline function itself if diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index db18d095fb..b31c8b1a95 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -813,6 +813,14 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs | FSharpExprPatterns.Const(value, typ) -> let typ = makeType ctx.GenericArgs typ + + // Enum cases reach Fable as their numeric value, so files using them must follow the enum declaration + do + match typ with + | Fable.Number(_, Fable.NumberInfo.IsEnum entRef) -> + entRef.SourcePath |> Option.iter com.AddSignatureDependency + | _ -> () + let expr = makeTypeConst (makeRangeFrom fsExpr) typ value return expr @@ -1485,10 +1493,11 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs let! argExprs = transformExprList com ctx argExprs let genArgs = makeTypeGenArgs ctx.GenericArgs (getGenericArguments fsType) + let entRef = FsEnt.Ref fsType.TypeDefinition + // Record fields are passed by position, so a field reordering in the declaring file changes this call + entRef.SourcePath |> Option.iter com.AddSignatureDependency - return - Fable.NewRecord(argExprs, FsEnt.Ref fsType.TypeDefinition, genArgs) - |> makeValue r + return Fable.NewRecord(argExprs, entRef, genArgs) |> makeValue r | FSharpExprPatterns.NewAnonRecord(fsType, argExprs) -> let r = makeRangeFrom fsExpr @@ -2947,6 +2956,7 @@ type FableCompiler(com: Compiler) = member _.TryGetEntity(entRef) = com.TryGetEntity(entRef) member _.GetInlineExpr(fullName) = com.GetInlineExpr(fullName) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddSignatureDependency(fileName) = com.AddSignatureDependency(fileName) member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index b54de2efec..a009edd56c 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -87,6 +87,7 @@ type Compiler = abstract TryGetEntity: Fable.EntityRef -> Fable.Entity option abstract GetInlineExpr: string -> InlineExpr abstract AddWatchDependency: file: string -> unit + abstract AddSignatureDependency: file: string -> unit abstract AddLog: msg: string * severity: Severity * ?range: SourceLocation * ?fileName: string * ?tag: string -> unit diff --git a/src/Fable.Transforms/Php/Fable2Php.fs b/src/Fable.Transforms/Php/Fable2Php.fs index 4afbb5ee5b..278f7f8c92 100644 --- a/src/Fable.Transforms/Php/Fable2Php.fs +++ b/src/Fable.Transforms/Php/Fable2Php.fs @@ -2142,6 +2142,7 @@ type PhpCompiler(com: Fable.Compiler) = com.AddLog(msg, severity, ?range = rang, ?fileName = fileName, ?tag = tag) member this.AddWatchDependency(file) = com.AddWatchDependency(file) + member this.AddSignatureDependency(file) = com.AddSignatureDependency(file) member this.GetImplementationFile(fileName) = com.GetImplementationFile(fileName) diff --git a/src/Fable.Transforms/Python/PythonCompiler.fs b/src/Fable.Transforms/Python/PythonCompiler.fs index 63563c02bf..3b1f062230 100644 --- a/src/Fable.Transforms/Python/PythonCompiler.fs +++ b/src/Fable.Transforms/Python/PythonCompiler.fs @@ -136,6 +136,7 @@ type PythonCompiler(com: Compiler) = member _.GetInlineExpr(fullName) = com.GetInlineExpr(fullName) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddSignatureDependency(fileName) = com.AddSignatureDependency(fileName) member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/Rust/Fable2Rust.fs b/src/Fable.Transforms/Rust/Fable2Rust.fs index a904a4efd2..4bcf1bbe0c 100644 --- a/src/Fable.Transforms/Rust/Fable2Rust.fs +++ b/src/Fable.Transforms/Rust/Fable2Rust.fs @@ -6439,6 +6439,7 @@ module Compiler = member _.TryGetEntity(entRef) = com.TryGetEntity(entRef) member _.GetInlineExpr(fullName) = com.GetInlineExpr(fullName) member _.AddWatchDependency(fileName) = com.AddWatchDependency(fileName) + member _.AddSignatureDependency(fileName) = com.AddSignatureDependency(fileName) member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = com.AddLog(msg, severity, ?range = range, ?fileName = fileName, ?tag = tag) diff --git a/src/Fable.Transforms/State.fs b/src/Fable.Transforms/State.fs index fc414d6894..4ab442f112 100644 --- a/src/Fable.Transforms/State.fs +++ b/src/Fable.Transforms/State.fs @@ -135,6 +135,53 @@ type Assemblies(getPlugin, fsharpAssemblies: FSharpAssembly list, addLog: Severi member _.Plugins = plugins +type ParameterSignature = + { + Name: string option + IsOptional: bool + IsParamArray: bool + } + +type MemberSignature = + { + Type: Fable.Type + Parameters: ParameterSignature list list + IsInline: FSharpInlineAnnotation + IsMutable: bool + Attributes: (string * obj list) list + } + +type EntitySignature = + { + Attributes: (string * obj list) list + Kind: bool * bool * bool * bool * bool + Fields: (string * Fable.Type * obj) list + UnionCases: (string * (string * Fable.Type) list) list + } + +/// The parts of a file other files can depend on without naming them in their own typed tree +type FileSignature = + { + IsUnknown: bool + Literals: Map + Members: Map + Entities: Map + } + + static member Unknown = + { + IsUnknown = true + Literals = Map.empty + Members = Map.empty + Entities = Map.empty + } + + static member Changed(before: FileSignature, after: FileSignature) = + before.IsUnknown || after.IsUnknown || before <> after + + static member LiteralsChanged(before: FileSignature, after: FileSignature) = + before.IsUnknown || after.IsUnknown || before.Literals <> after.Literals + type ImplFile = { Declarations: FSharpImplementationFileDeclaration list @@ -142,6 +189,7 @@ type ImplFile = RootComment: FSharpXmlDoc option Entities: IReadOnlyDictionary InlineExprs: (string * InlineExprLazy) list + Signature: FileSignature } static member From(file: FSharpImplementationFileContents) = @@ -154,6 +202,96 @@ type ImplFile = loop entities e.NestedEntities + let signature (declarations: FSharpImplementationFileDeclaration list) = + try + let makeType = FSharp2Fable.TypeHelpers.makeType Map.empty + + let attributes (atts: FSharpAttribute seq) = + atts + |> Seq.map (fun att -> + FSharp2Fable.FsEnt.FullName att.AttributeType, + att.ConstructorArguments |> Seq.map snd |> List.ofSeq + ) + |> List.ofSeq + + let rec members (decls: FSharpImplementationFileDeclaration list) = + decls + |> List.collect ( + function + | FSharpImplementationFileDeclaration.Entity(_, decls) -> members decls + | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue(memb, _, _) when + not memb.IsCompilerGenerated && not memb.Accessibility.IsPrivate + -> + [ memb ] + | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue _ + | FSharpImplementationFileDeclaration.InitAction _ -> [] + ) + + let rec entities (ents: FSharpEntity seq) = + ents |> Seq.collect (fun e -> Seq.append [ e ] (entities e.NestedEntities)) + + let members = members declarations + + { + IsUnknown = false + Literals = + members + |> List.choose (fun memb -> + memb.LiteralValue + |> Option.map (fun value -> FSharp2Fable.Helpers.getMemberUniqueName memb, value) + ) + |> Map + Members = + members + |> List.map (fun memb -> + FSharp2Fable.Helpers.getMemberUniqueName memb, + { + Type = makeType memb.FullType + Parameters = + memb.CurriedParameterGroups + |> Seq.map ( + Seq.map (fun p -> + { + Name = p.Name + IsOptional = p.IsOptionalArg + IsParamArray = p.IsParamArrayArg + } + ) + >> List.ofSeq + ) + |> List.ofSeq + IsInline = memb.InlineAnnotation + IsMutable = memb.IsMutable + Attributes = attributes memb.Attributes + } + ) + |> Map + Entities = + FSharp2Fable.Compiler.getRootFSharpEntities declarations + |> entities + |> Seq.map (fun e -> + FSharp2Fable.FsEnt.FullName e, + { + Attributes = attributes e.Attributes + Kind = e.IsFSharpRecord, e.IsFSharpUnion, e.IsEnum, e.IsInterface, e.IsValueType + Fields = + e.FSharpFields + |> Seq.map (fun f -> f.Name, makeType f.FieldType, Option.toObj f.LiteralValue) + |> List.ofSeq + UnionCases = + e.UnionCases + |> Seq.map (fun c -> + c.Name, + c.Fields |> Seq.map (fun f -> f.Name, makeType f.FieldType) |> List.ofSeq + ) + |> List.ofSeq + } + ) + |> Map + } + with _ -> + FileSignature.Unknown + // add all entities to the entity cache let entities = Dictionary() @@ -172,6 +310,7 @@ type ImplFile = RootModule = rootModule RootComment = rootComment InlineExprs = FSharp2Fable.Compiler.getInlineExprs file.FileName declarations + Signature = signature declarations } type PrecompiledInfo = @@ -319,6 +458,7 @@ type CompilerImpl ?outType: OutputType, ?outDir: string, ?watchDependencies: HashSet, + ?signatureDependencies: HashSet, ?logs: ResizeArray, ?isPrecompilingInlineFunction: bool ) @@ -336,6 +476,11 @@ type CompilerImpl | Some w -> Array.ofSeq w | None -> [||] + member _.SignatureDependencies = + match signatureDependencies with + | Some w -> Array.ofSeq w + | None -> [||] + interface Compiler with member _.Options = options member _.Plugins = project.Assemblies.Plugins @@ -369,6 +514,7 @@ type CompilerImpl outType, ?outDir = outDir, ?watchDependencies = watchDependencies, + ?signatureDependencies = signatureDependencies, logs = logs, isPrecompilingInlineFunction = true ) @@ -429,6 +575,11 @@ type CompilerImpl | Some watchDependencies when file <> currentFile -> watchDependencies.Add(file) |> ignore | _ -> () + member _.AddSignatureDependency(file) = + match signatureDependencies with + | Some signatureDependencies when file <> currentFile -> signatureDependencies.Add(file) |> ignore + | _ -> () + member _.AddLog(msg, severity, ?range, ?fileName: string, ?tag: string) = LogEntry.Make(severity, msg, ?range = range, ?fileName = fileName, ?tag = tag) |> logs.Add