Reputation: 17969
Let's say I have a library (.NETStandard 2.0) named "C" that defines a type called "CRecord" (a record).
Let's say I consume this library from a .NET 4.7.2 library called "B". There's a "B" type that makes use of the "CRecord" type.
Now let's say I have an executable .NET4.7.2 project, called "A", which consumes the type from "B", but doesn't really use the component from "C".
When compiling "A", do you think you will get this compiler error below?
Error FS0074: The type referenced through 'C.CRecord' is defined in an assembly that is not referenced. You must add a reference to assembly 'C'. (FS0074) (A)
After much testing, I've come to realise that the answer to this is "it depends". The key seems to lie in the way the type in "B" is actually implemented. Examples:
namespace B
module BModule =
type BSimpleRecord =
{ Baz: uint32; C: CRecord }
type BComplexRecord = private { baz: uint32; c: CRecord } with
member public this.Baz = this.baz
member internal this.C = this.c
static member public New(baz, c) =
{ baz = baz; c = c }
type BComplexType internal (baz: uint32, c: CRecord) =
member val Baz = baz with get
member val internal C = c with get
What type would you say that generates the compiler error? The most logical thing to think would be to answer BSimpleRecord
, because it makes no effort to hide the C
element at all. Right? Right????
For reference, this is A and C code (along with the B functions that return the B instances):
namespace C
type CRecord =
{ Foo: int; Bar: string }
namespace B
module BModule =
let FunctionThatReturnsBSimpleRecord () =
let c = { Foo = 1; Bar = "" }
let b = { Baz = 1u; C = c }
b
let FunctionThatReturnsBComplexRecord () =
let c = { Foo = 1; Bar = "" }
let baz = 10u
let b = BComplexRecord.New(baz, c)
b
let FunctionThatReturnsBComplexType () =
let c = { Foo = 1; Bar = "" }
let baz = 10u
let b = BComplexType(baz, c)
b
(* Program.fs of A project below *)
[<EntryPoint>]
let main argv =
let b = B.BModule.FunctionThatReturnsBComplexType()
printfn "%s" (b.Baz.ToString())
0 // return an integer exit code
Well, my fellow F-sharpers, the answer is actually the least expected one: it's when you use BSimpleRecord
when you don't get the compiler error (and you get the error with the other two).
So I'm still left wondering why? why??? I don't understand this at all. Maybe it's a bug in the F# compiler?
Upvotes: 3
Views: 193
Reputation: 12687
A regular F# record type is identified by the attribute:
[<CompilationMapping(SourceConstructFlags.RecordType)>]
Whenever you change the access level of a record, the SourceConstructFlags
becomes
RecordType | NonPublicRepresentation
and for a regular class (like BComplexType
) it's just
ObjectType
That's the main difference between your working case and the others.
I haven't been able to figure out exactly where in the F# compiler, but it will do assembly exploration unless it's a RecordType
- this is likely happening in NameResolution.fs
in one of the resolve
calls.
Stacktrace here.
It's happening on ResolveExprDotLongIdentAndComputeRange
(for b.Baz
).
Upvotes: 0