F# int.MaxValue is "not a valid constant expression," but System.Int32.MaxValue is?

.net, data-annotations, f#

Solution

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:

- function `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.

Problem

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.

Original source