Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Fixed

* Fix internal error "no 'value__' field found for enumeration type" when an `enum` constraint is checked against an enum declared in the same recursive group (`module rec` or an `and` group). The constraint is now solved once the representations of the group are established. ([Issue #14580](https://github.com/dotnet/fsharp/issues/14580), [PR #20454](https://github.com/dotnet/fsharp/pull/20454))
* Better diagnostic for a bare `enum` constraint: `'T : enum` now reports FS0699 "An 'enum' constraint must be of the form 'enum<type>'" instead of "Unexpected identifier: 'enum (4)'", and parser messages for invalid constraints no longer leak internal ` (2)`/` (3)`/` (4)` markers. ([Issue #14580](https://github.com/dotnet/fsharp/issues/14580), [PR #20454](https://github.com/dotnet/fsharp/pull/20454))
* Fix `NativePtr.stackalloc` nested in a larger expression (e.g. a call argument or the right of an assignment) producing an assembly that throws `InvalidProgramException` at load. ([Issue #8083](https://github.com/dotnet/fsharp/issues/8083), [PR #20302](https://github.com/dotnet/fsharp/pull/20302))
* Fix internal error "Unexpected generalized type variables when compiling an active pattern" when an active pattern is used in a `let` binding whose right-hand side is a generic value, e.g. `let (T) = id`. Such a binding is now checked like the equivalent `match` and is not generalized. ([Issue #16856](https://github.com/dotnet/fsharp/issues/16856), [PR #20383](https://github.com/dotnet/fsharp/pull/20383))
* Fix Release-only (`--optimize+`) `System.InvalidProgramException` from `Seq.collect` / `yield!` over a value-type (struct) collection implementing `seq<'T>` (e.g. `ImmutableArray<_>`) when materialised with `List.ofSeq` / `Seq.toList` / `Seq.toArray` or a list/array comprehension. The collector lowering now boxes a struct sub-collection to `seq<'T>` before calling `AddMany`/`AddManyAndClose` (matching the coercion the type checker already inserts for `yield!`), and uses `unit` as the try/finally result type instead of the body type (removing a spurious `ldnull` store). ([Issue #20203](https://github.com/dotnet/fsharp/issues/20203))
Expand Down
17 changes: 16 additions & 1 deletion src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3121,7 +3121,22 @@ and SolveTypeIsEnum (csenv: ConstraintSolverEnv) ndeep m2 trace ty underlying =
AddConstraint csenv ndeep m2 trace destTypar (TyparConstraint.IsEnum(underlying, m))
| _ ->
if isEnumTy g ty then
SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace underlying (underlyingTypeOfEnumTy g ty)
match tryUnderlyingTypeOfEnumTy g ty with
| ValueSome underlyingTyOfEnum ->
SolveTypeEqualsTypeKeepAbbrevs csenv ndeep m2 trace underlying underlyingTyOfEnum
// The underlying type is unknown until the representations of the recursive group are established
| ValueNone ->
csenv.SolverState.PushPostInferenceCheck(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ Invalid enum constraints in recursive .fsi files go undiagnosed in FCS: CheckOneSigFile never runs this deferred check.

// Mismatch.fsi
module rec Mismatch
type E = A = 1
type I<'T when 'T : enum<int64>> = interface end
type Alias = I<E>

false,
fun () ->
PostponeOnFailedMemberConstraintResolution
csenv
NoTrace
(fun csenv -> SolveTypeIsEnum csenv ndeep m2 NoTrace ty underlying)
(fun res -> ErrorD(ErrorFromAddingConstraint(denv, res, m)))
|> RaiseOperationResult)

CompleteD
else
ErrorD (ConstraintSolverError(FSComp.SR.csTypeIsNotEnumType(NicePrint.minimalRichTextOfType denv ty), m, m2))

Expand Down
43 changes: 24 additions & 19 deletions src/Compiler/TypedTree/TypedTreeOps.ExprConstruction.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,38 +1267,43 @@ module internal TypeTesters =
| _ -> getErasedTypes g domainTy false @ getErasedTypes g rangeTy false
| TType_measure _ -> [ ty ]

let underlyingTypeOfEnumTy (g: TcGlobals) ty =
let tryUnderlyingTypeOfEnumTy (g: TcGlobals) ty =
assert (isEnumTy g ty)

match metadataOfTy g ty with
#if !NO_TYPEPROVIDERS
| ProvidedTypeMetadata info -> info.UnderlyingTypeOfEnum()
| ProvidedTypeMetadata info -> ValueSome(info.UnderlyingTypeOfEnum())
#endif
| ILTypeMetadata(TILObjectReprData(_, _, tdef)) ->

let info = computeILEnumInfo (tdef.Name, tdef.Fields)
let ilTy = getTyOfILEnumInfo info

match ilTy.TypeSpec.Name with
| "System.Byte" -> g.byte_ty
| "System.SByte" -> g.sbyte_ty
| "System.Int16" -> g.int16_ty
| "System.Int32" -> g.int32_ty
| "System.Int64" -> g.int64_ty
| "System.UInt16" -> g.uint16_ty
| "System.UInt32" -> g.uint32_ty
| "System.UInt64" -> g.uint64_ty
| "System.Single" -> g.float32_ty
| "System.Double" -> g.float_ty
| "System.Char" -> g.char_ty
| "System.Boolean" -> g.bool_ty
| _ -> g.int32_ty
| "System.Byte" -> ValueSome g.byte_ty
| "System.SByte" -> ValueSome g.sbyte_ty
| "System.Int16" -> ValueSome g.int16_ty
| "System.Int32" -> ValueSome g.int32_ty
| "System.Int64" -> ValueSome g.int64_ty
| "System.UInt16" -> ValueSome g.uint16_ty
| "System.UInt32" -> ValueSome g.uint32_ty
| "System.UInt64" -> ValueSome g.uint64_ty
| "System.Single" -> ValueSome g.float32_ty
| "System.Double" -> ValueSome g.float_ty
| "System.Char" -> ValueSome g.char_ty
| "System.Boolean" -> ValueSome g.bool_ty
| _ -> ValueSome g.int32_ty
| FSharpOrArrayOrByrefOrTupleOrExnTypeMetadata ->
let tycon = (tcrefOfAppTy g ty).Deref
match (tcrefOfAppTy g ty).Deref.GetFieldByName "value__" with
| Some rf -> ValueSome rf.FormalType
| None -> ValueNone

match tycon.GetFieldByName "value__" with
| Some rf -> rf.FormalType
| None -> error (InternalError("no 'value__' field found for enumeration type " + tycon.LogicalName, tycon.Range))
let underlyingTypeOfEnumTy (g: TcGlobals) ty =
match tryUnderlyingTypeOfEnumTy g ty with
| ValueSome underlyingTy -> underlyingTy
| ValueNone ->
let tycon = (tcrefOfAppTy g ty).Deref
error (InternalError("no 'value__' field found for enumeration type " + tycon.LogicalName, tycon.Range))

let normalizeEnumTy g ty =
(if isEnumTy g ty then underlyingTypeOfEnumTy g ty else ty)
Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/TypedTree/TypedTreeOps.ExprConstruction.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,10 @@ module internal TypeTesters =
// Return all components of this type expression that cannot be tested at runtime
val getErasedTypes: TcGlobals -> TType -> checkForNullness: bool -> TType list

/// Determine the underlying type of an enum type (normally int32).
/// ValueNone while the representation of an F# enum is still being established.
val tryUnderlyingTypeOfEnumTy: TcGlobals -> TType -> TType voption

/// Determine the underlying type of an enum type (normally int32)
val underlyingTypeOfEnumTy: TcGlobals -> TType -> TType

Expand Down
7 changes: 4 additions & 3 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -2719,7 +2719,7 @@ typeConstraint:
{ SynTypeConstraint.WhereTyparSupportsNull($1, lhs parseState) }

| typar COLON IDENT NULL
{ if $3 <> "not" then reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier($3 + " (2)"))
{ if $3 <> "not" then reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier($3))
let trivia : SynTypeConstraintWhereTyparNotSupportsNullTrivia = { ColonRange = rhs parseState 2; NotRange = rhs parseState 3 }
SynTypeConstraint.WhereTyparNotSupportsNull($1, lhs parseState, trivia) }

Expand All @@ -2741,14 +2741,15 @@ typeConstraint:
| "enum" ->
let _ltm, _gtm, args, _commas, mWhole = $4
SynTypeConstraint.WhereTyparIsEnum($1, args, unionRanges $1.Range mWhole)
| nm -> raiseParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier(nm + " (3)")) }
| nm -> raiseParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier(nm)) }

| typar COLON IDENT
{ match $3 with
| "comparison" -> SynTypeConstraint.WhereTyparIsComparable($1, lhs parseState)
| "equality" -> SynTypeConstraint.WhereTyparIsEquatable($1, lhs parseState)
| "unmanaged" -> SynTypeConstraint.WhereTyparIsUnmanaged($1, lhs parseState)
| nm -> raiseParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier(nm + " (4)")) }
| "enum" -> raiseParseErrorAt (rhs parseState 3) (FSComp.SR.tcInvalidEnumConstraint())
| nm -> raiseParseErrorAt (rhs parseState 3) (FSComp.SR.parsUnexpectedIdentifier(nm)) }

| appTypeWithoutNull
{ SynTypeConstraint.WhereSelfConstrained($1, lhs parseState) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Conformance.Constraints

open Xunit
open FSharp.Test.Compiler

module ConstraintSyntax =

// https://github.com/dotnet/fsharp/issues/14580
[<Fact>]
let ``Bare 'enum' constraint reports the enum constraint form error`` () =
Fsx """
type I<'T when 'T : enum> = interface end
"""
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 699, Line 2, Col 21, Line 2, Col 25, "An 'enum' constraint must be of the form 'enum<type>'")

[<Fact>]
let ``Unknown identifier constraint reports the identifier without internal markers`` () =
Fsx """
type I<'T when 'T : notAConstraint> = interface end
"""
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 571, Line 2, Col 21, Line 2, Col 35, "Unexpected identifier: 'notAConstraint'")

[<Fact>]
let ``Unknown identifier constraint with type arguments reports the identifier without internal markers`` () =
Fsx """
type I<'T when 'T : notAConstraint<int>> = interface end
"""
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 571, Line 2, Col 21, Line 2, Col 35, "Unexpected identifier: 'notAConstraint'")

[<Fact>]
let ``Unknown identifier before 'null' constraint reports the identifier without internal markers`` () =
Fsx """
type I<'T when 'T : maybe null> = interface end
"""
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 571, Line 2, Col 21, Line 2, Col 26, "Unexpected identifier: 'maybe'")

