Skip to content

Commit

Permalink
Parser: recover on unfinished interface member (#15943)
Browse files Browse the repository at this point in the history
  • Loading branch information
auduchinok authored Sep 18, 2023
1 parent f0c490d commit 4a9701c
Show file tree
Hide file tree
Showing 26 changed files with 519 additions and 28 deletions.
79 changes: 53 additions & 26 deletions src/Compiler/SyntaxTree/LexFilter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Context =
| CtxtElse of Position
| CtxtDo of Position
| CtxtInterfaceHead of Position
| CtxtTypeDefns of Position // 'type <here> =', not removed when we find the "="
| CtxtTypeDefns of Position * equalsEndPos: Position option // 'type <here> =', not removed when we find the "="

| CtxtNamespaceHead of Position * token
| CtxtModuleHead of Position * token * LexingModuleAttributes * isNested: bool
Expand All @@ -69,7 +69,7 @@ type Context =
member c.StartPos =
match c with
| CtxtNamespaceHead (p, _) | CtxtModuleHead (p, _, _, _) | CtxtException p | CtxtModuleBody (p, _) | CtxtNamespaceBody p
| CtxtLetDecl (_, p) | CtxtDo p | CtxtInterfaceHead p | CtxtTypeDefns p | CtxtParen (_, p) | CtxtMemberHead p | CtxtMemberBody p
| CtxtLetDecl (_, p) | CtxtDo p | CtxtInterfaceHead p | CtxtTypeDefns(p, _) | CtxtParen (_, p) | CtxtMemberHead p | CtxtMemberBody p
| CtxtWithAsLet p
| CtxtWithAsAugment p
| CtxtMatchClauses (_, p) | CtxtIf p | CtxtMatch p | CtxtFor p | CtxtWhile p | CtxtWhen p | CtxtFunction p | CtxtFun p | CtxtTry p | CtxtThen p | CtxtElse p | CtxtVanilla (p, _)
Expand Down Expand Up @@ -742,7 +742,7 @@ type LexFilterImpl (

//let indexerNotationWithoutDot = lexbuf.SupportsFeature LanguageFeature.IndexerNotationWithoutDot

let tryPushCtxt strict tokenTup (newCtxt: Context) =
let tryPushCtxt strict ignoreIndent tokenTup (newCtxt: Context) =
let rec undentationLimit strict stack =
match newCtxt, stack with
| _, [] -> PositionWithColumn(newCtxt.StartPos, -1)
Expand Down Expand Up @@ -960,8 +960,10 @@ type LexFilterImpl (
// These contexts can have their contents exactly aligning
| _, (CtxtParen _ | CtxtFor _ | CtxtWhen _ | CtxtWhile _ | CtxtTypeDefns _ | CtxtMatch _ | CtxtModuleBody (_, true) | CtxtNamespaceBody _ | CtxtTry _ | CtxtMatchClauses _ | CtxtSeqBlock _ as limitCtxt :: _)
-> PositionWithColumn(limitCtxt.StartPos, limitCtxt.StartCol)

let isCorrectIndent =
if ignoreIndent then true else

match newCtxt with
// Don't bother to check pushes of Vanilla blocks since we've
// always already pushed a SeqBlock at this position.
Expand Down Expand Up @@ -992,7 +994,7 @@ type LexFilterImpl (
true

let pushCtxt tokenTup newCtxt =
tryPushCtxt false tokenTup newCtxt |> ignore
tryPushCtxt false false tokenTup newCtxt |> ignore

let rec popCtxt() =
match offsideStack with
Expand All @@ -1008,6 +1010,10 @@ type LexFilterImpl (

let replaceCtxt p ctxt = popCtxt(); pushCtxt p ctxt

let replaceCtxtIgnoreIndent p ctxt =
popCtxt()
tryPushCtxt false true p ctxt |> ignore

//----------------------------------------------------------------------------
// Peek ahead at a token, either from the old lexer or from our delayedStack
//--------------------------------------------------------------------------
Expand Down Expand Up @@ -1679,7 +1685,7 @@ type LexFilterImpl (
// | B
//
// <TOKEN> <-- close the type context sequence block here *)
| _, CtxtTypeDefns posType :: _ when offsidePos.Column = posType.Column && not (isTypeSeqBlockElementContinuator token) -> -1
| _, CtxtTypeDefns(posType, _) :: _ when offsidePos.Column = posType.Column && not (isTypeSeqBlockElementContinuator token) -> -1

// This ensures we close a namespace body when we see the next namespace definition
//
Expand Down Expand Up @@ -1822,7 +1828,7 @@ type LexFilterImpl (
popCtxt()
reprocess()

| _, CtxtTypeDefns offsidePos :: _
| _, CtxtTypeDefns(offsidePos, _) :: _
when isSemiSemi || (if relaxWhitespace2OffsideRule || isTypeContinuator token then tokenStartCol + 1 else tokenStartCol) <= offsidePos.Column ->
if debug then dprintf "token at column %d is offside from TYPE(offsidePos=%a)! pop and reprocess\n" tokenStartCol outputPos offsidePos
popCtxt()
Expand Down Expand Up @@ -2076,8 +2082,9 @@ type LexFilterImpl (
pushCtxtSeqBlock tokenTup AddBlockEnd
returnToken tokenLexbufState token

| EQUALS, CtxtTypeDefns _ :: _ ->
| EQUALS, CtxtTypeDefns(p, _) :: _ ->
if debug then dprintf "CtxType: EQUALS, pushing CtxtSeqBlock\n"
replaceCtxtIgnoreIndent tokenTup (CtxtTypeDefns(p, Some tokenTup.EndPos))
pushCtxtSeqBlock tokenTup AddBlockEnd
returnToken tokenLexbufState token

Expand Down Expand Up @@ -2202,7 +2209,7 @@ type LexFilterImpl (
let leadingBar = match (peekNextToken()) with BAR -> true | _ -> false

if debug then dprintf "WITH, pushing CtxtMatchClauses, lookaheadTokenStartPos = %a, tokenStartPos = %a\n" outputPos lookaheadTokenStartPos outputPos tokenStartPos
tryPushCtxt strictIndentation lookaheadTokenTup (CtxtMatchClauses(leadingBar, lookaheadTokenStartPos)) |> ignore
tryPushCtxt strictIndentation false lookaheadTokenTup (CtxtMatchClauses(leadingBar, lookaheadTokenStartPos)) |> ignore

returnToken tokenLexbufState OWITH

Expand Down Expand Up @@ -2385,22 +2392,42 @@ type LexFilterImpl (
pushCtxt tokenTup (CtxtFun tokenStartPos)
returnToken tokenLexbufState OFUN

// type I = interface .... end
| INTERFACE, CtxtSeqBlock _ :: CtxtTypeDefns(typePos, Some equalsEndPos) :: _ when
(tokenTup.LastTokenPos = equalsEndPos &&

// Allow deindenting interface representation when started after `=`:
//
// type I = interface
// abstract P: int
// end

let allowDeindent = tokenTup.EndPos.Line = equalsEndPos.Line

let lookaheadTokenTup = peekNextTokenTup ()
let lookaheadTokenStartPos = startPosOfTokenTup lookaheadTokenTup
match lookaheadTokenTup.Token with
| END ->
lookaheadTokenStartPos.Column >= typePos.Column

| DEFAULT | OVERRIDE | INTERFACE | NEW | TYPE | STATIC | MEMBER | ABSTRACT | INHERIT | LBRACK_LESS ->
let limitPos = if allowDeindent then typePos else tokenTup.StartPos
lookaheadTokenStartPos.Column >= limitPos.Column + 1

| _ ->
false) ->

if debug then dprintf "INTERFACE, pushing CtxtParen, tokenStartPos = %a\n" outputPos tokenStartPos
pushCtxt tokenTup (CtxtParen (token, tokenStartPos))
pushCtxtSeqBlock tokenTup AddBlockEnd
returnToken tokenLexbufState token

// type C with interface .... with
// type C = interface .... with
| INTERFACE, _ ->
let lookaheadTokenTup = peekNextTokenTup()
let lookaheadTokenStartPos = startPosOfTokenTup lookaheadTokenTup
match lookaheadTokenTup.Token with
// type I = interface .... end
| DEFAULT | OVERRIDE | INTERFACE | NEW | TYPE | STATIC | END | MEMBER | ABSTRACT | INHERIT | LBRACK_LESS ->
if debug then dprintf "INTERFACE, pushing CtxtParen, tokenStartPos = %a, lookaheadTokenStartPos = %a\n" outputPos tokenStartPos outputPos lookaheadTokenStartPos
pushCtxt tokenTup (CtxtParen (token, tokenStartPos))
pushCtxtSeqBlock tokenTup AddBlockEnd
returnToken tokenLexbufState token
// type C with interface .... with
// type C = interface .... with
| _ ->
if debug then dprintf "INTERFACE, pushing CtxtInterfaceHead, tokenStartPos = %a, lookaheadTokenStartPos = %a\n" outputPos tokenStartPos outputPos lookaheadTokenStartPos
pushCtxt tokenTup (CtxtInterfaceHead tokenStartPos)
returnToken tokenLexbufState OINTERFACE_MEMBER
if debug then dprintf "INTERFACE, pushing CtxtInterfaceHead, tokenStartPos = %a, lookaheadTokenStartPos \n" outputPos tokenStartPos
pushCtxt tokenTup (CtxtInterfaceHead tokenStartPos)
returnToken tokenLexbufState OINTERFACE_MEMBER

| CLASS, _ ->
if debug then dprintf "CLASS, pushing CtxtParen(%a)\n" outputPos tokenStartPos
Expand All @@ -2411,7 +2438,7 @@ type LexFilterImpl (
| TYPE, _ ->
insertComingSoonTokens("TYPE", TYPE_COMING_SOON, TYPE_IS_HERE)
if debug then dprintf "TYPE, pushing CtxtTypeDefns(%a)\n" outputPos tokenStartPos
pushCtxt tokenTup (CtxtTypeDefns tokenStartPos)
pushCtxt tokenTup (CtxtTypeDefns(tokenStartPos, None))
pool.Return tokenTup
hwTokenFetch useBlockRule

Expand Down Expand Up @@ -2582,7 +2609,7 @@ type LexFilterImpl (
pushCtxtSeqBlockAt strictIndentation false fallbackToken (peekNextTokenTup ()) addBlockEnd

and pushCtxtSeqBlockAt strict (useFallback: bool) (fallbackToken: TokenTup) (tokenTup: TokenTup) addBlockEnd =
let pushed = tryPushCtxt strict tokenTup (CtxtSeqBlock(FirstInSeqBlock, startPosOfTokenTup tokenTup, addBlockEnd))
let pushed = tryPushCtxt strict false tokenTup (CtxtSeqBlock(FirstInSeqBlock, startPosOfTokenTup tokenTup, addBlockEnd))
if not pushed && useFallback then
// The upcoming token isn't sufficiently indented to start the new context.
// The parser expects proper contexts structure, so we push a new recovery context at the fallback token position.
Expand Down
9 changes: 8 additions & 1 deletion src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -1773,7 +1773,7 @@ tyconClassDefn:
| classOrInterfaceOrStruct classDefnBlock END
{ false, ($1, $2), Some(rhs2 parseState 1 3) }

| classOrInterfaceOrStruct classDefnBlock recover
| classOrInterfaceOrStruct classDefnBlock ends_coming_soon_or_recover
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedClassInterfaceOrStruct())
let m = (rhs parseState 1, $2) ||> unionRangeWithListBy (fun (d: SynMemberDefn) -> d.Range)
false, ($1, $2), Some(m) }
Expand Down Expand Up @@ -2019,6 +2019,13 @@ classDefnMember:
| Some(mWithKwd, members, m) -> Some mWithKwd, Some members, unionRanges (rhs2 parseState 1 4) m
[ SynMemberDefn.Interface($4, mWithKwd, members, mWhole) ] }

| opt_attributes opt_access interfaceMember recover
{ let mInterface = rhs parseState 3
if not (isNil $1) then errorR(Error(FSComp.SR.parsAttributesAreNotPermittedOnInterfaceImplementations(), rhs parseState 1))
if Option.isSome $2 then errorR(Error(FSComp.SR.parsInterfacesHaveSameVisibilityAsEnclosingType(), mInterface))
let ty = SynType.FromParseError(mInterface.EndRange)
[ SynMemberDefn.Interface(ty, None, None, rhs2 parseState 1 3) ] }

| opt_attributes opt_access abstractMemberFlags opt_inline nameop opt_explicitValTyparDecls COLON topTypeWithTypeConstraints classMemberSpfnGetSet opt_ODECLEND
{ let ty, arity = $8
let isInline, doc, id, explicitValTyparDecls = (Option.isSome $4), grabXmlDoc(parseState, $1, 1), $5, $6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ raise (new Exception("exit 1"))
EndLine = 5
EndColumn = 31 }
Message = "Expecting member body" }
{ Error = Error 3113
Range = { StartLine = 6
StartColumn = 9
EndLine = 6
EndColumn = 9 }
Message = "Unexpected end of input in type definition" }
] |> ignore

[<Theory; File "RelaxWhitespace2.fs">]
Expand Down
4 changes: 3 additions & 1 deletion tests/fsharp/typecheck/sigs/neg71.vsbsl
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ neg71.fsx(128,5,128,81): parse info FS3520: XML comment is not placed on a valid

neg71.fsx(168,1,168,28): typecheck error FS0960: 'let' and 'do' bindings must come before member and interface definitions in type definitions

neg71.fsx(168,9,168,18): typecheck error FS0039: The value or constructor 'Simulator' is not defined.
neg71.fsx(104,14,104,14): typecheck error FS0887: The type ''a' is not an interface type

neg71.fsx(104,14,104,14): typecheck error FS0909: All implemented interfaces should be declared on the initial declaration of the type
6 changes: 6 additions & 0 deletions tests/service/data/SyntaxTree/Member/Inherit 08.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Module

type T =
inherit as t

member this.P = 1
49 changes: 49 additions & 0 deletions tests/service/data/SyntaxTree/Member/Inherit 08.fs.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ImplFile
(ParsedImplFileInput
("/root/Member/Inherit 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,
[Inherit
(LongIdent (SynLongIdent ([], [], [])), None,
(4,4--4,11));
Member
(SynBinding
(None, Normal, false, false, [],
PreXmlDoc ((4,12), FSharp.Compiler.Xml.XmlDocCollector),
SynValData
(Some { IsInstance = true
IsDispatchSlot = false
IsOverrideOrExplicitImpl = false
IsFinal = false
GetterOrSetterIsCompilerGenerated = false
MemberKind = Member },
SynValInfo
([[SynArgInfo ([], false, None)]; []],
SynArgInfo ([], false, None)), None, None),
LongIdent
(SynLongIdent
([this; P], [(6,15--6,16)], [None; None]), None,
None, Pats [], None, (6,11--6,17)), None,
Const (Int32 1, (6,20--6,21)), (6,11--6,17),
NoneAtInvisible,
{ LeadingKeyword = Member (6,4--6,10)
InlineKeyword = None
EqualsRange = Some (6,18--6,19) }), (4,12--6,21))],
(4,4--6,21)), [], None, (3,5--6,21),
{ LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,7--3,8)
WithKeyword = None })], (3,0--6,21))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--6,21), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
CodeComments = [] }, set []))

