diff --git a/global.json b/global.json index 962754dd234..45e15cfad20 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "Microsoft.VisualStudio.Component.FSharp" ] }, - "xcopy-msbuild": "17.6.0-2" + "xcopy-msbuild": "17.7.0-test" }, "native-tools": { "perl": "5.38.0.1" diff --git a/src/Compiler/SyntaxTree/LexFilter.fs b/src/Compiler/SyntaxTree/LexFilter.fs index f5529d64de4..64bc43c6ebc 100644 --- a/src/Compiler/SyntaxTree/LexFilter.fs +++ b/src/Compiler/SyntaxTree/LexFilter.fs @@ -46,7 +46,7 @@ type Context = | CtxtElse of Position | CtxtDo of Position | CtxtInterfaceHead of Position - | CtxtTypeDefns of Position // 'type =', not removed when we find the "=" + | CtxtTypeDefns of Position * equalsEndPos: Position option // 'type =', not removed when we find the "=" | CtxtNamespaceHead of Position * token | CtxtModuleHead of Position * token * LexingModuleAttributes * isNested: bool @@ -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, _) @@ -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) @@ -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. @@ -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 @@ -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 //-------------------------------------------------------------------------- @@ -1679,7 +1685,7 @@ type LexFilterImpl ( // | B // // <-- 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 // @@ -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() @@ -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 @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 6e7c518260e..966d8dc393d 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -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) } @@ -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 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs index 3dd4b360463..99e45386bc5 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs @@ -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 [] diff --git a/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/TaskPerf.fs b/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/TaskPerf.fs index 5feafc4a3da..7f03c4d636b 100644 --- a/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/TaskPerf.fs +++ b/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerf/TaskPerf.fs @@ -1,6 +1,6 @@ (* -msbuild tests\fsharp\perf\tasks\FS\TaskPerf.fsproj /p:Configuration=Release -dotnet artifacts\bin\TaskPerf\Release\netcoreapp2.1\TaskPerf.dll +msbuild tests\benchmarks\CompiledCodeBenchmarks\TaskPerf\TaskPerf\TaskPerf.fsproj /p:Configuration=Release +dotnet artifacts\bin\TaskPerf\Release\net7.0\TaskPerf.dll *) namespace TaskPerf diff --git a/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerfCSharp/TaskPerfCSharp.csproj b/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerfCSharp/TaskPerfCSharp.csproj index f55b590cb26..323d4d5ce6f 100644 --- a/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerfCSharp/TaskPerfCSharp.csproj +++ b/tests/benchmarks/CompiledCodeBenchmarks/TaskPerf/TaskPerfCSharp/TaskPerfCSharp.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net7.0 Library 8.0 diff --git a/tests/fsharp/typecheck/sigs/neg71.vsbsl b/tests/fsharp/typecheck/sigs/neg71.vsbsl index bbdad6e76ff..433b951a1d9 100644 --- a/tests/fsharp/typecheck/sigs/neg71.vsbsl +++ b/tests/fsharp/typecheck/sigs/neg71.vsbsl @@ -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 diff --git a/tests/service/data/SyntaxTree/Member/Inherit 08.fs b/tests/service/data/SyntaxTree/Member/Inherit 08.fs new file mode 100644 index 00000000000..862c1727685 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Inherit 08.fs @@ -0,0 +1,6 @@ +module Module + +type T = + inherit as t + + member this.P = 1 diff --git a/tests/service/data/SyntaxTree/Member/Inherit 08.fs.bsl b/tests/service/data/SyntaxTree/Member/Inherit 08.fs.bsl new file mode 100644 index 00000000000..28dcc0d77f9 --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Inherit 08.fs.bsl @@ -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 diff --git a/tests/service/data/SyntaxTree/Member/Interface 08.fs b/tests/service/data/SyntaxTree/Member/Interface 08.fs new file mode 100644 index 00000000000..d0ccea9faae --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 08.fs @@ -0,0 +1,8 @@ +module Module + +type T = + interface + + member this.P = 1 + +() diff --git a/tests/service/data/SyntaxTree/Member/Interface 08.fs.bsl b/tests/service/data/SyntaxTree/Member/Interface 08.fs.bsl new file mode 100644 index 00000000000..f0e64cdad0a --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 08.fs.bsl @@ -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 diff --git a/tests/service/data/SyntaxTree/Member/Interface 09.fs b/tests/service/data/SyntaxTree/Member/Interface 09.fs new file mode 100644 index 00000000000..6f6bcb56dff --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 09.fs @@ -0,0 +1,8 @@ +module Module + +type T = + interface + +type T2 = int + +() diff --git a/tests/service/data/SyntaxTree/Member/Interface 09.fs.bsl b/tests/service/data/SyntaxTree/Member/Interface 09.fs.bsl new file mode 100644 index 00000000000..9260f21ff5b --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 09.fs.bsl @@ -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 diff --git a/tests/service/data/SyntaxTree/Member/Interface 10.fs b/tests/service/data/SyntaxTree/Member/Interface 10.fs new file mode 100644 index 00000000000..6f6bcb56dff --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 10.fs @@ -0,0 +1,8 @@ +module Module + +type T = + interface + +type T2 = int + +() diff --git a/tests/service/data/SyntaxTree/Member/Interface 10.fs.bsl b/tests/service/data/SyntaxTree/Member/Interface 10.fs.bsl new file mode 100644 index 00000000000..9ddc6fb80bb --- /dev/null +++ b/tests/service/data/SyntaxTree/Member/Interface 10.fs.bsl @@ -0,0 +1,39 @@ +ImplFile + (ParsedImplFileInput + ("/root/Member/Interface 10.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 diff --git a/tests/service/data/SyntaxTree/Type/Interface 01.fs b/tests/service/data/SyntaxTree/Type/Interface 01.fs new file mode 100644 index 00000000000..7364f174597 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 01.fs @@ -0,0 +1,6 @@ +module Module + +type T = + interface end + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 01.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 01.fs.bsl new file mode 100644 index 00000000000..27d0bc70287 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 01.fs.bsl @@ -0,0 +1,20 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 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 (Interface, [], (4,4--4,17)), [], None, + (3,5--4,17), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--4,17)); + 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 = [] + CodeComments = [] }, set [])) diff --git a/tests/service/data/SyntaxTree/Type/Interface 02.fs b/tests/service/data/SyntaxTree/Type/Interface 02.fs new file mode 100644 index 00000000000..05a0596bd93 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 02.fs @@ -0,0 +1,7 @@ +module Module + +type T = + interface + end + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 02.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 02.fs.bsl new file mode 100644 index 00000000000..9a516b1a4a4 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 02.fs.bsl @@ -0,0 +1,20 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 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 (Interface, [], (4,4--5,7)), [], None, (3,5--5,7), + { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--5,7)); + Expr (Const (Unit, (7,0--7,2)), (7,0--7,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--7,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) diff --git a/tests/service/data/SyntaxTree/Type/Interface 03.fs b/tests/service/data/SyntaxTree/Type/Interface 03.fs new file mode 100644 index 00000000000..ed4c97d1b5c --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 03.fs @@ -0,0 +1,8 @@ +module Module + +type T = + interface + abstract P: int + end + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 03.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 03.fs.bsl new file mode 100644 index 00000000000..b93004493d6 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 03.fs.bsl @@ -0,0 +1,41 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 03.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 + (Interface, + [AbstractSlot + (SynValSig + ([], SynIdent (P, None), + SynValTyparDecls (None, true), + LongIdent (SynLongIdent ([int], [], [None])), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((5,8), FSharp.Compiler.Xml.XmlDocCollector), + None, None, (5,8--5,23), + { LeadingKeyword = Abstract (5,8--5,16) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (5,8--5,23), + { GetSetKeywords = None })], (4,4--6,7)), [], None, + (3,5--6,7), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--6,7)); + 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 [])) diff --git a/tests/service/data/SyntaxTree/Type/Interface 04.fs b/tests/service/data/SyntaxTree/Type/Interface 04.fs new file mode 100644 index 00000000000..4c96474490e --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 04.fs @@ -0,0 +1,7 @@ +module Module + +type T = interface + abstract P: int +end + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 04.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 04.fs.bsl new file mode 100644 index 00000000000..1a6d2a60721 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 04.fs.bsl @@ -0,0 +1,41 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 04.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 + (Interface, + [AbstractSlot + (SynValSig + ([], SynIdent (P, None), + SynValTyparDecls (None, true), + LongIdent (SynLongIdent ([int], [], [None])), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((4,4), FSharp.Compiler.Xml.XmlDocCollector), + None, None, (4,4--4,19), + { 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,19), + { GetSetKeywords = None })], (3,9--5,3)), [], None, + (3,5--5,3), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--5,3)); + Expr (Const (Unit, (7,0--7,2)), (7,0--7,2))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--7,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) diff --git a/tests/service/data/SyntaxTree/Type/Interface 05.fs b/tests/service/data/SyntaxTree/Type/Interface 05.fs new file mode 100644 index 00000000000..e43b2614d69 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 05.fs @@ -0,0 +1,7 @@ +module Module + +type T = + interface + abstract P: int + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 05.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 05.fs.bsl new file mode 100644 index 00000000000..b8c09362664 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 05.fs.bsl @@ -0,0 +1,13 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 05.fs", false, QualifiedNameOfFile Interface 05, [], + [], + [SynModuleOrNamespace + ([Interface 05], false, AnonModule, [], PreXmlDocEmpty, [], None, + (8,0--8,0), { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(7,0)-(7,1) parse error Possible incorrect indentation: this token is offside of context started at position (3:1). Try indenting this token further or using standard formatting conventions. +(7,0)-(7,1) parse error Unexpected symbol '(' in type definition +(4,4)-(4,13) parse error Unmatched 'class', 'interface' or 'struct' diff --git a/tests/service/data/SyntaxTree/Type/Interface 06.fs b/tests/service/data/SyntaxTree/Type/Interface 06.fs new file mode 100644 index 00000000000..a6d8dda2a6f --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 06.fs @@ -0,0 +1,6 @@ +module Module + +type T = interface + abstract P: int + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 06.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 06.fs.bsl new file mode 100644 index 00000000000..615ad4d56bc --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 06.fs.bsl @@ -0,0 +1,13 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 06.fs", false, QualifiedNameOfFile Interface 06, [], + [], + [SynModuleOrNamespace + ([Interface 06], false, AnonModule, [], PreXmlDocEmpty, [], None, + (7,0--7,0), { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(6,0)-(6,1) parse error Possible incorrect indentation: this token is offside of context started at position (3:1). Try indenting this token further or using standard formatting conventions. +(6,0)-(6,1) parse error Unexpected symbol '(' in type definition +(3,9)-(3,18) parse error Unmatched 'class', 'interface' or 'struct' diff --git a/tests/service/data/SyntaxTree/Type/Interface 07.fs b/tests/service/data/SyntaxTree/Type/Interface 07.fs new file mode 100644 index 00000000000..92d704524bf --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 07.fs @@ -0,0 +1,8 @@ +module Module + +type T = + interface + abstract P: int + end + +() diff --git a/tests/service/data/SyntaxTree/Type/Interface 07.fs.bsl b/tests/service/data/SyntaxTree/Type/Interface 07.fs.bsl new file mode 100644 index 00000000000..2030a97bf39 --- /dev/null +++ b/tests/service/data/SyntaxTree/Type/Interface 07.fs.bsl @@ -0,0 +1,46 @@ +ImplFile + (ParsedImplFileInput + ("/root/Type/Interface 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, + [Interface + (FromParseError (4,13--4,13), None, None, (4,4--4,13)); + AbstractSlot + (SynValSig + ([], SynIdent (P, None), + SynValTyparDecls (None, true), + LongIdent (SynLongIdent ([int], [], [None])), + SynValInfo ([], SynArgInfo ([], false, None)), false, + false, + PreXmlDoc ((5,4), FSharp.Compiler.Xml.XmlDocCollector), + None, None, (5,4--5,19), + { LeadingKeyword = Abstract (5,4--5,12) + InlineKeyword = None + WithKeyword = None + EqualsRange = None }), + { IsInstance = true + IsDispatchSlot = true + IsOverrideOrExplicitImpl = false + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = PropertyGet }, (5,4--5,19), + { GetSetKeywords = None })], (4,4--5,19)), [], None, + (3,5--5,19), { LeadingKeyword = Type (3,0--3,4) + EqualsRange = Some (3,7--3,8) + WithKeyword = None })], (3,0--5,19))], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, + (1,0--5,19), { LeadingKeyword = Module (1,0--1,6) })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(4,14)-(5,4) parse error Incomplete structured construct at or before this point in member definition +(6,4)-(6,7) parse error Unexpected keyword 'end' in type definition. Expected incomplete structured construct at or before this point or other token. +(8,0)-(8,1) parse error Unexpected symbol '(' in implementation file diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 1e547e86335..4329596ea49 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -76,6 +76,8 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = let! parseResults = document.GetFSharpParseResultsAsync("GetDiagnostics") + // Old logic, rollback once https://github.com/dotnet/fsharp/issues/15972 is fixed (likely on Roslyn side, since we're returning diagnostics, but they're not getting to VS). + (* match diagnosticType with | DiagnosticsType.Syntax -> for diagnostic in parseResults.Diagnostics do @@ -88,6 +90,23 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = errors.Add(diagnostic) |> ignore errors.ExceptWith(parseResults.Diagnostics) + *) + + // TODO: see comment above, this is a workaround for issue we have in current VS/Roslyn + match diagnosticType with + | DiagnosticsType.Syntax -> + for diagnostic in parseResults.Diagnostics do + errors.Add(diagnostic) |> ignore + + // We always add syntactic, and do not exclude them when semantic is requested + | DiagnosticsType.Semantic -> + for diagnostic in parseResults.Diagnostics do + errors.Add(diagnostic) |> ignore + + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync("GetDiagnostics") + + for diagnostic in checkResults.Diagnostics do + errors.Add(diagnostic) |> ignore if errors.Count = 0 then return ImmutableArray.Empty diff --git a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs index 8f8dbd37189..cf05a66cdd6 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs @@ -91,6 +91,72 @@ type DocumentDiagnosticAnalyzerTests() = actualError.Location.SourceSpan.End |> Assert.shouldBeEqualWith expectedEnd "Error end positions should match" + member private this.VerifyDiagnosticBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE + ( + fileContents: string, + expectedMessage: string, + expectedSeverity: DiagnosticSeverity + ) = + // TODO: once workaround (https://github.com/dotnet/fsharp/pull/15982) will not be needed, this should be reverted back to normal method (see PR) + let errors = + getDiagnostics fileContents + |> Seq.filter (fun e -> e.Severity = expectedSeverity) + |> Seq.toArray + + errors.Length + |> Assert.shouldBeEqualWith 2 "There should be two errors generated" + + let actualError = errors.[0] + Assert.Equal(expectedSeverity, actualError.Severity) + + actualError.GetMessage() + |> Assert.shouldBeEqualWith expectedMessage "Error messages should match" + + let expectedStart = fileContents.IndexOf(startMarker) + startMarker.Length + + actualError.Location.SourceSpan.Start + |> Assert.shouldBeEqualWith expectedStart "Error start positions should match" + + let expectedEnd = fileContents.IndexOf(endMarker) + + actualError.Location.SourceSpan.End + |> Assert.shouldBeEqualWith expectedEnd "Error end positions should match" + + member private this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE(fileContents: string, expectedMessage: string) = + // TODO: once workaround (https://github.com/dotnet/fsharp/pull/15982) will not be needed, this should be reverted back to normal method (see PR) + this.VerifyDiagnosticBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE(fileContents, expectedMessage, DiagnosticSeverity.Error) + + member private this.VerifyErrorAtMarker_HACK_PLEASE_REFER_TO_COMMENT_INSIDE + ( + fileContents: string, + expectedMarker: string, + ?expectedMessage: string + ) = + let errors = + getDiagnostics fileContents + |> Seq.filter (fun e -> e.Severity = DiagnosticSeverity.Error) + |> Seq.toArray + + errors.Length + |> Assert.shouldBeEqualWith 2 "There should be exactly two errors generated" + + let actualError = errors.[0] + + if expectedMessage.IsSome then + actualError.GetMessage() + |> Assert.shouldBeEqualWith expectedMessage.Value "Error messages should match" + + Assert.Equal(DiagnosticSeverity.Error, actualError.Severity) + let expectedStart = fileContents.IndexOf(expectedMarker) + + actualError.Location.SourceSpan.Start + |> Assert.shouldBeEqualWith expectedStart "Error start positions should match" + + let expectedEnd = expectedStart + expectedMarker.Length + + actualError.Location.SourceSpan.End + |> Assert.shouldBeEqualWith expectedEnd "Error end positions should match" + member private this.VerifyErrorBetweenMarkers(fileContents: string, expectedMessage: string) = this.VerifyDiagnosticBetweenMarkers(fileContents, expectedMessage, DiagnosticSeverity.Error) @@ -99,7 +165,7 @@ type DocumentDiagnosticAnalyzerTests() = [] member public this.Error_Expression_IllegalIntegerLiteral() = - this.VerifyErrorBetweenMarkers( + this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ let _ = 1 @@ -110,7 +176,7 @@ let a = 0.1(*start*).(*end*)0 [] member public this.Error_Expression_IncompleteDefine() = - this.VerifyErrorBetweenMarkers( + this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ let a = (*start*);(*end*) @@ -120,7 +186,7 @@ let a = (*start*);(*end*) [] member public this.Error_Expression_KeywordAsValue() = - this.VerifyErrorBetweenMarkers( + this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ let b = @@ -255,7 +321,7 @@ let f () = [] member public this.Error_Identifer_IllegalFloatPointLiteral() = - this.VerifyErrorBetweenMarkers( + this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ let x: float = 1.2(*start*).(*end*)3 @@ -368,7 +434,7 @@ async { if true then return 1 } |> ignore [] member public this.ExtraEndif() = - this.VerifyErrorBetweenMarkers( + this.VerifyErrorBetweenMarkers_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ #if UNDEFINED @@ -383,7 +449,7 @@ async { if true then return 1 } |> ignore [] member public this.Squiggles_HashNotFirstSymbol_If() = - this.VerifyErrorAtMarker( + this.VerifyErrorAtMarker_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ (*comment*) #if UNDEFINED @@ -398,7 +464,7 @@ async { if true then return 1 } |> ignore [] member public this.Squiggles_HashNotFirstSymbol_Endif() = - this.VerifyErrorAtMarker( + this.VerifyErrorAtMarker_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ #if DEBUG @@ -413,7 +479,7 @@ async { if true then return 1 } |> ignore [] member public this.Squiggles_HashIfWithMultilineComment() = - this.VerifyErrorAtMarker( + this.VerifyErrorAtMarker_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ #if DEBUG (*comment*) @@ -425,7 +491,7 @@ async { if true then return 1 } |> ignore [] member public this.Squiggles_HashIfWithUnexpected() = - this.VerifyErrorAtMarker( + this.VerifyErrorAtMarker_HACK_PLEASE_REFER_TO_COMMENT_INSIDE( fileContents = """ #if DEBUG TEST