[<Fact>]
let ``'enum' constraint with an underlying type is accepted`` () =
Fsx """
type I<'T when 'T : enum<int>> = interface end
type E = A = 1
type Ok = I<E>
"""
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/14580
[<Fact>]
let ``'enum' constraint on a type of the same recursive group is accepted`` () =
FSharp """
module rec MyModule

type MyEnum =
| Alpha = 1
| Beta = 2

type MyInter<'TEnum when 'TEnum : enum<int>> = interface end

type MyAlias = MyInter<MyEnum>
"""
|> asLibrary
|> compile
|> shouldSucceed

[<Fact>]
let ``'enum' constraint on a type of the same 'and' group is accepted`` () =
FSharp """
module MyModule

type MyEnum =
| Alpha = 1
| Beta = 2

and MyInter<'TEnum when 'TEnum : enum<int>> = interface end

and MyAlias = MyInter<MyEnum>
"""
|> asLibrary
|> compile
|> shouldSucceed

[<Fact>]
let ``'enum' constraint on an inherited interface of the same recursive group is accepted`` () =
FSharp """
module rec MyModule

type MyEnum =
| Alpha = 1

type MyInter<'TEnum when 'TEnum : enum<int>> = interface end

type C() =
interface MyInter<MyEnum>
"""
|> asLibrary
|> compile
|> shouldSucceed

