33
votes

We have an F# assembly (AssemblyOne) that references another F# assembly (AssemblyTwo) in a single Visual Studio 2012 solution. AssemblyTwo has a reference to a C# DLL (MyCSharpLib).

A function defined in AssemblyOne calls a function defined in AssemblyTwo:

namespace AssemblyOne

[<RequireQualifiedAccess>]
module MyModuleA =
    let FetchResult id =
        let result = AssemblyTwo.MyModuleC.FetchResult id
        result

The function called in AssemblyTwo calls another function (FetchActualResult()) in the same assembly that takes a parameter of type MyCSharpType that belongs to the referenced C# DLL (MyCSharpLib):

namespace AssemblyTwo

[<RequireQualifiedAccess>]
module MyModuleB  =
    let FetchActualResult(myCSharpType:MyCSharpLib.MyCSharpType, id:int)
        //return a result

[<RequireQualifiedAccess>]
module MyModuleC =
    let FetchResult id =
        let myCSharpType = new MyCSharpLib.MyCSharpType()
        MyModuleB.FetchActualResult(myCSharpType, id)

The solution compiles and builds in Visual Studio; however, when we try to build the project from the command line using MSBuild, the build fails, with the following error in the msbuild.log:

error FS0074: The type referenced through 'MyCSharpLib' is defined in an assembly that is not referenced. You must add a reference to assembly 'MyCSharpLib'.

It appears the type exposed as a parameter from MyCSharpLib in the FetchActualResult() function signature in AssemblyTwo is causing the error. AssemblyOne now needs a reference to MyCSharpLib, even though AssemblyOne does not directly use anything from MyCSharpLib. If we remove the parameter from the function signature the solution builds with no errors.

We have further explored this problem by replicating the code with the following use cases ('->' indicates assembly reference):

  • F# AssemblyOne -> F# AssemblyTwo -> MyCSharpLib (C# DLL) (does not build)
  • F# AssemblyOne -> F# AssemblyTwo -> MyFSharpLib (F# DLL) (does not build)
  • F# AssemblyOne -> F# AssemblyTwo -> C# AssemblyThree (assembly in same solution) (does not build)
  • F# AssemblyOne -> F# AssemblyTwo -> F# AssemblyThree (assembly in same solution) (builds)

Can this behaviour be explained?

2
I'm not sure what's causing this, but the usual things you can check are (1) do they all reference the same version of FSharp.Core.dll? (2) Are they are compiled for the same target framework? - Tomas Petricek
(1) they all reference the same version of FSharp.Core.dll - version 4.3.0.0 and (2) all assemblies target framework v4.5 - daithimurf
Are you sure you're using the same MSBuild version that VS is using? Could you post a full MSBuild log somewhere? - skolima
Both are using ToolsVersion="4.0". - daithimurf
Both VS and msbuild compile the assemblies fine with VS2013. - Rattle

2 Answers

1
votes

Assuming that there is a typo in your sources as DWright pointed out, I'd say that this error may arise just from the fact that by this code you define a static class MyModuleB with exposed method parameter of an external type MyCsharpType.

This is how the Fsharp code translates to IL (from ILSpy - retranslated to Csharp):

...
public static class MyModuleB
{
    public static string FetchActualResult(MyCSharpType myCSharpType, int id)
    {
        return myCSharpType.Fetch(id);
    }
}

If you don't expose the type so that it's statically visible, the error might not appear. This, however, would depend on the implementation of the compiler.

I can imagine, that during compilation of MyModuleA, one configuration of compilation process or version of compiler can try to "touch" MyModuleB, and thus try to reach the unreferenced parameter type, and other might just not touch MyModuleB. It depends.

So the problem seems to me being not in the compilation process, but in the fact, that you expose usage of a type for which you don't reference its assembly.

0
votes

I just now solved a similar case in this way. Try this.

At the end of MyModuleC, add this line:

let fetchResult = FetchResult

And then, in MyModuleA, call fetchResult instead of FetchResult. With argument of course.

Yes, I know it sounds silly, but do try. I believe it will break the unwanted dependency.

If you use AssemblyTwo from C# as is, you probably won't have this issue. It surfaces when you consume AssemblyTwo from F#, so I wonder if there is a problem with the F# compiler, or perhaps it has something to do with currying that is beyond me. Anyway, I wish the F# compiler was smarter. Maybe somebody should file an issue, unless it's already done.