Skip to content

Commit

Permalink
Transparent Compiler various fixes (#16643)
Browse files Browse the repository at this point in the history
  • Loading branch information
0101 authored Feb 2, 2024
1 parent 165e719 commit 589d350
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 64 deletions.
2 changes: 1 addition & 1 deletion docs/release-notes/.FSharp.Compiler.Service/8.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

* Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514))
* `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550))
* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588))
* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588), [PR #16643](https://github.com/dotnet/fsharp/pull/16643))
* Keep parens for problematic exprs (`if`, `match`, etc.) in `$"{(…):N0}"`, `$"{(…),-3}"`, etc. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578))
* Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode [#PR 16471](https://github.com/dotnet/fsharp/pull/16471))

Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Driver/GraphChecking/TrieMapping.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ let processSynModuleOrNamespace<'Decl>
// Only the last node can be a module, depending on the SynModuleOrNamespaceKind.
let rec visit continuation (xs: LongIdent) =
match xs with
| [] -> failwith "should not be empty"
| [] -> ImmutableDictionary.Empty |> continuation
| [ finalPart ] ->
let name = finalPart.idText

Expand Down
72 changes: 53 additions & 19 deletions src/Compiler/Service/FSharpProjectSnapshot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,10 @@ and internal ProjectCore
|> Md5Hasher.addDateTimes (ReferencesOnDisk |> Seq.map (fun r -> r.LastModified))
|> Md5Hasher.addBytes' (
ReferencedProjects
|> Seq.map (fun (FSharpReference(_name, p)) -> p.ProjectSnapshot.SignatureVersion)
|> Seq.map (function
| FSharpReference(_name, p) -> p.ProjectSnapshot.SignatureVersion
| PEReference(getStamp, _) -> Md5Hasher.empty |> Md5Hasher.addDateTime (getStamp ())
| ILModuleReference(_name, getStamp, _) -> Md5Hasher.empty |> Md5Hasher.addDateTime (getStamp ()))
))

let fullHashString = lazy (fullHash.Value |> Md5Hasher.toString)
Expand Down Expand Up @@ -429,20 +432,42 @@ and internal ProjectCore
member _.CacheKey = cacheKey.Value

and [<NoComparison; CustomEquality; Experimental("This FCS API is experimental and subject to change.")>] FSharpReferencedProjectSnapshot =
| FSharpReference of projectOutputFile: string * options: FSharpProjectSnapshot
//| PEReference of projectOutputFile: string * getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader
//| ILModuleReference of
// projectOutputFile: string *
// getStamp: (unit -> DateTime) *
// getReader: (unit -> ILModuleReader)
/// <summary>
/// A reference to an F# project. The physical data for it is stored/cached inside of the compiler service.
/// </summary>
/// <param name="projectOutputFile">The fully qualified path to the output of the referenced project. This should be the same value as the <c>-r</c> reference in the project options for this referenced project.</param>
/// <param name="snapshot">Snapshot of the referenced F# project</param>
| FSharpReference of projectOutputFile: string * snapshot: FSharpProjectSnapshot
/// <summary>
/// A reference to any portable executable, including F#. The stream is owned by this reference.
/// The stream will be automatically disposed when there are no references to FSharpReferencedProject and is GC collected.
/// Once the stream is evaluated, the function that constructs the stream will no longer be referenced by anything.
/// If the stream evaluation throws an exception, it will be automatically handled.
/// </summary>
/// <param name="getStamp">A function that calculates a last-modified timestamp for this reference. This will be used to determine if the reference is up-to-date.</param>
/// <param name="delayedReader">A function that opens a Portable Executable data stream for reading.</param>
| PEReference of getStamp: (unit -> DateTime) * delayedReader: DelayedILModuleReader

/// <summary>
/// A reference to an ILModuleReader.
/// </summary>
/// <param name="projectOutputFile">The fully qualified path to the output of the referenced project. This should be the same value as the <c>-r</c> reference in the project options for this referenced project.</param>
/// <param name="getStamp">A function that calculates a last-modified timestamp for this reference. This will be used to determine if the reference is up-to-date.</param>
/// <param name="getReader">A function that creates an ILModuleReader for reading module data.</param>
| ILModuleReference of
projectOutputFile: string *
getStamp: (unit -> DateTime) *
getReader: (unit -> FSharp.Compiler.AbstractIL.ILBinaryReader.ILModuleReader)

/// <summary>
/// The fully qualified path to the output of the referenced project. This should be the same value as the <c>-r</c>
/// reference in the project options for this referenced project.
/// </summary>
member this.OutputFile =
match this with
| FSharpReference(projectOutputFile, _) -> projectOutputFile
| FSharpReference(projectOutputFile = projectOutputFile)
| ILModuleReference(projectOutputFile = projectOutputFile) -> projectOutputFile
| PEReference(delayedReader = reader) -> reader.OutputFile

/// <summary>
/// Creates a reference for an F# project. The physical data for it is stored/cached inside of the compiler service.
Expand All @@ -458,6 +483,11 @@ and [<NoComparison; CustomEquality; Experimental("This FCS API is experimental a
match this, o with
| FSharpReference(projectOutputFile1, options1), FSharpReference(projectOutputFile2, options2) ->
projectOutputFile1 = projectOutputFile2 && options1 = options2
| PEReference(getStamp1, reader1), PEReference(getStamp2, reader2) ->
reader1.OutputFile = reader2.OutputFile && (getStamp1 ()) = (getStamp2 ())
| ILModuleReference(projectOutputFile1, getStamp1, _), ILModuleReference(projectOutputFile2, getStamp2, _) ->
projectOutputFile1 = projectOutputFile2 && (getStamp1 ()) = (getStamp2 ())
| _ -> false

| _ -> false

Expand Down Expand Up @@ -524,17 +554,19 @@ and [<Experimental("This FCS API is experimental and subject to change.")>] FSha

let! referencedProjects =
options.ReferencedProjects
|> Seq.choose (function
|> Seq.map (function
| FSharpReferencedProject.FSharpReference(outputName, options) ->
Some(
async {
let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator)

return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot)
}
)
// TODO: other types
| _ -> None)
async {
let! snapshot = FSharpProjectSnapshot.FromOptions(options, getFileSnapshot, snapshotAccumulator)

return FSharpReferencedProjectSnapshot.FSharpReference(outputName, snapshot)
}
| FSharpReferencedProject.PEReference(getStamp, reader) ->
async.Return <| FSharpReferencedProjectSnapshot.PEReference(getStamp, reader)
| FSharpReferencedProject.ILModuleReference(outputName, getStamp, getReader) ->
async.Return
<| FSharpReferencedProjectSnapshot.ILModuleReference(outputName, getStamp, getReader))

|> Async.Sequential

let referencesOnDisk, otherOptions =
Expand Down Expand Up @@ -601,7 +633,9 @@ let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshotBase<_>) =
ReferencedProjects =
projectSnapshot.ReferencedProjects
|> Seq.map (function
| FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions))
| FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions)
| PEReference(getStamp, reader) -> FSharpReferencedProject.PEReference(getStamp, reader)
| ILModuleReference(name, getStamp, getReader) -> FSharpReferencedProject.ILModuleReference(name, getStamp, getReader))
|> Seq.toArray
IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment
UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules
Expand Down
81 changes: 58 additions & 23 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,41 @@ type internal TransparentCompiler

member x.FileName = nm
}
| FSharpReferencedProjectSnapshot.PEReference(getStamp, delayedReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
node {
let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable

match ilReaderOpt with
| Some ilReader ->
let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
return ProjectAssemblyDataResult.Available data
| _ ->
// Note 'false' - if a PEReference doesn't find an ILModuleReader then we don't
// continue to try to use an on-disk DLL
return ProjectAssemblyDataResult.Unavailable false
}

member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = delayedReader.OutputFile
}

| FSharpReferencedProjectSnapshot.ILModuleReference(nm, getStamp, getReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
cancellable {
let ilReader = getReader ()
let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
return ProjectAssemblyDataResult.Available data
}
|> NodeCode.FromCancellable

member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = nm
}
]

let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshotBase<_>) =
Expand Down Expand Up @@ -762,20 +797,23 @@ type internal TransparentCompiler
|> List.map (fun (m, fileName) -> m, FSharpFileSnapshot.CreateFromFileSystem(fileName))

return
Some
{
Id = bootstrapId
AssemblyName = assemblyName
OutFile = outFile
TcConfig = tcConfig
TcImports = tcImports
TcGlobals = tcGlobals
InitialTcInfo = initialTcInfo
LoadedSources = loadedSources
LoadClosure = loadClosureOpt
LastFileName = sourceFiles |> List.last
//ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider
}
match sourceFiles with
| [] -> None
| _ ->
Some
{
Id = bootstrapId
AssemblyName = assemblyName
OutFile = outFile
TcConfig = tcConfig
TcImports = tcImports
TcGlobals = tcGlobals
InitialTcInfo = initialTcInfo
LoadedSources = loadedSources
LoadClosure = loadClosureOpt
LastFileName = sourceFiles |> List.tryLast |> Option.defaultValue ""
//ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider
}
}

let ComputeBootstrapInfo (projectSnapshot: ProjectSnapshot) =
Expand Down Expand Up @@ -1112,7 +1150,7 @@ type internal TransparentCompiler
ApplyMetaCommandsFromInputToTcConfig(tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider)
|> ignore

let sink = TcResultsSinkImpl(tcGlobals)
let sink = TcResultsSinkImpl(tcGlobals, file.SourceText)

let hadParseErrors = not (Array.isEmpty file.ParseErrors)

Expand Down Expand Up @@ -1141,16 +1179,13 @@ type internal TransparentCompiler

//fileChecked.Trigger fileName

let newErrors =
Array.append file.ParseErrors (errHandler.CollectedPhasedDiagnostics)

fileChecked.Trigger(fileName, Unchecked.defaultof<_>)

return
{
finisher = finisher
moduleNamesDict = moduleNamesDict
tcDiagnosticsRev = [ newErrors ]
tcDiagnosticsRev = [ errHandler.CollectedPhasedDiagnostics ]
tcDependencyFiles = [ fileName ]
sink = sink
}
Expand Down Expand Up @@ -1353,7 +1388,7 @@ type internal TransparentCompiler
let! snapshotWithSources = LoadSources bootstrapInfo priorSnapshot
let file = snapshotWithSources.SourceFiles |> List.last

let! parseResults = getParseResult projectSnapshot creationDiags file bootstrapInfo.TcConfig
let! parseResults = getParseResult projectSnapshot Seq.empty file bootstrapInfo.TcConfig

let! result, tcInfo = ComputeTcLastFile bootstrapInfo snapshotWithSources

Expand Down Expand Up @@ -1405,8 +1440,7 @@ type internal TransparentCompiler
Some symbolEnv
)

let tcDiagnostics =
[| yield! creationDiags; yield! extraDiagnostics; yield! tcDiagnostics |]
let tcDiagnostics = [| yield! extraDiagnostics; yield! tcDiagnostics |]

let loadClosure = None // TODO: script support

Expand Down Expand Up @@ -1649,7 +1683,8 @@ type internal TransparentCompiler
| ProjectAssemblyDataResult.Available data -> Some data
| _ -> None

let symbolUses = tcInfo.sink |> Seq.map (fun sink -> sink.GetSymbolUses())
let symbolUses =
tcInfo.sink |> Seq.rev |> Seq.map (fun sink -> sink.GetSymbolUses())

let details =
(bootstrapInfo.TcGlobals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,12 +479,12 @@ type SignatureFiles = Yes = 1 | No = 2 | Some = 3
let fuzzingTest seed (project: SyntheticProject) = task {
let rng = System.Random seed

let checkingThreads = 3
let maxModificationDelayMs = 10
let checkingThreads = 10
let maxModificationDelayMs = 50
let maxCheckingDelayMs = 20
//let runTimeMs = 30000
let signatureFileModificationProbability = 0.25
let modificationLoopIterations = 10
let modificationLoopIterations = 50
let checkingLoopIterations = 5

let minCheckingTimeoutMs = 0
Expand Down Expand Up @@ -622,7 +622,7 @@ let fuzzingTest seed (project: SyntheticProject) = task {
}
try
let! _x = threads |> Seq.skip 1 |> Task.WhenAll
let! _x = threads |> Task.WhenAll
()
with
| e ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,27 @@ let ``Tries are built up incrementally`` () =
ParsedInput = parseSourceCode ("D.fs", "module D")
}
|]

for idx, t in trie do
Assert.AreEqual(idx + 1, t.Children.Count)


module InvalidSyntax =

[<Test>]
let ``Unnamed module`` () =
let trie =
getLastTrie
[| { Idx = 0
FileName = "A.fs"
ParsedInput =
parseSourceCode (
"A.fs",
"""
module
()
"""
) } |]

Assert.True trie.Children.IsEmpty
Loading

0 comments on commit 589d350

Please sign in to comment.