knocte
knocte

Reputation: 17969

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

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

Answers (1)

Asti
Asti

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

Related Questions