(4,12)-(4,14) parse error Unexpected keyword 'as' in type definition
8 changes: 8 additions & 0 deletions tests/service/data/SyntaxTree/Member/Interface 08.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Module

type T =
interface

member this.P = 1

()
49 changes: 49 additions & 0 deletions tests/service/data/SyntaxTree/Member/Interface 08.fs.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ImplFile
(ParsedImplFileInput
("/root/Member/Interface 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,
[Interface
(FromParseError (4,13--4,13), None, None, (4,4--4,13));
Member
(SynBinding
(None, Normal, false, false, [],
PreXmlDoc ((6,4), FSharp.Compiler.Xml.XmlDocCollector),
SynValData
(Some { IsInstance = true
IsDispatchSlot = false
IsOverrideOrExplicitImpl = false
IsFinal = false
GetterOrSetterIsCompilerGenerated = false
MemberKind = Member },
SynValInfo
([[SynArgInfo ([], false, None)]; []],
SynArgInfo ([], false, None)), None, None),
LongIdent
(SynLongIdent
([this; P], [(6,15--6,16)], [None; None]), None,
None, Pats [], None, (6,11--6,17)), None,
Const (Int32 1, (6,20--6,21)), (6,11--6,17),
NoneAtInvisible,
{ LeadingKeyword = Member (6,4--6,10)
InlineKeyword = None
EqualsRange = Some (6,18--6,19) }), (6,4--6,21))],
(4,4--6,21)), [], None, (3,5--6,21),
{ LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,7--3,8)
WithKeyword = None })], (3,0--6,21));
Expr (Const (Unit, (8,0--8,2)), (8,0--8,2))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--8,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
CodeComments = [] }, set []))

