From 4ae20b6ec02692984a7879c51a8f50dff7b1490e Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:55:48 +0200 Subject: [PATCH] [RFC FS-1132] Interpolated strings syntax with multiple dollar signs (#14640) New language feature - extended string interpolation - RFC FS-1132 --- src/Compiler/Checking/CheckFormatStrings.fs | 64 ++- src/Compiler/FSComp.txt | 5 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/Service/FSharpCheckerResults.fs | 24 +- src/Compiler/Service/ServiceLexing.fs | 108 +++-- src/Compiler/Service/ServiceLexing.fsi | 3 +- src/Compiler/Service/service.fs | 2 +- src/Compiler/SyntaxTree/LexHelpers.fs | 2 + src/Compiler/SyntaxTree/LexHelpers.fsi | 3 +- src/Compiler/SyntaxTree/ParseHelpers.fs | 14 +- src/Compiler/SyntaxTree/ParseHelpers.fsi | 4 +- src/Compiler/lex.fsl | 415 ++++++++++++++---- src/Compiler/xlf/FSComp.txt.cs.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.de.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.es.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.fr.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.it.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.ja.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.ko.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.pl.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.ru.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.tr.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 25 ++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 25 ++ .../Language/InterpolatedStringsTests.fs | 53 +++ ...vice.SurfaceArea.netstandard20.release.bsl | 3 +- tests/service/EditorTests.fs | 8 +- tests/service/SyntaxTreeTests.fs | 3 +- tests/service/TokenizerTests.fs | 2 +- ...tedStringWithTripleQuoteMultipleDollars.fs | 2 + ...tringWithTripleQuoteMultipleDollars.fs.bsl | 30 ++ .../BraceCompletionSessionProvider.fs | 1 + .../Classification/ClassificationService.fs | 3 +- .../CodeFix/AddMissingFunKeyword.fs | 5 +- .../AddMissingRecToMutuallyRecFunctions.fs | 7 +- .../CodeFix/AddOpenCodeFixProvider.fs | 5 +- .../ImplementInterfaceCodeFixProvider.fs | 3 + .../Commands/HelpContextService.fs | 12 +- .../Completion/CompletionProvider.fs | 21 +- .../Completion/CompletionService.fs | 7 +- .../Completion/CompletionUtils.fs | 23 +- .../HashDirectiveCompletionProvider.fs | 16 +- .../FSharp.Editor/Completion/SignatureHelp.fs | 8 +- .../Debugging/LanguageDebugInfoService.fs | 12 +- .../Formatting/EditorFormattingService.fs | 10 +- .../Formatting/IndentationService.fs | 10 +- .../FSharpProjectOptionsManager.fs | 6 +- .../LanguageService/SymbolHelpers.fs | 3 +- .../LanguageService/Tokenizer.fs | 13 +- .../LanguageService/WorkspaceExtensions.fs | 19 +- .../FSharp.Editor/TaskList/TaskListService.fs | 13 +- .../ProjectSitesAndFiles.fs | 12 +- .../BraceMatchingServiceTests.fs | 80 +++- .../CompletionProviderTests.fs | 41 +- .../GoToDefinitionServiceTests.fs | 12 +- .../HelpContextServiceTests.fs | 10 +- .../LanguageDebugInfoServiceTests.fs | 1 + .../SignatureHelpProviderTests.fs | 2 + .../SyntacticColorizationServiceTests.fs | 91 +++- .../TaskListServiceTests.fs | 5 +- .../Salsa/FSharpLanguageServiceTestable.fs | 2 +- .../Tests.LanguageService.General.fs | 2 +- 64 files changed, 1269 insertions(+), 260 deletions(-) create mode 100644 tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs create mode 100644 tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index e9224ebd586..ea15e196fbd 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -72,6 +72,11 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: let sourceText = context.SourceText let lineStartPositions = context.LineStartPositions + // Number of curly braces required to delimiter interpolation holes + // = Number of $ chars starting a (triple quoted) string literal + // Set when we process first fragment range, default = 1 + let mutable delimLen = 1 + let mutable nQuotes = 1 [ for i, r in List.indexed fragRanges do if r.StartLine - 1 < lineStartPositions.Length && r.EndLine - 1 < lineStartPositions.Length then @@ -79,9 +84,16 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: let rLength = lineStartPositions[r.EndLine - 1] + r.EndColumn - startIndex let offset = if i = 0 then - match sourceText.GetSubTextString(startIndex, rLength) with - | PrefixedBy "$\"\"\"" len - | PrefixedBy "\"\"\"" len -> + let fullRangeText = sourceText.GetSubTextString(startIndex, rLength) + delimLen <- + fullRangeText + |> Seq.takeWhile (fun c -> c = '$') + |> Seq.length + let tripleQuotePrefix = + [String.replicate delimLen "$"; "\"\"\""] + |> String.concat "" + match fullRangeText with + | PrefixedBy tripleQuotePrefix len -> nQuotes <- 3 len | PrefixedBy "$@\"" len @@ -91,13 +103,13 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: | _ -> 1 else 1 // <- corresponds to '}' that's closing an interpolation hole - let fragLen = rLength - offset - (if i = numFrags - 1 then nQuotes else 1) + let fragLen = rLength - offset - (if i = numFrags - 1 then nQuotes else delimLen) (offset, sourceText.GetSubTextString(startIndex + offset, fragLen), r) else (1, fmt, r) - ] + ], delimLen -module internal Parsing = +module internal Parse = let flags (info: FormatInfoRegister) (fmt: string) (fmtPos: int) = let len = fmt.Length @@ -231,10 +243,10 @@ let parseFormatStringInternal // let escapeFormatStringEnabled = g.langVersion.SupportsFeature Features.LanguageFeature.EscapeDotnetFormattableStrings - let fmt, fragments = + let fmt, fragments, delimLen = match context with | Some context when fragRanges.Length > 0 -> - let fmts = makeFmts context fragRanges fmt + let fmts, delimLen = makeFmts context fragRanges fmt // Join the fragments with holes. Note this join is only used on the IDE path, // the CheckExpressions.fs does its own joining with the right alignments etc. substituted @@ -245,11 +257,11 @@ let parseFormatStringInternal (0, fmts) ||> List.mapFold (fun i (offset, fmt, fragRange) -> (i, offset, fragRange), i + fmt.Length + 4) // the '4' is the length of '%P()' joins - fmt, fragments + fmt, fragments, delimLen | _ -> // Don't muck with the fmt when there is no source code context to go get the original // source code (i.e. when compiling or background checking) - (if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ] + (if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ], 1 let len = fmt.Length @@ -299,32 +311,44 @@ let parseFormatStringInternal and parseSpecifier acc (i, fragLine, fragCol) fragments = let startFragCol = fragCol - let fragCol = fragCol+1 - if fmt[i..(i+1)] = "%%" then + let nPercentSigns = + fmt[i..] + |> Seq.takeWhile (fun c -> c = '%') + |> Seq.length + if delimLen <= 1 && fmt[i..(i+1)] = "%%" then match context with | Some _ -> specifierLocations.Add( (Range.mkFileIndexRange m.FileIndex - (Position.mkPos fragLine startFragCol) - (Position.mkPos fragLine (fragCol + 1))), 0) + (Position.mkPos fragLine fragCol) + (Position.mkPos fragLine (fragCol+2))), 0) | None -> () appendToDotnetFormatString "%" - parseLoop acc (i+2, fragLine, fragCol+1) fragments + parseLoop acc (i+2, fragLine, fragCol+2) fragments + elif delimLen > 1 && nPercentSigns < delimLen then + appendToDotnetFormatString fmt[i..(i+nPercentSigns-1)] + parseLoop acc (i + nPercentSigns, fragLine, fragCol + nPercentSigns) fragments else - let i = i+1 + let fragCol, i = + if delimLen > 1 then + if nPercentSigns > delimLen then + "%" |> String.replicate (nPercentSigns - delimLen) |> appendToDotnetFormatString + fragCol + nPercentSigns, i + nPercentSigns + else + fragCol + 1, i + 1 if i >= len then failwith (FSComp.SR.forMissingFormatSpecifier()) let info = newInfo() let oldI = i - let posi, i = Parsing.position fmt i + let posi, i = Parse.position fmt i let fragCol = fragCol + i - oldI let oldI = i - let i = Parsing.flags info fmt i + let i = Parse.flags info fmt i let fragCol = fragCol + i - oldI let oldI = i - let widthArg,(widthValue, (precisionArg,i)) = Parsing.widthAndPrecision info fmt i + let widthArg,(widthValue, (precisionArg,i)) = Parse.widthAndPrecision info fmt i let fragCol = fragCol + i - oldI if i >= len then failwith (FSComp.SR.forBadPrecision()) @@ -340,7 +364,7 @@ let parseFormatStringInternal | Some n -> failwith (FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), n.ToString())) | None -> () - let skipPossibleInterpolationHole pos = Parsing.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos + let skipPossibleInterpolationHole pos = Parse.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos // Implicitly typed holes in interpolated strings are translated to '... %P(...)...' in the // type checker. They should always have '(...)' after for format string. diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 26ef0736dc1..83062836f10 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1122,6 +1122,10 @@ lexIfOCaml,"IF-FSHARP/IF-CAML regions are no longer supported" 1245,lexInvalidUnicodeLiteral,"\U%s is not a valid Unicode character escape sequence" 1246,tcCallerInfoWrongType,"'%s' must be applied to an argument of type '%s', but has been applied to an argument of type '%s'" 1247,tcCallerInfoNotOptional,"'%s' can only be applied to optional arguments" +1248,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." +1249,lexUnmatchedRBracesInTripleQuote,"The interpolated string contains unmatched closing braces." +1250,lexTooManyPercentsInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%%' characters." +1251,lexExtendedStringInterpolationNotSupported,"Extended string interpolation is not supported in this version of F#." # reshapedmsbuild.fs 1300,toolLocationHelperUnsupportedFrameworkVersion,"The specified .NET Framework version '%s' is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion." # ----------------------------------------------------------------------------- @@ -1567,6 +1571,7 @@ featureWarningWhenCopyAndUpdateRecordChangesAllFields,"Raises warnings when an c featureStaticMembersInInterfaces,"Static members in interfaces" featureNonInlineLiteralsAsPrintfFormat,"String values marked as literals and IL constants as printf format" featureNestedCopyAndUpdate,"Nested record field copy-and-update" +featureExtendedStringInterpolation,"Extended string interpolation similar to C# raw string literals." 3353,fsiInvalidDirective,"Invalid directive '#%s %s'" 3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." 3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 3e4b2465fc3..f46b0dbeee1 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -66,6 +66,7 @@ type LanguageFeature = | StaticMembersInInterfaces | NonInlineLiteralsAsPrintfFormat | NestedCopyAndUpdate + | ExtendedStringInterpolation /// LanguageVersion management type LanguageVersion(versionText) = @@ -155,6 +156,7 @@ type LanguageVersion(versionText) = LanguageFeature.StaticMembersInInterfaces, previewVersion LanguageFeature.NonInlineLiteralsAsPrintfFormat, previewVersion LanguageFeature.NestedCopyAndUpdate, previewVersion + LanguageFeature.ExtendedStringInterpolation, previewVersion ] @@ -276,6 +278,7 @@ type LanguageVersion(versionText) = | LanguageFeature.StaticMembersInInterfaces -> FSComp.SR.featureStaticMembersInInterfaces () | LanguageFeature.NonInlineLiteralsAsPrintfFormat -> FSComp.SR.featureNonInlineLiteralsAsPrintfFormat () | LanguageFeature.NestedCopyAndUpdate -> FSComp.SR.featureNestedCopyAndUpdate () + | LanguageFeature.ExtendedStringInterpolation -> FSComp.SR.featureExtendedStringInterpolation () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index c5c407e80b8..d12c77fdcbe 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -56,6 +56,7 @@ type LanguageFeature = | StaticMembersInInterfaces | NonInlineLiteralsAsPrintfFormat | NestedCopyAndUpdate + | ExtendedStringInterpolation /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index c0dcd3f2962..fabb4d8f497 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2352,7 +2352,7 @@ module internal ParseAndCheckFile = let rec matchBraces stack = match lexfun lexbuf, stack with - | tok2, (tok1, m1) :: stackAfterMatch when parenTokensBalance tok1 tok2 -> + | tok2, (tok1, braceOffset, m1) :: stackAfterMatch when parenTokensBalance tok1 tok2 -> let m2 = lexbuf.LexemeRange // For INTERP_STRING_PART and INTERP_STRING_END grab the one character @@ -2360,7 +2360,11 @@ module internal ParseAndCheckFile = let m2Start = match tok2 with | INTERP_STRING_PART _ - | INTERP_STRING_END _ -> mkFileIndexRange m2.FileIndex m2.Start (mkPos m2.Start.Line (m2.Start.Column + 1)) + | INTERP_STRING_END _ -> + mkFileIndexRange + m2.FileIndex + (mkPos m2.Start.Line (m2.Start.Column - braceOffset)) + (mkPos m2.Start.Line (m2.Start.Column + 1)) | _ -> m2 matchingBraces.Add(m1, m2Start) @@ -2371,15 +2375,15 @@ module internal ParseAndCheckFile = match tok2 with | INTERP_STRING_PART _ -> let m2End = - mkFileIndexRange m2.FileIndex (mkPos m2.End.Line (max (m2.End.Column - 1) 0)) m2.End + mkFileIndexRange m2.FileIndex (mkPos m2.End.Line (max (m2.End.Column - 1 - braceOffset) 0)) m2.End - (tok2, m2End) :: stackAfterMatch + (tok2, braceOffset, m2End) :: stackAfterMatch | _ -> stackAfterMatch matchBraces stackAfterMatch | LPAREN | LBRACE _ | LBRACK | LBRACE_BAR | LBRACK_BAR | LQUOTE _ | LBRACK_LESS as tok, _ -> - matchBraces ((tok, lexbuf.LexemeRange) :: stack) + matchBraces ((tok, 0, lexbuf.LexemeRange) :: stack) // INTERP_STRING_BEGIN_PART corresponds to $"... {" at the start of an interpolated string // @@ -2389,12 +2393,18 @@ module internal ParseAndCheckFile = // // Either way we start a new potential match at the last character | INTERP_STRING_BEGIN_PART _ | INTERP_STRING_PART _ as tok, _ -> + let braceOffset = + match tok with + | INTERP_STRING_BEGIN_PART (_, SynStringKind.TripleQuote, (LexerContinuation.Token (_, (_, _, dl, _) :: _))) -> + dl - 1 + | _ -> 0 + let m = lexbuf.LexemeRange let m2 = - mkFileIndexRange m.FileIndex (mkPos m.End.Line (max (m.End.Column - 1) 0)) m.End + mkFileIndexRange m.FileIndex (mkPos m.End.Line (max (m.End.Column - 1 - braceOffset) 0)) m.End - matchBraces ((tok, m2) :: stack) + matchBraces ((tok, braceOffset, m2) :: stack) | (EOF _ | LEX_FAILURE _), _ -> () | _ -> matchBraces stack diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index ac28ec245aa..b14b0cb3092 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -475,6 +475,7 @@ type FSharpTokenizerColorState = | EndLineThenToken = 12 | TripleQuoteString = 13 | TripleQuoteStringInComment = 14 + | ExtendedInterpolatedString = 15 | InitialState = 0 module internal LexerStateEncoding = @@ -512,6 +513,7 @@ module internal LexerStateEncoding = let ifdefstackNumBits = 24 // 0 means if, 1 means else let stringKindBits = 3 let nestingBits = 12 + let delimLenBits = 3 let _ = assert @@ -522,6 +524,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + nestingBits + + delimLenBits <= 64) let lexstateStart = 0 @@ -547,6 +550,15 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + let delimLenStart = + lexstateNumBits + + ncommentsNumBits + + hardwhiteNumBits + + ifdefstackCountNumBits + + ifdefstackNumBits + + stringKindBits + + nestingBits + let lexstateMask = Bits.mask64 lexstateStart lexstateNumBits let ncommentsMask = Bits.mask64 ncommentsStart ncommentsNumBits let hardwhitePosMask = Bits.mask64 hardwhitePosStart hardwhiteNumBits @@ -554,6 +566,7 @@ module internal LexerStateEncoding = let ifdefstackMask = Bits.mask64 ifdefstackStart ifdefstackNumBits let stringKindMask = Bits.mask64 stringKindStart stringKindBits let nestingMask = Bits.mask64 nestingStart nestingBits + let delimLenMask = Bits.mask64 delimLenStart delimLenBits let bitOfBool b = if b then 1 else 0 let boolOfBit n = (n = 1L) @@ -569,12 +582,14 @@ module internal LexerStateEncoding = | LexerStringStyle.SingleQuote -> 0 | LexerStringStyle.Verbatim -> 1 | LexerStringStyle.TripleQuote -> 2 + | LexerStringStyle.ExtendedInterpolated -> 3 let decodeStringStyle kind = match kind with | 0 -> LexerStringStyle.SingleQuote | 1 -> LexerStringStyle.Verbatim | 2 -> LexerStringStyle.TripleQuote + | 3 -> LexerStringStyle.ExtendedInterpolated | _ -> assert false LexerStringStyle.SingleQuote @@ -587,7 +602,8 @@ module internal LexerStateEncoding = ifdefStack, light, stringKind: LexerStringKind, - stringNest + stringNest, + delimLen: int ) = let mutable ifdefStackCount = 0 let mutable ifdefStackBits = 0 @@ -608,12 +624,12 @@ module internal LexerStateEncoding = let tag1, i1, kind1, rest = match stringNest with | [] -> false, 0, 0, [] - | (i1, kind1, _) :: rest -> true, i1, encodeStringStyle kind1, rest + | (i1, kind1, _, _) :: rest -> true, i1, encodeStringStyle kind1, rest let tag2, i2, kind2 = match rest with | [] -> false, 0, 0 - | (i2, kind2, _) :: _ -> true, i2, encodeStringStyle kind2 + | (i2, kind2, _, _) :: _ -> true, i2, encodeStringStyle kind2 (if tag1 then 0b100000000000 else 0) ||| (if tag2 then 0b010000000000 else 0) @@ -622,6 +638,8 @@ module internal LexerStateEncoding = ||| ((kind1 <<< 2) &&& 0b000000001100) ||| ((kind2 <<< 0) &&& 0b000000000011) + let delimLen = min delimLen (Bits.pown32 delimLenBits) + let bits = lexStateOfColorState colorState ||| ((numComments <<< ncommentsStart) &&& ncommentsMask) @@ -630,6 +648,7 @@ module internal LexerStateEncoding = ||| ((int64 ifdefStackBits <<< ifdefstackStart) &&& ifdefstackMask) ||| ((int64 stringKindValue <<< stringKindStart) &&& stringKindMask) ||| ((int64 nestingValue <<< nestingStart) &&& nestingMask) + ||| ((int64 delimLen <<< delimLenStart) &&& delimLenMask) { PosBits = b.Encoding @@ -677,19 +696,33 @@ module internal LexerStateEncoding = let kind1 = ((nestingValue &&& 0b000000001100) >>> 2) let kind2 = ((nestingValue &&& 0b000000000011) >>> 0) - [ - if tag1 then - i1, decodeStringStyle kind1, range0 - if tag2 then - i2, decodeStringStyle kind2, range0 - ] + let nest = + [ + if tag1 then + i1, decodeStringStyle kind1, 0, range0 + if tag2 then + i2, decodeStringStyle kind2, 0, range0 + ] + + nest - (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest) + let delimLen = int32 ((bits &&& delimLenMask) >>> delimLenStart) + + (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest, delimLen) let encodeLexInt indentationSyntaxStatus (lexcont: LexerContinuation) = match lexcont with | LexCont.Token (ifdefs, stringNest) -> - encodeLexCont (FSharpTokenizerColorState.Token, 0L, pos0, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest) + encodeLexCont ( + FSharpTokenizerColorState.Token, + 0L, + pos0, + ifdefs, + indentationSyntaxStatus, + LexerStringKind.String, + stringNest, + 0 + ) | LexCont.IfDefSkip (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.IfDefSkip, @@ -698,7 +731,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.EndLine (ifdefs, stringNest, econt) -> match econt with @@ -710,7 +744,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexerEndlineContinuation.Token -> encodeLexCont ( @@ -720,16 +755,18 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) - | LexCont.String (ifdefs, stringNest, style, kind, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, delimLen, m) -> let state = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.String | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimString | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteString + | LexerStringStyle.ExtendedInterpolated -> FSharpTokenizerColorState.ExtendedInterpolatedString - encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest) + encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest, delimLen) | LexCont.Comment (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.Comment, @@ -738,7 +775,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.SingleLineComment (ifdefs, stringNest, n, m) -> encodeLexCont ( @@ -748,16 +786,18 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.StringInComment (ifdefs, stringNest, style, n, m) -> let state = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.StringInComment | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimStringInComment - | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteStringInComment + | LexerStringStyle.TripleQuote + | LexerStringStyle.ExtendedInterpolated -> FSharpTokenizerColorState.TripleQuoteStringInComment - encodeLexCont (state, int64 n, m.Start, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest) + encodeLexCont (state, int64 n, m.Start, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest, 0) | LexCont.MLOnly (ifdefs, stringNest, m) -> encodeLexCont ( FSharpTokenizerColorState.CamlOnly, @@ -766,11 +806,12 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) let decodeLexInt (state: FSharpTokenizerLexState) = - let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest = + let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest, delimLen = decodeLexCont state let lexcont = @@ -778,7 +819,7 @@ module internal LexerStateEncoding = | FSharpTokenizerColorState.Token -> LexCont.Token(ifdefs, stringNest) | FSharpTokenizerColorState.IfDefSkip -> LexCont.IfDefSkip(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.String -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.Comment -> LexCont.Comment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.SingleLineComment -> LexCont.SingleLineComment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.StringInComment -> @@ -789,9 +830,11 @@ module internal LexerStateEncoding = LexCont.StringInComment(ifdefs, stringNest, LexerStringStyle.TripleQuote, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.CamlOnly -> LexCont.MLOnly(ifdefs, stringNest, mkRange "file" p1 p1) | FSharpTokenizerColorState.VerbatimString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, delimLen, mkRange "file" p1 p1) + | FSharpTokenizerColorState.ExtendedInterpolatedString -> + LexCont.String(ifdefs, stringNest, LexerStringStyle.ExtendedInterpolated, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Skip(n1, mkRange "file" p1 p1)) | FSharpTokenizerColorState.EndLineThenToken -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Token) @@ -923,9 +966,10 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi lexargs.stringNest <- stringNest Lexer.ifdefSkip n m lexargs skip lexbuf - | LexCont.String (ifdefs, stringNest, style, kind, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, delimLen, m) -> lexargs.ifdefStack <- ifdefs lexargs.stringNest <- stringNest + lexargs.interpolationDelimiterLength <- delimLen use buf = ByteBuffer.Create Lexer.StringCapacity let args = (buf, LexerStringFinisher.Default, m, kind, lexargs) @@ -933,6 +977,7 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi | LexerStringStyle.SingleQuote -> Lexer.singleQuoteString args skip lexbuf | LexerStringStyle.Verbatim -> Lexer.verbatimString args skip lexbuf | LexerStringStyle.TripleQuote -> Lexer.tripleQuoteString args skip lexbuf + | LexerStringStyle.ExtendedInterpolated -> Lexer.extendedInterpolatedString args skip lexbuf | LexCont.Comment (ifdefs, stringNest, n, m) -> lexargs.ifdefStack <- ifdefs @@ -952,7 +997,8 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi match style with | LexerStringStyle.SingleQuote -> Lexer.stringInComment n m lexargs skip lexbuf | LexerStringStyle.Verbatim -> Lexer.verbatimStringInComment n m lexargs skip lexbuf - | LexerStringStyle.TripleQuote -> Lexer.tripleQuoteStringInComment n m lexargs skip lexbuf + | LexerStringStyle.TripleQuote + | LexerStringStyle.ExtendedInterpolated -> Lexer.tripleQuoteStringInComment n m lexargs skip lexbuf | LexCont.MLOnly (ifdefs, stringNest, m) -> lexargs.ifdefStack <- ifdefs @@ -1172,9 +1218,13 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi } [] -type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string option) = +type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string option, langVersion: string option) = + + let langVersion = + langVersion + |> Option.map LanguageVersion + |> Option.defaultValue LanguageVersion.Default - let langVersion = LanguageVersion.Default let reportLibraryOnlyFeatures = true let lexResourceManager = LexResourceManager() diff --git a/src/Compiler/Service/ServiceLexing.fsi b/src/Compiler/Service/ServiceLexing.fsi index 39b2febf315..5abca96922d 100755 --- a/src/Compiler/Service/ServiceLexing.fsi +++ b/src/Compiler/Service/ServiceLexing.fsi @@ -6,6 +6,7 @@ open System open System.Threading open FSharp.Compiler open FSharp.Compiler.Text +open FSharp.Compiler.Features #nowarn "57" @@ -325,7 +326,7 @@ type FSharpLineTokenizer = type FSharpSourceTokenizer = /// Create a tokenizer for a source file. - new: conditionalDefines: string list * fileName: string option -> FSharpSourceTokenizer + new: conditionalDefines: string list * fileName: string option * langVersion: string option -> FSharpSourceTokenizer /// Create a tokenizer for a line of this source file member CreateLineTokenizer: lineText: string -> FSharpLineTokenizer diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index cf5487e25df..2cfd7a53c6b 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -1699,7 +1699,7 @@ type FSharpChecker /// Tokenize a single line, returning token information and a tokenization state represented by an integer member _.TokenizeLine(line: string, state: FSharpTokenizerLexState) = - let tokenizer = FSharpSourceTokenizer([], None) + let tokenizer = FSharpSourceTokenizer([], None, None) let lineTokenizer = tokenizer.CreateLineTokenizer line let mutable state = (None, state) diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index f5c83752477..704526f544f 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -65,6 +65,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting + mutable interpolationDelimiterLength: int } /// possible results of lexing a long Unicode escape sequence in a string literal, e.g. "\U0001F47D", @@ -93,6 +94,7 @@ let mkLexargs applyLineDirectives = applyLineDirectives stringNest = [] pathMap = pathMap + interpolationDelimiterLength = 0 } /// Register the lexbuf and call the given function diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index 49f73abbabf..292adaf5be5 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -37,7 +37,8 @@ type LexArgs = pathMap: PathMap mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus - mutable stringNest: LexerInterpolatedStringNesting } + mutable stringNest: LexerInterpolatedStringNesting + mutable interpolationDelimiterLength: int } type LongUnicodeLexResult = | SurrogatePair of uint16 * uint16 diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index e181801324d..7c3a68644b8 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -268,6 +268,7 @@ type LexerStringStyle = | Verbatim | TripleQuote | SingleQuote + | ExtendedInterpolated [] type LexerStringKind = @@ -307,7 +308,7 @@ type LexerStringKind = /// Represents the degree of nesting of '{..}' and the style of the string to continue afterwards, in an interpolation fill. /// Nesting counters and styles of outer interpolating strings are pushed on this stack. -type LexerInterpolatedStringNesting = (int * LexerStringStyle * range) list +type LexerInterpolatedStringNesting = (int * LexerStringStyle * int * range) list /// The parser defines a number of tokens for whitespace and /// comments eliminated by the lexer. These carry a specification of @@ -323,6 +324,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * + delimLen: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range @@ -924,19 +926,20 @@ let checkEndOfFileError t = match t with | LexCont.IfDefSkip (_, _, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInHashIf ()) - | LexCont.String (_, _, LexerStringStyle.SingleQuote, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.SingleQuote, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedString ()) else reportParseErrorAt m (FSComp.SR.parsEofInString ()) - | LexCont.String (_, _, LexerStringStyle.TripleQuote, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.ExtendedInterpolated, kind, _, m) + | LexCont.String (_, _, LexerStringStyle.TripleQuote, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedTripleQuoteString ()) else reportParseErrorAt m (FSComp.SR.parsEofInTripleQuoteString ()) - | LexCont.String (_, _, LexerStringStyle.Verbatim, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.Verbatim, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedVerbatimString ()) else @@ -951,6 +954,7 @@ let checkEndOfFileError t = | LexCont.StringInComment (_, _, LexerStringStyle.Verbatim, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInVerbatimStringInComment ()) + | LexCont.StringInComment (_, _, LexerStringStyle.ExtendedInterpolated, _, m) | LexCont.StringInComment (_, _, LexerStringStyle.TripleQuote, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInTripleQuoteStringInComment ()) @@ -966,7 +970,7 @@ let checkEndOfFileError t = match nesting with | [] -> () - | (_, _, m) :: _ -> reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedStringFill ()) + | (_, _, _, m) :: _ -> reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedStringFill ()) type BindingSet = BindingSetPreAttrs of range * bool * bool * (SynAttributes -> SynAccess option -> SynAttributes * SynBinding list) * range diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index 46ae73f1ab0..8d158862f4f 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -102,6 +102,7 @@ type LexerStringStyle = | Verbatim | TripleQuote | SingleQuote + | ExtendedInterpolated [] type LexerStringKind = @@ -117,7 +118,7 @@ type LexerStringKind = static member String: LexerStringKind -type LexerInterpolatedStringNesting = (int * LexerStringStyle * range) list +type LexerInterpolatedStringNesting = (int * LexerStringStyle * int * range) list [] type LexerContinuation = @@ -128,6 +129,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * + delimLen: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index c9900ea1acd..9a803d63a31 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -600,11 +600,12 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.ExtendedInterpolated, _, _) :: _ + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.interpolationDelimiterLength, m)) else singleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '$' '"' '"' '"' @@ -615,55 +616,90 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, m)) + args.interpolationDelimiterLength <- 1 + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, 1, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + | ('$'+) '"' '"' '"' + { let buf, fin, m = startString args lexbuf + + if lexbuf.SupportsFeature LanguageFeature.ExtendedStringInterpolation then + // Single quote in triple quote ok, others disallowed + match args.stringNest with + | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) + | [] -> () + + args.interpolationDelimiterLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + else + let result = + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + fail args lexbuf (FSComp.SR.lexExtendedStringInterpolationNotSupported()) result + } + | '$' '"' { let buf,fin,m = startString args lexbuf // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, style, _, _) :: _ when style = LexerStringStyle.ExtendedInterpolated || style = LexerStringStyle.TripleQuote -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, m)) - else singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '"' '"' '"' { let buf, fin, m = startString args lexbuf + args.interpolationDelimiterLength <- 0 + // Single quote in triple quote ok, others disallowed match args.stringNest with | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, m)) - else tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.interpolationDelimiterLength, m)) + else + tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '@' '"' { let buf, fin, m = startString args lexbuf // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.ExtendedInterpolated, _, _) :: _ + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, m)) - else verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.interpolationDelimiterLength, m)) + else + verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | ("$@" | "@$") '"' { let buf, fin, m = startString args lexbuf // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, style, _, _) :: _ when style = LexerStringStyle.ExtendedInterpolated || style = LexerStringStyle.TripleQuote -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, m)) - else verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | truewhite+ { if skip then token args skip lexbuf @@ -861,10 +897,10 @@ rule token args skip = parse { match args.stringNest with | [] -> () - | (counter, style, m) :: rest -> + | (counter, style, d, m) :: rest -> // Note, we do not update the 'm', any incomplete-interpolation error // will be reported w.r.t. the first '{' - args.stringNest <- (counter + 1, style, m) :: rest + args.stringNest <- (counter + 1, style, d, m) :: rest // To continue token-by-token lexing may involve picking up the new args.stringNes let cont = LexCont.Token(args.ifdefStack, args.stringNest) LBRACE cont @@ -877,25 +913,29 @@ rule token args skip = parse // We encounter a '}' in the expression token stream. First check if we're in an interpolated string expression // and continue the string if necessary match args.stringNest with - | (1, style, _) :: rest -> + | (1, LexerStringStyle.ExtendedInterpolated, delimLength, r) :: rest when delimLength > 1 -> + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, delimLength - 1, r) :: rest + token args skip lexbuf + | (1, style, _, _) :: rest -> args.stringNest <- rest let buf, fin, m = startString args lexbuf if not skip then - STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, m)) + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.interpolationDelimiterLength, m)) else match style with | LexerStringStyle.Verbatim -> verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.SingleQuote -> singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.TripleQuote -> tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf + | LexerStringStyle.ExtendedInterpolated -> extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf - | (counter, style, m) :: rest -> + | (counter, style, d, m) :: rest -> // Note, we do not update the 'm', any incomplete-interpolation error // will be reported w.r.t. the first '{' - args.stringNest <- (counter - 1, style, m) :: rest + args.stringNest <- (counter - 1, style, d, m) :: rest let cont = LexCont.Token(args.ifdefStack, args.stringNest) RBRACE cont - | _ -> + | _ -> let cont = LexCont.Token(args.ifdefStack, args.stringNest) RBRACE cont } @@ -1142,40 +1182,52 @@ and singleQuoteString sargs skip = parse let text = lexeme lexbuf let text2 = text |> String.filter (fun c -> c <> ' ' && c <> '\t') advanceColumnBy lexbuf (text.Length - text2.Length) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | escape_char { let (buf, _fin, m, kind, args) = sargs addByteChar buf (escape (lexeme lexbuf).[1]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | trigraph { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addByteChar buf (trigraph s.[1] s.[2] s.[3]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | hexGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (hexGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | unicodeGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (unicodeGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | unicodeGraphLong { let (buf, _fin, m, kind, args) = sargs let hexChars = lexemeTrimLeft lexbuf 2 let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf match unicodeGraphLong hexChars with | Invalid -> fail args lexbuf (FSComp.SR.lexInvalidUnicodeLiteral hexChars) (result()) @@ -1190,92 +1242,109 @@ and singleQuoteString sargs skip = parse | '"' { let (buf, fin, _m, kind, args) = sargs let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(0)) cont + fin.Finish buf kind (LexerStringFinisherContext()) cont } | '"''B' { let (buf, fin, _m, kind, args) = sargs let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf { kind with IsByteString = true } (enum(0)) cont + fin.Finish buf { kind with IsByteString = true } (LexerStringFinisherContext()) cont } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.SingleQuote, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.SingleQuote, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.InterpolatedPart cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else addUnicodeString buf (lexeme lexbuf) - (result()) - } + (result()) } | newline { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } and verbatimString sargs skip = parse | '"' '"' { let (buf, _fin, m, kind, args) = sargs addByteChar buf '\"' - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | '"' { let (buf, fin, _m, kind, args) = sargs @@ -1293,35 +1362,43 @@ and verbatimString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.Verbatim, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.Verbatim, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(3)) cont + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.Verbatim) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else @@ -1332,92 +1409,115 @@ and verbatimString sargs skip = parse | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } and tripleQuoteString sargs skip = parse | '"' '"' '"' { let (buf, fin, _m, kind, args) = sargs + args.interpolationDelimiterLength <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(4)) cont } + fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } | newline { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } // The rest is to break into pieces to allow double-click-on-word and other such things | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(5)) cont + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else @@ -1427,14 +1527,137 @@ and tripleQuoteString sargs skip = parse | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } + +and extendedInterpolatedString sargs skip = parse + | '"' '"' '"' + { let (buf, fin, _m, kind, args) = sargs + args.interpolationDelimiterLength <- 0 + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } + + | newline + { let (buf, _fin, m, kind, args) = sargs + newline lexbuf + addUnicodeString buf (lexeme lexbuf) + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } + +// The rest is to break into pieces to allow double-click-on-word and other such things + | ident + | integer + | xinteger + | anywhite + + { let (buf, _fin, m, kind, args) = sargs + addUnicodeString buf (lexeme lexbuf) + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } + + | "%" + + { let (buf, _fin, m, kind, args) = sargs + let numPercents = lexeme lexbuf |> String.length + let result() = + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + // interpolationDelimiterLength is number of $ chars prepended to opening quotes + // If number of consecutive % chars in content is equal to interpolationDelimiterLength, + // then that sequence is treated as a format specifier, + // as in $"""%3d{42}""" or (equivalent) $$"""%%3d{{42}}""". + // Any extra % chars up to interpolationDelimiterLength, are treated simply as regular string content. + // 2x interpolationDelimiterLength or more % chars in a sequence will result in an error. + let maxPercents = 2 * args.interpolationDelimiterLength - 1 + if numPercents > maxPercents then + let m2 = lexbuf.LexemeRange + let rest = result() + errorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) + rest + else + // Add two % chars for each % that is supposed to be treated as regular string content + // + 1 for a format specifier. + let percentsToEmit = + if numPercents < args.interpolationDelimiterLength then 2 * numPercents + else 2 * (numPercents - args.interpolationDelimiterLength) + 1 + let s = String.replicate percentsToEmit "%" + addUnicodeString buf s + result() } + + | "{" + + { let (buf, fin, m, kind, args) = sargs + let numBraces = String.length (lexeme lexbuf) + // Extended interpolated strings starts with at least 2 $ + // Number of leading $s is the number of '{' needed to open interpolation expression (interpolationDelimiterLength) + // 2x interpolationDelimiterLength (or more) of '{' in a row would be unambiguous, so it's disallowed + let maxBraces = 2 * args.interpolationDelimiterLength - 1 + if numBraces > maxBraces then + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.interpolationDelimiterLength, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fail args lexbuf + (FSComp.SR.lexTooManyLBracesInTripleQuote()) + (fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont) + elif numBraces < args.interpolationDelimiterLength then + // Less than interpolationDelimiterLength means we treat '{' as normal content + addUnicodeString buf (lexeme lexbuf) + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + // numBraces in [interpolationDelimiterLength; maxBraces) + else + // A sequence of interpolationDelimiterLength * '{' starts interpolation expression. + // Any extra '{' are treated as normal string content. + let extraBraces = numBraces - args.interpolationDelimiterLength + if extraBraces > 0 then + String.replicate extraBraces "{" |> addUnicodeString buf + // get a new range for where the fill starts + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.interpolationDelimiterLength, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont + } + + | "}" + + { let (buf, _fin, m, kind, args) = sargs + let numBraces = lexeme lexbuf |> String.length + let result() = + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + if args.interpolationDelimiterLength > numBraces then + lexeme lexbuf |> addUnicodeString buf + (result()) + else + fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) + } + + | eof + { let (_buf, _fin, m, kind, args) = sargs + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) } + + | surrogateChar surrogateChar // surrogate code points always come in pairs + | _ + { let (buf, _fin, m, kind, args) = sargs + addUnicodeString buf (lexeme lexbuf) + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } // Parsing single-line comment - we need to split it into words for Visual Studio IDE and singleLineComment cargs skip = parse diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 669a1cf177e..c7dc0284bba 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -252,6 +252,11 @@ více typů podporuje měrné jednotky + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d řez 3d/4d s pevným indexem @@ -522,6 +527,11 @@ Bajtový řetězec se nedá interpolovat. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Oblasti IF-FSHARP/IF-CAML už nejsou podporovány. @@ -542,11 +552,26 @@ Neplatný interpolovaný řetězec. Literály s jednoduchou uvozovkou nebo doslovné řetězcové literály se nedají použít v interpolovaných výrazech v řetězcích s jednoduchou uvozovkou nebo v doslovných řetězcích. Zvažte možnost použít pro výraz interpolace explicitní vazbu let nebo řetězec s trojitými uvozovkami jako vnější řetězcový literál. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Neplatný interpolovaný řetězec. V interpolovaných výrazech se nedají použít řetězcové literály s trojitými uvozovkami. Zvažte možnost použít pro interpolovaný výraz explicitní vazbu let. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Všechny prvky seznamu musí být implicitně převoditelné na typ prvního prvku, což je řazená kolekce členů o délce {0} typu\n {1} \nTento element je řazená kolekce členů o délce {2} typu\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 99cf7d8407c..c398aaf2baf 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -252,6 +252,11 @@ Maßeinheitenunterstützung durch weitere Typen + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d Segment 3D/4D mit feststehendem Index @@ -522,6 +527,11 @@ Eine Bytezeichenfolge darf nicht interpoliert werden. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP-/IF-CAML-Regionen werden nicht mehr unterstützt @@ -542,11 +552,26 @@ Ungültige interpolierte Zeichenfolge. Literale mit einzelnen Anführungszeichen oder ausführliche Zeichenfolgenliterale dürfen nicht in interpolierten Ausdrücken in Zeichenfolgen mit einfachen Anführungszeichen oder ausführlichen Zeichenfolgen verwendet werden. Erwägen Sie die Verwendung einer expliziten let-Bindung für den Interpolationsausdruck, oder verwenden Sie eine Zeichenfolge mit dreifachen Anführungszeichen als äußeres Zeichenfolgenliteral. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Ungültige interpolierte Zeichenfolge. Zeichenfolgenliterale mit dreifachen Anführungszeichen dürfen in interpolierten Ausdrücken nicht verwendet werden. Erwägen Sie die Verwendung einer expliziten let-Bindung für den Interpolationsausdruck. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Alle Elemente einer Liste müssen implizit in den Typ des ersten Elements konvertiert werden. Hierbei handelt es sich um ein Tupel der Länge {0} vom Typ\n {1} \nDieses Element ist ein Tupel der Länge {2} vom Typ\n {3}. \n diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 1e889b6e329..60225d12eb4 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -252,6 +252,11 @@ más tipos admiten las unidades de medida + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d segmento de índice fijo 3d/4d @@ -522,6 +527,11 @@ no se puede interpolar una cadena de bytes + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Ya no se admiten las regiones IF-FSHARP/IF-CAML @@ -542,11 +552,26 @@ Cadena interpolada no válida. No se pueden usar literales de cadena textual o de comillas simples en expresiones interpoladas en cadenas textuales o de comillas simples. Puede usar un enlace "let" explícito para la expresión de interpolación o utilizar una cadena de comillas triples como literal de cadena externo. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadena interpolada no válida. No se pueden usar literales de cadena de comillas triples en las expresiones interpoladas. Puede usar un enlace "let" explícito para la expresión de interpolación. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Todos los elementos de una lista deben convertirse implícitamente en el tipo del primer elemento, que aquí es una tupla de longitud {0} de tipo\n {1} \nEste elemento es una tupla de longitud {2} de tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index cb6d5cfe076..64f9f7f9630 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -252,6 +252,11 @@ d'autres types prennent en charge les unités de mesure + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d section à index fixe 3D/4D @@ -522,6 +527,11 @@ une chaîne d'octets ne peut pas être interpolée + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Les régions IF-FSHARP/IF-CAML ne sont plus prises en charge. @@ -542,11 +552,26 @@ Chaîne interpolée non valide. Les littéraux de chaîne verbatim ou à guillemet simple ne peuvent pas être utilisés dans les expressions interpolées dans des chaînes verbatim ou à guillemet simple. Utilisez une liaison 'let' explicite pour l'expression d'interpolation ou une chaîne à guillemets triples comme littéral de chaîne externe. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Chaîne interpolée non valide. Les littéraux de chaîne à guillemets triples ne peuvent pas être utilisés dans des expressions interpolées. Utilisez une liaison 'let' explicite pour l'expression d'interpolation. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Tous les éléments d’une liste doivent être implicitement convertibles en type du premier élément, qui est ici un tuple de longueur {0} de type\n {1} \nCet élément est un tuple de longueur {2} de type\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 48a1e719e87..2a9adff3428 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -252,6 +252,11 @@ più tipi supportano le unità di misura + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d sezione a indice fisso 3D/4D @@ -522,6 +527,11 @@ non è possibile interpolare una stringa di byte + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Le aree IF-FSHARP/IF-CAML non sono più supportate @@ -542,11 +552,26 @@ La stringa interpolata non è valida. Non è possibile usare valori letterali stringa tra virgolette singole o verbatim in espressioni interpolate in stringhe verbatim o tra virgolette singole. Provare a usare un binding 'let' esplicito per l'espressione di interpolazione oppure usare una stringa tra virgolette triple come valore letterale stringa esterno. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. La stringa interpolata non è valida. Non è possibile usare valori letterali stringa tra virgolette triple in espressioni interpolate. Provare a usare un binding 'let' esplicito per l'espressione di interpolazione. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Tutti gli elementi di un elenco devono essere convertibili in modo implicito nel tipo del primo elemento, che qui è una tupla di lunghezza {0} di tipo\n {1} \nQuesto elemento è una tupla di lunghezza {2} di tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 126d2e4709c..207e80955dd 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -252,6 +252,11 @@ 単位をサポートするその他の型 + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d 固定インデックス スライス 3d/4d @@ -522,6 +527,11 @@ バイト文字列は補間されていない可能性があります + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML リージョンは現在サポートされていません @@ -542,11 +552,26 @@ 補間された文字列が無効です。単一引用符または逐語的文字列リテラルは、単一引用符または逐語的文字列内の補間された式では使用できません。補間式に対して明示的な 'let' バインドを使用するか、外部文字列リテラルとして三重引用符文字列を使用することをご検討ください。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 補間された文字列が無効です。三重引用符文字列リテラルは、補間された式では使用できません。補間式に対して明示的な 'let' バインドを使用することをご検討ください。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n リストのすべての要素は、最初の要素の型に暗黙的に変換できる必要があります。これは、型の長さ {0} のタプルです\n {1} \nこの要素は、型の長さ {2} のタプルです\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 639df27e2f8..9d83ebe684f 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -252,6 +252,11 @@ 더 많은 형식이 측정 단위를 지원함 + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d 고정 인덱스 슬라이스 3d/4d @@ -522,6 +527,11 @@ 바이트 문자열을 보간하지 못할 수 있습니다. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML 영역은 더 이상 지원되지 않습니다. @@ -542,11 +552,26 @@ 잘못된 보간 문자열. 작은 따옴표 또는 축자 문자열 리터럴은 작은 따옴표 또는 축자 문자열의 보간 식에 사용할 수 없습니다. 보간 식에 명시적 'let' 바인딩을 사용하거나 삼중 따옴표 문자열을 외부 문자열 리터럴로 사용해 보세요. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 잘못된 보간 문자열. 삼중 따옴표 문자열 리터럴은 보간 식에 사용할 수 없습니다. 보간 식에 명시적 'let' 바인딩을 사용해 보세요. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 목록의 모든 요소는 첫 번째 요소의 형식으로 암시적으로 변환할 수 있어야 합니다. 여기서는 형식이 \n {1}이고 길이가 {0}인 튜플입니다. \n이 요소는 형식이 \n {3}이고 길이가 {2}인 튜플입니다. \n diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index c137d76cc4b..ce2756532cd 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -252,6 +252,11 @@ więcej typów obsługuje jednostki miary + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d część o stałym indeksie 3d/4d @@ -522,6 +527,11 @@ ciąg bajtowy nie może być interpolowany + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Regiony IF-FSHARP/IF-CAML nie są już obsługiwane @@ -542,11 +552,26 @@ Nieprawidłowy ciąg interpolowany. Literały z pojedynczymi cudzysłowami lub literały ciągów dosłownych nie mogą być używane w wyrażeniach interpolowanych w ciągach z pojedynczymi cudzysłowami lub ciągach dosłownych. Rozważ użycie jawnego powiązania „let” dla wyrażenia interpolacji lub użycie ciągu z potrójnymi cudzysłowami jako zewnętrznego literału ciągu. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Nieprawidłowy ciąg interpolowany. Literały ciągów z potrójnymi cudzysłowami nie mogą być używane w wyrażeniach interpolowanych. Rozważ użycie jawnego powiązania „let” dla wyrażenia interpolacji. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Wszystkie elementy tablicy muszą być niejawnie konwertowalne na typ pierwszego elementu, który w tym miejscu jest krotką o długości {0} typu\n {1} \nTen element jest krotką o długości {2} typu\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 09419ca7384..bd67aca995f 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -252,6 +252,11 @@ mais tipos dão suporte para unidades de medida + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d fatia de índice fixo 3d/4d @@ -522,6 +527,11 @@ uma cadeia de caracteres de byte não pode ser interpolada + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported As regiões IF-FSHARP/IF-CAML não são mais suportadas @@ -542,11 +552,26 @@ Cadeia de caracteres interpolada inválida. Literais de cadeia de caracteres verbatim ou com aspas simples não podem ser usados em expressões interpoladas em cadeias de caracteres verbatim ou com aspas simples. Considere usar uma associação 'let' explícita para a expressão de interpolação ou use uma cadeia de caracteres de aspas triplas como o literal da cadeia de caracteres externa. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadeia de caracteres interpolada inválida. Literais de cadeia de caracteres de aspas triplas não podem ser usados em expressões interpoladas. Considere usar uma associação 'let' explícita para a expressão de interpolação. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Todos os elementos de uma lista devem ser implicitamente conversíveis ao tipo do primeiro elemento, que aqui é uma tupla de comprimento {0} do tipo\n {1} \nEste elemento é uma tupla de comprimento {2} do tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 5a9d0aae1f8..70aca08735c 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -252,6 +252,11 @@ другие типы поддерживают единицы измерения + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d срез с фиксированным индексом 3d/4d @@ -522,6 +527,11 @@ невозможно выполнить интерполяцию для строки байтов + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Регионы IF-FSHARP/IF-CAML больше не поддерживаются @@ -542,11 +552,26 @@ Недопустимая интерполированная строка. Строковые литералы с одинарными кавычками или буквальные строковые литералы запрещено использовать в интерполированных выражениях в строках с одинарными кавычками или буквальных строках. Рекомендуется использовать явную привязку "let" для выражения интерполяции или использовать строку с тройными кавычками как внешний строковый литерал. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Недопустимая интерполированная строка. Строковые литералы с тройными кавычками запрещено использовать в интерполированных выражениях. Рекомендуется использовать явную привязку "let" для выражения интерполяции. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Все элементы списка должны поддерживать неявное преобразование в тип первого элемента, который здесь является кортежем длиной {0} типа\n {1} \nЭтот элемент является кортежем длиной {2} типа\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index b40b29f5438..583e55f7bcb 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -252,6 +252,11 @@ tür daha ölçü birimlerini destekler + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d sabit dizinli dilim 3d/4d @@ -522,6 +527,11 @@ bir bayt dizesi, düz metin arasına kod eklenerek kullanılamaz + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML bölgeleri artık desteklenmiyor @@ -542,11 +552,26 @@ Geçersiz düz metin arasına kod eklenmiş dize. Tek tırnaklı veya düz metin dizesi sabitleri, tek tırnaklı veya düz metin dizelerinde düz metin arasına kod eklenmiş ifadelerde kullanılamaz. Düz metin arasına kod ekleme ifadesi için açık bir 'let' bağlaması kullanmayı düşünün veya dış dize sabiti olarak üç tırnaklı bir dize kullanın. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Geçersiz düz metin arasına kod eklenmiş dize. Üç tırnaklı dize sabitleri, düz metin arasına kod eklenmiş ifadelerde kullanılamaz. Düz metin arasına kod ekleme ifadesi için açık bir 'let' bağlaması kullanmayı düşünün. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Bir listenin tüm öğeleri örtük olarak ilk öğenin türüne dönüştürülebilir olmalıdır. Burada ilk öğe {0} uzunluğunda türü\n {1} \nolan bir demet. Bu öğe ise {2} uzunluğunda türü\n {3} \nolan bir demet. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 7a660c0c0a8..6fedbd6a23f 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -252,6 +252,11 @@ 更多类型支持度量单位 + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d 固定索引切片 3d/4d @@ -522,6 +527,11 @@ 不能内插字节字符串 + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported 不再支持 IF-FSHARP/IF-CAML 区域 @@ -542,11 +552,26 @@ 内插字符串无效。在单引号字符串或逐字字符串的内插表达式中不能使用单引号或逐字字符串文本。请考虑对内插表达式使用显式 "let" 绑定,或使用三重引号字符串作为外部字符串文本。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 内插字符串无效。在内插表达式中不能使用三重引号字符串文字。请考虑对内插表达式使用显式的 "let" 绑定。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 列表的所有元素必须可隐式转换为第一个元素的类型,这是一个长度为 {0} 的类型的元组\n {1} \n此元素是长度为 {2} 类型的元组\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 105c38cd0b7..a00f23792eb 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -252,6 +252,11 @@ 更多支援測量單位的類型 + + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. + + fixed-index slice 3d/4d 固定索引切割 3d/4d @@ -522,6 +527,11 @@ 位元組字串不能是插補字串 + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported 不再支援 IF-FSHARP/IF-CAML 區域 @@ -542,11 +552,26 @@ 插補字串無效。單引號或逐字字串常值不能用於單引號或逐字字串的插補運算式中。請考慮為內插補點運算式使用明確的 'let' 繫結,或使用三引號字串作為外部字串常值。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 插補字串無效。三引號字串常值不可用於插補運算式。請考慮為內插補點運算式使用明確的 'let' 繫結。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 清單的所有元素必須以隱含方式轉換成第一個元素的類型,這是類型為\n {1} \n的元組長度 {0}此元素是類型為\n {3} \n的元組長度 {2} diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index fa320ea3f7a..5d1489354cc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -43,12 +43,65 @@ printf $"{a.Format}" |> shouldSucceed |> withStdOutContains "{{hello}} world" + [] + let ``Interpolated string with 2 leading dollar characters uses double braces for delimiters`` () = + // let s = $$"""{{42 + 0}} = {41 + 1}""" + // printfn "%s" s + Fsx "let s = $$\"\"\"{{42 + 0}} = {41 + 1}\"\"\"\n\ +printfn \"%s\" s" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "42 = {41 + 1}" + + [] + let ``Too many consecutive opening braces in interpolated string result in an error`` () = + // $$"""{{{{42 - 0}}""" + Fsx "$$\"\"\"{{{{42 - 0}}\"\"\"" + |> withLangVersionPreview + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 1248, Line 1, Col 1, Line 1, Col 10, "The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content.") + + [] + let ``Too many consecutive closing braces in interpolated string result in an error`` () = + // $$"""{{42 - 0}}}}""" + Fsx "$$\"\"\"{{42 - 0}}}}\"\"\"" + |> withLangVersionPreview + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 1249, Line 1, Col 15, Line 1, Col 21, "The interpolated string contains unmatched closing braces.") + [] let ``Percent sign characters in interpolated strings`` () = Assert.Equal("%", $"%%") Assert.Equal("42%", $"{42}%%") Assert.Equal("% 42", $"%%%3d{42}") + [] + let ``Double percent sign characters in triple quote interpolated strings`` () = + Fsx "printfn \"%s\" $$$\"\"\"%%\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "%%" + + [] + let ``Percent sign after interpolation hole in triple quote strings`` () = + Fsx "printfn \"%s\" $$\"\"\"{{42}}%\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "42%" + + [] + let ``Percent sign before format specifier in triple quote interpolated strings`` () = + Fsx "printfn \"%s\" $$\"\"\"%%%3d{{42}}\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "% 42" + [] let ``Percent signs separated by format specifier's flags`` () = Fsx """ diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 964ddbf6e56..95b6226047f 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -10133,7 +10133,7 @@ FSharp.Compiler.Tokenization.FSharpLineTokenizer: FSharp.Compiler.Tokenization.F FSharp.Compiler.Tokenization.FSharpLineTokenizer: System.Tuple`2[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Tokenization.FSharpTokenInfo],FSharp.Compiler.Tokenization.FSharpTokenizerLexState] ScanToken(FSharp.Compiler.Tokenization.FSharpTokenizerLexState) FSharp.Compiler.Tokenization.FSharpSourceTokenizer: FSharp.Compiler.Tokenization.FSharpLineTokenizer CreateBufferTokenizer(Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[System.Char[],System.Int32,System.Int32],System.Int32]) FSharp.Compiler.Tokenization.FSharpSourceTokenizer: FSharp.Compiler.Tokenization.FSharpLineTokenizer CreateLineTokenizer(System.String) -FSharp.Compiler.Tokenization.FSharpSourceTokenizer: Void .ctor(Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.Tokenization.FSharpSourceTokenizer: Void .ctor(Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.Tokenization.FSharpToken: Boolean IsCommentTrivia FSharp.Compiler.Tokenization.FSharpToken: Boolean IsIdentifier FSharp.Compiler.Tokenization.FSharpToken: Boolean IsKeyword @@ -11302,6 +11302,7 @@ FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokeniza FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState Comment FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState EndLineThenSkip FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState EndLineThenToken +FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState ExtendedInterpolatedString FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState IfDefSkip FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState InitialState FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState SingleLineComment diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index 9a50646ed35..74a96b665de 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -586,7 +586,9 @@ let s3 = $"abc %d{s.Length} [] let ``Printf specifiers for triple quote interpolated strings`` () = let input = - "let _ = $\"\"\"abc %d{1} and %d{2+3}def\"\"\" " + "let _ = $\"\"\"abc %d{1} and %d{2+3}def\"\"\" +let _ = $$\"\"\"abc %%d{{1}} and %%d{{2}}def\"\"\" +let _ = $$$\"\"\"%% %%%d{{{4}}} % %%%d{{{5}}}\"\"\" " let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScriptWithOptions(file, input, [| "/langversion:preview" |]) @@ -595,7 +597,9 @@ let ``Printf specifiers for triple quote interpolated strings`` () = typeCheckResults.GetFormatSpecifierLocationsAndArity() |> Array.map (fun (range,numArgs) -> range.StartLine, range.StartColumn, range.EndLine, range.EndColumn, numArgs) |> shouldEqual - [|(1, 16, 1, 18, 1); (1, 26, 1, 28, 1)|] + [|(1, 16, 1, 18, 1); (1, 26, 1, 28, 1) + (2, 17, 2, 20, 1); (2, 30, 2, 33, 1) + (3, 17, 3, 21, 1); (3, 31, 3, 35, 1)|] #endif // ASSUME_PREVIEW_FSHARP_CORE diff --git a/tests/service/SyntaxTreeTests.fs b/tests/service/SyntaxTreeTests.fs index 2650ad1720e..fdd82ce56a7 100644 --- a/tests/service/SyntaxTreeTests.fs +++ b/tests/service/SyntaxTreeTests.fs @@ -133,7 +133,8 @@ let parseSourceCode (name: string, code: string) = SourceText.ofString code, { FSharpParsingOptions.Default with SourceFiles = [| location |] - IsExe = true } + IsExe = true + LangVersionText = "preview" } ) |> Async.RunImmediate diff --git a/tests/service/TokenizerTests.fs b/tests/service/TokenizerTests.fs index 8b0fc2c8995..7be5312b325 100644 --- a/tests/service/TokenizerTests.fs +++ b/tests/service/TokenizerTests.fs @@ -12,7 +12,7 @@ open FSharp.Compiler.Tokenization open NUnit.Framework -let sourceTok = FSharpSourceTokenizer([], Some "C:\\test.fsx") +let sourceTok = FSharpSourceTokenizer([], Some "C:\\test.fsx", None) let rec parseLine(line: string, state: FSharpTokenizerLexState ref, tokenizer: FSharpLineTokenizer) = seq { match tokenizer.ScanToken(state.Value) with diff --git a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs new file mode 100644 index 00000000000..6006b58162c --- /dev/null +++ b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs @@ -0,0 +1,2 @@ + +let s = $$$"""1 + {{{41}}} = {{{6}}} * 7""" \ No newline at end of file diff --git a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl new file mode 100644 index 00000000000..8fbc4d9d76c --- /dev/null +++ b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl @@ -0,0 +1,30 @@ +ImplFile + (ParsedImplFileInput + ("/root/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs", + false, + QualifiedNameOfFile + SynExprInterpolatedStringWithTripleQuoteMultipleDollars, [], [], + [SynModuleOrNamespace + ([SynExprInterpolatedStringWithTripleQuoteMultipleDollars], false, + AnonModule, + [Let + (false, + [SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((2,0), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (None, SynValInfo ([], SynArgInfo ([], false, None)), None), + Named (SynIdent (s, None), false, None, (2,4--2,5)), None, + InterpolatedString + ([String ("1 + ", (2,8--2,21)); + FillExpr (Const (Int32 41, (2,21--2,23)), None); + String (" = ", (2,25--2,32)); + FillExpr (Const (Int32 6, (2,32--2,33)), None); + String (" * 7", (2,35--2,43))], TripleQuote, (2,8--2,43)), + (2,4--2,5), Yes (2,0--2,43), { LeadingKeyword = Let (2,0--2,3) + InlineKeyword = None + EqualsRange = Some (2,6--2,7) })], + (2,0--2,43))], PreXmlDocEmpty, [], None, (2,0--2,43), + { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs index bed47fcb673..9f00d6a0b07 100644 --- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs +++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs @@ -506,6 +506,7 @@ type EditorBraceCompletionSessionFactory() = TextSpan(position - 1, 1), Some(document.FilePath), [], + None, cancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index be79f40569a..43673355abc 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -177,7 +177,7 @@ type internal FSharpClassificationService [] () = async { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask // For closed documents, only get classification for the text within the span. @@ -193,6 +193,7 @@ type internal FSharpClassificationService [] () = textSpan, Some(document.FilePath), defines, + Some langVersion, cancellationToken ) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs index 3f337cfbee3..5c07cb6e799 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs @@ -28,8 +28,8 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [] // Only trigger when failing to parse `->`, which arises when `fun` is missing do! Option.guard (textOfError = "->") - let! defines = - document.GetFSharpCompilationDefinesAsync(nameof (FSharpAddMissingFunKeywordCodeFixProvider)) + let! defines, langVersion = + document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (FSharpAddMissingFunKeywordCodeFixProvider)) |> liftAsync let adjustedPosition = @@ -51,6 +51,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [] SymbolLookupKind.Greedy, false, false, + Some langVersion, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs index 2739fde263b..ec9c7dad660 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs @@ -26,8 +26,10 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [ liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) @@ -51,6 +53,7 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [] (assemblyCon let line = sourceText.Lines.GetLineFromPosition(context.Span.End) let linePos = sourceText.Lines.GetLinePosition(context.Span.End) - let! defines = - document.GetFSharpCompilationDefinesAsync(nameof (FSharpAddOpenCodeFixProvider)) + let! defines, langVersion = + document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (FSharpAddOpenCodeFixProvider)) |> liftAsync let! symbol = @@ -95,6 +95,7 @@ type internal FSharpAddOpenCodeFixProvider [] (assemblyCon SymbolLookupKind.Greedy, false, false, + Some langVersion, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs index f0d23c45aeb..d0630bbba0d 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs @@ -202,6 +202,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( |> liftAsync let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + let langVersionOpt = Some parsingOptions.LangVersionText // Notice that context.Span doesn't return reliable ranges to find tokens at exact positions. // That's why we tokenize the line and try to find the last successive identifier token let tokens = @@ -211,6 +212,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( context.Span.Start, context.Document.FilePath, defines, + langVersionOpt, context.CancellationToken ) @@ -249,6 +251,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( SymbolLookupKind.Greedy, false, false, + langVersionOpt, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 2bbec8d2c45..88f5e5ae669 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -105,11 +105,19 @@ type internal FSharpHelpContextService [] () = member this.GetHelpTermAsync(document, textSpan, cancellationToken) = asyncMaybe { let! sourceText = document.GetTextAsync(cancellationToken) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start) let classifiedSpans = - Tokenizer.getClassifiedSpans (document.Id, sourceText, textLine.Span, Some document.Name, defines, cancellationToken) + Tokenizer.getClassifiedSpans ( + document.Id, + sourceText, + textLine.Span, + Some document.Name, + defines, + Some langVersion, + cancellationToken + ) return! FSharpHelpContextService.GetHelpTerm(document, textSpan, classifiedSpans) } diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 935a18b3bb8..963997eb804 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -102,7 +102,7 @@ type internal FSharpCompletionProvider sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, - getInfo: (unit -> DocumentId * string * string list), + getInfo: (unit -> DocumentId * string * string list * string option), intelliSenseOptions: IntelliSenseOptions ) = if caretPosition = 0 then @@ -127,9 +127,9 @@ type internal FSharpCompletionProvider then false else - let documentId, filePath, defines = getInfo () + let documentId, filePath, defines, langVersion = getInfo () - CompletionUtils.shouldProvideCompletion (documentId, filePath, defines, sourceText, triggerPosition) + CompletionUtils.shouldProvideCompletion (documentId, filePath, defines, langVersion, sourceText, triggerPosition) && (triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord (sourceText, triggerPosition))) @@ -287,8 +287,8 @@ type internal FSharpCompletionProvider let getInfo () = let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = document.GetFSharpQuickDefines() - (documentId, document.FilePath, defines) + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() + (documentId, document.FilePath, defines, Some langVersion) FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo, settings.IntelliSense) @@ -299,11 +299,18 @@ type internal FSharpCompletionProvider let document = context.Document let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() do! Option.guard ( - CompletionUtils.shouldProvideCompletion (document.Id, document.FilePath, defines, sourceText, context.Position) + CompletionUtils.shouldProvideCompletion ( + document.Id, + document.FilePath, + defines, + Some langVersion, + sourceText, + context.Position + ) ) let getAllSymbols (fileCheckResults: FSharpCheckFileResults) = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs index bcb4e3ea942..4311a1e552d 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs @@ -54,8 +54,11 @@ type internal FSharpCompletionService override _.GetDefaultCompletionListSpan(sourceText, caretIndex) = let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretIndex, documentId, document.FilePath, defines) + + let defines, langVersion = + projectInfoManager.GetCompilationDefinesAndLangVersionForEditingDocument(document) + + CompletionUtils.getDefaultCompletionListSpan (sourceText, caretIndex, documentId, document.FilePath, defines, Some langVersion) [] [, FSharpConstants.FSharpLanguageName)>] diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs index 0257d2425b4..c261cea6cc5 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs @@ -95,6 +95,7 @@ module internal CompletionUtils = documentId: DocumentId, filePath: string, defines: string list, + langVersion: string option, sourceText: SourceText, triggerPosition: int ) : bool = @@ -102,7 +103,15 @@ module internal CompletionUtils = let triggerLine = textLines.GetLineFromPosition triggerPosition let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, triggerLine.Span, Some filePath, defines, CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + triggerLine.Span, + Some filePath, + defines, + langVersion, + CancellationToken.None + ) classifiedSpans.Count = 0 || // we should provide completion at the start of empty line, where there are no tokens at all @@ -131,7 +140,7 @@ module internal CompletionUtils = | CompletionItemKind.Method(isExtension = true) -> 7 /// Indicates the text span to be replaced by a committed completion list item. - let getDefaultCompletionListSpan (sourceText: SourceText, caretIndex, documentId, filePath, defines) = + let getDefaultCompletionListSpan (sourceText: SourceText, caretIndex, documentId, filePath, defines, langVersion) = // Gets connected identifier-part characters backward and forward from caret. let getIdentifierChars () = @@ -167,7 +176,15 @@ module internal CompletionUtils = // the majority of common cases. let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, line.Span, Some filePath, defines, CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + line.Span, + Some filePath, + defines, + langVersion, + CancellationToken.None + ) let isBacktickIdentifier (classifiedSpan: ClassifiedSpan) = classifiedSpan.ClassificationType = ClassificationTypeNames.Identifier diff --git a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs index 417daf2acc4..539df9b6a7c 100644 --- a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs @@ -67,10 +67,22 @@ type internal HashDirectiveCompletionProvider let getClassifiedSpans (text: SourceText, position: int) : ResizeArray = let documentId = workspace.GetDocumentIdInCurrentContext(text.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + + let defines, langVersion = + projectInfoManager.GetCompilationDefinesAndLangVersionForEditingDocument(document) + let textLines = text.Lines let triggerLine = textLines.GetLineFromPosition(position) - Tokenizer.getClassifiedSpans (documentId, text, triggerLine.Span, Some document.FilePath, defines, CancellationToken.None) + + Tokenizer.getClassifiedSpans ( + documentId, + text, + triggerLine.Span, + Some document.FilePath, + defines, + Some langVersion, + CancellationToken.None + ) let isInStringLiteral (text: SourceText, position: int) : bool = getClassifiedSpans (text, position) diff --git a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs index f08aaa568f0..c8e408d4321 100644 --- a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs @@ -284,6 +284,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults: FSharpCheckFileResults, documentId: DocumentId, defines: string list, + langVersion: string option, documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, @@ -320,6 +321,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi SymbolLookupKind.Greedy, false, false, + langVersion, ct ) @@ -595,6 +597,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi ( document: Document, defines: string list, + langVersion: string option, documentationBuilder: IDocumentationBuilder, caretPosition: int, triggerTypedChar: char option, @@ -636,6 +639,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults, document.Id, defines, + langVersion, documentationBuilder, sourceText, caretPosition, @@ -653,6 +657,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults, document.Id, defines, + langVersion, documentationBuilder, sourceText, caretPosition, @@ -683,7 +688,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi member _.GetItemsAsync(document, position, triggerInfo, cancellationToken) = asyncMaybe { - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let triggerTypedChar = if @@ -700,6 +705,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi FSharpSignatureHelpProvider.ProvideSignatureHelp( document, defines, + Some langVersion, documentationBuilder, position, triggerTypedChar, diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index 919be73c7f8..30a3d8a148d 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -55,13 +55,21 @@ type internal FSharpLanguageDebugInfoService [] () = cancellationToken: CancellationToken ) : Task = async { - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let! cancellationToken = Async.CancellationToken let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let textSpan = TextSpan.FromBounds(0, sourceText.Length) let classifiedSpans = - Tokenizer.getClassifiedSpans (document.Id, sourceText, textSpan, Some(document.Name), defines, cancellationToken) + Tokenizer.getClassifiedSpans ( + document.Id, + sourceText, + textSpan, + Some(document.Name), + defines, + Some langVersion, + cancellationToken + ) let result = match FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, classifiedSpans) with diff --git a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs index baa5b0a1f03..cb7e2c5a0a6 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs @@ -50,7 +50,15 @@ type internal FSharpEditorFormattingService [] (settings: let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions let tokens = - Tokenizer.tokenizeLine (documentId, sourceText, line.Start, filePath, defines, cancellationToken) + Tokenizer.tokenizeLine ( + documentId, + sourceText, + line.Start, + filePath, + defines, + Some parsingOptions.LangVersionText, + cancellationToken + ) let! firstMeaningfulToken = tokens diff --git a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs index 1e5b5110d0d..c2897774a7d 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs @@ -34,7 +34,15 @@ type internal FSharpIndentationService [] () = let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions let tokens = - Tokenizer.tokenizeLine (documentId, sourceText, position, filePath, defines, CancellationToken.None) + Tokenizer.tokenizeLine ( + documentId, + sourceText, + position, + filePath, + defines, + Some parsingOptions.LangVersionText, + CancellationToken.None + ) tokens |> Array.rev diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 0cf3c14676b..fa4cef1bd41 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -526,10 +526,10 @@ type internal FSharpProjectOptionsManager(checker: FSharpChecker, workspace: Wor member _.ClearSingleFileOptionsCache(documentId) = reactor.ClearSingleFileOptionsCache(documentId) - /// Get compilation defines relevant for syntax processing. + /// Get compilation defines and language version relevant for syntax processing. /// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project /// options for a script. - member _.GetCompilationDefinesForEditingDocument(document: Document) = + member _.GetCompilationDefinesAndLangVersionForEditingDocument(document: Document) = let parsingOptions = match reactor.TryGetCachedOptionsByProjectId(document.Project.Id) with | Some (_, parsingOptions, _) -> parsingOptions @@ -539,7 +539,7 @@ type internal FSharpProjectOptionsManager(checker: FSharpChecker, workspace: Wor IsInteractive = CompilerEnvironment.IsScriptFile document.Name } - CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, parsingOptions.LangVersionText member _.TryGetOptionsByProject(project) = reactor.TryGetOptionsByProjectAsync(project) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index a96a5979baf..1fcf662bdc0 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -22,7 +22,7 @@ module internal SymbolHelpers = asyncMaybe { let userOpName = "getSymbolUsesOfSymbolAtLocationInDocument" let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync - let! defines = document.GetFSharpCompilationDefinesAsync(userOpName) |> liftAsync + let! defines, langVersion = document.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) |> liftAsync let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) @@ -40,6 +40,7 @@ module internal SymbolHelpers = SymbolLookupKind.Greedy, false, false, + Some langVersion, cancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs index 0f173a29e31..00c85540299 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs @@ -695,12 +695,13 @@ module internal Tokenizer = textSpan: TextSpan, fileName: string option, defines: string list, + langVersion, cancellationToken: CancellationToken ) : ResizeArray = let result = new ResizeArray() try - let sourceTokenizer = FSharpSourceTokenizer(defines, fileName) + let sourceTokenizer = FSharpSourceTokenizer(defines, fileName, langVersion) let lines = sourceText.Lines let sourceTextData = getSourceTextData (documentKey, defines, lines.Count) @@ -896,10 +897,11 @@ module internal Tokenizer = position: int, fileName: string, defines: string list, + langVersion, cancellationToken ) = let textLinePos = sourceText.Lines.GetLinePosition(position) - let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName) + let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName, langVersion) // We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines) let sourceTextData = getSourceTextData (documentKey, defines, sourceText.Lines.Count) @@ -912,10 +914,10 @@ module internal Tokenizer = lineData, textLinePos, contents - let tokenizeLine (documentKey, sourceText, position, fileName, defines, cancellationToken) = + let tokenizeLine (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) = try let lineData, _, _ = - getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, cancellationToken) + getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) lineData.SavedTokens with ex -> @@ -932,12 +934,13 @@ module internal Tokenizer = lookupKind: SymbolLookupKind, wholeActivePatterns: bool, allowStringToken: bool, + langVersion, cancellationToken ) : LexerSymbol option = try let lineData, textLinePos, lineContents = - getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, cancellationToken) + getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) getSymbolFromSavedTokens ( fileName, diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 3538e8bbd69..ebc208baae1 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -168,6 +168,13 @@ type Document with return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions } + /// Get the compilation defines and language version from F# project that is associated with the given F# document. + member this.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) = + async { + let! _, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) + return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, parsingOptions.LangVersionText + } + /// Get the instance of the FSharpChecker from the workspace by the given F# document. member this.GetFSharpChecker() = let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() @@ -184,11 +191,16 @@ type Document with let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() workspaceService.FSharpProjectOptionsManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(this.Id, this.FilePath) + /// A non-async call that quickly gets the defines and F# language version of the given F# document. + /// This tries to get the data by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the defines and the language version. + member this.GetFSharpQuickDefinesAndLangVersion() = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + workspaceService.FSharpProjectOptionsManager.GetCompilationDefinesAndLangVersionForEditingDocument(this) + /// A non-async call that quickly gets the defines of the given F# document. /// This tries to get the defines by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the defines. member this.GetFSharpQuickDefines() = - let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() - workspaceService.FSharpProjectOptionsManager.GetCompilationDefinesForEditingDocument(this) + this.GetFSharpQuickDefinesAndLangVersion() |> fst /// Parses the given F# document. member this.GetFSharpParseResultsAsync(userOpName) = @@ -238,7 +250,7 @@ type Document with /// Try to find a F# lexer/token symbol of the given F# document and position. member this.TryFindFSharpLexerSymbolAsync(position, lookupKind, wholeActivePattern, allowStringToken, userOpName) = async { - let! defines = this.GetFSharpCompilationDefinesAsync(userOpName) + let! defines, langVersion = this.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) let! ct = Async.CancellationToken let! sourceText = this.GetTextAsync(ct) |> Async.AwaitTask @@ -252,6 +264,7 @@ type Document with lookupKind, wholeActivePattern, allowStringToken, + Some langVersion, ct ) } diff --git a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs index 5097c01ffc1..f3edadb7746 100644 --- a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs +++ b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs @@ -17,15 +17,15 @@ open System.Diagnostics [)>] type internal FSharpTaskListService [] () as this = - let getDefines (doc: Microsoft.CodeAnalysis.Document) = + let getDefinesAndLangVersion (doc: Microsoft.CodeAnalysis.Document) = asyncMaybe { let! _, _, parsingOptions, _ = doc.GetFSharpCompilationOptionsAsync(nameof (FSharpTaskListService)) |> liftAsync - return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, Some parsingOptions.LangVersionText } - |> Async.map (Option.defaultValue []) + |> Async.map (Option.defaultValue ([], None)) let extractContractedComments (tokens: Tokenizer.SavedTokenInfo[]) = let granularTokens = @@ -52,6 +52,7 @@ type internal FSharpTaskListService [] () as this = doc: Microsoft.CodeAnalysis.Document, sourceText: SourceText, defines: string list, + langVersion: string option, descriptors: (string * FSharpTaskListDescriptor)[], cancellationToken ) = @@ -61,7 +62,7 @@ type internal FSharpTaskListService [] () as this = for line in sourceText.Lines do let contractedTokens = - Tokenizer.tokenizeLine (doc.Id, sourceText, line.Span.Start, doc.FilePath, defines, cancellationToken) + Tokenizer.tokenizeLine (doc.Id, sourceText, line.Span.Start, doc.FilePath, defines, langVersion, cancellationToken) |> extractContractedComments for ct in contractedTokens do @@ -89,6 +90,6 @@ type internal FSharpTaskListService [] () as this = backgroundTask { let descriptors = desc |> Seq.map (fun d -> d.Text, d) |> Array.ofSeq let! sourceText = doc.GetTextAsync(cancellationToken) - let! defines = doc |> getDefines - return this.GetTaskListItems(doc, sourceText, defines, descriptors, cancellationToken) + let! defines, langVersion = doc |> getDefinesAndLangVersion + return this.GetTaskListItems(doc, sourceText, defines, langVersion, descriptors, cancellationToken) } diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 986f3fddf46..a4415b3a1a6 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -250,17 +250,17 @@ type internal ProjectSitesAndFiles() = member art.GetDefinesForFile_DEPRECATED(rdt:IVsRunningDocumentTable, fileName : string, checker:FSharpChecker) = - // The only caller of this function calls it each time it needs to colorize a line, so this call must execute very fast. + // The only caller of this function calls it each time it needs to colorize a line, so this call must execute very fast. if CompilerEnvironment.MustBeSingleFileProject(fileName) then let parsingOptions = { FSharpParsingOptions.Default with IsInteractive = true} CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions - else - let siteOpt = - match VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,fileName) with - | Some(hier,_) -> tryGetProjectSite(hier) + else + let siteOpt = + match VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,fileName) with + | Some(hier,_) -> tryGetProjectSite(hier) | None -> None - let site = + let site = match siteOpt with | Some site -> site | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName) diff --git a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs index 9e8bce2aba3..8699e6fd42f 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs @@ -14,13 +14,18 @@ type BraceMatchingServiceTests() = let fileName = "C:\\test.fs" - member private this.VerifyNoBraceMatch(fileContents: string, marker: string) = + member private this.VerifyNoBraceMatch(fileContents: string, marker: string, ?langVersion: string) = let sourceText = SourceText.From(fileContents) let position = fileContents.IndexOf(marker) Assert.True(position >= 0, $"Cannot find marker '{marker}' in file contents") - let parsingOptions, _ = - checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + let parsingOptions = + let parsingOptions, _ = + checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + + { parsingOptions with + LangVersionText = langVersion |> Option.defaultValue "preview" + } match FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest") @@ -29,7 +34,7 @@ type BraceMatchingServiceTests() = | None -> () | Some (left, right) -> failwith $"Found match for brace '{marker}'" - member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string) = + member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string, ?langVersion: string) = let sourceText = SourceText.From(fileContents) let startMarkerPosition = fileContents.IndexOf(startMarker) let endMarkerPosition = fileContents.IndexOf(endMarker) @@ -37,8 +42,13 @@ type BraceMatchingServiceTests() = Assert.True(startMarkerPosition >= 0, $"Cannot find start marker '{startMarkerPosition}' in file contents") Assert.True(endMarkerPosition >= 0, $"Cannot find end marker '{endMarkerPosition}' in file contents") - let parsingOptions, _ = - checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + let parsingOptions = + let parsingOptions, _ = + checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + + { parsingOptions with + LangVersionText = langVersion |> Option.defaultValue "preview" + } match FSharpBraceMatchingService.GetBraceMatchingResult( @@ -92,6 +102,64 @@ type BraceMatchingServiceTests() = member this.BraceInInterpolatedStringSimple() = this.VerifyBraceMatch("let x = $\"abc{1}def\"", "{1", "}def") + [] + member this.BraceInInterpolatedStringWith2Dollars() = + this.VerifyBraceMatch("let x = $$\"\"\"abc{{1}}}def\"\"\"", "{{", "}}") + + [] + member this.BraceInInterpolatedStringWith3Dollars() = + this.VerifyBraceMatch("let x = $$$\"\"\"abc{{{1}}}def\"\"\"", "{{{", "}}}") + + [] + [] + [] + [] + [] + member this.BraceNoMatchInNestedInterpolatedStrings3Dollars(marker) = + let source = + "let x = $$$\"\"\"{{not a }}match +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyNoBraceMatch(source, marker) + + [] + [] + [] + [] + [] + member this.BraceNoMatchInNestedInterpolatedStrings2Dollars(marker) = + let source = + "let x = $$\"\"\"{not a }match +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyNoBraceMatch(source, marker) + + [] + [] + [] + [] + member this.BraceMatchInNestedInterpolatedStrings3Dollars(startMark, endMark) = + let source = + "let x = $$$\"\"\"a{{{01}}}b --- c{{{23}}}d +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyBraceMatch(source, startMark, endMark) + + [] + [] + [] + [] + member this.BraceMatchInNestedInterpolatedStrings2Dollars(startMark, endMark) = + let source = + "let x = $$\"\"\"a{{{01}}}b --- c{{{23}}}d +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyBraceMatch(source, startMark, endMark) + [] member this.BraceInInterpolatedStringTwoHoles() = this.VerifyBraceMatch("let x = $\"abc{1}def{2+3}hij\"", "{2", "}hij") diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index de8d75bd642..8bcf3168379 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -16,6 +16,9 @@ module CompletionProviderTests = let filePath = "C:\\test.fs" + let mkGetInfo documentId = + fun () -> documentId, filePath, [], (Some "preview") + let formatCompletions (completions: string seq) = "\n\t" + String.Join("\n\t", completions) @@ -102,7 +105,7 @@ module CompletionProviderTests = let sourceText = SourceText.From(fileContents) let resultSpan = - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, []) + CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, [], None) Assert.Equal(expected, sourceText.ToString(resultSpan)) @@ -130,14 +133,13 @@ System.Console.WriteLine(x + y) let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -152,14 +154,13 @@ System.Console.WriteLine(x + y) let fileContents = "System.Console.WriteLine(123)" let caretPosition = fileContents.IndexOf("rite") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, triggerKind, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -170,14 +171,13 @@ System.Console.WriteLine(x + y) let fileContents = "let literal = \"System.Console.WriteLine()\"" let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -195,14 +195,13 @@ System.Console.WriteLine() let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -233,14 +232,13 @@ let z = $"abc {System.Console.WriteLine(x + y)} def" for (marker, shouldBeTriggered) in testCases do let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -260,14 +258,13 @@ System.Console.WriteLine() let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -284,14 +281,13 @@ let f() = let caretPosition = fileContents.IndexOf("|.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -308,14 +304,13 @@ module Foo = module end let marker = "A" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -332,14 +327,13 @@ printfn "%d" !f let marker = "!f" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -357,14 +351,13 @@ use ptr = fixed &p let marker = "&p" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -391,14 +384,13 @@ xVal**y for marker in markers do let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -413,14 +405,13 @@ l""" let marker = "l" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) diff --git a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs index f34c0640c55..face43ab811 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs @@ -14,7 +14,14 @@ module GoToDefinitionServiceTests = let userOpName = "GoToDefinitionServiceTests" - let private findDefinition (document: Document, sourceText: SourceText, position: int, defines: string list) : range option = + let private findDefinition + ( + document: Document, + sourceText: SourceText, + position: int, + defines: string list, + langVersion: string option + ) : range option = maybe { let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position @@ -30,6 +37,7 @@ module GoToDefinitionServiceTests = SymbolLookupKind.Greedy, false, false, + langVersion, System.Threading.CancellationToken.None ) @@ -62,7 +70,7 @@ module GoToDefinitionServiceTests = |> RoslynTestHelpers.GetSingleDocument let actual = - findDefinition (document, sourceText, caretPosition, []) + findDefinition (document, sourceText, caretPosition, [], None) |> Option.map (fun range -> (range.StartLine, range.EndLine, range.StartColumn, range.EndColumn)) if actual <> expected then diff --git a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs index c48506c78b3..6088e79e9e6 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs @@ -42,7 +42,15 @@ type HelpContextServiceTests() = let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, textLine.Span, Some "test.fs", [], CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + textLine.Span, + Some "test.fs", + [], + None, + CancellationToken.None + ) FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans) |> Async.RunSynchronously diff --git a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs index e24c4e93d30..2f34a78f38b 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs @@ -59,6 +59,7 @@ let main argv = TextSpan.FromBounds(0, sourceText.Length), Some(fileName), defines, + None, CancellationToken.None ) diff --git a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs index a96c2374333..af6f3f3016f 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs @@ -169,6 +169,7 @@ module SignatureHelpProvider = checkFileResults, document.Id, [], + None, DefaultDocumentationProvider, sourceText, caretPosition, @@ -510,6 +511,7 @@ M.f checkFileResults, document.Id, [], + None, DefaultDocumentationProvider, sourceText, caretPosition, diff --git a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs index 6fcb469ff89..5120cd71d8f 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs @@ -12,7 +12,14 @@ open FSharp.Test type SyntacticClassificationServiceTests() = - member private this.ExtractMarkerData(fileContents: string, marker: string, defines: string list, isScriptFile: Option) = + member private this.ExtractMarkerData + ( + fileContents: string, + marker: string, + defines: string list, + langVersion: string option, + isScriptFile: Option + ) = let textSpan = TextSpan(0, fileContents.Length) let fileName = @@ -30,6 +37,7 @@ type SyntacticClassificationServiceTests() = textSpan, Some(fileName), defines, + langVersion, CancellationToken.None ) @@ -43,10 +51,13 @@ type SyntacticClassificationServiceTests() = marker: string, defines: string list, classificationType: string, - ?isScriptFile: bool + ?isScriptFile: bool, + ?langVersion: string ) = + let langVersion = langVersion |> Option.orElse (Some "preview") + let (tokens, markerPosition) = - this.ExtractMarkerData(fileContents, marker, defines, isScriptFile) + this.ExtractMarkerData(fileContents, marker, defines, langVersion, isScriptFile) match tokens |> Seq.tryFind (fun token -> token.TextSpan.Contains(markerPosition)) with | None -> failwith "Cannot find colorization data for start of marker" @@ -60,10 +71,13 @@ type SyntacticClassificationServiceTests() = marker: string, defines: string list, classificationType: string, - ?isScriptFile: bool + ?isScriptFile: bool, + ?langVersion: string ) = + let langVersion = langVersion |> Option.orElse (Some "preview") + let (tokens, markerPosition) = - this.ExtractMarkerData(fileContents, marker, defines, isScriptFile) + this.ExtractMarkerData(fileContents, marker, defines, langVersion, isScriptFile) match tokens @@ -1203,3 +1217,70 @@ type SyntacticClassificationServiceTests() = defines = [], classificationType = ClassificationTypeNames.Keyword ) + + [] + member public this.InterpolatedString_1Dollar() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + [] + member public this.InterpolatedString_2Dollars() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) + + [] + member public this.InterpolatedString_6Dollars() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "40+2", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) + + [] + [] + [] + [] + member public this.InterpolatedString_1DollarNestedIn2Dollars(marker: string, classificationType: string) = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{ $\"{10+1}\" }} {20+2} {{30+3}}\"\"\"", + marker = marker, + defines = [], + classificationType = classificationType + ) diff --git a/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs index 0abfccccecc..f342b0e92aa 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs @@ -24,7 +24,10 @@ let private descriptors = let assertTasks expectedTasks fileContents = let doc = createDocument fileContents let sourceText = doc.GetTextAsync().Result - let t = service.GetTaskListItems(doc, sourceText, [], descriptors, ct) + + let t = + service.GetTaskListItems(doc, sourceText, [], (Some "preview"), descriptors, ct) + let tasks = t |> Seq.map (fun t -> t.Message) |> List.ofSeq Assert.Equal(expectedTasks |> List.sort, tasks |> List.sort) diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index feb4d143032..f0189d75b4c 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -212,7 +212,7 @@ type internal FSharpLanguageServiceTestable() as this = let fileName = VsTextLines.GetFilename buffer let rdt = this.ServiceProvider.RunningDocumentTable let defines = this.ProjectSitesAndFiles.GetDefinesForFile_DEPRECATED(rdt, fileName, this.FSharpChecker) - let sourceTokenizer = FSharpSourceTokenizer(defines,Some(fileName)) + let sourceTokenizer = FSharpSourceTokenizer(defines,Some(fileName), None) sourceTokenizer.CreateLineTokenizer(source)) let colorizer = new FSharpColorizer_DEPRECATED(this.CloseColorizer, buffer, scanner) diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs index 11a6b231431..214d6ddf054 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs @@ -133,7 +133,7 @@ type UsingMSBuild() = let fileName = "test.fs" let defines = [ "COMPILED"; "EDITING" ] - FSharpSourceTokenizer(defines,Some(fileName)).CreateLineTokenizer(source)) + FSharpSourceTokenizer(defines,Some(fileName),None).CreateLineTokenizer(source)) let cm = Microsoft.VisualStudio.FSharp.LanguageService.TokenColor.Comment let kw = Microsoft.VisualStudio.FSharp.LanguageService.TokenColor.Keyword