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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,4 @@
* `FSharp.Compiler.Syntax.SynComponentInfo` now holds the type name as `synType: SynType option` instead of the previous `longId: LongIdent` field, so tuple-type extensions such as `type ('T1 * 'T2) with` can be represented. A `member LongIdent` compatibility property returns the long identifier for named types and an empty list for tuple or erroneous type names. AST consumers that pattern-matched on the `longId` field must switch to the `synType` field or the `LongIdent` member. ([PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
* LexFilter: drop non-strict mode ([PR #20106](https://github.com/dotnet/fsharp/pull/20106))
* `FSharp.Compiler.EditorServices.Structure.getOutliningRanges` now takes the source lines as `ReadOnlyMemory<char>[]` instead of `string[]`, so a caller that already holds the whole text can slice it instead of building a string per line. Callers passing a `string[]` can migrate with `Array.map (fun line -> line.AsMemory())`. ([PR #20443](https://github.com/dotnet/fsharp/pull/20443))
1 change: 1 addition & 0 deletions docs/release-notes/.VisualStudio/18.vNext.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
* Rename "inline hints" to "inlay hints" in VS options for consistency with industry terminology. ([PR #19318](https://github.com/dotnet/fsharp/pull/19318))
* Unused analyzers: disable in VS when file has errors ([PR #19892](https://github.com/dotnet/fsharp/pull/19892))
* Move to Roslyn's unified ExternalAccess library ([PR #20099](https://github.com/dotnet/fsharp/pull/20099))
* Reduce editor allocations when computing block structure (code folding) by slicing the source text once per outlining pass instead of copying every line. ([PR #20443](https://github.com/dotnet/fsharp/pull/20443))
79 changes: 42 additions & 37 deletions src/Compiler/Service/ServiceStructure.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace FSharp.Compiler.EditorServices

open System
open Internal.Utilities.Library
open FSharp.Compiler.Syntax
open FSharp.Compiler.SyntaxTreeOps
Expand Down Expand Up @@ -186,27 +187,39 @@ module Structure =
}

type LineNumber = int
type LineStr = string

type CommentType =
| SingleLine
| XmlDoc

/// <summary>
/// Determines if a line is a single line or xml documentation comment.
/// </summary>
/// <remarks>
/// Kept at module scope: a local recursive function capturing a <see cref="T:System.ReadOnlySpan`1"/>-typed
/// helper as a closure field would need to instantiate <see cref="T:Microsoft.FSharp.Core.FSharpFunc`2"/>
/// over it, which the CLR disallows for byref-like type arguments (FS0412).
/// </remarks>
let commentTypeOf (line: ReadOnlySpan<char>) =
if line.StartsWithOrdinal("///") then ValueSome XmlDoc
elif line.StartsWithOrdinal("//") then ValueSome SingleLine
else ValueNone

[<NoComparison>]
type CommentList =
{
Lines: ResizeArray<LineNumber * LineStr>
Lines: ResizeArray<LineNumber>
Type: CommentType
}

static member New ty lineStr =
static member New ty lineNum =
{
Type = ty
Lines = ResizeArray [ lineStr ]
Lines = ResizeArray [ lineNum ]
}

/// Returns outlining ranges for given parsed input.
let getOutliningRanges (sourceLines: string[]) (parsedInput: ParsedInput) =
let getOutliningRanges (sourceLines: ReadOnlyMemory<char>[]) (parsedInput: ParsedInput) =
let acc = ResizeArray()

/// Validation function to ensure that ranges yielded for outlining span 2 or more lines
Expand Down Expand Up @@ -661,7 +674,7 @@ module Structure =
| r :: rest, last :: _ when
r.StartLine = last.EndLine + 1
|| sourceLines[last.EndLine .. r.StartLine - 2]
|> Array.forall System.String.IsNullOrWhiteSpace
|> Array.forall _.Span.IsWhiteSpace()
->
loop rest res (r :: currentBulk)
| r :: rest, _ -> loop rest (currentBulk :: res) [ r ]
Expand Down Expand Up @@ -719,7 +732,7 @@ module Structure =

let collectConditionalDirectives directives sourceLines =
// Adds a fold region from prevRange.Start to the line above nextLine
let addSectionFold (prevRange: range) (nextLine: int) (sourceLines: string array) =
let addSectionFold (prevRange: range) (nextLine: int) (sourceLines: ReadOnlyMemory<char>[]) =
let startLineIndex = nextLine - 2

if startLineIndex >= 0 then
Expand Down Expand Up @@ -753,7 +766,7 @@ module Structure =
| ConditionalDirectiveTrivia.Else r -> ValueSome r
| _ -> ValueNone

let rec group directives stack (sourceLines: string array) =
let rec group directives stack (sourceLines: ReadOnlyMemory<char>[]) =
match directives with
| [] -> ()
| ConditionalDirectiveTrivia.If _ as ifDirective :: directives -> group directives (ifDirective :: stack) sourceLines
Expand Down Expand Up @@ -822,50 +835,42 @@ module Structure =
collectOpens decls
List.iter parseDeclaration decls

/// Determine if a line is a single line or xml documentation comment
let (|Comment|_|) (line: string) =
if line.StartsWithOrdinal("///") then Some XmlDoc
elif line.StartsWithOrdinal("//") then Some SingleLine
else None

let getCommentRanges trivia (lines: string[]) =
let rec loop (lastLineNum, currentComment, result as state) (lines: string list) lineNum =
match lines with
| [] -> state
| lineStr :: rest ->
match lineStr.TrimStart(), currentComment with
| Comment commentType, Some comment ->
let getCommentRanges trivia (lines: ReadOnlyMemory<char>[]) =
let rec loop (lastLineNum, currentComment, result as state) lineNum =
if lineNum = lines.Length then
state
else
match commentTypeOf (lines[lineNum].Span.TrimStart()), currentComment with
| ValueSome commentType, Some comment ->
loop
(if comment.Type = commentType && lineNum = lastLineNum + 1 then
comment.Lines.Add(lineNum, lineStr)
comment.Lines.Add lineNum
lineNum, currentComment, result
else
let comments = CommentList.New commentType (lineNum, lineStr)
let comments = CommentList.New commentType lineNum
lineNum, Some comments, comment :: result)
rest
(lineNum + 1)
| Comment commentType, None ->
let comments = CommentList.New commentType (lineNum, lineStr)
loop (lineNum, Some comments, result) rest (lineNum + 1)
| _, Some comment -> loop (lineNum, None, comment :: result) rest (lineNum + 1)
| _ -> loop (lineNum, None, result) rest (lineNum + 1)
| ValueSome commentType, None ->
let comments = CommentList.New commentType lineNum
loop (lineNum, Some comments, result) (lineNum + 1)
| ValueNone, Some comment -> loop (lineNum, None, comment :: result) (lineNum + 1)
| ValueNone, None -> loop (lineNum, None, result) (lineNum + 1)

let comments =
let _, lastComment, comments = loop (-1, None, []) (List.ofArray lines) 0
let _, lastComment, comments = loop (-1, None, []) 0

match lastComment with
| Some comment -> comment :: comments
| _ -> comments
|> List.rev

comments
|> List.filter (fun comment -> comment.Lines.Count > 1)
|> List.map (fun comment ->
let lines = comment.Lines
let startLine, startStr = lines[0]
let endLine, endStr = lines[lines.Count - 1]
let startCol = startStr.IndexOf '/'
let endCol = endStr.TrimEnd().Length
|> Seq.filter (fun comment -> comment.Lines.Count > 1)
|> Seq.map (fun comment ->
let startLine = comment.Lines[0]
let endLine = comment.Lines[comment.Lines.Count - 1]
let startCol = lines[startLine].Span.IndexOf '/'
let endCol = lines[endLine].Span.TrimEnd().Length

let scopeType =
match comment.Type with
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/Service/ServiceStructure.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace FSharp.Compiler.EditorServices

open System
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text

Expand Down Expand Up @@ -79,4 +80,4 @@ module public Structure =
}

/// Returns outlining ranges for given parsed input.
val getOutliningRanges: sourceLines: string[] -> parsedInput: ParsedInput -> seq<ScopeRange>
val getOutliningRanges: sourceLines: ReadOnlyMemory<char>[] -> parsedInput: ParsedInput -> seq<ScopeRange>
49 changes: 49 additions & 0 deletions src/Compiler/Utilities/illib.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open System.Collections.Generic
open System.Collections.Concurrent
open System.Diagnostics
open System.IO
open System.Linq
open System.Threading
open System.Threading.Tasks
open System.Runtime.CompilerServices
Expand Down Expand Up @@ -112,6 +113,54 @@ module internal PervasiveAutoOpens =
member inline x.IndexOfOrdinal(value: string, startIndex, count) =
x.IndexOf(value, startIndex, count, StringComparison.Ordinal)

[<AbstractClass; Sealed>]
type ReadOnlySpanCharExtensions =

static member inline StartsWithOrdinal(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>) =
str.StartsWith(value, StringComparison.Ordinal)

static member inline StartsWithOrdinal(str: ReadOnlySpan<char>, value: string) =
str.StartsWith(value.AsSpan(), StringComparison.Ordinal)

static member inline EndsWithOrdinal(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>) =
str.EndsWith(value, StringComparison.Ordinal)

static member inline EndsWithOrdinal(str: ReadOnlySpan<char>, value: string) =
str.EndsWith(value.AsSpan(), StringComparison.Ordinal)

static member inline EndsWithOrdinalIgnoreCase(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>) =
str.EndsWith(value, StringComparison.OrdinalIgnoreCase)

static member inline EndsWithOrdinalIgnoreCase(str: ReadOnlySpan<char>, value: string) =
str.EndsWith(value.AsSpan(), StringComparison.OrdinalIgnoreCase)

static member IndexOf(str: ReadOnlySpan<char>, value: char) =
let mutable index = -1
let mutable i = 0

while i < str.Length && index = -1 do
if str[i] = value then index <- i else i <- i + 1

index

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>) =
str.IndexOf(value, StringComparison.Ordinal)

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: string) =
str.IndexOf(value.AsSpan(), StringComparison.Ordinal)

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>, startIndex) =
str.Slice(startIndex).IndexOf(value, StringComparison.Ordinal)

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: string, startIndex) =
str.Slice(startIndex).IndexOf(value.AsSpan(), StringComparison.Ordinal)

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: ReadOnlySpan<char>, startIndex, count) =
str.Slice(startIndex, count).IndexOf(value, StringComparison.Ordinal)

static member inline IndexOfOrdinal(str: ReadOnlySpan<char>, value: string, startIndex, count) =
str.Slice(startIndex, count).IndexOf(value.AsSpan(), StringComparison.Ordinal)

/// Get an initialization hole
let getHole (r: _ ref) =
match r.Value with
Expand Down
45 changes: 45 additions & 0 deletions src/Compiler/Utilities/illib.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,51 @@ module internal PervasiveAutoOpens =

member inline IndexOfOrdinal: value: string * startIndex: int * count: int -> int

[<AbstractClass; Sealed; Extension>]
type ReadOnlySpanCharExtensions =

[<Extension>]
static member inline StartsWithOrdinal: str: ReadOnlySpan<char> * value: ReadOnlySpan<char> -> bool

[<Extension>]
static member inline StartsWithOrdinal: str: ReadOnlySpan<char> * value: string -> bool

[<Extension>]
static member inline EndsWithOrdinal: str: ReadOnlySpan<char> * value: ReadOnlySpan<char> -> bool

[<Extension>]
static member inline EndsWithOrdinal: str: ReadOnlySpan<char> * value: string -> bool

[<Extension>]
static member inline EndsWithOrdinalIgnoreCase: str: ReadOnlySpan<char> * value: ReadOnlySpan<char> -> bool

[<Extension>]
static member inline EndsWithOrdinalIgnoreCase: str: ReadOnlySpan<char> * value: string -> bool

[<Extension>]
static member IndexOf: str: ReadOnlySpan<char> * value: char -> int

[<Extension>]
static member inline IndexOfOrdinal: str: ReadOnlySpan<char> * value: ReadOnlySpan<char> -> int

[<Extension>]
static member inline IndexOfOrdinal: str: ReadOnlySpan<char> * value: string -> int

[<Extension>]
static member inline IndexOfOrdinal:
str: ReadOnlySpan<char> * value: ReadOnlySpan<char> * startIndex: int -> int

[<Extension>]
static member inline IndexOfOrdinal: str: ReadOnlySpan<char> * value: string * startIndex: int -> int

[<Extension>]
static member inline IndexOfOrdinal:
str: ReadOnlySpan<char> * value: ReadOnlySpan<char> * startIndex: int * count: int -> int

[<Extension>]
static member inline IndexOfOrdinal:
str: ReadOnlySpan<char> * value: string * startIndex: int * count: int -> int

type Async with

/// Runs the computation synchronously, always starting on the current thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4744,7 +4744,7 @@ FSharp.Compiler.EditorServices.Structure+ScopeRange: Void .ctor(Scope, Collapse,
FSharp.Compiler.EditorServices.Structure: FSharp.Compiler.EditorServices.Structure+Collapse
FSharp.Compiler.EditorServices.Structure: FSharp.Compiler.EditorServices.Structure+Scope
FSharp.Compiler.EditorServices.Structure: FSharp.Compiler.EditorServices.Structure+ScopeRange
FSharp.Compiler.EditorServices.Structure: System.Collections.Generic.IEnumerable`1[FSharp.Compiler.EditorServices.Structure+ScopeRange] getOutliningRanges(System.String[], FSharp.Compiler.Syntax.ParsedInput)
FSharp.Compiler.EditorServices.Structure: System.Collections.Generic.IEnumerable`1[FSharp.Compiler.EditorServices.Structure+ScopeRange] getOutliningRanges(System.ReadOnlyMemory`1[System.Char][], FSharp.Compiler.Syntax.ParsedInput)
FSharp.Compiler.EditorServices.ToolTipElement+CompositionError: System.String errorText
FSharp.Compiler.EditorServices.ToolTipElement+CompositionError: System.String get_errorText()
FSharp.Compiler.EditorServices.ToolTipElement+Group: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.EditorServices.ToolTipElementData] elements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,12 @@
<ProjectReference Include="..\service\data\CSharp_Analysis\CSharp_Analysis.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<!-- FSharp.Compiler.Service's PackageReference to System.Memory (for ReadOnlySpan/ReadOnlyMemory) is only
transitive here, so the SetTargetFramework override above does not carry it in on net472; pin it directly.
Not needed on the .NET Core inner build: there System.Memory ships with the framework and NuGet's
package-pruning check (NU1510) errors on an explicit reference to it. -->
<PackageReference Include="System.Memory" />
</ItemGroup>

</Project>
Loading
Loading