(4,14)-(6,4) parse error Incomplete structured construct at or before this point in member definition
8 changes: 8 additions & 0 deletions tests/service/data/SyntaxTree/Member/Interface 09.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Module

type T =
interface

type T2 = int

()
39 changes: 39 additions & 0 deletions tests/service/data/SyntaxTree/Member/Interface 09.fs.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
ImplFile
(ParsedImplFileInput
("/root/Member/Interface 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,
[Interface
(FromParseError (4,13--4,13), None, None, (4,4--4,13))],
(4,4--4,13)), [], None, (3,5--4,13),
{ LeadingKeyword = Type (3,0--3,4)
EqualsRange = Some (3,7--3,8)
WithKeyword = None })], (3,0--4,13));
Types
([SynTypeDefn
(SynComponentInfo
([], None, [], [T2],
PreXmlDoc ((6,0), FSharp.Compiler.Xml.XmlDocCollector),
false, None, (6,5--6,7)),
Simple
(TypeAbbrev
(Ok, LongIdent (SynLongIdent ([int], [], [None])),
(6,10--6,13)), (6,10--6,13)), [], None, (6,5--6,13),
{ LeadingKeyword = Type (6,0--6,4)
EqualsRange = Some (6,8--6,9)
WithKeyword = None })], (6,0--6,13));
Expr (Const (Unit, (8,0--8,2)), (8,0--8,2))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--8,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
CodeComments = [] }, set []))

(6,0)-(6,4) parse error Incomplete structured construct at or before this point in member definition
8 changes: 8 additions & 0 deletions tests/service/data/SyntaxTree/Member/Interface 10.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Module

type T =
interface

type T2 = int

()
Loading

0 comments on commit 4a9701c

Please sign in to comment.