Skip to content
Merged
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
30 changes: 30 additions & 0 deletions src/fsharp/CompileOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3378,6 +3378,36 @@ let PostParseModuleSpecs (defaultNamespace,filename,isLastCompiland,ParsedSigFil

ParsedInput.SigFile(ParsedSigFileInput(filename,qualName,scopedPragmas,hashDirectives,specs))


/// Checks if a module name is already given and deduplicates the name if needed.
let DeduplicateModuleName (moduleNamesDict:Dictionary<string,Set<string>>) (paths: Set<string>) path (qualifiedNameOfFile: QualifiedNameOfFile) =
let count = if paths.Contains path then paths.Count else paths.Count + 1
moduleNamesDict.[qualifiedNameOfFile.Text] <- Set.add path paths
let id = qualifiedNameOfFile.Id
if count = 1 then qualifiedNameOfFile else QualifiedNameOfFile(Ident(id.idText + "___" + count.ToString(),id.idRange))

/// Checks if a ParsedInput is using a module name that was already given and deduplicates the name if needed.
let DeduplicateParsedInputModuleName (moduleNamesDict:Dictionary<string,Set<string>>) input =
match input with
| ParsedInput.ImplFile (ParsedImplFileInput.ParsedImplFileInput(fileName,isScript,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules,(isLastCompiland,isExe))) ->
let path = Path.GetDirectoryName fileName
match moduleNamesDict.TryGetValue qualifiedNameOfFile.Text with
| true, paths ->
let qualifiedNameOfFile = DeduplicateModuleName moduleNamesDict paths path qualifiedNameOfFile
ParsedInput.ImplFile(ParsedImplFileInput.ParsedImplFileInput(fileName,isScript,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules,(isLastCompiland,isExe)))
| _ ->
moduleNamesDict.Add(qualifiedNameOfFile.Text,Set.singleton path)
input
| ParsedInput.SigFile (ParsedSigFileInput.ParsedSigFileInput(fileName,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules)) ->
let path = Path.GetDirectoryName fileName
match moduleNamesDict.TryGetValue qualifiedNameOfFile.Text with
| true, paths ->
let qualifiedNameOfFile = DeduplicateModuleName moduleNamesDict paths path qualifiedNameOfFile
ParsedInput.SigFile (ParsedSigFileInput.ParsedSigFileInput(fileName,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules))
| _ ->
moduleNamesDict.Add(qualifiedNameOfFile.Text,Set.singleton path)
input

let ParseInput (lexer,errorLogger:ErrorLogger,lexbuf:UnicodeLexing.Lexbuf,defaultNamespace,filename,isLastCompiland) =
// The assert below is almost ok, but it fires in two cases:
// - fsi.exe sometimes passes "stdin" as a dummy filename
Expand Down
6 changes: 6 additions & 0 deletions src/fsharp/CompileOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ val ComputeQualifiedNameOfFileFromUniquePath : range * string list -> Ast.Qualif

val PrependPathToInput : Ast.Ident list -> Ast.ParsedInput -> Ast.ParsedInput

/// Checks if a module name is already given and deduplicates the name if needed.
val DeduplicateModuleName : Dictionary<string,Set<string>> -> Set<string> -> string -> Ast.QualifiedNameOfFile -> Ast.QualifiedNameOfFile

/// Checks if a ParsedInput is using a module name that was already given and deduplicates the name if needed.
val DeduplicateParsedInputModuleName : Dictionary<string,Set<string>> -> Ast.ParsedInput -> Ast.ParsedInput

val ParseInput : (UnicodeLexing.Lexbuf -> Parser.token) * ErrorLogger * UnicodeLexing.Lexbuf * string option * string * isLastCompiland:(bool * bool) -> Ast.ParsedInput

//----------------------------------------------------------------------------
Expand Down
30 changes: 2 additions & 28 deletions src/fsharp/fsc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1774,35 +1774,9 @@ let main0(ctok, argv, referenceResolver, bannerAlreadyPrinted, openBinariesInMem

let inputs =
// Deduplicate module names
let seen = Dictionary<string,Set<string>>()
let deduplicate (paths: Set<string>) path (qualifiedNameOfFile: QualifiedNameOfFile) =
let count = if paths.Contains path then paths.Count else paths.Count + 1
seen.[qualifiedNameOfFile.Text] <- Set.add path paths
let id = qualifiedNameOfFile.Id
if count = 1 then qualifiedNameOfFile else QualifiedNameOfFile(Ident(id.idText + "___" + count.ToString(),id.idRange))
let moduleNamesDict = Dictionary<string,Set<string>>()
inputs
|> List.map (fun (input,x) ->
match input with
| ParsedInput.ImplFile (ParsedImplFileInput.ParsedImplFileInput(fileName,isScript,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules,(isLastCompiland,isExe))) ->
let path = Path.GetDirectoryName fileName
match seen.TryGetValue qualifiedNameOfFile.Text with
| true, paths ->
let qualifiedNameOfFile = deduplicate paths path qualifiedNameOfFile
let input = ParsedInput.ImplFile(ParsedImplFileInput.ParsedImplFileInput(fileName,isScript,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules,(isLastCompiland,isExe)))
input,x
| _ ->
seen.Add(qualifiedNameOfFile.Text,Set.singleton path)
input,x
| ParsedInput.SigFile (ParsedSigFileInput.ParsedSigFileInput(fileName,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules)) ->
let path = Path.GetDirectoryName fileName
match seen.TryGetValue qualifiedNameOfFile.Text with
| true, paths ->
let qualifiedNameOfFile = deduplicate paths path qualifiedNameOfFile
let input = ParsedInput.SigFile (ParsedSigFileInput.ParsedSigFileInput(fileName,qualifiedNameOfFile,scopedPragmas,hashDirectives,modules))
input,x
| _ ->
seen.Add(qualifiedNameOfFile.Text,Set.singleton path)
input,x)
|> List.map (fun (input,x) -> DeduplicateParsedInputModuleName moduleNamesDict input,x)

if tcConfig.parseOnly then exiter.Exit 0
if not tcConfig.continueAfterParseFailure then
Expand Down
7 changes: 6 additions & 1 deletion src/fsharp/vs/IncrementalBuild.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,9 @@ type IncrementalBuilder(tcGlobals,frameworkTcImports, nonFrameworkAssemblyInputs
let StampFileNameTask (cache: TimeStampCache) _ctok (_m:range, filename:string, _isLastCompiland) =
assertNotDisposed()
cache.GetFileTimeStamp filename

// Deduplicate module names
let moduleNamesDict = Dictionary<string,Set<string>>()

/// This is a build task function that gets placed into the build rules as the computation for a VectorMap
///
Expand All @@ -1365,8 +1368,10 @@ type IncrementalBuilder(tcGlobals,frameworkTcImports, nonFrameworkAssemblyInputs

try
IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed filename)
let result = ParseOneInputFile(tcConfig,lexResourceManager, [], filename ,isLastCompiland,errorLogger,(*retryLocked*)true)
let input = ParseOneInputFile(tcConfig,lexResourceManager, [], filename ,isLastCompiland,errorLogger,(*retryLocked*)true)
fileParsed.Trigger (filename)
let result = Option.map (DeduplicateParsedInputModuleName moduleNamesDict) input

result,sourceRange,filename,errorLogger.GetErrors ()
with exn ->
System.Diagnostics.Debug.Assert(false, sprintf "unexpected failure in IncrementalFSharpBuild.Parse\nerror = %s" (exn.ToString()))
Expand Down
20 changes: 19 additions & 1 deletion tests/service/ProjectAnalysisTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5066,4 +5066,22 @@ let ``Test request for parse and check doesn't check whole project`` () =
(backgroundCheckCount.Value <= 8) |> shouldEqual true // only two extra typechecks of files

()


[<Test>]
// Simplified repro for https://github.com/Microsoft/visualfsharp/issues/2679
let ``add files with same name from different folders`` () =
let fileNames =
[ __SOURCE_DIRECTORY__ + "/data/samename/folder1/a.fs"
__SOURCE_DIRECTORY__ + "/data/samename/folder2/a.fs" ]
let projFileName = __SOURCE_DIRECTORY__ + "/data/samename/tempet.fsproj"
let args = mkProjectCommandLineArgs ("test.dll", fileNames)
let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args)
let wholeProjectResults = checker.ParseAndCheckProject(options) |> Async.RunSynchronously
let errors =
wholeProjectResults.Errors
|> Array.filter (fun x -> x.Severity = FSharpErrorSeverity.Error)
if errors.Length > 0 then
printfn "add files with same name from different folders"
for err in errors do
printfn "ERROR: %s" err.Message
shouldEqual 0 errors.Length
5 changes: 5 additions & 0 deletions tests/service/data/samename/folder1/a.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace tempet

module SayA =
let hello name =
printfn "Hello %s" name
5 changes: 5 additions & 0 deletions tests/service/data/samename/folder2/a.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace tempet

module SayB =
let hello name =
printfn "Hello %s" name
13 changes: 13 additions & 0 deletions tests/service/data/samename/tempet.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="FSharp.NET.Sdk;Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="folder1/a.fs" />
<Compile Include="folder2/a.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FSharp.Core" Version="4.1.*" />
<PackageReference Include="FSharp.NET.Sdk" Version="1.0.*" PrivateAssets="All" />
</ItemGroup>
</Project>