[<Fact>]
let ``Mismatched 'enum' constraint on a type of the same recursive group is reported`` () =
FSharp """
module rec MyModule

type MyEnum =
| Alpha = 1

type MyInter<'TEnum when 'TEnum : enum<int64>> = interface end

type MyAlias = MyInter<MyEnum>
"""
|> asLibrary
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withDiagnosticMessageMatches "The type 'int64' does not match the type 'int'"

[<Fact>]
let ``Mismatched 'enum' constraint outside a recursive group is reported`` () =
FSharp """
module MyModule

type MyEnum =
| Alpha = 1

type MyInter<'TEnum when 'TEnum : enum<int64>> = interface end

type MyAlias = MyInter<MyEnum>
"""
|> asLibrary
|> withOptions ["--test:ErrorRanges"]
|> typecheck
|> shouldFail
|> withSingleDiagnostic (Error 1, Line 9, Col 16, Line 9, Col 31, "The type 'int64' does not match the type 'int'")

[<Fact>]
let ``Generic code over an 'enum' constraint in a recursive module runs`` () =
FSharp """
module rec MyModule

type Color =
| Red = 1
| Blue = 4

let combine<'T when 'T : enum<int>> (a: 'T) (b: 'T) : 'T =
LanguagePrimitives.EnumOfValue (LanguagePrimitives.EnumToValue a ||| LanguagePrimitives.EnumToValue b)

[<EntryPoint>]
let main _ =
if LanguagePrimitives.EnumToValue (combine Color.Red Color.Blue) <> 5 then
failwith "expected Red ||| Blue to be 5"
0
"""
|> asExe
|> compileExeAndRun
|> shouldSucceed
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
<Compile Include="Conformance\BasicGrammarElements\UseBindings\UseBangBindings.fs" />
<Compile Include="Conformance\BasicGrammarElements\UseBindings\UseBindingsAndExtensionMembers.fs" />
<Compile Include="Conformance\Constraints\Unmanaged.fs" />
<Compile Include="Conformance\Constraints\ConstraintSyntax.fs" />
<Compile Include="Conformance\GeneratedEqualityHashingComparison\Attributes\Diags\Attributes_Diags.fs" />
<Compile Include="Conformance\GeneratedEqualityHashingComparison\Attributes\Legacy\Attributes_Legacy.fs" />
<Compile Include="Conformance\GeneratedEqualityHashingComparison\Basic\Basic.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Module

