TL;DR: the F# compiler interprets int
in this context as the int
operator, as determined by Eugene Fotin and expanded upon by Gene Belitski. The best workaround is to use System.Int32.MaxValue
or a unique type alias as described below.
Consider the following record type:
type User = {
Username : string
}
I want Username
to be at least three characters long, so I use the StringLength
attribute. There is no maximum length, so I set it to int.MaxValue
:
type User = {
[<StringLength(int.MaxValue, MinimumLength=3)>]
Username : string
}
This gives me the following error:
This is not a valid constant expression or custom attribute value.
Everything is peachy if I use System.Int32
instead:
type User = {
[<StringLength(System.Int32.MaxValue, MinimumLength=3)>]
Username : string
}
It also compiles if I alias int
:
type User = {
[<StringLength(num.MaxValue, MinimumLength=3)>]
Username : string
}
and num = int
Or fully qualify the type:
type User = {
[<StringLength(Microsoft.FSharp.Core.int.MaxValue, MinimumLength=3)>]
Username : string
}
I checked in the F# source and int
is defined exactly as you would expect:
type int32 = System.Int32
// Then, a few lines later…
type int = int32
What's going on? I assumed that F# primitive types were interchangeable with other types in most contexts, but it looks like something is missing from my mental model.
That's how F# type inference works in different contexts with different syntactic entities coincidentally having the same name, which in case of int
may be any of:
int:'T->int
of full name Microsoft.FSharp.Core.Operators.int
type int = int32
of full name Microsoft.FSharp.Core.int
type int<'Measure> = int
of full name Microsoft.FSharp.Core.int<_>
One way to demo this workings would be the following scenario: if we just enter
int;;
in FSI we'll get something like
val it : (int -> int) = <fun:it@3>
in other words, it's a function that cannot have MaxValue
property associated with it:
> int.MaxValue;;
int.MaxValue;;
----^^^^^^^^
... error FS0039: The field, constructor or member 'MaxValue' is not defined
The same would apply to int32
, which, when being used in a context of expression is inferred by FSI as just another function with signature (int -> int32)
.
Now when it comes to
type num = int
in this context int
is inferred to be a type name abbreviation for System.Int32
, so num
is a type abbreviation as well, but now name ambiguity does not have place, so num.MaxValue
is inferred exactly what we expect it to be, giving in FSI
> num.MaxValue;;
val it : int = 2147483647
Finally, when you use Microsoft.FSharp.Core.int
you explicitly refer to the type entity, there is no place for ambiguity, so it works as expected.
Back to your use case with attribute parameter - in this context int
is treated by type inference as part of expression to deliver argument value, i.e. as function, unless you explicitly or indirectly set another interpretation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With