From 7ef32cb56a43846988a0bb1167f062b10ce82457 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 21 Jul 2026 11:35:31 +0400 Subject: [PATCH 1/3] Parser: recover on unfinished abstract members --- src/Compiler/Checking/CheckDeclarations.fs | 21 ++++---- src/Compiler/SyntaxTree/ParseHelpers.fs | 29 +++++++++++ src/Compiler/SyntaxTree/ParseHelpers.fsi | 13 +++++ src/Compiler/pars.fsy | 48 ++++++++++-------- .../SyntaxTree/Member/Abstract - Method 01.fs | 6 +++ .../Member/Abstract - Method 01.fs.bsl | 47 ++++++++++++++++++ .../SyntaxTree/Member/Abstract - Method 02.fs | 6 +++ .../Member/Abstract - Method 02.fs.bsl | 49 +++++++++++++++++++ .../Member/Abstract - Property 06.fs | 6 +++ .../Member/Abstract - Property 06.fs.bsl | 45 +++++++++++++++++ .../Member/Abstract - Property 07.fs | 6 +++ .../Member/Abstract - Property 07.fs.bsl | 45 +++++++++++++++++ .../Member/Abstract - Property 08.fs | 6 +++ .../Member/Abstract - Property 08.fs.bsl | 44 +++++++++++++++++ .../Member/Abstract - Property 09.fs | 6 +++ .../Member/Abstract - Property 09.fs.bsl | 45 +++++++++++++++++ 16 files changed, 390 insertions(+), 32 deletions(-) create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs.bsl create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs.bsl create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs.bsl create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs.bsl create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs.bsl create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs create mode 100644 tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs.bsl diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index b5055ce2dd9..7b81000a792 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -3723,17 +3723,16 @@ module EstablishTypeDefinitionCores = let abstractSlots = [ for synValSig, memberFlags in slotsigs do - - let (SynValSig(range=m)) = synValSig - - CheckMemberFlags None NewSlotsOK OverridesOK memberFlags m - - let slots = fst (TcAndPublishValSpec (cenv, envinner, containerInfo, ModuleOrMemberBinding, Some memberFlags, tpenv, synValSig)) - // Multiple slots may be returned, e.g. for - // abstract P: int with get, set - - for slot in slots do - yield mkLocalValRef slot ] + let (SynValSig(ident = (SynIdent(id, _)); range = m)) = synValSig + if id.idText <> "" then + CheckMemberFlags None NewSlotsOK OverridesOK memberFlags m + + let slots = fst (TcAndPublishValSpec (cenv, envinner, containerInfo, ModuleOrMemberBinding, Some memberFlags, tpenv, synValSig)) + // Multiple slots may be returned, e.g. for + // abstract P: int with get, set + + for slot in slots do + yield mkLocalValRef slot ] let kind = match kind with diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index b329d48ee34..dc05d5cab6d 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -1136,3 +1136,32 @@ let mkLetBangExpression Trivia = { InKeyword = mIn } IsFromSource = true // User-written let!/use! bindings } + +let mkAbstractMember parseState attrs (accessBeforeKeyword: SynAccess option) memberFlags (accessBeforeId: SynAccess option) mInline id typeParams typeWithConstraints accessors = + if Option.isSome accessBeforeKeyword then + errorR (Error(FSComp.SR.parsVisibilityDeclarationsShouldComePriorToIdentifier (), rhs parseState 2)) + + let (ty: SynType), arity = typeWithConstraints + let isInline, doc, explicitValTyparDecls = Option.isSome mInline, grabXmlDoc (parseState, attrs, 1), typeParams + let mWith, (getSet, getSetRangeOpt: GetSetKeywords option, getterAccess, setterAccess) = accessors + let getSetAdjuster arity = + match arity, getSet with + | SynValInfo([], _), SynMemberKind.Member -> SynMemberKind.PropertyGet + | _ -> getSet + + let mWhole = + let m = rhs parseState 1 + match getSetRangeOpt with + | None -> unionRanges m ty.Range + | Some gs -> unionRanges m gs.Range + |> unionRangeWithXmlDoc doc + + [ accessBeforeKeyword; accessBeforeId; getterAccess; setterAccess ] + |> List.iter (function None -> () | Some access -> errorR (Error(FSComp.SR.parsAccessibilityModsIllegalForAbstract (), access.Range))) + + let mkFlags, leadingKeyword = memberFlags + let trivia = { LeadingKeyword = leadingKeyword; InlineKeyword = mInline; WithKeyword = mWith; EqualsRange = None } + let vis2 = SynValSigAccess.Single None + let valSpfn = SynValSig(attrs, id, explicitValTyparDecls, ty, arity, isInline, false, doc, vis2, None, mWhole, trivia) + let trivia: SynMemberDefnAbstractSlotTrivia = { GetSetKeywords = getSetRangeOpt } + [ SynMemberDefn.AbstractSlot(valSpfn, mkFlags (getSetAdjuster arity), mWhole, trivia) ] diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index aae952d210c..3685e686870 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -285,3 +285,16 @@ val mkSynField: SynField val leadingKeywordIsAbstract: SynLeadingKeyword -> bool + +val mkAbstractMember: + parseState: IParseState -> + attrs: SynAttributeList list -> + accessBeforeKeyword: SynAccess option -> + abstractMemberFlags: (SynMemberKind -> SynMemberFlags) * SynLeadingKeyword -> + accessBeforeId: SynAccess option -> + mInline: range option -> + id: SynIdent -> + typeParams: SynValTyparDecls -> + typeWithConstraints: SynType * SynValInfo -> + accessors: range option * (SynMemberKind * GetSetKeywords option * SynAccess option * SynAccess option) -> + SynMemberDefn list diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 01120123a36..6674604940c 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -2058,27 +2058,33 @@ classDefnMember: [ SynMemberDefn.Interface(ty, None, None, rhs2 parseState 1 3) ] } | opt_attributes opt_access abstractMemberFlags opt_access opt_inline nameop opt_explicitValTyparDecls COLON topTypeWithTypeConstraints classMemberSpfnGetSet opt_ODECLEND - { if Option.isSome $2 then errorR(Error(FSComp.SR.parsVisibilityDeclarationsShouldComePriorToIdentifier(), rhs parseState 2)) - let ty, arity = $9 - let isInline, doc, id, explicitValTyparDecls = (Option.isSome $5), grabXmlDoc(parseState, $1, 1), $6, $7 - let mWith, (getSet, getSetRangeOpt, getterAccess, setterAccess) = $10 - let getSetAdjuster arity = match arity, getSet with SynValInfo([], _), SynMemberKind.Member -> SynMemberKind.PropertyGet | _ -> getSet - let mWhole = - let m = rhs parseState 1 - match getSetRangeOpt with - | None -> unionRanges m ty.Range - | Some gs -> unionRanges m gs.Range - |> unionRangeWithXmlDoc doc - - [ $2; $4; getterAccess; setterAccess ] - |> List.iter (function None -> () | Some access -> errorR(Error(FSComp.SR.parsAccessibilityModsIllegalForAbstract(), access.Range))) - - let mkFlags, leadingKeyword = $3 - let trivia = { LeadingKeyword = leadingKeyword; InlineKeyword = $5; WithKeyword = mWith; EqualsRange = None } - let vis2 = SynValSigAccess.Single(None) - let valSpfn = SynValSig($1, id, explicitValTyparDecls, ty, arity, isInline, false, doc, vis2, None, mWhole, trivia) - let trivia: SynMemberDefnAbstractSlotTrivia = { GetSetKeywords = getSetRangeOpt } - [ SynMemberDefn.AbstractSlot(valSpfn, mkFlags (getSetAdjuster arity), mWhole, trivia) ] } + { mkAbstractMember parseState $1 $2 $3 $4 $5 $6 $7 $9 $10 } + + | opt_attributes opt_access abstractMemberFlags opt_access opt_inline nameop opt_explicitValTyparDecls COLON recover opt_ODECLEND + { let id = $6 + let typeWithConstraints = SynType.FromParseError(id.Range.EndRange), SynValInfo([], SynInfo.unnamedRetVal) + let accessors = None, (SynMemberKind.Member, None, None, None) + mkAbstractMember parseState $1 $2 $3 $4 $5 id $7 typeWithConstraints accessors } + + | opt_attributes opt_access abstractMemberFlags opt_access opt_inline nameop opt_explicitValTyparDecls recover opt_ODECLEND + { let id = $6 + let typeWithConstraints = SynType.FromParseError(id.Range.EndRange), SynValInfo([], SynInfo.unnamedRetVal) + let accessors = None, (SynMemberKind.Member, None, None, None) + mkAbstractMember parseState $1 $2 $3 $4 $5 id $7 typeWithConstraints accessors } + + | opt_attributes opt_access abstractMemberFlags opt_access opt_inline recover opt_ODECLEND + { let mBeforeId = + match $2 with + | Some access -> access.Range + | _ -> + let _, leadingKeyword = $3 + leadingKeyword.Range + + let id = SynIdent(mkSynId mBeforeId.EndRange "", None) + let typeParams = SynValTyparDecls(None, true) + let typeWithConstraints = SynType.FromParseError(id.Range.EndRange), SynValInfo([], SynInfo.unnamedRetVal) + let accessors = None, (SynMemberKind.Member, None, None, None) + mkAbstractMember parseState $1 $2 $3 $4 $5 id typeParams typeWithConstraints accessors } | opt_attributes opt_access inheritsDefn { if not (isNil $1) then errorR(Error(FSComp.SR.parsAttributesIllegalOnInherit(), rhs parseState 1)) diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs b/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs new file mode 100644 index 00000000000..0b53d40f7c5 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract M: unit -> unit + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs.bsl new file mode 100644 index 00000000000..657523d0bf6 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Method 01.fs.bsl @@ -0,0 +1,47 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Method 01.fs", false, QualifiedNameOfFile Module, + [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (M, None), + SynValTyparDecls (None, true), + Fun + (LongIdent (SynLongIdent ([unit], [], [None])), + LongIdent (SynLongIdent ([unit], [], [None])), + (4,16--4,28), { ArrowRange = (4,21--4,23) }), + SynValInfo + ([[SynArgInfo ([], false, None)]], + SynArgInfo ([], false, None)), false, false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--4,28), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = Member }, (4,4--4,28), + { GetSetKeywords = None })], (4,4--4,28)), [], None, + (3,5--4,28), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,28)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs b/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs new file mode 100644 index 00000000000..c1ed8cc87e2 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract M: unit -> + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs.bsl new file mode 100644 index 00000000000..19dd586af47 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Method 02.fs.bsl @@ -0,0 +1,49 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Method 02.fs", false, QualifiedNameOfFile Module, + [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (M, None), + SynValTyparDecls (None, true), + Fun + (LongIdent (SynLongIdent ([unit], [], [None])), + FromParseError (4,23--4,23), (4,16--6,1), + { ArrowRange = (4,21--4,23) }), + SynValInfo + ([[SynArgInfo ([], false, None)]], + SynArgInfo ([], false, None)), false, false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--6,1), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = Member }, (4,4--6,1), + { GetSetKeywords = None })], (4,4--6,1)), [], None, + (3,5--6,1), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--6,1)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in member definition diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs b/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs new file mode 100644 index 00000000000..205e747f228 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract P: + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs.bsl new file mode 100644 index 00000000000..1f7e40460e0 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 06.fs.bsl @@ -0,0 +1,45 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Property 06.fs", false, + QualifiedNameOfFile Module, [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (P, None), + SynValTyparDecls (None, true), + FromParseError (4,14--4,14), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--4,14), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (4,4--4,14), + { GetSetKeywords = None })], (4,4--4,14)), [], None, + (3,5--4,14), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,14)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in member definition diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs b/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs new file mode 100644 index 00000000000..92411d7b86a --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract P + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs.bsl new file mode 100644 index 00000000000..cd6eb0ca2bf --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 07.fs.bsl @@ -0,0 +1,45 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Property 07.fs", false, + QualifiedNameOfFile Module, [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (P, None), + SynValTyparDecls (None, true), + FromParseError (4,14--4,14), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--4,14), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (4,4--4,14), + { GetSetKeywords = None })], (4,4--4,14)), [], None, + (3,5--4,14), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,14)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in member definition. Expected ':' or other token. diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs b/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs new file mode 100644 index 00000000000..94854918086 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs.bsl new file mode 100644 index 00000000000..b42b585f3d2 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 08.fs.bsl @@ -0,0 +1,44 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Property 08.fs", false, + QualifiedNameOfFile Module, [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (, None), SynValTyparDecls (None, true), + FromParseError (4,12--4,12), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--4,12), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (4,4--4,12), + { GetSetKeywords = None })], (4,4--4,12)), [], None, + (3,5--4,12), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,12)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in member definition. Expected identifier, '(', '(*)' or other token. diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs b/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs new file mode 100644 index 00000000000..7f6917e8cdf --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs @@ -0,0 +1,6 @@ +module Module + +type T = + abstract private + +() diff --git a/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs.bsl b/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs.bsl new file mode 100644 index 00000000000..9577ce92270 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Abstract - Property 09.fs.bsl @@ -0,0 +1,45 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Abstract - Property 09.fs", false, + QualifiedNameOfFile Module, [], + [SynModuleOrNamespace + ([Module], false, NamedModule, + [Types + ([SynTypeDefn + (SynComponentInfo + ([], None, [], [T], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + false, None, (3,5--3,6)), + ObjectModel + (Unspecified, + [AbstractSlot + (SynValSig + ([], SynIdent (, None), SynValTyparDecls (None, true), + FromParseError (4,12--4,12), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + Single None, None, (4,4--4,12), + { LeadingKeyword = Abstract (4,4--4,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (4,4--4,12), + { GetSetKeywords = None })], (4,4--4,12)), [], None, + (3,5--4,12), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,12)); + Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + WarnDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Incomplete structured construct at or before this point in member definition. Expected identifier, '(', '(*)' or other token. +(4,13)-(4,20) parse error Accessibility modifiers are not allowed on this member. Abstract slots always have the same visibility as the enclosing type. From 88465d131a2885921b2e619043b3574de7dec143 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 21 Jul 2026 12:51:27 +0300 Subject: [PATCH 2/3] Fantomas --- src/Compiler/SyntaxTree/ParseHelpers.fs | 47 ++++++++++++++++++++---- src/Compiler/SyntaxTree/ParseHelpers.fsi | 14 +++---- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index dc05d5cab6d..4365847293b 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -1137,13 +1137,29 @@ let mkLetBangExpression IsFromSource = true // User-written let!/use! bindings } -let mkAbstractMember parseState attrs (accessBeforeKeyword: SynAccess option) memberFlags (accessBeforeId: SynAccess option) mInline id typeParams typeWithConstraints accessors = +let mkAbstractMember + parseState + attrs + (accessBeforeKeyword: SynAccess option) + memberFlags + (accessBeforeId: SynAccess option) + mInline + id + typeParams + typeWithConstraints + accessors + = if Option.isSome accessBeforeKeyword then errorR (Error(FSComp.SR.parsVisibilityDeclarationsShouldComePriorToIdentifier (), rhs parseState 2)) let (ty: SynType), arity = typeWithConstraints - let isInline, doc, explicitValTyparDecls = Option.isSome mInline, grabXmlDoc (parseState, attrs, 1), typeParams - let mWith, (getSet, getSetRangeOpt: GetSetKeywords option, getterAccess, setterAccess) = accessors + + let isInline, doc, explicitValTyparDecls = + Option.isSome mInline, grabXmlDoc (parseState, attrs, 1), typeParams + + let mWith, (getSet, getSetRangeOpt: GetSetKeywords option, getterAccess, setterAccess) = + accessors + let getSetAdjuster arity = match arity, getSet with | SynValInfo([], _), SynMemberKind.Member -> SynMemberKind.PropertyGet @@ -1151,17 +1167,34 @@ let mkAbstractMember parseState attrs (accessBeforeKeyword: SynAccess option) me let mWhole = let m = rhs parseState 1 + match getSetRangeOpt with | None -> unionRanges m ty.Range | Some gs -> unionRanges m gs.Range |> unionRangeWithXmlDoc doc [ accessBeforeKeyword; accessBeforeId; getterAccess; setterAccess ] - |> List.iter (function None -> () | Some access -> errorR (Error(FSComp.SR.parsAccessibilityModsIllegalForAbstract (), access.Range))) + |> List.iter (function + | None -> () + | Some access -> errorR (Error(FSComp.SR.parsAccessibilityModsIllegalForAbstract (), access.Range))) let mkFlags, leadingKeyword = memberFlags - let trivia = { LeadingKeyword = leadingKeyword; InlineKeyword = mInline; WithKeyword = mWith; EqualsRange = None } + + let trivia = + { + LeadingKeyword = leadingKeyword + InlineKeyword = mInline + WithKeyword = mWith + EqualsRange = None + } + let vis2 = SynValSigAccess.Single None - let valSpfn = SynValSig(attrs, id, explicitValTyparDecls, ty, arity, isInline, false, doc, vis2, None, mWhole, trivia) + + let valSpfn = + SynValSig(attrs, id, explicitValTyparDecls, ty, arity, isInline, false, doc, vis2, None, mWhole, trivia) + let trivia: SynMemberDefnAbstractSlotTrivia = { GetSetKeywords = getSetRangeOpt } - [ SynMemberDefn.AbstractSlot(valSpfn, mkFlags (getSetAdjuster arity), mWhole, trivia) ] + + [ + SynMemberDefn.AbstractSlot(valSpfn, mkFlags (getSetAdjuster arity), mWhole, trivia) + ] diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index 3685e686870..27b78503f5c 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -291,10 +291,10 @@ val mkAbstractMember: attrs: SynAttributeList list -> accessBeforeKeyword: SynAccess option -> abstractMemberFlags: (SynMemberKind -> SynMemberFlags) * SynLeadingKeyword -> - accessBeforeId: SynAccess option -> - mInline: range option -> - id: SynIdent -> - typeParams: SynValTyparDecls -> - typeWithConstraints: SynType * SynValInfo -> - accessors: range option * (SynMemberKind * GetSetKeywords option * SynAccess option * SynAccess option) -> - SynMemberDefn list + accessBeforeId: SynAccess option -> + mInline: range option -> + id: SynIdent -> + typeParams: SynValTyparDecls -> + typeWithConstraints: SynType * SynValInfo -> + accessors: range option * (SynMemberKind * GetSetKeywords option * SynAccess option * SynAccess option) -> + SynMemberDefn list From 4aefde1e2853ad5b104c7d01512ebee9bbfef70a Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 21 Jul 2026 12:54:40 +0300 Subject: [PATCH 3/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index bd4c804b4f2..bbbe65cf1a4 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -125,6 +125,7 @@ * Fix FSI pretty printing to distinguish anonymous records (`{| ... |}`) from nominal records (`{ ... }`). ([Issue #6116](https://github.com/dotnet/fsharp/issues/6116), [PR #19919](https://github.com/dotnet/fsharp/pull/19919)) * Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934)) * Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873)) +* Parser: recover on unfinished abstract members ([PR #20070](https://github.com/dotnet/fsharp/pull/20070)) ### Added