type I<'T when 'T : enum> = interface end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ImplFile
(ParsedImplFileInput
("/root/SynTyparDecl/Constraint - Enum 01.fs", false,
QualifiedNameOfFile Module, [],
[SynModuleOrNamespace
([Module], false, NamedModule,
[Types
([SynTypeDefn
(SynComponentInfo
([], None, [], None,
PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector),
false, None, (3,5--3,25)),
ObjectModel (Interface, [], (3,28--3,41)), [], None,
(3,5--3,41), { LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,26--3,27)
WithKeyword = None })], (3,0--3,41))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--3,41), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
WarnDirectives = []
CodeComments = [] }, set []))

(3,20)-(3,24) parse error An 'enum' constraint must be of the form 'enum<type>'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Module

type I<'T when 'T : notAConstraint> = interface end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ImplFile
(ParsedImplFileInput
("/root/SynTyparDecl/Constraint - Unknown identifier 01.fs", false,
QualifiedNameOfFile Module, [],
[SynModuleOrNamespace
([Module], false, NamedModule,
[Types
([SynTypeDefn
(SynComponentInfo
([], None, [], None,
PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector),
false, None, (3,5--3,35)),
ObjectModel (Interface, [], (3,38--3,51)), [], None,
(3,5--3,51), { LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,36--3,37)
WithKeyword = None })], (3,0--3,51))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--3,51), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
WarnDirectives = []
CodeComments = [] }, set []))

(3,20)-(3,34) parse error Unexpected identifier: 'notAConstraint'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Module

type I<'T when 'T : notAConstraint<int>> = interface end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ImplFile
(ParsedImplFileInput
("/root/SynTyparDecl/Constraint - Unknown identifier 02.fs", false,
QualifiedNameOfFile Module, [],
[SynModuleOrNamespace
([Module], false, NamedModule,
[Types
([SynTypeDefn
(SynComponentInfo
([], None, [], None,
PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector),
false, None, (3,5--3,39)),
ObjectModel (Interface, [], (3,43--3,56)), [], None,
(3,5--3,56), { LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,41--3,42)
WithKeyword = None })], (3,0--3,56))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--3,56), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
WarnDirectives = []
CodeComments = [] }, set []))

(3,20)-(3,34) parse error Unexpected identifier: 'notAConstraint'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Module

type I<'T when 'T : maybe null> = interface end
Loading
Loading