diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index c80dbf9fc0d..36e2c65b0a6 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,9 +1,9 @@
-
+ https://github.com/dotnet/source-build-reference-packages
- b88b567fbf54c5404d039b80cfb86f09a681f604
+ 05ffbf9df6c1dc621665ee1864874c4fe6de874c
@@ -39,25 +39,25 @@
194f32828726c3f1f63f79f3dc09b9e99c157b11
-
+ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- ee166d79f3a269d2a1c6b7d400df7e284b1aa67b
+ 9d70382f52bc311fa51e523bb066ebb012bf8035
-
+ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- ee166d79f3a269d2a1c6b7d400df7e284b1aa67b
+ 9d70382f52bc311fa51e523bb066ebb012bf8035
-
+ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- ee166d79f3a269d2a1c6b7d400df7e284b1aa67b
+ 9d70382f52bc311fa51e523bb066ebb012bf8035
-
+ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- ee166d79f3a269d2a1c6b7d400df7e284b1aa67b
+ 9d70382f52bc311fa51e523bb066ebb012bf8035
-
+ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- ee166d79f3a269d2a1c6b7d400df7e284b1aa67b
+ 9d70382f52bc311fa51e523bb066ebb012bf8035
diff --git a/eng/Versions.props b/eng/Versions.props
index 1012c555f2e..ad194714eff 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -206,10 +206,10 @@
5.10.32.2.0
- 1.0.0-prerelease.23471.3
- 1.0.0-prerelease.23471.3
- 1.0.0-prerelease.23471.3
- 1.0.0-prerelease.23471.3
- 1.0.0-prerelease.23471.3
+ 1.0.0-prerelease.23507.6
+ 1.0.0-prerelease.23507.6
+ 1.0.0-prerelease.23507.6
+ 1.0.0-prerelease.23507.6
+ 1.0.0-prerelease.23507.6
diff --git a/global.json b/global.json
index 8b3ca467ab8..1ffce949700 100644
--- a/global.json
+++ b/global.json
@@ -1,8 +1,7 @@
{
"sdk": {
"version": "8.0.100-rc.1.23455.8",
- "allowPrerelease": true,
- "rollForward": "latestMajor"
+ "allowPrerelease": true
},
"tools": {
"dotnet": "8.0.100-rc.1.23455.8",
diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs
index c52fb1cf8aa..452e9d5c7c2 100644
--- a/src/Compiler/Checking/CheckExpressions.fs
+++ b/src/Compiler/Checking/CheckExpressions.fs
@@ -10759,6 +10759,15 @@ and TcNonrecBindingTyparDecls cenv env tpenv bind =
TcBindingTyparDecls true cenv env tpenv synTyparDecls
and TcNonRecursiveBinding declKind cenv env tpenv ty binding =
+ // Check for unintended shadowing
+ match binding with
+ | SynBinding(headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = [ident]); range = headPatRange)) ->
+ match env.eNameResEnv.ePatItems.TryFind ident.idText with
+ | Some (Item.UnionCase(_, false)) ->
+ warning(Error(FSComp.SR.tcInfoIfFunctionShadowsUnionCase(), headPatRange))
+ | _ -> ()
+ | _ -> ()
+
let binding = BindingNormalization.NormalizeBinding ValOrMemberBinding cenv env binding
let explicitTyparInfo, tpenv = TcNonrecBindingTyparDecls cenv env tpenv binding
TcNormalizedBinding declKind cenv env tpenv ty None NoSafeInitInfo ([], explicitTyparInfo) binding
diff --git a/src/Compiler/Checking/TailCallChecks.fs b/src/Compiler/Checking/TailCallChecks.fs
index 4f2185220a0..9371f59955b 100644
--- a/src/Compiler/Checking/TailCallChecks.fs
+++ b/src/Compiler/Checking/TailCallChecks.fs
@@ -216,7 +216,31 @@ and CheckForNonTailRecCall (cenv: cenv) expr (tailCall: TailCall) =
| _ -> ()
/// Check call arguments, including the return argument.
-and CheckCall cenv args ctxts = CheckExprs cenv args ctxts TailCall.No
+and CheckCall cenv args ctxts (tailCall: TailCall) =
+ // detect CPS-like expressions
+ let rec (|IsAppInLambdaBody|_|) e =
+ match stripDebugPoints e with
+ | Expr.TyLambda (bodyExpr = bodyExpr)
+ | Expr.Lambda (bodyExpr = bodyExpr) ->
+ match (stripDebugPoints bodyExpr) with
+ | Expr.App _ -> Some(TailCall.YesFromExpr cenv.g e)
+ | IsAppInLambdaBody t -> Some t
+ | _ -> None
+ | _ -> None
+
+ // if we haven't already decided this is no tail call, try to detect CPS-like expressions
+ let tailCall =
+ if tailCall = TailCall.No then
+ tailCall
+ else
+ args
+ |> List.tryPick (fun a ->
+ match a with
+ | IsAppInLambdaBody t -> Some t
+ | _ -> None)
+ |> Option.defaultValue TailCall.No
+
+ CheckExprs cenv args ctxts tailCall
/// Check call arguments, including the return argument. The receiver argument is handled differently.
and CheckCallWithReceiver cenv args ctxts =
@@ -330,7 +354,25 @@ and CheckExpr (cenv: cenv) origExpr (ctxt: PermitByRefExpr) (tailCall: TailCall)
| TypeDefOfExpr g ty when isVoidTy g ty -> ()
// Check an application
- | Expr.App (f, _fty, _tyargs, argsl, _m) -> CheckApplication cenv (f, argsl) tailCall
+ | Expr.App (f, _fty, _tyargs, argsl, _m) ->
+ // detect expressions like List.collect
+ let checkArgForLambdaWithAppOfMustTailCall e =
+ match stripDebugPoints e with
+ | Expr.TyLambda (bodyExpr = bodyExpr)
+ | Expr.Lambda (bodyExpr = bodyExpr) ->
+ match bodyExpr with
+ | Expr.App (ValUseAtApp (vref, _valUseFlags), _formalType, _typeArgs, _exprs, _range) ->
+ cenv.mustTailCall.Contains vref.Deref
+ | _ -> false
+ | _ -> false
+
+ let tailCall =
+ if argsl |> List.exists checkArgForLambdaWithAppOfMustTailCall then
+ TailCall.No
+ else
+ tailCall
+
+ CheckApplication cenv (f, argsl) tailCall
| Expr.Lambda (_, _, _, argvs, _, m, bodyTy) -> CheckLambda cenv expr (argvs, m, bodyTy) tailCall
@@ -388,7 +430,7 @@ and CheckApplication cenv (f, argsl) (tailCall: TailCall) : unit =
if hasReceiver then
CheckCallWithReceiver cenv argsl ctxts
else
- CheckCall cenv argsl ctxts
+ CheckCall cenv argsl ctxts tailCall
and CheckLambda cenv expr (argvs, m, bodyTy) (tailCall: TailCall) =
let valReprInfo =
@@ -470,12 +512,12 @@ and CheckExprOp cenv (op, tyargs, args, m) ctxt : unit =
if hasReceiver then
CheckCallWithReceiver cenv args argContexts
else
- CheckCall cenv args argContexts
+ CheckCall cenv args argContexts TailCall.No
| _ ->
if hasReceiver then
CheckCallWithReceiver cenv args argContexts
else
- CheckCall cenv args argContexts
+ CheckCall cenv args argContexts TailCall.No
| TOp.Tuple tupInfo, _, _ when not (evalTupInfoIsStruct tupInfo) ->
match ctxt with
@@ -604,7 +646,7 @@ and CheckLambdas
// allow byref to occur as return position for byref-typed top level function or method
CheckExprPermitReturnableByRef cenv body
else
- CheckExprNoByrefs cenv (TailCall.YesFromExpr cenv.g body) body // TailCall.Yes for CPS
+ CheckExprNoByrefs cenv tailCall body
// This path is for expression bindings that are not actually lambdas
| _ ->
diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs
index 0ec2a8ce98b..842044aab14 100644
--- a/src/Compiler/Driver/CompilerDiagnostics.fs
+++ b/src/Compiler/Driver/CompilerDiagnostics.fs
@@ -388,6 +388,7 @@ type PhasedDiagnostic with
| 3395 -> false // tcImplicitConversionUsedForMethodArg - off by default
| 3559 -> false // typrelNeverRefinedAwayFromTop - off by default
| 3579 -> false // alwaysUseTypedStringInterpolation - off by default
+ | 3582 -> false // infoIfFunctionShadowsUnionCase - off by default
| _ ->
match x.Exception with
| DiagnosticEnabledWithLanguageFeature (_, _, _, enabled) -> enabled
diff --git a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
index 33e5b6450d5..c3206efbdfb 100644
--- a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
+++ b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
@@ -17,29 +17,33 @@ let queryTriePartial (trie: TrieNode) (path: LongIdentifier) : TrieNode option =
visit trie path
-let mapNodeToQueryResult (node: TrieNode option) : QueryTrieNodeResult =
+let mapNodeToQueryResult (currentFileIndex: FileIndex) (node: TrieNode option) : QueryTrieNodeResult =
match node with
| Some finalNode ->
- if Set.isEmpty finalNode.Files then
+ if
+ Set.isEmpty finalNode.Files
+ // If this node exposes files which the current index cannot see, we consider it not to have data at all.
+ || Set.forall (fun idx -> idx >= currentFileIndex) finalNode.Files
+ then
QueryTrieNodeResult.NodeDoesNotExposeData
else
QueryTrieNodeResult.NodeExposesData(finalNode.Files)
| None -> QueryTrieNodeResult.NodeDoesNotExist
/// Find a path in the Trie.
-let queryTrie (trie: TrieNode) (path: LongIdentifier) : QueryTrieNodeResult =
- queryTriePartial trie path |> mapNodeToQueryResult
+let queryTrie (currentFileIndex: FileIndex) (trie: TrieNode) (path: LongIdentifier) : QueryTrieNodeResult =
+ queryTriePartial trie path |> mapNodeToQueryResult currentFileIndex
/// Same as 'queryTrie' but allows passing in a path combined from two parts, avoiding list allocation.
-let queryTrieDual (trie: TrieNode) (path1: LongIdentifier) (path2: LongIdentifier) : QueryTrieNodeResult =
+let queryTrieDual (currentFileIndex: FileIndex) (trie: TrieNode) (path1: LongIdentifier) (path2: LongIdentifier) : QueryTrieNodeResult =
match queryTriePartial trie path1 with
| Some intermediateNode -> queryTriePartial intermediateNode path2
| None -> None
- |> mapNodeToQueryResult
+ |> mapNodeToQueryResult currentFileIndex
/// Process namespace declaration.
let processNamespaceDeclaration (trie: TrieNode) (path: LongIdentifier) (state: FileContentQueryState) : FileContentQueryState =
- let queryResult = queryTrie trie path
+ let queryResult = queryTrie state.CurrentFile trie path
match queryResult with
| QueryTrieNodeResult.NodeDoesNotExist -> state
@@ -49,7 +53,7 @@ let processNamespaceDeclaration (trie: TrieNode) (path: LongIdentifier) (state:
/// Process an "open" statement.
/// The statement could link to files and/or should be tracked as an open namespace.
let processOpenPath (trie: TrieNode) (path: LongIdentifier) (state: FileContentQueryState) : FileContentQueryState =
- let queryResult = queryTrie trie path
+ let queryResult = queryTrie state.CurrentFile trie path
match queryResult with
| QueryTrieNodeResult.NodeDoesNotExist -> state
@@ -99,12 +103,13 @@ let rec processStateEntry (trie: TrieNode) (state: FileContentQueryState) (entry
||> Array.fold (fun state takeParts ->
let path = List.take takeParts path
// process the name was if it were a FQN
- let stateAfterFullIdentifier = processIdentifier (queryTrieDual trie [] path) state
+ let stateAfterFullIdentifier =
+ processIdentifier (queryTrieDual state.CurrentFile trie [] path) state
// Process the name in combination with the existing open namespaces
(stateAfterFullIdentifier, state.OpenNamespaces)
||> Set.fold (fun acc openNS ->
- let queryResult = queryTrieDual trie openNS path
+ let queryResult = queryTrieDual state.CurrentFile trie openNS path
processIdentifier queryResult acc))
| FileContentEntry.NestedModule (nestedContent = nestedContent) ->
@@ -137,7 +142,7 @@ let collectGhostDependencies (fileIndex: FileIndex) (trie: TrieNode) (result: Fi
// For each opened namespace, if none of already resolved dependencies define it, return the top-most file that defines it.
Set.toArray result.OpenedNamespaces
|> Array.choose (fun path ->
- match queryTrie trie path with
+ match queryTrie fileIndex trie path with
| QueryTrieNodeResult.NodeExposesData _
| QueryTrieNodeResult.NodeDoesNotExist -> None
| QueryTrieNodeResult.NodeDoesNotExposeData ->
diff --git a/src/Compiler/Driver/GraphChecking/DependencyResolution.fsi b/src/Compiler/Driver/GraphChecking/DependencyResolution.fsi
index 3ceac318ec7..a2c52adcea9 100644
--- a/src/Compiler/Driver/GraphChecking/DependencyResolution.fsi
+++ b/src/Compiler/Driver/GraphChecking/DependencyResolution.fsi
@@ -1,9 +1,12 @@
/// Logic for constructing a file dependency graph for the purposes of parallel type-checking.
module internal FSharp.Compiler.GraphChecking.DependencyResolution
-/// Query a TrieNode to find a certain path.
+///
+/// Query a TrieNode to find a certain path.
+/// The result will take the current file index into account to determine if the result node contains data.
+///
/// This code is only used directly in unit tests.
-val queryTrie: trie: TrieNode -> path: LongIdentifier -> QueryTrieNodeResult
+val queryTrie: currentFileIndex: FileIndex -> trie: TrieNode -> path: LongIdentifier -> QueryTrieNodeResult
/// Process an open path (found in the ParsedInput) with a given FileContentQueryState.
/// This code is only used directly in unit tests.
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index 9f091d07533..d10b689cae5 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -1509,7 +1509,7 @@ notAFunctionButMaybeDeclaration,"This value is not a function and cannot be appl
3234,chkNoSpanLikeVariable,"The Span or IsByRefLike variable '%s' cannot be used at this point. This is to ensure the address of the local value does not escape its scope."
3235,chkNoSpanLikeValueFromExpression,"A Span or IsByRefLike value returned from the expression cannot be used at ths point. This is to ensure the address of the local value does not escape its scope."
3236,tastCantTakeAddressOfExpression,"Cannot take the address of the value returned from the expression. Assign the returned value to a let-bound value before taking the address."
-3237,tcCannotCallExtensionMethodInrefToByref,"Cannot call the byref extension method '%s. The first parameter requires the value to be mutable or a non-readonly byref type."
+3237,tcCannotCallExtensionMethodInrefToByref,"Cannot call the byref extension method '%s. 'this' parameter requires the value to be mutable or a non-readonly byref type."
3238,tcByrefsMayNotHaveTypeExtensions,"Byref types are not allowed to have optional type extensions."
3239,tcCannotPartiallyApplyExtensionMethodForByref,"Cannot partially apply the extension method '%s' because the first parameter is a byref type."
3242,tcTypeDoesNotInheritAttribute,"This type does not inherit Attribute, it will not work correctly with other .NET languages."
@@ -1724,3 +1724,4 @@ featureUnmanagedConstraintCsharpInterop,"Interop between C#'s and F#'s unmanaged
3578,chkCopyUpdateSyntaxInAnonRecords,"This expression is an anonymous record, use {{|...|}} instead of {{...}}."
3579,alwaysUseTypedStringInterpolation,"Interpolated string contains untyped identifiers. Adding typed format specifiers is recommended."
3580,tcUnexpectedFunTypeInUnionCaseField,"Unexpected function type in union case field definition. If you intend the field to be a function, consider wrapping the function signature with parens, e.g. | Case of a -> b into | Case of (a -> b)."
+3582,tcInfoIfFunctionShadowsUnionCase,"This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses."
diff --git a/src/Compiler/Facilities/prim-lexing.fs b/src/Compiler/Facilities/prim-lexing.fs
index 785b7fbdf6f..0e073dc80ef 100644
--- a/src/Compiler/Facilities/prim-lexing.fs
+++ b/src/Compiler/Facilities/prim-lexing.fs
@@ -6,7 +6,6 @@ namespace FSharp.Compiler.Text
open System
open System.IO
-open FSharp.Compiler
type ISourceText =
@@ -28,6 +27,8 @@ type ISourceText =
abstract CopyTo: sourceIndex: int * destination: char[] * destinationIndex: int * count: int -> unit
+ abstract GetSubTextFromRange: range: range -> string
+
[]
type StringText(str: string) =
@@ -108,6 +109,41 @@ type StringText(str: string) =
member _.CopyTo(sourceIndex, destination, destinationIndex, count) =
str.CopyTo(sourceIndex, destination, destinationIndex, count)
+ member this.GetSubTextFromRange(range) =
+ let totalAmountOfLines = getLines.Value.Length
+
+ if
+ range.StartLine = 0
+ && range.StartColumn = 0
+ && range.EndLine = 0
+ && range.EndColumn = 0
+ then
+ String.Empty
+ elif
+ range.StartLine < 1
+ || (range.StartLine - 1) > totalAmountOfLines
+ || range.EndLine < 1
+ || (range.EndLine - 1) > totalAmountOfLines
+ then
+ invalidArg (nameof range) "The range is outside the file boundaries"
+ else
+ let sourceText = this :> ISourceText
+ let startLine = range.StartLine - 1
+ let line = sourceText.GetLineString startLine
+
+ if range.StartLine = range.EndLine then
+ let length = range.EndColumn - range.StartColumn
+ line.Substring(range.StartColumn, length)
+ else
+ let firstLineContent = line.Substring(range.StartColumn)
+ let sb = System.Text.StringBuilder().AppendLine(firstLineContent)
+
+ for lineNumber in range.StartLine .. range.EndLine - 2 do
+ sb.AppendLine(sourceText.GetLineString lineNumber) |> ignore
+
+ let lastLine = sourceText.GetLineString(range.EndLine - 1)
+ sb.Append(lastLine.Substring(0, range.EndColumn)).ToString()
+
module SourceText =
let ofString str = StringText(str) :> ISourceText
diff --git a/src/Compiler/Facilities/prim-lexing.fsi b/src/Compiler/Facilities/prim-lexing.fsi
index a7c919991d3..6e5f6da4f25 100644
--- a/src/Compiler/Facilities/prim-lexing.fsi
+++ b/src/Compiler/Facilities/prim-lexing.fsi
@@ -35,6 +35,10 @@ type ISourceText =
/// Copies a section of the input to the given destination ad the given index
abstract CopyTo: sourceIndex: int * destination: char[] * destinationIndex: int * count: int -> unit
+ /// Gets a section of the input based on a given range.
+ /// Throws an exception when the input range is outside the file boundaries.
+ abstract GetSubTextFromRange: range: range -> string
+
/// Functions related to ISourceText objects
module SourceText =
diff --git a/src/Compiler/Symbols/Exprs.fsi b/src/Compiler/Symbols/Exprs.fsi
index e05c7b31560..f98dbf73408 100644
--- a/src/Compiler/Symbols/Exprs.fsi
+++ b/src/Compiler/Symbols/Exprs.fsi
@@ -251,6 +251,3 @@ module public FSharpExprPatterns =
/// Indicates a witness argument index from the witness arguments supplied to the enclosing method
val (|WitnessArg|_|): FSharpExpr -> int option
-
- /// Matches an expression with a debug point
- val (|DebugPoint|_|): FSharpExpr -> (DebugPointAtLeafExpr * FSharpExpr) option
diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf
index 950c8d3c846..540c47a52b8 100644
--- a/src/Compiler/xlf/FSComp.txt.cs.xlf
+++ b/src/Compiler/xlf/FSComp.txt.cs.xlf
@@ -1147,6 +1147,11 @@
Tento výraz používá implicitní převod {0} pro převod typu {1} na typ {2}. Přečtěte si téma https://aka.ms/fsharp-implicit-convs. Toto upozornění může být vypnuté pomocí '#nowarn \"3391\".
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationVlastnost init-only „{0}“ nelze nastavit mimo inicializační kód. Zobrazit https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Nejde volat metodu rozšíření byref {0}. První parametr vyžaduje, aby hodnota byla měnitelná nebo typu byref, která není jen pro čtení.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Nejde volat metodu rozšíření byref {0}. První parametr vyžaduje, aby hodnota byla měnitelná nebo typu byref, která není jen pro čtení.
diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf
index 228da2df3b1..16dca0c1450 100644
--- a/src/Compiler/xlf/FSComp.txt.de.xlf
+++ b/src/Compiler/xlf/FSComp.txt.de.xlf
@@ -1147,6 +1147,11 @@
Dieser Ausdruck verwendet die implizite Konvertierung "{0}", um den Typ "{1}" in den Typ "{2}" zu konvertieren. Siehe https://aka.ms/fsharp-implicit-convs. Diese Warnung kann durch "#nowarn \" 3391 \ " deaktiviert werden.
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationDie Eigenschaft „{0}“ nur für die Initialisierung kann nicht außerhalb des Initialisierungscodes festgelegt werden. Siehe https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Die ByRef-Erweiterungsmethode "{0}" kann nicht aufgerufen werden. Für den ersten Parameter muss der Wert änderbar sein oder einem nicht schreibgeschützten ByRef-Typ entsprechen.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Die ByRef-Erweiterungsmethode "{0}" kann nicht aufgerufen werden. Für den ersten Parameter muss der Wert änderbar sein oder einem nicht schreibgeschützten ByRef-Typ entsprechen.
diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf
index 7db7543091d..33e49493def 100644
--- a/src/Compiler/xlf/FSComp.txt.es.xlf
+++ b/src/Compiler/xlf/FSComp.txt.es.xlf
@@ -1147,6 +1147,11 @@
Esta expresión usa la conversión implícita '{0}' para convertir el tipo '{1}' al tipo '{2}'. Consulte https://aka.ms/fsharp-implicit-convs. Esta advertencia se puede deshabilitar mediante '#nowarn \"3391\".
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationNo se puede establecer la propiedad init-only '{0}' fuera del código de inicialización. Ver https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- No se puede llamar al método de extensión de byref "{0}". El primer parámetro requiere que el valor sea mutable o un tipo de byref que no sea de solo lectura.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ No se puede llamar al método de extensión de byref "{0}". El primer parámetro requiere que el valor sea mutable o un tipo de byref que no sea de solo lectura.
diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf
index a00e94a4d64..6232803b002 100644
--- a/src/Compiler/xlf/FSComp.txt.fr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.fr.xlf
@@ -1147,6 +1147,11 @@
Cette expression utilise la conversion implicite '{0}' pour convertir le type '{1}' en type '{2}'. Voir https://aka.ms/fsharp-implicit-convs. Cet avertissement peut être désactivé en utilisant '#nowarn \"3391\".
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationLa propriété init-only '{0}' ne peut pas être définie en dehors du code d’initialisation. Voir https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Impossible d’appeler la méthode d’extension byref « {0} ». Le premier paramètre nécessite que la valeur soit mutable ou un type byref autre qu'en lecture seule.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Impossible d’appeler la méthode d’extension byref « {0} ». Le premier paramètre nécessite que la valeur soit mutable ou un type byref autre qu'en lecture seule.
diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf
index a70f3500a2a..26052d847ce 100644
--- a/src/Compiler/xlf/FSComp.txt.it.xlf
+++ b/src/Compiler/xlf/FSComp.txt.it.xlf
@@ -1147,6 +1147,11 @@
Questa espressione usa la conversione implicita '{0}' per convertire il tipo '{1}' nel tipo '{2}'. Vedere https://aka.ms/fsharp-implicit-convs. Per disabilitare questo avviso, usare '#nowarn \"3391\"'.
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationLa proprietà init-only '{0}' non può essere impostata al di fuori del codice di inizializzazione. Vedere https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Non è possibile chiamare il metodo di estensione byref '{0}. Il valore del primo parametro deve essere modificabile oppure un tipo byref non di sola lettura.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Non è possibile chiamare il metodo di estensione byref '{0}. Il valore del primo parametro deve essere modificabile oppure un tipo byref non di sola lettura.
diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf
index 85b79f2aa38..29316f0846e 100644
--- a/src/Compiler/xlf/FSComp.txt.ja.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ja.xlf
@@ -1147,6 +1147,11 @@
この式では、暗黙的な変換 '{0}' を使用して、型 '{1}' を型 '{2}' に変換しています。https://aka.ms/fsharp-implicit-convs をご確認ください。'#nowarn\"3391\" を使用して、この警告を無効にできる可能性があります。
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization初期化コードの外部で init 専用プロパティ '{0}' を設定することはできません。https://aka.ms/fsharp-assigning-values-to-properties-at-initialization を参照してください
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- byref 拡張メソッド '{0} を呼び出すことはできません。最初のパラメーターでは、値を変更可能な byref 型または読み取り専用以外の byref 型にする必要があります。
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ byref 拡張メソッド '{0} を呼び出すことはできません。最初のパラメーターでは、値を変更可能な byref 型または読み取り専用以外の byref 型にする必要があります。
diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf
index fbedcdb01c7..aa7f03a46e7 100644
--- a/src/Compiler/xlf/FSComp.txt.ko.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ko.xlf
@@ -1147,6 +1147,11 @@
이 식은 암시적 변환 '{0}'을 사용하여 '{1}' 형식을 '{2}' 형식으로 변환 합니다. https://aka.ms/fsharp-implicit-convs 참조. ’#Nowarn \ "3391\"을 (를) 사용하여 이 경고를 사용 하지 않도록 설정할 수 있습니다.
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization초기화 코드 외부에서는 '{0}' 초기화 전용 속성을 설정할 수 없습니다. https://aka.ms/fsharp-assigning-values-to-properties-at-initialization을 참조하세요.
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- byref 확장 메서드 '{0}'을(를) 호출할 수 없습니다. 첫 번째 매개 변수는 변경할 수 있거나 읽기 전용이 아닌 byref 형식인 값이 필요합니다.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ byref 확장 메서드 '{0}'을(를) 호출할 수 없습니다. 첫 번째 매개 변수는 변경할 수 있거나 읽기 전용이 아닌 byref 형식인 값이 필요합니다.
diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf
index 7bf3c8187d8..def1a890060 100644
--- a/src/Compiler/xlf/FSComp.txt.pl.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pl.xlf
@@ -1147,6 +1147,11 @@
W tym wyrażeniu jest używana bezwzględna konwersja "{0}" w celu przekonwertowania typu "{1}" na typ "{2}". Zobacz https://aka.ms/fsharp-implicit-convs. To ostrzeżenie można wyłączyć przy użyciu polecenia "#nowarn \" 3391 \ ".
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationWłaściwość init-only „{0}” nie może być ustawiona poza kodem inicjowania. Zobacz https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Nie można wywołać metody rozszerzenia byref „{0}”. Pierwszy parametr wymaga, aby wartość była typem byref zmiennym lub innym niż tylko do odczytu.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Nie można wywołać metody rozszerzenia byref „{0}”. Pierwszy parametr wymaga, aby wartość była typem byref zmiennym lub innym niż tylko do odczytu.
diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
index 5e3a492618d..06cb00f8631 100644
--- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
@@ -1147,6 +1147,11 @@
Essa expressão usa a conversão implícita '{0}' para converter o tipo '{1}' ao tipo '{2}'. Consulte https://aka.ms/fsharp-implicit-convs. Este aviso pode ser desabilitado usando '#nowarn\"3391\".
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationA propriedade somente inicialização '{0}' não pode ser definida fora do código de inicialização. Confira https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Não é possível chamar o método de extensão de byref '{0}. O primeiro parâmetro requer que o valor seja mutável ou não seja byref somente leitura.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Não é possível chamar o método de extensão de byref '{0}. O primeiro parâmetro requer que o valor seja mutável ou não seja byref somente leitura.
diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf
index 3e50524becd..e446e0634d7 100644
--- a/src/Compiler/xlf/FSComp.txt.ru.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ru.xlf
@@ -1147,6 +1147,11 @@
Это выражение использует неявное преобразование "{0}" для преобразования типа "{1}" в тип "{2}". См. сведения на странице https://aka.ms/fsharp-implicit-convs. Это предупреждение можно отключить, используя параметр '#nowarn \"3391\"
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initializationСвойство только для инициализации "{0}" невозможно установить за пределами кода инициализации. См. https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- Не удается вызвать метод расширения byref "{0}". В качестве первого параметра необходимо указать изменяемое значение или значение типа byref, доступное не только для чтения.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ Не удается вызвать метод расширения byref "{0}". В качестве первого параметра необходимо указать изменяемое значение или значение типа byref, доступное не только для чтения.
diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf
index 59a6cc71fee..dae854f696b 100644
--- a/src/Compiler/xlf/FSComp.txt.tr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.tr.xlf
@@ -1147,6 +1147,11 @@
Bu ifade '{1}' türünü '{2}' türüne dönüştürmek için '{0}' örtük dönüştürmesini kullanır. https://aka.ms/fsharp-implicit-convs adresine bakın. Bu uyarı '#nowarn \"3391\" kullanılarak devre dışı bırakılabilir.
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization'{0}' yalnızca başlatma özelliği başlatma kodunun dışında ayarlanamaz. Bkz. https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- '{0}' byref genişletme metodu çağrılamıyor. İlk parametre, değerin değişebilir olmasını veya salt okunur olmayan bir byref türünde olmasını gerektiriyor.
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ '{0}' byref genişletme metodu çağrılamıyor. İlk parametre, değerin değişebilir olmasını veya salt okunur olmayan bir byref türünde olmasını gerektiriyor.
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
index a836bd75cb1..68bce66e73f 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
@@ -1147,6 +1147,11 @@
此表达式使用隐式转换“{0}”将类型“{1}”转换为类型“{2}”。请参阅 https://aka.ms/fsharp-implicit-convs。可使用 '#nowarn \"3391\" 禁用此警告。
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization不能在初始化代码外部设置仅限 init 的属性 "{0}"。请参阅 https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- 无法调用 byref 扩展方法 "{0}"。第一个参数要求该值是可变的或非只读的 byref 类型。
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ 无法调用 byref 扩展方法 "{0}"。第一个参数要求该值是可变的或非只读的 byref 类型。
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
index 9b7c435824e..e8dd655ea3e 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
@@ -1147,6 +1147,11 @@
此運算式使用隱含轉換 '{0}' 將類型 '{1}' 轉換為類型 '{2}'。請參閱 https://aka.ms/fsharp-implicit-convs。可使用 '#nowarn \"3391\" 停用此警告。
+
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+ This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.
+
+ Init-only property '{0}' cannot be set outside the initialization code. See https://aka.ms/fsharp-assigning-values-to-properties-at-initialization初始化程式碼之外不能設定僅初始化屬性 '{0}'。請參閱 https://aka.ms/fsharp-assigning-values-to-properties-at-initialization
@@ -8428,8 +8433,8 @@
- Cannot call the byref extension method '{0}. The first parameter requires the value to be mutable or a non-readonly byref type.
- 無法呼叫 byref 擴充方法 '{0}。第一個參數需要值可變動,或為非唯讀 byref 類型。
+ Cannot call the byref extension method '{0}. 'this' parameter requires the value to be mutable or a non-readonly byref type.
+ 無法呼叫 byref 擴充方法 '{0}。第一個參數需要值可變動,或為非唯讀 byref 類型。
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
index ea00b30e1be..4c31d9e5802 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
@@ -271,3 +271,36 @@ module LetBindings_Basic =
compilation
|> verifyCompileAndRun
|> shouldSucceed
+
+ // https://github.com/dotnet/fsharp/issues/15559
+ let private sourceWarnIfFunctionShadowsUnionCase =
+ """
+type T = Case1 of int
+let t = Case1 42
+let Case1 x = t // first warning
+let Some x = 42 // second warning
+[] type U = U1 of int
+let u = U.U1 42
+let U1 x = t // no warning
+type C() =
+ member _.Some x = 1 // no warning
+"""
+
+ []
+ let ``No default warning if function shadows union case`` () =
+ sourceWarnIfFunctionShadowsUnionCase
+ |> FSharp
+ |> verifyCompileAndRun
+ |> shouldSucceed
+
+ []
+ let ``Info if function shadows union case`` () =
+ sourceWarnIfFunctionShadowsUnionCase
+ |> FSharp
+ |> withOptions ["--warnon:FS3582"]
+ |> typecheck
+ |> shouldFail
+ |> withDiagnostics [
+ (Warning 3582, Line 4, Col 5, Line 4, Col 12, "This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.")
+ (Warning 3582, Line 5, Col 5, Line 5, Col 11, "This is a function definition that shadows a union case. If this is what you want, ignore or suppress this warning. If you want it to be a union case deconstruction, add parentheses.")
+ ]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs
index edc579d2cfb..073111d8504 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs
@@ -134,15 +134,15 @@ module ByrefSafetyAnalysis =
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3237, Line 23, Col 18, Line 23, Col 28, "Cannot call the byref extension method 'Test2. The first parameter requires the value to be mutable or a non-readonly byref type.")
+ (Error 3237, Line 23, Col 18, Line 23, Col 28, "Cannot call the byref extension method 'Test2. 'this' parameter requires the value to be mutable or a non-readonly byref type.")
(Error 1, Line 24, Col 9, Line 24, Col 11, "Type mismatch. Expecting a
'byref'
but given a
'inref'
The type 'ByRefKinds.InOut' does not match the type 'ByRefKinds.In'")
- (Error 3237, Line 28, Col 9, Line 28, Col 20, "Cannot call the byref extension method 'Change. The first parameter requires the value to be mutable or a non-readonly byref type.")
- (Error 3237, Line 33, Col 19, Line 33, Col 30, "Cannot call the byref extension method 'Test2. The first parameter requires the value to be mutable or a non-readonly byref type.")
- (Error 3237, Line 39, Col 9, Line 39, Col 21, "Cannot call the byref extension method 'Change. The first parameter requires the value to be mutable or a non-readonly byref type.")
+ (Error 3237, Line 28, Col 9, Line 28, Col 20, "Cannot call the byref extension method 'Change. 'this' parameter requires the value to be mutable or a non-readonly byref type.")
+ (Error 3237, Line 33, Col 19, Line 33, Col 30, "Cannot call the byref extension method 'Test2. 'this' parameter requires the value to be mutable or a non-readonly byref type.")
+ (Error 3237, Line 39, Col 9, Line 39, Col 21, "Cannot call the byref extension method 'Change. 'this' parameter requires the value to be mutable or a non-readonly byref type.")
(Error 3239, Line 43, Col 17, Line 43, Col 29, "Cannot partially apply the extension method 'NotChange' because the first parameter is a byref type.")
(Error 3239, Line 44, Col 17, Line 44, Col 24, "Cannot partially apply the extension method 'Test' because the first parameter is a byref type.")
(Error 3239, Line 45, Col 17, Line 45, Col 26, "Cannot partially apply the extension method 'Change' because the first parameter is a byref type.")
diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs
index 3365b3e1265..9b512c5d7e2 100644
--- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs
+++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs
@@ -884,6 +884,37 @@ namespace N
Message =
"The member or function 'findMax' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." }
]
+
+ []
+ let ``Warn for non tail-rec traversal with List.collect`` () =
+ """
+namespace N
+
+ module M =
+
+ type Tree =
+ | Leaf of int
+ | Node of Tree list
+
+ []
+ let rec loop tree =
+ match tree with
+ | Leaf n -> [ n ]
+ | Node branches -> branches |> List.collect loop
+ """
+ |> FSharp
+ |> withLangVersionPreview
+ |> compile
+ |> shouldFail
+ |> withResults [
+ { Error = Warning 3569
+ Range = { StartLine = 14
+ StartColumn = 57
+ EndLine = 14
+ EndColumn = 61 }
+ Message =
+ "The member or function 'loop' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." }
+ ]
[]
let ``Don't warn for Continuation Passing Style func using [] func in continuation lambda`` () =
diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/QueryTrieTests.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/QueryTrieTests.fs
index 1c06fe9dee2..b82014b178a 100644
--- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/QueryTrieTests.fs
+++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/QueryTrieTests.fs
@@ -762,7 +762,7 @@ let private fantomasCoreTrie: TrieNode =
[]
let ``Query non existing node in trie`` () =
let result =
- queryTrie fantomasCoreTrie [ "System"; "System"; "Runtime"; "CompilerServices" ]
+ queryTrie 7 fantomasCoreTrie [ "System"; "System"; "Runtime"; "CompilerServices" ]
match result with
| QueryTrieNodeResult.NodeDoesNotExist -> Assert.Pass()
@@ -770,7 +770,7 @@ let ``Query non existing node in trie`` () =
[]
let ``Query node that does not expose data in trie`` () =
- let result = queryTrie fantomasCoreTrie [ "Fantomas"; "Core" ]
+ let result = queryTrie 7 fantomasCoreTrie [ "Fantomas"; "Core" ]
match result with
| QueryTrieNodeResult.NodeDoesNotExposeData -> Assert.Pass()
@@ -779,7 +779,7 @@ let ``Query node that does not expose data in trie`` () =
[]
let ``Query module node that exposes one file`` () =
let result =
- queryTrie fantomasCoreTrie [ "Fantomas"; "Core"; "ISourceTextExtensions" ]
+ queryTrie 7 fantomasCoreTrie [ "Fantomas"; "Core"; "ISourceTextExtensions" ]
match result with
| QueryTrieNodeResult.NodeExposesData file ->
diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
index a60e8e7c116..1211e6b19f3 100644
--- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
+++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
@@ -747,6 +747,28 @@ module S
let main _ =
F.br ()
0
+"""
+ (set [| 0 |])
+ ]
+ scenario
+ "Ghost dependency via top-level namespace"
+ [
+ sourceFile
+ "Graph.fs"
+ """
+namespace Graphoscope.Graph
+
+type UndirectedGraph = obj
+"""
+ Set.empty
+ sourceFile
+ "DiGraph.fs"
+ """
+namespace Graphoscope
+
+open Graphoscope
+
+type DiGraph = obj
"""
(set [| 0 |])
]
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
index 62e28427927..e5d58f2ed14 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl
@@ -10179,6 +10179,7 @@ FSharp.Compiler.Text.ISourceText: Int32 GetLineCount()
FSharp.Compiler.Text.ISourceText: Int32 Length
FSharp.Compiler.Text.ISourceText: Int32 get_Length()
FSharp.Compiler.Text.ISourceText: System.String GetLineString(Int32)
+FSharp.Compiler.Text.ISourceText: System.String GetSubTextFromRange(FSharp.Compiler.Text.Range)
FSharp.Compiler.Text.ISourceText: System.String GetSubTextString(Int32, Int32)
FSharp.Compiler.Text.ISourceText: System.Tuple`2[System.Int32,System.Int32] GetLastCharacterPosition()
FSharp.Compiler.Text.ISourceText: Void CopyTo(Int32, Char[], Int32, Int32)
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 62e28427927..e5d58f2ed14 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
@@ -10179,6 +10179,7 @@ FSharp.Compiler.Text.ISourceText: Int32 GetLineCount()
FSharp.Compiler.Text.ISourceText: Int32 Length
FSharp.Compiler.Text.ISourceText: Int32 get_Length()
FSharp.Compiler.Text.ISourceText: System.String GetLineString(Int32)
+FSharp.Compiler.Text.ISourceText: System.String GetSubTextFromRange(FSharp.Compiler.Text.Range)
FSharp.Compiler.Text.ISourceText: System.String GetSubTextString(Int32, Int32)
FSharp.Compiler.Text.ISourceText: System.Tuple`2[System.Int32,System.Int32] GetLastCharacterPosition()
FSharp.Compiler.Text.ISourceText: Void CopyTo(Int32, Char[], Int32, Int32)
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
index 7cb2dfd22a2..1511ec6ddfc 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
@@ -94,6 +94,7 @@
RangeTests.fs
+
Program.fs
diff --git a/tests/FSharp.Compiler.Service.Tests/SourceTextTests.fs b/tests/FSharp.Compiler.Service.Tests/SourceTextTests.fs
new file mode 100644
index 00000000000..0190c2f467d
--- /dev/null
+++ b/tests/FSharp.Compiler.Service.Tests/SourceTextTests.fs
@@ -0,0 +1,47 @@
+module FSharp.Compiler.Service.Tests.SourceTextTests
+
+open System
+open FSharp.Compiler.Text
+open NUnit.Framework
+
+[]
+let ``Select text from a single line via the range`` () =
+ let sourceText = SourceText.ofString """
+let a = 2
+"""
+ let m = Range.mkRange "Sample.fs" (Position.mkPos 2 4) (Position.mkPos 2 5)
+ let v = sourceText.GetSubTextFromRange m
+ Assert.AreEqual("a", v)
+
+[]
+let ``Select text from multiple lines via the range`` () =
+ let sourceText = SourceText.ofString """
+let a b c =
+ // comment
+ 2
+"""
+ let m = Range.mkRange "Sample.fs" (Position.mkPos 2 4) (Position.mkPos 4 5)
+ let v = sourceText.GetSubTextFromRange m
+ let sanitized = v.Replace("\r", "")
+ Assert.AreEqual("a b c =\n // comment\n 2", sanitized)
+
+[]
+let ``Inconsistent return carriage return correct text`` () =
+ let sourceText = SourceText.ofString "let a =\r\n // foo\n 43"
+ let m = Range.mkRange "Sample.fs" (Position.mkPos 1 4) (Position.mkPos 3 6)
+ let v = sourceText.GetSubTextFromRange m
+ let sanitized = v.Replace("\r", "")
+ Assert.AreEqual("a =\n // foo\n 43", sanitized)
+
+[]
+let ``Zero range should return empty string`` () =
+ let sourceText = SourceText.ofString "a"
+ let v = sourceText.GetSubTextFromRange Range.Zero
+ Assert.AreEqual(String.Empty, v)
+
+[]
+let ``Invalid range should throw argument exception`` () =
+ let sourceText = SourceText.ofString "a"
+ let mInvalid = Range.mkRange "Sample.fs" (Position.mkPos 3 6) (Position.mkPos 1 4)
+ Assert.Throws(fun () -> sourceText.GetSubTextFromRange mInvalid |> ignore)
+ |> ignore
diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SourceText.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SourceText.fs
index 49d46568ff4..89d911d9b71 100644
--- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SourceText.fs
+++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/SourceText.fs
@@ -64,6 +64,40 @@ module internal SourceText =
member __.CopyTo(sourceIndex, destination, destinationIndex, count) =
sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
+
+ member this.GetSubTextFromRange range =
+ let totalAmountOfLines = sourceText.Lines.Count
+
+ if
+ range.StartLine = 0
+ && range.StartColumn = 0
+ && range.EndLine = 0
+ && range.EndColumn = 0
+ then
+ String.Empty
+ elif
+ range.StartLine < 1
+ || (range.StartLine - 1) > totalAmountOfLines
+ || range.EndLine < 1
+ || (range.EndLine - 1) > totalAmountOfLines
+ then
+ invalidArg (nameof range) "The range is outside the file boundaries"
+ else
+ let startLine = range.StartLine - 1
+ let line = this.GetLineString startLine
+
+ if range.StartLine = range.EndLine then
+ let length = range.EndColumn - range.StartColumn
+ line.Substring(range.StartColumn, length)
+ else
+ let firstLineContent = line.Substring(range.StartColumn)
+ let sb = System.Text.StringBuilder().AppendLine(firstLineContent)
+
+ for lineNumber in range.StartLine .. range.EndLine - 2 do
+ sb.AppendLine(this.GetLineString lineNumber) |> ignore
+
+ let lastLine = this.GetLineString(range.EndLine - 1)
+ sb.Append(lastLine.Substring(0, range.EndColumn)).ToString()
}
sourceText
diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs
index d9339a099d7..6c090529f2c 100644
--- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs
+++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs
@@ -22,14 +22,11 @@ open Microsoft.CodeAnalysis.Classification
[]
module BraceCompletionSessionProviderHelpers =
- let tryGetCaretPoint (buffer: ITextBuffer) (session: IBraceCompletionSession) =
- let point =
- session.TextView.Caret.Position.Point.GetPoint(buffer, PositionAffinity.Predecessor)
+ let inline tryGetCaretPoint (buffer: ITextBuffer) (session: IBraceCompletionSession) =
+ ValueOption.ofNullable (session.TextView.Caret.Position.Point.GetPoint(buffer, PositionAffinity.Predecessor))
- if point.HasValue then Some point.Value else None
-
- let tryGetCaretPosition session =
- session |> tryGetCaretPoint session.SubjectBuffer
+ let inline tryGetCaretPosition (session: IBraceCompletionSession) =
+ tryGetCaretPoint session.SubjectBuffer session
let tryInsertAdditionalBracePair (session: IBraceCompletionSession) openingChar closingChar =
let sourceCode = session.TextView.TextSnapshot
@@ -134,13 +131,13 @@ type BraceCompletionSession
// exit without setting the closing point which will take us off the stack
edit.Cancel()
undo.Cancel()
- None
+ ValueNone
else
- Some(edit.Apply()) // FIXME: perhaps, it should be ApplyAndLogExceptions()
+ ValueSome(edit.Apply()) // FIXME: perhaps, it should be ApplyAndLogExceptions()
match nextSnapshot with
- | None -> ()
- | Some (nextSnapshot) ->
+ | ValueNone -> ()
+ | ValueSome (nextSnapshot) ->
let beforePoint = beforeTrackingPoint.GetPoint(textView.TextSnapshot)
@@ -185,7 +182,7 @@ type BraceCompletionSession
if closingSnapshotPoint.Position > 0 then
match tryGetCaretPosition this with
- | Some caretPos when not (this.HasNoForwardTyping(caretPos, closingSnapshotPoint.Subtract(1))) -> true
+ | ValueSome caretPos when not (this.HasNoForwardTyping(caretPos, closingSnapshotPoint.Subtract(1))) -> true
| _ -> false
else
false
@@ -265,7 +262,7 @@ type BraceCompletionSession
match caretPos with
// ensure that we are within the session before clearing
- | Some caretPos when
+ | ValueSome caretPos when
caretPos.Position < closingSnapshotPoint.Position
&& closingSnapshotPoint.Position > 0
->
@@ -310,7 +307,7 @@ type BraceCompletionSession
member this.PostReturn() =
match tryGetCaretPosition this with
- | Some caretPos ->
+ | ValueSome caretPos ->
let closingSnapshotPoint = closingPoint.GetPoint(subjectBuffer.CurrentSnapshot)
if
@@ -580,13 +577,13 @@ type BraceCompletionSessionProvider []
maybe {
let! document =
openingPoint.Snapshot.GetOpenDocumentInCurrentContextWithChanges()
- |> Option.ofObj
+ |> ValueOption.ofObj
let! sessionFactory = document.TryGetLanguageService()
let! session =
sessionFactory.TryCreateSession(document, openingPoint.Position, openingBrace, CancellationToken.None)
- |> Option.ofObj
+ |> ValueOption.ofObj
let undoHistory =
undoManager.GetTextBufferUndoManager(textView.TextBuffer).TextBufferUndoHistory
diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
index b837385c1a9..28c6e96220c 100644
--- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
+++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs
@@ -52,7 +52,7 @@ type internal FSharpClassificationService [] () =
ClassificationTypeNames.Text
match RoslynHelpers.TryFSharpRangeToTextSpan(text, tok.Range) with
- | Some span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind))
+ | ValueSome span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind))
| _ -> ()
let flags =
@@ -79,8 +79,8 @@ type internal FSharpClassificationService [] () =
=
for item in items do
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with
- | None -> ()
- | Some span ->
+ | ValueNone -> ()
+ | ValueSome span ->
let span =
match item.Type with
| SemanticClassificationType.Printf -> span
@@ -111,7 +111,7 @@ type internal FSharpClassificationService [] () =
| true, items -> items
| _ ->
let items = ResizeArray()
- lookup.[dataItem.Range.StartLine] <- items
+ lookup[dataItem.Range.StartLine] <- items
items
items.Add dataItem
@@ -120,8 +120,29 @@ type internal FSharpClassificationService [] () =
lookup :> IReadOnlyDictionary<_, _>
- let semanticClassificationCache =
- new DocumentCache("fsharp-semantic-classification-cache")
+ static let itemToSemanticClassificationLookup (d: SemanticClassificationItem array) =
+ let lookup = Dictionary>()
+
+ for item in d do
+ let items =
+ let startLine = item.Range.StartLine
+
+ match lookup.TryGetValue startLine with
+ | true, items -> items
+ | _ ->
+ let items = ResizeArray()
+ lookup[startLine] <- items
+ items
+
+ items.Add item
+
+ lookup :> IReadOnlyDictionary<_, _>
+
+ static let unopenedDocumentsSemanticClassificationCache =
+ new DocumentCache("fsharp-unopened-documents-semantic-classification-cache", 5.)
+
+ static let openedDocumentsSemanticClassificationCache =
+ new DocumentCache("fsharp-opened-documents-semantic-classification-cache", 2.)
interface IFSharpClassificationService with
// Do not perform classification if we don't have project options (#defines matter)
@@ -197,7 +218,7 @@ type internal FSharpClassificationService [] () =
let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id
if not isOpenDocument then
- match! semanticClassificationCache.TryGetValueAsync document with
+ match! unopenedDocumentsSemanticClassificationCache.TryGetValueAsync document with
| ValueSome classificationDataLookup ->
let eventProps: (string * obj) array =
[|
@@ -212,7 +233,7 @@ type internal FSharpClassificationService [] () =
TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps)
addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
- | _ ->
+ | ValueNone ->
let eventProps: (string * obj) array =
[|
"context.document.project.id", document.Project.Id.Id.ToString()
@@ -226,29 +247,53 @@ type internal FSharpClassificationService [] () =
TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps)
let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof (FSharpClassificationService))
+
let classificationDataLookup = toSemanticClassificationLookup classificationData
- do! semanticClassificationCache.SetAsync(document, classificationDataLookup)
+ do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup)
addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
else
- let eventProps: (string * obj) array =
- [|
- "context.document.project.id", document.Project.Id.Id.ToString()
- "context.document.id", document.Id.Id.ToString()
- "isOpenDocument", isOpenDocument
- "textSpanLength", textSpan.Length
- "cacheHit", false
- |]
- use _eventDuration =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps)
+ match! openedDocumentsSemanticClassificationCache.TryGetValueAsync document with
+ | ValueSome classificationDataLookup ->
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", true
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps)
+
+ addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result
+ | ValueNone ->
+
+ let eventProps: (string * obj) array =
+ [|
+ "context.document.project.id", document.Project.Id.Id.ToString()
+ "context.document.id", document.Id.Id.ToString()
+ "isOpenDocument", isOpenDocument
+ "textSpanLength", textSpan.Length
+ "cacheHit", false
+ |]
+
+ use _eventDuration =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps)
+
+ let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService))
+
+ let targetRange =
+ RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
- let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService))
+ let classificationData = checkResults.GetSemanticClassification(Some targetRange)
- let targetRange =
- RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
+ if classificationData.Length > 0 then
+ let classificationDataLookup = itemToSemanticClassificationLookup classificationData
+ do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup)
- let classificationData = checkResults.GetSemanticClassification(Some targetRange)
- addSemanticClassification sourceText textSpan classificationData result
+ addSemanticClassification sourceText textSpan classificationData result
}
|> CancellableTask.startAsTask cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs
index ac0b952f65f..e2db1a58dd4 100644
--- a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs
+++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs
@@ -188,8 +188,8 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr
let entities =
assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults
- |> List.collect (fun s ->
- [
+ |> Array.collect (fun s ->
+ [|
yield s.TopRequireQualifiedAccessParent, s.AutoOpenParent, s.Namespace, s.CleanedIdents
if isAttribute then
let lastIdent = s.CleanedIdents.[s.CleanedIdents.Length - 1]
@@ -204,7 +204,7 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr
s.Namespace,
s.CleanedIdents
|> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9))
- ])
+ |])
ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End
|> Option.bind (fun longIdent ->
diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs
index f2bcf18e870..5b762414c59 100644
--- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs
+++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs
@@ -16,6 +16,8 @@ open Microsoft.VisualStudio.TextManager.Interop
open Microsoft.VisualStudio.LanguageServices
open Microsoft.VisualStudio.Utilities
open FSharp.Compiler.EditorServices
+open CancellableTasks.CancellableTaskBuilder
+open CancellableTasks
type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, workspace: VisualStudioWorkspace) =
@@ -67,7 +69,12 @@ type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, w
let! document = getLastDocument ()
let! cancellationToken = Async.CancellationToken |> liftAsync
let! sourceText = document.GetTextAsync(cancellationToken)
- let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) |> liftAsync
+
+ let! parseResults =
+ document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter))
+ |> CancellableTask.start cancellationToken
+ |> Async.AwaitTask
+ |> liftAsync
let xmlDocables =
XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree)
diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
index 6e703fc2c59..1cd71d4228e 100644
--- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
+++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs
@@ -451,13 +451,13 @@ module CancellableTasks =
///
/// Builds a cancellableTask using computation expression syntax.
- /// Default behaviour when binding (v)options is to return a cacnelled task.
+ /// Default behaviour when binding (v)options is to return a cancelled task.
///
let foregroundCancellableTask = CancellableTaskBuilder(false)
///
/// Builds a cancellableTask using computation expression syntax which switches to execute on a background thread if not already doing so.
- /// Default behaviour when binding (v)options is to return a cacnelled task.
+ /// Default behaviour when binding (v)options is to return a cancelled task.
///
let cancellableTask = CancellableTaskBuilder(true)
@@ -1096,17 +1096,18 @@ module CancellableTasks =
let inline whenAll (tasks: CancellableTask<'a> seq) =
cancellableTask {
let! ct = getCancellationToken ()
- return! Task.WhenAll (seq { for task in tasks do yield start ct task })
+ let tasks = seq { for task in tasks do yield start ct task }
+ return! Task.WhenAll (tasks)
}
let inline whenAllTasks (tasks: CancellableTask seq) =
cancellableTask {
let! ct = getCancellationToken ()
- return! Task.WhenAll (seq { for task in tasks do yield startTask ct task })
+ let tasks = seq { for task in tasks do yield startTask ct task }
+ return! Task.WhenAll (tasks)
}
- let inline ignore (ctask: CancellableTask<_>) =
- ctask |> toUnit
+ let inline ignore ([] ctask: CancellableTask<_>) = toUnit ctask
///
[]
diff --git a/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs b/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs
index 888505ecda3..c42ace1a366 100644
--- a/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs
+++ b/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs
@@ -53,17 +53,8 @@ type Solution with
// It's crucial to normalize file path here (specificaly, remove relative parts),
// otherwise Roslyn does not find documents.
self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath)
- |> Seq.tryHead
- |> Option.map (fun docId -> self.GetDocument docId)
-
- /// Try to find the document corresponding to the provided filepath and ProjectId within this solution
- member self.TryGetDocumentFromPath(filePath, projId: ProjectId) =
- // It's crucial to normalize file path here (specificaly, remove relative parts),
- // otherwise Roslyn does not find documents.
- self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath)
- |> Seq.filter (fun x -> x.ProjectId = projId)
- |> Seq.tryHead
- |> Option.map (fun docId -> self.GetDocument docId)
+ |> ImmutableArray.tryHeadV
+ |> ValueOption.map (fun docId -> self.GetDocument docId)
/// Try to get a project inside the solution using the project's id
member self.TryGetProject(projId: ProjectId) =
diff --git a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs
index f812e5f8b0f..6c88bc0f912 100644
--- a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs
+++ b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs
@@ -16,6 +16,9 @@ type DocumentCache<'Value when 'Value: not struct>(name: string, ?cacheItemPolic
let policy =
defaultArg cacheItemPolicy (CacheItemPolicy(SlidingExpiration = (TimeSpan.FromSeconds defaultSlidingExpiration)))
+ new(name: string, slidingExpirationSeconds: float) =
+ new DocumentCache<'Value>(name, CacheItemPolicy(SlidingExpiration = (TimeSpan.FromSeconds slidingExpirationSeconds)))
+
member _.TryGetValueAsync(doc: Document) =
cancellableTask {
let! ct = CancellableTask.getCancellationToken ()
diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs
index d05b5166a08..efcee384b99 100644
--- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs
+++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs
@@ -163,6 +163,40 @@ module private SourceText =
member _.CopyTo(sourceIndex, destination, destinationIndex, count) =
sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
+
+ member this.GetSubTextFromRange range =
+ let totalAmountOfLines = sourceText.Lines.Count
+
+ if
+ range.StartLine = 0
+ && range.StartColumn = 0
+ && range.EndLine = 0
+ && range.EndColumn = 0
+ then
+ String.Empty
+ elif
+ range.StartLine < 1
+ || (range.StartLine - 1) > totalAmountOfLines
+ || range.EndLine < 1
+ || (range.EndLine - 1) > totalAmountOfLines
+ then
+ invalidArg (nameof range) "The range is outside the file boundaries"
+ else
+ let startLine = range.StartLine - 1
+ let line = this.GetLineString startLine
+
+ if range.StartLine = range.EndLine then
+ let length = range.EndColumn - range.StartColumn
+ line.Substring(range.StartColumn, length)
+ else
+ let firstLineContent = line.Substring(range.StartColumn)
+ let sb = System.Text.StringBuilder().AppendLine(firstLineContent)
+
+ for lineNumber in range.StartLine .. range.EndLine - 2 do
+ sb.AppendLine(this.GetLineString lineNumber) |> ignore
+
+ let lastLine = this.GetLineString(range.EndLine - 1)
+ sb.Append(lastLine.Substring(0, range.EndColumn)).ToString()
}
sourceText
@@ -269,7 +303,7 @@ module String =
[]
module Option =
- let guard (x: bool) : Option = if x then Some() else None
+ let guard (x: bool) : ValueOption = if x then ValueSome() else ValueNone
let attempt (f: unit -> 'T) =
try
@@ -298,11 +332,63 @@ module ValueOption =
| ValueSome v -> Some v
| _ -> None
+[]
+module IEnumerator =
+ let chooseV f (e: IEnumerator<'T>) =
+ let mutable started = false
+ let mutable curr = ValueNone
+
+ let get () =
+ if not started then
+ raise (InvalidOperationException("Not started"))
+
+ match curr with
+ | ValueNone -> raise (InvalidOperationException("Already finished"))
+ | ValueSome x -> x
+
+ { new IEnumerator<'U> with
+ member _.Current = get ()
+ interface System.Collections.IEnumerator with
+ member _.Current = box (get ())
+
+ member _.MoveNext() =
+ if not started then
+ started <- true
+
+ curr <- ValueNone
+
+ while (curr.IsNone && e.MoveNext()) do
+ curr <- f e.Current
+
+ ValueOption.isSome curr
+
+ member _.Reset() =
+ raise (NotSupportedException("Reset is not supported"))
+ interface System.IDisposable with
+ member _.Dispose() = e.Dispose()
+ }
+
[]
module Seq =
+ let mkSeq f =
+ { new IEnumerable<'U> with
+ member _.GetEnumerator() = f ()
+ interface System.Collections.IEnumerable with
+ member _.GetEnumerator() =
+ (f () :> System.Collections.IEnumerator)
+ }
+
+ let inline revamp f (ie: seq<_>) =
+ mkSeq (fun () -> f (ie.GetEnumerator()))
+
let toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray()
+ let inline tryHeadV (source: seq<_>) =
+ use e = source.GetEnumerator()
+
+ if (e.MoveNext()) then ValueSome e.Current else ValueNone
+
let inline tryFindV ([] predicate) (source: seq<'T>) =
use e = source.GetEnumerator()
let mutable res = ValueNone
@@ -326,6 +412,18 @@ module Seq =
loop 0
+ let inline tryPickV ([] chooser) (source: seq<'T>) =
+ use e = source.GetEnumerator()
+ let mutable res = ValueNone
+
+ while (ValueOption.isNone res && e.MoveNext()) do
+ res <- chooser e.Current
+
+ res
+
+ let chooseV (chooser: 'a -> 'b voption) source =
+ revamp (IEnumerator.chooseV chooser) source
+
[]
module Array =
let inline foldi ([] folder: 'State -> int -> 'T -> 'State) (state: 'State) (xs: 'T[]) =
@@ -340,6 +438,9 @@ module Array =
let toImmutableArray (xs: 'T[]) = xs.ToImmutableArray()
+ let inline tryHeadV (array: _[]) =
+ if array.Length = 0 then ValueNone else ValueSome array[0]
+
let inline tryFindV ([] predicate) (array: _[]) =
let rec loop i =
@@ -349,6 +450,75 @@ module Array =
loop 0
+ let inline chooseV ([] chooser: 'T -> 'U voption) (array: 'T[]) =
+
+ let mutable i = 0
+ let mutable first = Unchecked.defaultof<'U>
+ let mutable found = false
+
+ while i < array.Length && not found do
+ let element = array.[i]
+
+ match chooser element with
+ | ValueNone -> i <- i + 1
+ | ValueSome b ->
+ first <- b
+ found <- true
+
+ if i <> array.Length then
+
+ let chunk1: 'U[] = Array.zeroCreate ((array.Length >>> 2) + 1)
+
+ chunk1.[0] <- first
+ let mutable count = 1
+ i <- i + 1
+
+ while count < chunk1.Length && i < array.Length do
+ let element = array.[i]
+
+ match chooser element with
+ | ValueNone -> ()
+ | ValueSome b ->
+ chunk1.[count] <- b
+ count <- count + 1
+
+ i <- i + 1
+
+ if i < array.Length then
+ let chunk2: 'U[] = Array.zeroCreate (array.Length - i)
+
+ count <- 0
+
+ while i < array.Length do
+ let element = array.[i]
+
+ match chooser element with
+ | ValueNone -> ()
+ | ValueSome b ->
+ chunk2.[count] <- b
+ count <- count + 1
+
+ i <- i + 1
+
+ let res: 'U[] = Array.zeroCreate (chunk1.Length + count)
+
+ Array.Copy(chunk1, res, chunk1.Length)
+ Array.Copy(chunk2, 0, res, chunk1.Length, count)
+ res
+ else
+ Array.sub chunk1 0 count
+ else
+ Array.empty
+
+[]
+module ImmutableArray =
+ let inline tryHeadV (xs: ImmutableArray<'T>) : 'T voption =
+ if xs.Length = 0 then ValueNone else ValueSome xs[0]
+
+ let inline empty<'T> = ImmutableArray<'T>.Empty
+
+ let inline create<'T> (x: 'T) = ImmutableArray.Create<'T>(x)
+
[]
module List =
let rec tryFindV predicate list =
@@ -379,6 +549,10 @@ module Exception =
|> flattenInner
|> String.concat " ---> "
+[]
+module TextSpan =
+ let empty = TextSpan()
+
type Async with
static member RunImmediateExceptOnUI(computation: Async<'T>, ?cancellationToken) =
diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs
index 18e5c463c72..bbd45cdbb43 100644
--- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs
+++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs
@@ -6,18 +6,18 @@ open System.IO
open System.Diagnostics
/// Checks if the filePath ends with ".fsi"
-let isSignatureFile (filePath: string) =
+let inline isSignatureFile (filePath: string) =
String.Equals(Path.GetExtension filePath, ".fsi", StringComparison.OrdinalIgnoreCase)
/// Returns the corresponding signature file path for given implementation file path or vice versa
-let getOtherFile (filePath: string) =
+let inline getOtherFile (filePath: string) =
if isSignatureFile filePath then
Path.ChangeExtension(filePath, ".fs")
else
Path.ChangeExtension(filePath, ".fsi")
/// Checks if the file paht ends with '.fsx' or '.fsscript'
-let isScriptFile (filePath: string) =
+let inline isScriptFile (filePath: string) =
let ext = Path.GetExtension filePath
String.Equals(ext, ".fsx", StringComparison.OrdinalIgnoreCase)
@@ -42,7 +42,7 @@ type MaybeBuilder() =
// (unit -> M<'T>) -> M<'T>
[]
- member _.Delay(f: unit -> 'T option) : 'T option = f ()
+ member inline _.Delay([] f: unit -> 'T option) : 'T option = f ()
// M<'T> -> M<'T> -> M<'T>
// or
@@ -55,11 +55,18 @@ type MaybeBuilder() =
// M<'T> * ('T -> M<'U>) -> M<'U>
[]
- member inline _.Bind(value, f: 'T -> 'U option) : 'U option = Option.bind f value
+ member inline _.Bind(value, [] f: 'T -> 'U option) : 'U option = Option.bind f value
+
+ // M<'T> * ('T -> M<'U>) -> M<'U>
+ []
+ member inline _.Bind(value: 'T voption, [] f: 'T -> 'U option) : 'U option =
+ match value with
+ | ValueNone -> None
+ | ValueSome value -> f value
// 'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable
[]
- member _.Using(resource: ('T :> System.IDisposable), body: _ -> _ option) : _ option =
+ member inline _.Using(resource: ('T :> System.IDisposable), [] body: _ -> _ option) : _ option =
try
body resource
finally
@@ -79,28 +86,28 @@ type MaybeBuilder() =
// or
// seq<'T> * ('T -> M<'U>) -> seq>
[]
- member x.For(sequence: seq<_>, body: 'T -> unit option) : _ option =
+ member inline x.For(sequence: seq<_>, [] body: 'T -> unit option) : _ option =
// OPTIMIZE: This could be simplified so we don't need to make calls to Using, While, Delay.
x.Using(sequence.GetEnumerator(), (fun enum -> x.While(enum.MoveNext, x.Delay(fun () -> body enum.Current))))
let maybe = MaybeBuilder()
-[]
+[]
type AsyncMaybeBuilder() =
[]
- member _.Return value : Async<'T option> = Some value |> async.Return
+ member inline _.Return value : Async<'T option> = async.Return(Some value)
[]
- member _.ReturnFrom value : Async<'T option> = value
+ member inline _.ReturnFrom value : Async<'T option> = value
[]
- member _.ReturnFrom(value: 'T option) : Async<'T option> = async.Return value
+ member inline _.ReturnFrom(value: 'T option) : Async<'T option> = async.Return value
[]
- member _.Zero() : Async = Some() |> async.Return
+ member inline _.Zero() : Async = async.Return(Some())
[]
- member _.Delay(f: unit -> Async<'T option>) : Async<'T option> = async.Delay f
+ member inline _.Delay([] f: unit -> Async<'T option>) : Async<'T option> = async.Delay f
[]
member _.Combine(r1, r2: Async<'T option>) : Async<'T option> =
@@ -113,7 +120,7 @@ type AsyncMaybeBuilder() =
}
[]
- member _.Bind(value: Async<'T option>, f: 'T -> Async<'U option>) : Async<'U option> =
+ member inline _.Bind(value: Async<'T option>, [] f: 'T -> Async<'U option>) : Async<'U option> =
async {
let! value' = value
@@ -123,14 +130,14 @@ type AsyncMaybeBuilder() =
}
[]
- member _.Bind(value: System.Threading.Tasks.Task<'T>, f: 'T -> Async<'U option>) : Async<'U option> =
+ member inline _.Bind(value: System.Threading.Tasks.Task<'T>, [] f: 'T -> Async<'U option>) : Async<'U option> =
async {
let! value' = Async.AwaitTask value
return! f value'
}
[]
- member _.Bind(value: 'T option, f: 'T -> Async<'U option>) : Async<'U option> =
+ member inline _.Bind(value: 'T option, [] f: 'T -> Async<'U option>) : Async<'U option> =
async {
match value with
| None -> return None
@@ -138,7 +145,15 @@ type AsyncMaybeBuilder() =
}
[]
- member _.Using(resource: ('T :> IDisposable), body: 'T -> Async<'U option>) : Async<'U option> =
+ member inline _.Bind(value: 'T voption, [] f: 'T -> Async<'U option>) : Async<'U option> =
+ async {
+ match value with
+ | ValueNone -> return None
+ | ValueSome result -> return! f result
+ }
+
+ []
+ member inline _.Using(resource: ('T :> IDisposable), [] body: 'T -> Async<'U option>) : Async<'U option> =
async {
use resource = resource
return! body resource
@@ -152,7 +167,7 @@ type AsyncMaybeBuilder() =
x.Zero()
[]
- member x.For(sequence: seq<_>, body: 'T -> Async) : Async =
+ member inline x.For(sequence: seq<_>, [] body: 'T -> Async) : Async =
x.Using(sequence.GetEnumerator(), (fun enum -> x.While(enum.MoveNext, x.Delay(fun () -> body enum.Current))))
[]
diff --git a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs
index 41dee649d81..eec57d2a238 100644
--- a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs
+++ b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs
@@ -46,12 +46,12 @@ module internal RoslynHelpers =
TextSpan(startPosition, endPosition - startPosition)
- let TryFSharpRangeToTextSpan (sourceText: SourceText, range: range) : TextSpan option =
+ let TryFSharpRangeToTextSpan (sourceText: SourceText, range: range) : TextSpan voption =
try
- Some(FSharpRangeToTextSpan(sourceText, range))
+ ValueSome(FSharpRangeToTextSpan(sourceText, range))
with e ->
//Assert.Exception(e)
- None
+ ValueNone
let TextSpanToFSharpRange (fileName: string, textSpan: TextSpan, sourceText: SourceText) : range =
let startLine = sourceText.Lines.GetLineFromPosition textSpan.Start
@@ -62,13 +62,6 @@ module internal RoslynHelpers =
(Position.fromZ startLine.LineNumber (textSpan.Start - startLine.Start))
(Position.fromZ endLine.LineNumber (textSpan.End - endLine.Start))
- let GetCompletedTaskResult (task: Task<'TResult>) =
- if task.Status = TaskStatus.RanToCompletion then
- task.Result
- else
- Assert.Exception(task.Exception.GetBaseException())
- raise (task.Exception.GetBaseException())
-
/// maps from `TextTag` of the F# Compiler to Roslyn `TextTags` for use in tooltips
let roslynTag =
function
diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs
index d0ce229f64b..7d121bf4ab6 100644
--- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs
+++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs
@@ -150,14 +150,14 @@ type internal FSharpCompletionProvider
(
document: Document,
caretPosition: int,
- getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list
+ getAllSymbols: FSharpCheckFileResults -> AssemblySymbol array
) =
cancellableTask {
- let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux")
-
let! ct = CancellableTask.getCancellationToken ()
+ let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux")
+
let! sourceText = document.GetTextAsync(ct)
let textLines = sourceText.Lines
let caretLinePos = textLines.GetLinePosition(caretPosition)
@@ -194,9 +194,8 @@ type internal FSharpCompletionProvider
let results = List()
- declarationItems <-
- declarations.Items
- |> Array.sortWith (fun x y ->
+ Array.sortInPlaceWith
+ (fun (x: DeclarationListItem) (y: DeclarationListItem) ->
let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved)
if n <> 0 then
@@ -220,6 +219,9 @@ type internal FSharpCompletionProvider
n
else
x.MinorPriority.CompareTo(y.MinorPriority))
+ declarations.Items
+
+ declarationItems <- declarations.Items
let maxHints =
if mruItems.Values.Count = 0 then
@@ -227,23 +229,20 @@ type internal FSharpCompletionProvider
else
Seq.max mruItems.Values
- declarationItems
- |> Array.iteri (fun number declarationItem ->
+ for number = 0 to declarationItems.Length - 1 do
+ let declarationItem = declarationItems[number]
+
let glyph =
Tokenizer.FSharpGlyphToRoslynGlyph(declarationItem.Glyph, declarationItem.Accessibility)
- let namespaceName =
- match declarationItem.NamespaceToOpen with
- | Some namespaceToOpen -> namespaceToOpen
- | _ -> null // Icky, but this is how roslyn handles it
-
- let filterText =
+ let namespaceName, filterText =
match declarationItem.NamespaceToOpen, declarationItem.NameInList.Split '.' with
// There is no namespace to open and the item name does not contain dots, so we don't need to pass special FilterText to Roslyn.
- | None, [| _ |] -> null
+ | None, [| _ |] -> null, null
+ | Some namespaceToOpen, idents -> namespaceToOpen, Array.last idents
// Either we have a namespace to open ("DateTime (open System)") or item name contains dots ("Array.map"), or both.
// We are passing last part of long ident as FilterText.
- | _, idents -> Array.last idents
+ | None, idents -> null, Array.last idents
let completionItem =
FSharpCommonCompletionItem
@@ -282,7 +281,7 @@ type internal FSharpCompletionProvider
let sortText = priority.ToString("D6")
let completionItem = completionItem.WithSortText(sortText)
- results.Add(completionItem))
+ results.Add(completionItem)
if
results.Count > 0
@@ -352,11 +351,11 @@ type internal FSharpCompletionProvider
)
if shouldProvideCompetion then
- let getAllSymbols (fileCheckResults: FSharpCheckFileResults) =
+ let inline getAllSymbols (fileCheckResults: FSharpCheckFileResults) =
if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then
assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults)
else
- []
+ Array.empty
let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols)
@@ -369,31 +368,26 @@ type internal FSharpCompletionProvider
(
document: Document,
completionItem: Completion.CompletionItem,
- cancellationToken: CancellationToken
+ _cancellationToken: CancellationToken
) : Task =
match completionItem.Properties.TryGetValue IndexPropName with
| true, completionItemIndexStr when int completionItemIndexStr >= declarationItems.Length ->
Task.FromResult CompletionDescription.Empty
| true, completionItemIndexStr ->
- // TODO: Not entirely sure why do we use tasks here, since everything here is synchronous.
- cancellableTask {
- use _logBlock =
- Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync
-
- let completionItemIndex = int completionItemIndexStr
+ use _logBlock =
+ Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync
- let declarationItem = declarationItems.[completionItemIndex]
- let description = declarationItem.Description
- let documentation = List()
- let collector = RoslynHelpers.CollectTaggedText documentation
- // mix main description and xmldoc by using one collector
- XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description)
+ let completionItemIndex = int completionItemIndexStr
- return CompletionDescription.Create(documentation.ToImmutableArray())
- }
- |> CancellableTask.start cancellationToken
+ let declarationItem = declarationItems.[completionItemIndex]
+ let description = declarationItem.Description
+ let documentation = List()
+ let collector = RoslynHelpers.CollectTaggedText documentation
+ // mix main description and xmldoc by using one collector
+ XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description)
+ Task.FromResult(CompletionDescription.Create(documentation.ToImmutableArray()))
| _ ->
match completionItem.Properties.TryGetValue KeywordDescription with
| true, keywordDescription -> Task.FromResult(CompletionDescription.FromText(keywordDescription))
@@ -406,8 +400,8 @@ type internal FSharpCompletionProvider
let fullName =
match item.Properties.TryGetValue FullNamePropName with
- | true, x -> Some x
- | _ -> None
+ | true, x -> ValueSome x
+ | _ -> ValueNone
// do not add extension members and unresolved symbols to the MRU list
if
@@ -415,7 +409,7 @@ type internal FSharpCompletionProvider
&& not (item.Properties.ContainsKey IsExtensionMemberPropName)
then
match fullName with
- | Some fullName ->
+ | ValueSome fullName ->
match mruItems.TryGetValue fullName with
| true, hints -> mruItems.[fullName] <- hints + 1
| _ -> mruItems.[fullName] <- 1
@@ -439,7 +433,9 @@ type internal FSharpCompletionProvider
let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider))
let fullNameIdents =
- fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||]
+ fullName
+ |> ValueOption.map (fun x -> x.Split '.')
+ |> ValueOption.defaultValue [||]
let insertionPoint =
if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then
diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs
index d73b1a61d05..56ad5a26518 100644
--- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs
+++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs
@@ -33,18 +33,17 @@ type internal FSharpBreakpointResolutionService [] () =
sourceText.GetSubText(sourceText.Lines.[textLinePos.Line].Span).ToString()
if String.IsNullOrWhiteSpace textInLine then
- return None
+ return ValueNone
else
let textLineColumn = textLinePos.Character
let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based
- let! parseResults =
- document.GetFSharpParseResultsAsync(nameof (FSharpBreakpointResolutionService))
- |> liftAsync
+ let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpBreakpointResolutionService))
- match parseResults with
- | Some parseResults -> return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn)
- | _ -> return None
+ let location =
+ parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn)
+
+ return ValueOption.ofOption location
}
interface IFSharpBreakpointResolutionService with
@@ -58,14 +57,14 @@ type internal FSharpBreakpointResolutionService [] () =
let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(document, textSpan)
match range with
- | None -> return Unchecked.defaultof<_>
- | Some range ->
+ | ValueNone -> return Unchecked.defaultof<_>
+ | ValueSome range ->
let! sourceText = document.GetTextAsync(cancellationToken)
let span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)
match span with
- | None -> return Unchecked.defaultof<_>
- | Some span -> return FSharpBreakpointResolutionResult.CreateSpanResult(document, span)
+ | ValueNone -> return Unchecked.defaultof<_>
+ | ValueSome span -> return FSharpBreakpointResolutionResult.CreateSpanResult(document, span)
}
|> CancellableTask.start cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
index 4329596ea49..0b6bb92a1c8 100644
--- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
+++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
@@ -16,7 +16,7 @@ open FSharp.Compiler.Diagnostics
open CancellableTasks
open Microsoft.VisualStudio.FSharp.Editor.Telemetry
-[]
+[]
type internal DiagnosticsType =
| Syntax
| Semantic
@@ -144,12 +144,12 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () =
if document.Project.IsFSharpMetadata then
Task.FromResult ImmutableArray.Empty
else
- cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) }
+ FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax)
|> CancellableTask.start cancellationToken
member _.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) : Task> =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then
Task.FromResult ImmutableArray.Empty
else
- cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) }
+ FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic)
|> CancellableTask.start cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs
index aaf2a8a5db1..7947b2dec99 100644
--- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs
+++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs
@@ -97,8 +97,8 @@ type internal FSharpDocumentHighlightsService [] () =
[|
for symbolUse in symbolUses do
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with
- | None -> ()
- | Some span ->
+ | ValueNone -> ()
+ | ValueSome span ->
yield
{
IsDefinition = symbolUse.IsFromDefinition
diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
index b2a685d2347..7b9162b1f24 100644
--- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
+++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
@@ -18,7 +18,7 @@
-
+ trueMicrosoft.VisualStudio.FSharp.Editor.SR
diff --git a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs
index 11fa356b1f4..1b58774e7a1 100644
--- a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs
+++ b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs
@@ -28,8 +28,8 @@ type internal FSharpBraceMatchingService [] () =
let isPositionInRange range =
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with
- | None -> false
- | Some span ->
+ | ValueNone -> false
+ | ValueSome span ->
if forFormatting then
let length = position - span.Start
length >= 0 && length <= span.Length
diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs
index 0cb40be0b10..d63e2d016df 100644
--- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs
+++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs
@@ -167,10 +167,10 @@ type internal InlineRenameInfo
[|
for symbolUse in symbolUses do
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with
- | Some span ->
+ | ValueSome span ->
let textSpan = Tokenizer.fixupSpan (sourceText, span)
yield FSharpInlineRenameLocation(document, textSpan)
- | None -> ()
+ | ValueNone -> ()
|]
}
}
@@ -220,8 +220,8 @@ type internal InlineRenameService [] () =
let span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range)
match span with
- | None -> return Unchecked.defaultof<_>
- | Some span ->
+ | ValueNone -> return Unchecked.defaultof<_>
+ | ValueSome span ->
let triggerSpan = Tokenizer.fixupSpan (sourceText, span)
let result =
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs
index f34ca68a2dc..7a4b39c6737 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs
@@ -12,8 +12,8 @@ open FSharp.Compiler.EditorServices
type internal AssemblyContentProvider() =
let entityCache = EntityCache()
- member x.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults: FSharpCheckFileResults) =
- [
+ member _.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults: FSharpCheckFileResults) =
+ [|
yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full fileCheckResults.PartialAssemblySignature
// FCS sometimes returns several FSharpAssembly for single referenced assembly.
// For example, it returns two different ones for Swensen.Unquote; the first one
@@ -24,11 +24,9 @@ type internal AssemblyContentProvider() =
fileCheckResults.ProjectContext.GetReferencedAssemblies()
|> Seq.groupBy (fun asm -> asm.FileName)
|> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms)
- |> Seq.toList
- |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to
- // get Content.Entities from it.
+ |> Seq.rev // if mscorlib.dll is the first then FSC raises exception when we try to get Content.Entities from it.
for fileName, signatures in assembliesByFileName do
let contentType = AssemblyContentType.Public // it's always Public for now since we don't support InternalsVisibleTo attribute yet
yield! AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures
- ]
+ |]
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs
index 30baf56eb3e..305cd868921 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs
@@ -12,6 +12,8 @@ open Microsoft.VisualStudio.FSharp.Editor.Logging
open Microsoft.VisualStudio.Text.Editor.Commanding.Commands
open Microsoft.VisualStudio.Commanding
open Microsoft.VisualStudio.Utilities
+open CancellableTasks
+open Microsoft.VisualStudio.FSharp.Editor.Telemetry
// This causes re-analysis to happen when a F# document is saved.
// We do this because FCS relies on the file system and existing open documents
@@ -46,42 +48,52 @@ type internal FSharpAnalysisSaveFileCommandHandler [] (ana
| _ ->
let document = solution.GetDocument(documentId)
- async {
- try
- if document.Project.Language = LanguageNames.FSharp then
+ if document.Project.Language <> LanguageNames.FSharp then
+ ()
+ else
+ cancellableTask {
+ try
let openDocIds = workspace.GetOpenDocumentIds()
let docIdsToReanalyze =
if document.IsFSharpScript then
- openDocIds
- |> Seq.filter (fun x ->
- x <> document.Id
- && (let doc = solution.GetDocument(x)
-
- match doc with
- | null -> false
- | _ -> doc.IsFSharpScript))
- |> Array.ofSeq
+ [|
+ for x in openDocIds do
+ if
+ x <> document.Id
+ && (let doc = solution.GetDocument(x)
+
+ match doc with
+ | null -> false
+ | _ -> doc.IsFSharpScript)
+ then
+ yield x
+ |]
else
let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id)
- openDocIds
- |> Seq.filter (fun x ->
- depProjIds.Contains(x.ProjectId)
- && x <> document.Id
- && (let doc = solution.GetDocument(x)
+ [|
+ for x in openDocIds do
+ if
+ depProjIds.Contains(x.ProjectId)
+ && x <> document.Id
+ && (let doc = solution.GetDocument(x)
- match box doc with
- | null -> false
- | _ -> doc.Project.Language = LanguageNames.FSharp))
- |> Array.ofSeq
+ match box doc with
+ | null -> false
+ | _ -> doc.Project.Language = LanguageNames.FSharp)
+ then
+ yield x
+ |]
if docIdsToReanalyze.Length > 0 then
analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze)
- with ex ->
- logException ex
- }
- |> Async.Start // fire and forget
+ with ex ->
+ TelemetryReporter.ReportFault(TelemetryEvents.AnalysisSaveFileHandler, e = ex)
+ logException ex
+ }
+ |> CancellableTask.startWithoutCancellation
+ |> ignore // fire and forget
nextCommandHandler.Invoke()
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs
index 345bd3d707f..4fcc180201b 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs
@@ -15,6 +15,7 @@ open Microsoft.VisualStudio.FSharp.Editor
open System.Threading
open Microsoft.VisualStudio.FSharp.Interactive.Session
open System.Runtime.CompilerServices
+open CancellableTasks
[]
module private FSharpProjectOptionsHelpers =
@@ -55,7 +56,7 @@ module private FSharpProjectOptionsHelpers =
and set (v) = errorReporter <- v
}
- let hasProjectVersionChanged (oldProject: Project) (newProject: Project) =
+ let inline hasProjectVersionChanged (oldProject: Project) (newProject: Project) =
oldProject.Version <> newProject.Version
let hasDependentVersionChanged (oldProject: Project) (newProject: Project) (ct: CancellationToken) =
@@ -97,10 +98,10 @@ module private FSharpProjectOptionsHelpers =
type private FSharpProjectOptionsMessage =
| TryGetOptionsByDocument of
Document *
- AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) option> *
+ AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) voption> *
CancellationToken *
userOpName: string
- | TryGetOptionsByProject of Project * AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) option> * CancellationToken
+ | TryGetOptionsByProject of Project * AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) voption> * CancellationToken
| ClearOptions of ProjectId
| ClearSingleFileOptionsCache of DocumentId
@@ -188,13 +189,14 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
weakPEReferences.Add(comp, fsRefProj)
fsRefProj
- let rec tryComputeOptionsBySingleScriptOrFile (document: Document) (ct: CancellationToken) userOpName =
- async {
- let! fileStamp = document.GetTextVersionAsync(ct) |> Async.AwaitTask
+ let rec tryComputeOptionsBySingleScriptOrFile (document: Document) userOpName =
+ cancellableTask {
+ let! ct = CancellableTask.getCancellationToken ()
+ let! fileStamp = document.GetTextVersionAsync(ct)
match singleFileCache.TryGetValue(document.Id) with
| false, _ ->
- let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask
+ let! sourceText = document.GetTextAsync(ct)
let! scriptProjectOptions, _ =
checker.GetProjectOptionsFromScript(
@@ -243,29 +245,30 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
singleFileCache.[document.Id] <- (document.Project, fileStamp, parsingOptions, projectOptions)
- return Some(parsingOptions, projectOptions)
+ return ValueSome(parsingOptions, projectOptions)
| true, (oldProject, oldFileStamp, parsingOptions, projectOptions) ->
if fileStamp <> oldFileStamp || isProjectInvalidated document.Project oldProject ct then
singleFileCache.TryRemove(document.Id) |> ignore
- return! tryComputeOptionsBySingleScriptOrFile document ct userOpName
+ return! tryComputeOptionsBySingleScriptOrFile document userOpName
else
- return Some(parsingOptions, projectOptions)
+ return ValueSome(parsingOptions, projectOptions)
}
let tryGetProjectSite (project: Project) =
// Cps
if commandLineOptions.ContainsKey project.Id then
- Some(mapCpsProjectToSite (project, commandLineOptions))
+ ValueSome(mapCpsProjectToSite (project, commandLineOptions))
else
// Legacy
match legacyProjectSites.TryGetValue project.Id with
- | true, site -> Some site
- | _ -> None
+ | true, site -> ValueSome site
+ | _ -> ValueNone
- let rec tryComputeOptions (project: Project) ct =
- async {
+ let rec tryComputeOptions (project: Project) =
+ cancellableTask {
let projectId = project.Id
+ let! ct = CancellableTask.getCancellationToken ()
match cache.TryGetValue(projectId) with
| false, _ ->
@@ -281,24 +284,23 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
let referencedProject = project.Solution.GetProject(projectReference.ProjectId)
if referencedProject.Language = FSharpConstants.FSharpLanguageName then
- match! tryComputeOptions referencedProject ct with
- | None -> canBail <- true
- | Some (_, projectOptions) ->
+ match! tryComputeOptions referencedProject with
+ | ValueNone -> canBail <- true
+ | ValueSome (_, projectOptions) ->
referencedProjects.Add(
FSharpReferencedProject.FSharpReference(referencedProject.OutputFilePath, projectOptions)
)
elif referencedProject.SupportsCompilation then
- let! comp = referencedProject.GetCompilationAsync(ct) |> Async.AwaitTask
+ let! comp = referencedProject.GetCompilationAsync(ct)
let peRef = createPEReference referencedProject comp
referencedProjects.Add(peRef)
if canBail then
- return None
+ return ValueNone
else
-
match tryGetProjectSite project with
- | None -> return None
- | Some projectSite ->
+ | ValueNone -> return ValueNone
+ | ValueSome projectSite ->
let otherOptions =
[|
@@ -321,7 +323,7 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
"--ignorelinedirectives"
|]
- let! ver = project.GetDependentVersionAsync(ct) |> Async.AwaitTask
+ let! ver = project.GetDependentVersionAsync(ct)
let projectOptions =
{
@@ -340,7 +342,7 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
// This can happen if we didn't receive the callback from HandleCommandLineChanges yet.
if Array.isEmpty projectOptions.SourceFiles then
- return None
+ return ValueNone
else
// Clear any caches that need clearing and invalidate the project.
let currentSolution = project.Solution.Workspace.CurrentSolution
@@ -371,14 +373,14 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
cache.[projectId] <- (project, parsingOptions, projectOptions)
- return Some(parsingOptions, projectOptions)
+ return ValueSome(parsingOptions, projectOptions)
| true, (oldProject, parsingOptions, projectOptions) ->
if isProjectInvalidated oldProject project ct then
cache.TryRemove(projectId) |> ignore
return! tryComputeOptions project ct
else
- return Some(parsingOptions, projectOptions)
+ return ValueSome(parsingOptions, projectOptions)
}
let loop (agent: MailboxProcessor) =
@@ -387,17 +389,20 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
match! agent.Receive() with
| FSharpProjectOptionsMessage.TryGetOptionsByDocument (document, reply, ct, userOpName) ->
if ct.IsCancellationRequested then
- reply.Reply None
+ reply.Reply ValueNone
else
try
// For now, disallow miscellaneous workspace since we are using the hacky F# miscellaneous files project.
if document.Project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles then
- reply.Reply None
+ reply.Reply ValueNone
elif document.Project.IsFSharpMiscellaneousOrMetadata then
- let! options = tryComputeOptionsBySingleScriptOrFile document ct userOpName
+ let! options =
+ tryComputeOptionsBySingleScriptOrFile document userOpName
+ |> CancellableTask.start ct
+ |> Async.AwaitTask
if ct.IsCancellationRequested then
- reply.Reply None
+ reply.Reply ValueNone
else
reply.Reply options
else
@@ -407,43 +412,43 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) =
document.Project.Solution.Workspace.CurrentSolution.GetProject(document.Project.Id)
if not (isNull project) then
- let! options = tryComputeOptions project ct
+ let! options = tryComputeOptions project |> CancellableTask.start ct |> Async.AwaitTask
if ct.IsCancellationRequested then
- reply.Reply None
+ reply.Reply ValueNone
else
reply.Reply options
else
- reply.Reply None
+ reply.Reply ValueNone
with _ ->
- reply.Reply None
+ reply.Reply ValueNone
| FSharpProjectOptionsMessage.TryGetOptionsByProject (project, reply, ct) ->
if ct.IsCancellationRequested then
- reply.Reply None
+ reply.Reply ValueNone
else
try
if
project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles
|| project.IsFSharpMiscellaneousOrMetadata
then
- reply.Reply None
+ reply.Reply ValueNone
else
// We only care about the latest project in the workspace's solution.
// We do this to prevent any possible cache thrashing in FCS.
let project = project.Solution.Workspace.CurrentSolution.GetProject(project.Id)
if not (isNull project) then
- let! options = tryComputeOptions project ct
+ let! options = tryComputeOptions project |> CancellableTask.start ct |> Async.AwaitTask
if ct.IsCancellationRequested then
- reply.Reply None
+ reply.Reply ValueNone
else
reply.Reply options
else
- reply.Reply None
+ reply.Reply ValueNone
with _ ->
- reply.Reply None
+ reply.Reply ValueNone
| FSharpProjectOptionsMessage.ClearOptions (projectId) ->
match cache.TryRemove(projectId) with
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs
index ad5156e3082..0512a48c1ad 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs
@@ -94,11 +94,13 @@ type internal FSharpWorkspaceServiceFactory []
let getSource filename =
async {
+ let! ct = Async.CancellationToken
+
match workspace.CurrentSolution.TryGetDocumentFromPath filename with
- | Some document ->
- let! text = document.GetTextAsync() |> Async.AwaitTask
+ | ValueSome document ->
+ let! text = document.GetTextAsync(ct) |> Async.AwaitTask
return Some(text.ToFSharpSourceText())
- | None -> return None
+ | ValueNone -> return None
}
lock gate (fun () ->
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs
index b567c74a199..9871d042f2f 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs
@@ -110,12 +110,12 @@ module internal SymbolHelpers =
let! otherFileCheckResults =
match currentDocument.Project.Solution.TryGetDocumentFromPath otherFile with
- | Some doc ->
+ | ValueSome doc ->
cancellableTask {
let! _, checkFileResults = doc.GetFSharpParseAndCheckResultsAsync("findReferencedSymbolsAsync")
return [ checkFileResults, doc ]
}
- | None -> CancellableTask.singleton []
+ | ValueNone -> CancellableTask.singleton []
let symbolUses =
(checkFileResults, currentDocument) :: otherFileCheckResults
diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
index b8bc19cc4a3..ddc2e593b76 100644
--- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
+++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs
@@ -2,10 +2,7 @@
module internal Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions
open System
-open System.Diagnostics
open System.Runtime.CompilerServices
-open System.Threading
-open System.Threading.Tasks
open Microsoft.CodeAnalysis
open Microsoft.VisualStudio.FSharp.Editor
@@ -15,9 +12,6 @@ open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks
-open CancellableTasks
-open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks
-
[]
module private CheckerExtensions =
@@ -25,9 +19,9 @@ module private CheckerExtensions =
/// Parse the source text from the Roslyn document.
member checker.ParseDocument(document: Document, parsingOptions: FSharpParsingOptions, userOpName: string) =
- async {
- let! ct = Async.CancellationToken
- let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask
+ cancellableTask {
+ let! ct = CancellableTask.getCancellationToken ()
+ let! sourceText = document.GetTextAsync(ct)
return! checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName = userOpName)
}
@@ -100,8 +94,7 @@ module private CheckerExtensions =
return results
else
- let! results = parseAndCheckFile
- return results
+ return! parseAndCheckFile
}
/// Parse and check the source text from the Roslyn document.
@@ -152,8 +145,8 @@ type Document with
let! ct = CancellableTask.getCancellationToken ()
match! projectOptionsManager.TryGetOptionsForDocumentOrProject(this, ct, userOpName) with
- | None -> return raise (OperationCanceledException("FSharp project options not found."))
- | Some (parsingOptions, projectOptions) ->
+ | ValueNone -> return raise (OperationCanceledException("FSharp project options not found."))
+ | ValueSome (parsingOptions, projectOptions) ->
let result =
(service.Checker, projectOptionsManager, parsingOptions, projectOptions)
@@ -161,13 +154,6 @@ type Document with
ProjectCache.Projects.GetValue(this.Project, ConditionalWeakTable<_, _>.CreateValueCallback (fun _ -> result))
}
- /// Get the compilation defines from F# project that is associated with the given F# document.
- member this.GetFSharpCompilationDefinesAsync(userOpName) =
- async {
- let! _, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName)
- return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions
- }
-
/// Get the compilation defines and language version from F# project that is associated with the given F# document.
member this.GetFsharpParsingOptionsAsync(userOpName) =
async {
@@ -209,7 +195,7 @@ type Document with
/// Parses the given F# document.
member this.GetFSharpParseResultsAsync(userOpName) =
- async {
+ cancellableTask {
let! checker, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName)
return! checker.ParseDocument(this, parsingOptions, userOpName)
}
@@ -258,10 +244,10 @@ 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 {
+ cancellableTask {
let! defines, langVersion, strictIndentation = this.GetFsharpParsingOptionsAsync(userOpName)
- let! ct = Async.CancellationToken
- let! sourceText = this.GetTextAsync(ct) |> Async.AwaitTask
+ let! ct = CancellableTask.getCancellationToken ()
+ let! sourceText = this.GetTextAsync(ct)
return
Tokenizer.getSymbolAtPosition (
@@ -344,8 +330,8 @@ type Project with
let projectOptionsManager = service.FSharpProjectOptionsManager
match! projectOptionsManager.TryGetOptionsByProject(this, ct) with
- | None -> return raise (OperationCanceledException("FSharp project options not found."))
- | Some (parsingOptions, projectOptions) ->
+ | ValueNone -> return raise (OperationCanceledException("FSharp project options not found."))
+ | ValueSome (parsingOptions, projectOptions) ->
let result =
(service.Checker, projectOptionsManager, parsingOptions, projectOptions)
diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs
index 1864e99c508..bc408c01741 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs
@@ -10,15 +10,15 @@ open FSharp.Compiler.Text.Range
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.GoToDefinition
-open System.Collections.Immutable
-open System.Threading.Tasks
+open CancellableTasks
[)>]
[)>]
type internal FSharpFindDefinitionService [] (metadataAsSource: FSharpMetadataAsSourceService) =
interface IFSharpFindDefinitionService with
member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) =
- let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
-
- let definitions = navigation.FindDefinitions(position, cancellationToken) // TODO: probably will need to be async all the way down
- ImmutableArray.CreateRange(definitions) |> Task.FromResult
+ cancellableTask {
+ let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
+ return! navigation.FindDefinitionsAsync(position)
+ }
+ |> CancellableTask.start cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs
index 88aad129dc5..429b21ce64a 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs
@@ -33,8 +33,8 @@ module FSharpFindUsagesService =
match declarationRange, RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with
| Some declRange, _ when Range.equals declRange symbolUse -> ()
- | _, None -> ()
- | _, Some textSpan ->
+ | _, ValueNone -> ()
+ | _, ValueSome textSpan ->
if allReferences then
let definitionItem =
if isExternal then
@@ -71,10 +71,10 @@ module FSharpFindUsagesService =
let! sourceText = doc.GetTextAsync(cancellationToken)
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with
- | Some span ->
+ | ValueSome span ->
let span = Tokenizer.fixupSpan (sourceText, span)
return Some(FSharpDocumentSpan(doc, span))
- | None -> return None
+ | ValueNone -> return None
}
}
|> CancellableTask.whenAll
@@ -122,14 +122,14 @@ module FSharpFindUsagesService =
let! declarationSpans =
match declarationRange with
- | Some range -> cancellableTask { return! rangeToDocumentSpans (document.Project.Solution, range) }
+ | Some range -> rangeToDocumentSpans (document.Project.Solution, range)
| None -> CancellableTask.singleton [||]
let declarationSpans =
declarationSpans
|> Array.distinctBy (fun x -> x.Document.FilePath, x.Document.Project.FilePath)
- let isExternal = declarationSpans |> Array.isEmpty
+ let isExternal = Array.isEmpty declarationSpans
let displayParts =
ImmutableArray.Create(Microsoft.CodeAnalysis.TaggedText(TextTags.Text, symbol.Ident.idText))
diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs
index 1199b14477d..3ad1a4c9c48 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs
@@ -17,7 +17,6 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation
open Microsoft.VisualStudio
open Microsoft.VisualStudio.Shell
-open Microsoft.VisualStudio.Shell.Interop
open Microsoft.VisualStudio.LanguageServices
open FSharp.Compiler.CodeAnalysis
@@ -30,6 +29,7 @@ open System.Text.RegularExpressions
open CancellableTasks
open Microsoft.VisualStudio.FSharp.Editor.Telemetry
open Microsoft.VisualStudio.Telemetry
+open System.Collections.Generic
module private Symbol =
let fullName (root: ISymbol) : string =
@@ -110,45 +110,6 @@ module private ExternalSymbol =
| _ -> []
-// TODO: Uncomment code when VS has a fix for updating the status bar.
-type StatusBar() =
- let statusBar =
- ServiceProvider.GlobalProvider.GetService()
-
- let mutable _searchIcon =
- int16 Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Find :> obj
-
- let _clear () =
- // unfreeze the statusbar
- statusBar.FreezeOutput 0 |> ignore
- statusBar.Clear() |> ignore
-
- member _.Message(_msg: string) = ()
- //let _, frozen = statusBar.IsFrozen()
- //// unfreeze the status bar
- //if frozen <> 0 then statusBar.FreezeOutput 0 |> ignore
- //statusBar.SetText msg |> ignore
- //// freeze the status bar
- //statusBar.FreezeOutput 1 |> ignore
-
- member this.TempMessage(_msg: string) = ()
- //this.Message msg
- //async {
- // do! Async.Sleep 4000
- // match statusBar.GetText() with
- // | 0, currentText when currentText <> msg -> ()
- // | _ -> clear()
- //}|> Async.Start
-
- member _.Clear() = () //clear()
-
- /// Animated magnifying glass that displays on the status bar while a symbol search is in progress.
- member _.Animate() : IDisposable =
- //statusBar.Animation (1, &searchIcon) |> ignore
- { new IDisposable with
- member _.Dispose() = ()
- } //statusBar.Animation(0, &searchIcon) |> ignore }
-
type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) =
inherit FSharpNavigableItem(Glyph.BasicFile, ImmutableArray.Empty, document, sourceSpan)
@@ -158,10 +119,65 @@ type internal FSharpGoToDefinitionResult =
| ExternalAssembly of FSharpSymbolUse * MetadataReference seq
type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
+
+ let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) =
+ let ty1 = ty1.StripAbbreviations()
+ let ty2 = ty2.StripAbbreviations()
+
+ let generic =
+ ty1.IsGenericParameter && ty2.IsGenericParameter
+ || (ty1.GenericArguments.Count = ty2.GenericArguments.Count
+ && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual)
+
+ if generic then
+ true
+ else
+ let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName
+ let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath
+ namesEqual && accessPathsEqual
+
+ let tryFindExternalSymbolUse (targetSymbolUse: FSharpSymbolUse) (x: FSharpSymbolUse) =
+ match x.Symbol, targetSymbolUse.Symbol with
+ | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> symbol1.DisplayName = symbol2.DisplayName
+
+ | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) ->
+ symbol1.DisplayName = symbol2.DisplayName
+ && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with
+ | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName
+ | _ -> false)
+ && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count
+ && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count
+ && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups)
+ ||> Seq.forall2 (fun pg1 pg2 ->
+ let pg1, pg2 = pg1.ToArray(), pg2.ToArray()
+ // We filter out/fixup first "unit" parameter in the group, since it just represents the `()` call notation, for example `"string".Clone()` will have one curried group with one parameter which type is unit.
+ let pg1 = // If parameter has no name and it's unit type, filter it out
+ if
+ pg1.Length > 0
+ && Option.isNone pg1[0].Name
+ && pg1[0].Type.StripAbbreviations().TypeDefinition.DisplayName = "Unit"
+ then
+ pg1[1..]
+ else
+ pg1
+
+ pg1.Length = pg2.Length
+ && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type))))
+ && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type
+ | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition ->
+ symbol1.DisplayName = symbol2.DisplayName
+ && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with
+ | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName
+ | _ -> false)
+ | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) ->
+ symbol1.DisplayName = symbol2.DisplayName
+ && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName
+ | _ -> false
+
/// Use an origin document to provide the solution & workspace used to
/// find the corresponding textSpan and INavigableItem for the range
let rangeToNavigableItem (range: range, document: Document) =
- async {
+ cancellableTask {
let fileName =
try
System.IO.Path.GetFullPath range.FileName
@@ -177,62 +193,86 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask
match RoslynHelpers.TryFSharpRangeToTextSpan(refSourceText, range) with
- | None -> return None
- | Some refTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(refDocument, refTextSpan))
+ | ValueNone -> return None
+ | ValueSome refTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(refDocument, refTextSpan))
else
return None
}
/// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files.
member private _.FindSymbolHelper(originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) =
- asyncMaybe {
- let userOpName = "FindSymbolHelper"
- let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, originRange)
- let position = originTextSpan.Start
- let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName)
- let textLinePos = sourceText.Lines.GetLinePosition position
- let fcsTextLineNumber = Line.fromZ textLinePos.Line
- let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
- let idRange = lexerSymbol.Ident.idRange
+ let originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, originRange)
- let! ct = Async.CancellationToken |> liftAsync
+ match originTextSpan with
+ | ValueNone -> CancellableTask.singleton None
+ | ValueSome originTextSpan ->
+ cancellableTask {
+ let userOpName = "FindSymbolHelper"
- let! _, checkFileResults =
- originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition))
- |> CancellableTask.start ct
- |> Async.AwaitTask
- |> liftAsync
+ let position = originTextSpan.Start
+ let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName)
- let! fsSymbolUse =
- checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
+ match lexerSymbol with
+ | None -> return None
+ | Some lexerSymbol ->
+ let textLinePos = sourceText.Lines.GetLinePosition position
+ let fcsTextLineNumber = Line.fromZ textLinePos.Line
+ let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
+ let idRange = lexerSymbol.Ident.idRange
- let symbol = fsSymbolUse.Symbol
- // if the tooltip was spawned in an implementation file and we have a range targeting
- // a signature file, try to find the corresponding implementation file and target the
- // desired symbol
- if isSignatureFile fsSymbolUse.FileName && preferSignature = false then
- let fsfilePath = Path.ChangeExtension(originRange.FileName, "fs")
+ let! ct = CancellableTask.getCancellationToken ()
- if not (File.Exists fsfilePath) then
- return! None
- else
- let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath
- let! implSourceText = implDoc.GetTextAsync()
-
- let! _, checkFileResults =
- implDoc.GetFSharpParseAndCheckResultsAsync(userOpName)
- |> CancellableTask.start ct
- |> Async.AwaitTask
- |> liftAsync
-
- let symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol
- let! implSymbol = symbolUses |> Array.tryHead
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range)
- return FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan)
- else
- let! targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range
- return! rangeToNavigableItem (fsSymbolUse.Range, targetDocument)
- }
+ let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition))
+
+ let fsSymbolUse =
+ checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
+
+ match fsSymbolUse with
+ | None -> return None
+ | Some fsSymbolUse ->
+
+ let symbol = fsSymbolUse.Symbol
+ // if the tooltip was spawned in an implementation file and we have a range targeting
+ // a signature file, try to find the corresponding implementation file and target the
+ // desired symbol
+ if isSignatureFile fsSymbolUse.FileName && preferSignature = false then
+ let fsfilePath = Path.ChangeExtension(originRange.FileName, "fs")
+
+ if not (File.Exists fsfilePath) then
+ return None
+ else
+ let implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath
+
+ match implDoc with
+ | ValueNone -> return None
+ | ValueSome implDoc ->
+ let! implSourceText = implDoc.GetTextAsync(ct)
+
+ let! _, checkFileResults = implDoc.GetFSharpParseAndCheckResultsAsync(userOpName)
+
+ let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbol, ct)
+
+ let implSymbol = Array.tryHeadV symbolUses
+
+ match implSymbol with
+ | ValueNone -> return None
+ | ValueSome implSymbol ->
+ let implTextSpan =
+ RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range)
+
+ match implTextSpan with
+ | ValueNone -> return None
+ | ValueSome implTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan))
+ else
+ let targetDocument =
+ originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range
+
+ match targetDocument with
+ | None -> return None
+ | Some targetDocument ->
+ let! navItem = rangeToNavigableItem (fsSymbolUse.Range, targetDocument)
+ return navItem
+ }
/// if the symbol is defined in the given file, return its declaration location, otherwise use the targetSymbol to find the first
/// instance of its presence in the provided source file. The first case is needed to return proper declaration location for
@@ -261,9 +301,10 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
return implSymbol.Range
}
- member private this.FindDefinitionAtPosition(originDocument: Document, position: int, cancellationToken: CancellationToken) =
- asyncMaybe {
+ member internal this.FindDefinitionAtPosition(originDocument: Document, position: int) =
+ cancellableTask {
let userOpName = "FindDefinitionAtPosition"
+ let! cancellationToken = CancellableTask.getCancellationToken ()
let! sourceText = originDocument.GetTextAsync(cancellationToken)
let textLine = sourceText.Lines.GetLineFromPosition position
let textLinePos = sourceText.Lines.GetLinePosition position
@@ -271,131 +312,196 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
- let! ct = Async.CancellationToken |> liftAsync
+ let! cancellationToken = CancellableTask.getCancellationToken ()
let preferSignature = isSignatureFile originDocument.FilePath
let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName)
- let idRange = lexerSymbol.Ident.idRange
-
- let! _, checkFileResults =
- originDocument.GetFSharpParseAndCheckResultsAsync(userOpName)
- |> CancellableTask.start ct
- |> Async.AwaitTask
- |> liftAsync
-
- let declarations =
- checkFileResults.GetDeclarationLocation(
- fcsTextLineNumber,
- lexerSymbol.Ident.idRange.EndColumn,
- textLineString,
- lexerSymbol.FullIsland,
- preferSignature
- )
- let! targetSymbolUse =
- checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
-
- match declarations with
- | FindDeclResult.ExternalDecl (assembly, targetExternalSym) ->
- let projectOpt =
- originDocument.Project.Solution.Projects
- |> Seq.tryFindV (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase))
-
- match projectOpt with
- | ValueSome project ->
- let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true))
-
- let roslynSymbols =
- symbols |> Seq.collect ExternalSymbol.ofRoslynSymbol |> Array.ofSeq
-
- let! symbol =
- roslynSymbols
- |> Seq.tryPick (fun (sym, externalSym) -> if externalSym = targetExternalSym then Some sym else None)
-
- let! location = symbol.Locations |> Seq.tryHead
-
- return
- (FSharpGoToDefinitionResult.NavigableItem(
- FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)
- ),
- idRange)
- | _ ->
- let metadataReferences = originDocument.Project.MetadataReferences
- return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange)
-
- | FindDeclResult.DeclFound targetRange ->
- // If the file is not associated with a document, it's considered external.
- if not (originDocument.Project.Solution.ContainsDocumentWithFilePath(targetRange.FileName)) then
- let metadataReferences = originDocument.Project.MetadataReferences
- return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange)
- else
- // if goto definition is called at we are alread at the declaration location of a symbol in
- // either a signature or an implementation file then we jump to it's respective postion in thethe
- if
- lexerSymbol.Range = targetRange
- then
- // jump from signature to the corresponding implementation
- if isSignatureFile originDocument.FilePath then
- let implFilePath = Path.ChangeExtension(originDocument.FilePath, "fs")
-
- if not (File.Exists implFilePath) then
- return! None
- else
- let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
-
- let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument)
- let! implSourceText = implDocument.GetTextAsync(cancellationToken) |> liftTaskAsync
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange)
- let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan)
- return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
- else // jump from implementation to the corresponding signature
- let declarations =
- checkFileResults.GetDeclarationLocation(
- fcsTextLineNumber,
- lexerSymbol.Ident.idRange.EndColumn,
- textLineString,
- lexerSymbol.FullIsland,
- true
- )
-
- match declarations with
- | FindDeclResult.DeclFound targetRange ->
- let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
- let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) |> liftTaskAsync
- let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange)
- let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan)
- return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
- | _ -> return! None
- // when the target range is different follow the navigation convention of
- // - gotoDefn origin = signature , gotoDefn destination = signature
- // - gotoDefn origin = implementation, gotoDefn destination = implementation
- else
- let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
- let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) |> liftTaskAsync
- let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange)
- // if the gotodef call originated from a signature and the returned target is a signature, navigate there
- if isSignatureFile targetRange.FileName && preferSignature then
- let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan)
- return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
- else // we need to get an FSharpSymbol from the targetRange found in the signature
- // that symbol will be used to find the destination in the corresponding implementation file
- let implFilePath =
- // Bugfix: apparently sigDocument not always is a signature file
- if isSignatureFile sigDocument.FilePath then
- Path.ChangeExtension(sigDocument.FilePath, "fs")
- else
- sigDocument.FilePath
+ match lexerSymbol with
+ | None -> return ValueNone
+ | Some lexerSymbol ->
+
+ let idRange = lexerSymbol.Ident.idRange
- let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
+ let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(userOpName)
- let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument)
+ let declarations =
+ checkFileResults.GetDeclarationLocation(
+ fcsTextLineNumber,
+ idRange.EndColumn,
+ textLineString,
+ lexerSymbol.FullIsland,
+ preferSignature
+ )
- let! implSourceText = implDocument.GetTextAsync() |> liftTaskAsync
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange)
- let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan)
- return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
- | _ -> return! None
+ let targetSymbolUse =
+ checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
+
+ match targetSymbolUse with
+ | None -> return ValueNone
+ | Some targetSymbolUse ->
+
+ match declarations with
+ | FindDeclResult.ExternalDecl (assembly, targetExternalSym) ->
+ let projectOpt =
+ originDocument.Project.Solution.Projects
+ |> Seq.tryFindV (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase))
+
+ match projectOpt with
+ | ValueSome project ->
+ let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true), cancellationToken)
+
+ let roslynSymbols = Seq.collect ExternalSymbol.ofRoslynSymbol symbols
+
+ let symbol =
+ Seq.tryPickV
+ (fun (sym, externalSym) ->
+ if externalSym = targetExternalSym then
+ ValueSome sym
+ else
+ ValueNone)
+ roslynSymbols
+
+ let location =
+ symbol
+ |> ValueOption.map (fun s -> s.Locations)
+ |> ValueOption.bind Seq.tryHeadV
+
+ match location with
+ | ValueNone -> return ValueNone
+ | ValueSome location ->
+
+ return
+ ValueSome(
+ FSharpGoToDefinitionResult.NavigableItem(
+ FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)
+ ),
+ idRange
+ )
+ | _ ->
+ let metadataReferences = originDocument.Project.MetadataReferences
+ return ValueSome(FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange)
+
+ | FindDeclResult.DeclFound targetRange ->
+ // If the file is not associated with a document, it's considered external.
+ if not (originDocument.Project.Solution.ContainsDocumentWithFilePath(targetRange.FileName)) then
+ let metadataReferences = originDocument.Project.MetadataReferences
+ return ValueSome(FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange)
+ else
+ // if goto definition is called at we are alread at the declaration location of a symbol in
+ // either a signature or an implementation file then we jump to it's respective postion in thethe
+ if
+ lexerSymbol.Range = targetRange
+ then
+ // jump from signature to the corresponding implementation
+ if isSignatureFile originDocument.FilePath then
+ let implFilePath = Path.ChangeExtension(originDocument.FilePath, "fs")
+
+ if not (File.Exists implFilePath) then
+ return ValueNone
+ else
+ let implDocument =
+ originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
+
+ match implDocument with
+ | ValueNone -> return ValueNone
+ | ValueSome implDocument ->
+ let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument)
+
+ match targetRange with
+ | None -> return ValueNone
+ | Some targetRange ->
+ let! implSourceText = implDocument.GetTextAsync(cancellationToken)
+
+ let implTextSpan =
+ RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange)
+
+ match implTextSpan with
+ | ValueNone -> return ValueNone
+ | ValueSome implTextSpan ->
+ let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan)
+ return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
+
+ else // jump from implementation to the corresponding signature
+ let declarations =
+ checkFileResults.GetDeclarationLocation(
+ fcsTextLineNumber,
+ idRange.EndColumn,
+ textLineString,
+ lexerSymbol.FullIsland,
+ true
+ )
+
+ match declarations with
+ | FindDeclResult.DeclFound targetRange ->
+ let sigDocument =
+ originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
+
+ match sigDocument with
+ | ValueNone -> return ValueNone
+ | ValueSome sigDocument ->
+ let! sigSourceText = sigDocument.GetTextAsync(cancellationToken)
+
+ let sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange)
+
+ match sigTextSpan with
+ | ValueNone -> return ValueNone
+ | ValueSome sigTextSpan ->
+ let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan)
+ return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
+ | _ -> return ValueNone
+ // when the target range is different follow the navigation convention of
+ // - gotoDefn origin = signature , gotoDefn destination = signature
+ // - gotoDefn origin = implementation, gotoDefn destination = implementation
+ else
+ let sigDocument =
+ originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
+
+ match sigDocument with
+ | ValueNone -> return ValueNone
+ | ValueSome sigDocument ->
+ let! sigSourceText = sigDocument.GetTextAsync(cancellationToken)
+ let sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange)
+
+ match sigTextSpan with
+ | ValueNone -> return ValueNone
+ | ValueSome sigTextSpan ->
+ // if the gotodef call originated from a signature and the returned target is a signature, navigate there
+ if isSignatureFile targetRange.FileName && preferSignature then
+ let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan)
+ return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
+ else // we need to get an FSharpSymbol from the targetRange found in the signature
+ // that symbol will be used to find the destination in the corresponding implementation file
+ let implFilePath =
+ // Bugfix: apparently sigDocument not always is a signature file
+ if isSignatureFile sigDocument.FilePath then
+ Path.ChangeExtension(sigDocument.FilePath, "fs")
+ else
+ sigDocument.FilePath
+
+ let implDocument =
+ originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
+
+ match implDocument with
+ | ValueNone -> return ValueNone
+ | ValueSome implDocument ->
+ let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument)
+
+ match targetRange with
+ | None -> return ValueNone
+ | Some targetRange ->
+ let! implSourceText = implDocument.GetTextAsync(cancellationToken)
+
+ let implTextSpan =
+ RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange)
+
+ match implTextSpan with
+ | ValueNone -> return ValueNone
+ | ValueSome implTextSpan ->
+ let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan)
+ return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange)
+ | _ -> return ValueNone
}
/// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
@@ -406,42 +512,24 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
member this.FindDefinitionOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSourceText: SourceText) =
this.FindSymbolHelper(targetDocument, symbolRange, targetSourceText, preferSignature = false)
- member this.FindDefinitionsForPeekTask(originDocument: Document, position: int, cancellationToken: CancellationToken) =
- this.FindDefinitionAtPosition(originDocument, position, cancellationToken)
- |> Async.map (Option.toArray >> Array.toSeq)
- |> RoslynHelpers.StartAsyncAsTask cancellationToken
-
/// Construct a task that will return a navigation target for the implementation definition of the symbol
/// at the provided position in the document.
- member this.FindDefinitionTask(originDocument: Document, position: int, cancellationToken: CancellationToken) =
- this.FindDefinitionAtPosition(originDocument, position, cancellationToken)
- |> RoslynHelpers.StartAsyncAsTask cancellationToken
+ member this.FindDefinitionAsync(originDocument: Document, position: int) =
+ this.FindDefinitionAtPosition(originDocument, position)
/// Navigate to the positon of the textSpan in the provided document
/// used by quickinfo link navigation when the tooltip contains the correct destination range.
- member _.TryNavigateToTextSpan
- (
- document: Document,
- textSpan: Microsoft.CodeAnalysis.Text.TextSpan,
- cancellationToken: CancellationToken
- ) =
+ member _.TryNavigateToTextSpan(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken) =
let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan)
let workspace = document.Project.Solution.Workspace
let navigationService =
workspace.Services.GetService()
- let navigationSucceeded =
- navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken)
-
- if not navigationSucceeded then
- StatusBar().TempMessage(SR.CannotNavigateUnknown())
+ navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken)
+ |> ignore
member _.NavigateToItem(navigableItem: FSharpNavigableItem, cancellationToken: CancellationToken) =
- let statusBar = StatusBar()
- use __ = statusBar.Animate()
-
- statusBar.Message(SR.NavigatingTo())
let workspace = navigableItem.Document.Project.Solution.Workspace
@@ -452,46 +540,38 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
let result =
navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken)
- if result then
- statusBar.Clear()
- else
- statusBar.TempMessage(SR.CannotNavigateUnknown())
+ result
/// Find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
- member this.NavigateToSymbolDeclarationAsync
- (
- targetDocument: Document,
- targetSourceText: SourceText,
- symbolRange: range,
- cancellationToken: CancellationToken
- ) =
- asyncMaybe {
+ member this.NavigateToSymbolDeclarationAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range) =
+ cancellableTask {
let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
- return this.NavigateToItem(item, cancellationToken)
+
+ match item with
+ | None -> return false
+ | Some item ->
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+ return this.NavigateToItem(item, cancellationToken)
}
/// Find the definition location (implementation file/.fs) of the target symbol
- member this.NavigateToSymbolDefinitionAsync
- (
- targetDocument: Document,
- targetSourceText: SourceText,
- symbolRange: range,
- cancellationToken: CancellationToken
- ) =
- asyncMaybe {
+ member this.NavigateToSymbolDefinitionAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range) =
+ cancellableTask {
let! item = this.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
- return this.NavigateToItem(item, cancellationToken)
+
+ match item with
+ | None -> return false
+ | Some item ->
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+ return this.NavigateToItem(item, cancellationToken)
}
member this.NavigateToExternalDeclaration
(
targetSymbolUse: FSharpSymbolUse,
metadataReferences: seq,
- cancellationToken: CancellationToken,
- statusBar: StatusBar
+ cancellationToken: CancellationToken
) =
- use __ = statusBar.Animate()
- statusBar.Message(SR.NavigatingTo())
let textOpt =
match targetSymbolUse.Symbol with
@@ -513,104 +593,55 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
|> Option.map (fun text -> text, symbol.DisplayName)
| _ -> None
- let result =
- match textOpt with
- | Some (text, fileName) ->
- let tmpProjInfo, tmpDocInfo =
- MetadataAsSource.generateTemporaryDocument (
- AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName),
- fileName,
- metadataReferences
- )
+ match textOpt with
+ | Some (text, fileName) ->
+ let tmpProjInfo, tmpDocInfo =
+ MetadataAsSource.generateTemporaryDocument (
+ AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName),
+ fileName,
+ metadataReferences
+ )
- let tmpShownDocOpt =
- metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString()))
+ let tmpShownDocOpt =
+ metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString()))
- match tmpShownDocOpt with
- | ValueSome tmpShownDoc ->
- let goToAsync =
- asyncMaybe {
+ match tmpShownDocOpt with
+ | ValueSome tmpShownDoc ->
+ let goToAsync =
+ cancellableTask {
- let! ct = Async.CancellationToken |> liftAsync
+ let! cancellationToken = CancellableTask.getCancellationToken ()
- let! _, checkResults =
- tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration")
- |> CancellableTask.start ct
- |> Async.AwaitTask
- |> liftAsync
+ let! _, checkResults = tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration")
- let! r =
- let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) =
- let ty1 = ty1.StripAbbreviations()
- let ty2 = ty2.StripAbbreviations()
+ let r =
+ // This tries to find the best possible location of the target symbol's location in the metadata source.
+ // We really should rely on symbol equality within FCS instead of doing it here,
+ // but the generated metadata as source isn't perfect for symbol equality.
+ let symbols = checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken)
- let generic =
- ty1.IsGenericParameter && ty2.IsGenericParameter
- || (ty1.GenericArguments.Count = ty2.GenericArguments.Count
- && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual)
+ symbols
+ |> Seq.tryFindV (tryFindExternalSymbolUse targetSymbolUse)
+ |> ValueOption.map (fun x -> x.Range)
+ |> ValueOption.toOption
- if generic then
- true
- else
- let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName
- let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath
- namesEqual && accessPathsEqual
-
- // This tries to find the best possible location of the target symbol's location in the metadata source.
- // We really should rely on symbol equality within FCS instead of doing it here,
- // but the generated metadata as source isn't perfect for symbol equality.
- checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken)
- |> Seq.tryFindV (fun x ->
- match x.Symbol, targetSymbolUse.Symbol with
- | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition ->
- symbol1.DisplayName = symbol2.DisplayName
- | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) ->
- symbol1.DisplayName = symbol2.DisplayName
- && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with
- | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName
- | _ -> false)
- && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count
- && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count
- && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups)
- ||> Seq.forall2 (fun pg1 pg2 ->
- pg1.Count = pg2.Count
- && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type))))
- && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type
- | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition ->
- symbol1.DisplayName = symbol2.DisplayName
- && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with
- | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName
- | _ -> false)
- | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) ->
- symbol1.DisplayName = symbol2.DisplayName
- && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName
- | _ -> false)
- |> ValueOption.map (fun x -> x.Range)
- |> ValueOption.toOption
-
- let span =
- match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with
- | Some span -> span
- | _ -> TextSpan()
-
- return span
- }
-
- let span =
- match Async.RunImmediateExceptOnUI(goToAsync, cancellationToken = cancellationToken) with
- | Some span -> span
- | _ -> TextSpan()
-
- let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span)
- this.NavigateToItem(navItem, cancellationToken)
- true
- | _ -> false
- | _ -> false
+ match r with
+ | None -> return TextSpan.empty
+ | Some r ->
+ let! text = tmpShownDoc.GetTextAsync(cancellationToken)
- if result then
- statusBar.Clear()
- else
- statusBar.TempMessage(SR.CannotNavigateUnknown())
+ match RoslynHelpers.TryFSharpRangeToTextSpan(text, r) with
+ | ValueSome span -> return span
+ | _ -> return TextSpan.empty
+
+ }
+
+ let span = CancellableTask.runSynchronously cancellationToken goToAsync
+
+ let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span)
+ this.NavigateToItem(navItem, cancellationToken)
+ | _ -> false
+ | _ -> false
type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, initialDoc: Document, thisSymbolUseRange: range) =
@@ -628,33 +659,40 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService,
ThreadHelper.JoinableTaskFactory.Run(
SR.NavigatingTo(),
(fun _progress cancellationToken ->
- Async.StartImmediateAsTask(
- asyncMaybe {
- let! targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id)
+ cancellableTask {
+ let targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id)
+
+ match targetDoc with
+ | None -> ()
+ | Some targetDoc ->
+
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+
let! targetSource = targetDoc.GetTextAsync(cancellationToken)
- let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range)
- let gtd = GoToDefinition(metadataAsSource)
-
- // Whenever possible:
- // - signature files (.fsi) should navigate to other signature files
- // - implementation files (.fs) should navigate to other implementation files
- if isSignatureFile initialDoc.FilePath then
- // Target range will point to .fsi file if only there is one so we can just use Roslyn navigation service.
- return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
- else
- // Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range.
- // This is the part that may take some time, because of type checks involved.
- let! result =
- gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, cancellationToken)
- |> liftAsync
-
- if result.IsNone then
- // In case the above fails, we just navigate to target range.
- return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
- }
- |> Async.Ignore,
- cancellationToken
- )),
+ let targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range)
+
+ match targetTextSpan with
+ | ValueNone -> ()
+ | ValueSome targetTextSpan ->
+
+ let gtd = GoToDefinition(metadataAsSource)
+
+ // Whenever possible:
+ // - signature files (.fsi) should navigate to other signature files
+ // - implementation files (.fs) should navigate to other implementation files
+ if isSignatureFile initialDoc.FilePath then
+ // Target range will point to .fsi file if only there is one so we can just use Roslyn navigation service.
+ do gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
+ else
+ // Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range.
+ // This is the part that may take some time, because of type checks involved.
+ let! result = gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range)
+
+ if not result then
+ // In case the above fails, we just navigate to target range.
+ do gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
+ }
+ |> CancellableTask.start cancellationToken),
// Default wait time before VS shows the dialog allowing to cancel the long running task is 2 seconds.
// This seems a bit too long to leave the user without any feedback, so we shorten it to 1 second.
// Note: it seems anything less than 1 second will get rounded down to zero, resulting in flashing dialog
@@ -664,52 +702,46 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService,
with :? OperationCanceledException ->
()
- member _.FindDefinitions(position, cancellationToken) =
- let gtd = GoToDefinition(metadataAsSource)
- let task = gtd.FindDefinitionsForPeekTask(initialDoc, position, cancellationToken)
- task.Wait(cancellationToken)
- let results = task.Result
+ member _.FindDefinitionsAsync(position) =
+ cancellableTask {
+ let gtd = GoToDefinition(metadataAsSource)
+ let! result = gtd.FindDefinitionAtPosition(initialDoc, position)
- results
- |> Seq.choose (fun (result, _) ->
- match result with
- | FSharpGoToDefinitionResult.NavigableItem (navItem) -> Some navItem
- | _ -> None)
+ return
+ match result with
+ | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> ImmutableArray.create navItem
+ | _ -> ImmutableArray.empty
+ }
member _.TryGoToDefinition(position, cancellationToken) =
- let gtd = GoToDefinition(metadataAsSource)
- let statusBar = StatusBar()
- let gtdTask = gtd.FindDefinitionTask(initialDoc, position, cancellationToken)
-
+ // Once we migrate to Roslyn-exposed MAAS and sourcelink (https://github.com/dotnet/fsharp/issues/13951), this can be a "normal" task
// Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled.
// Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
try
- // This call to Wait() is fine because we want to be able to provide the error message in the status bar.
use _ =
TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||])
- gtdTask.Wait(cancellationToken)
+ let gtd = GoToDefinition(metadataAsSource)
+ let gtdTask = gtd.FindDefinitionAsync (initialDoc, position) cancellationToken
+
+ gtdTask.Wait()
if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then
- match gtdTask.Result.Value with
- | FSharpGoToDefinitionResult.NavigableItem (navItem), _ ->
- gtd.NavigateToItem(navItem, cancellationToken)
- // 'true' means do it, like Sheev Palpatine would want us to.
+ match gtdTask.Result with
+ | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) ->
+ gtd.NavigateToItem(navItem, cancellationToken) |> ignore
true
- | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _ ->
- gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken, statusBar)
- // 'true' means do it, like Sheev Palpatine would want us to.
+ | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) ->
+ gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken)
+ |> ignore
+
true
+ | _ -> false
else
- statusBar.TempMessage(SR.CannotDetermineSymbol())
false
with exc ->
TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinition, FaultSeverity.General, exc)
- statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc))
-
- // Don't show the dialog box as it's most likely that the user cancelled.
- // Don't make them click twice.
- true
+ false
[]
type internal SymbolMemberType =
@@ -744,24 +776,31 @@ type internal DocCommentId =
type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, symbolRange: range, project: Project) =
interface IFSharpNavigableLocation with
member _.NavigateToAsync(_options: FSharpNavigationOptions2, cancellationToken: CancellationToken) : Task =
- asyncMaybe {
+ cancellableTask {
let targetPath = symbolRange.FileName
- let! targetDoc = project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id)
- let! targetSource = targetDoc.GetTextAsync(cancellationToken)
- let gtd = GoToDefinition(metadataAsSource)
-
- let (|Signature|Implementation|) filepath =
- if isSignatureFile filepath then
- Signature
- else
- Implementation
-
- match targetPath with
- | Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange, cancellationToken)
- | Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange, cancellationToken)
+
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+
+ let targetDoc =
+ project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id)
+
+ match targetDoc with
+ | None -> return false
+ | Some targetDoc ->
+ let! targetSource = targetDoc.GetTextAsync(cancellationToken)
+ let gtd = GoToDefinition(metadataAsSource)
+
+ let (|Signature|Implementation|) filepath =
+ if isSignatureFile filepath then
+ Signature
+ else
+ Implementation
+
+ match targetPath with
+ | Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange)
+ | Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange)
}
- |> Async.map Option.isSome
- |> RoslynHelpers.StartAsyncAsTask cancellationToken
+ |> CancellableTask.start cancellationToken
[)>]
[)>]
diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
index a3a800c9ef6..3130163b192 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
@@ -4,12 +4,13 @@ namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition
open System.Threading
-open System.Threading.Tasks
open FSharp.Compiler.Text.Range
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor
+open CancellableTasks
+open System.Collections.Generic
[)>]
[)>]
@@ -18,17 +19,15 @@ type internal FSharpGoToDefinitionService [] (metadataAsSo
interface IFSharpGoToDefinitionService with
/// Invoked with Peek Definition.
member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) =
- let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
-
- navigation.FindDefinitions(position, cancellationToken) |> Task.FromResult
+ cancellableTask {
+ let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
+ let! res = navigation.FindDefinitionsAsync(position)
+ return (res :> IEnumerable<_>)
+ }
+ |> CancellableTask.start cancellationToken
/// Invoked with Go to Definition.
/// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument
member _.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) =
- let statusBar = StatusBar()
- statusBar.Message(SR.LocatingSymbol())
- use __ = statusBar.Animate()
-
let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
-
navigation.TryGoToDefinition(position, cancellationToken)
diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs
index ab7a4b6a29a..6d918913fc5 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs
@@ -16,12 +16,14 @@ open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Utilities
open Microsoft.VisualStudio.FSharp.Editor.Telemetry
open Microsoft.VisualStudio.Telemetry
+open CancellableTasks.CancellableTaskBuilder
+open CancellableTasks
[]
type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition) =
interface INavigableSymbol with
member _.Navigate(_: INavigableRelationship) =
- gtd.NavigateToItem(item, CancellationToken.None)
+ gtd.NavigateToItem(item, CancellationToken.None) |> ignore
member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition }
@@ -31,7 +33,6 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
let mutable disposed = false
let gtd = GoToDefinition(metadataAsSource)
- let statusBar = StatusBar()
interface INavigableSymbolSource with
member _.GetNavigableSymbolAsync(triggerSpan: SnapshotSpan, cancellationToken: CancellationToken) =
@@ -39,29 +40,22 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
if disposed then
null
else
- asyncMaybe {
+ cancellableTask {
+ use _ =
+ TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinitionGetSymbol, [||])
+
let snapshot = triggerSpan.Snapshot
let position = triggerSpan.Start.Position
let document = snapshot.GetOpenDocumentInCurrentContextWithChanges()
- let! sourceText = document.GetTextAsync(cancellationToken) |> liftTaskAsync
-
- statusBar.Message(SR.LocatingSymbol())
- use _ = statusBar.Animate()
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+ let! sourceText = document.GetTextAsync(cancellationToken)
- let gtdTask = gtd.FindDefinitionTask(document, position, cancellationToken)
-
- // Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled.
- // Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
try
- // This call to Wait() is fine because we want to be able to provide the error message in the status bar.
- use _ =
- TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinitionGetSymbol, [||])
-
- gtdTask.Wait(cancellationToken)
- statusBar.Clear()
+ let! definition = gtd.FindDefinitionAsync(document, position)
- if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then
- let result, range = gtdTask.Result.Value
+ match definition with
+ | ValueNone -> return null
+ | ValueSome (result, range) ->
let declarationTextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)
let declarationSpan = Span(declarationTextSpan.Start, declarationTextSpan.Length)
@@ -78,8 +72,12 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
// Need to new up a CTS here instead of re-using the other one, since VS
// will navigate disconnected from the outer routine, leading to an
// OperationCancelledException if you use the one defined outside.
+ // TODO: review if it's a proper way of doing it
use ct = new CancellationTokenSource()
- gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token, statusBar)
+
+ do
+ gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token)
+ |> ignore
member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition }
@@ -87,21 +85,13 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
}
return nav
- else
- statusBar.TempMessage(SR.CannotDetermineSymbol())
- // The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
- return null
with exc ->
TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinitionGetSymbol, FaultSeverity.General, exc)
-
- statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc))
-
// The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
return null
}
- |> Async.map Option.toObj
- |> RoslynHelpers.StartAsyncAsTask cancellationToken
+ |> CancellableTask.start cancellationToken
member _.Dispose() = disposed <- true
diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs
index 1107e6f5869..8cce297ac0d 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs
@@ -44,7 +44,7 @@ type internal FSharpNavigateToSearchService []
| true, (version, items) when version = currentVersion -> return items
| _ ->
let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpNavigateToSearchService))
- let items = parseResults.ParseTree |> NavigateTo.GetNavigableItems
+ let items = NavigateTo.GetNavigableItems parseResults.ParseTree
cache[document.Id] <- currentVersion, items
return items
}
@@ -135,46 +135,58 @@ type internal FSharpNavigateToSearchService []
if item.NeedsBackticks then
match name.IndexOf(searchPattern, StringComparison.CurrentCultureIgnoreCase) with
- | i when i > 0 -> PatternMatch(PatternMatchKind.Substring, false, false) |> Some
- | 0 when name.Length = searchPattern.Length -> PatternMatch(PatternMatchKind.Exact, false, false) |> Some
- | 0 -> PatternMatch(PatternMatchKind.Prefix, false, false) |> Some
- | _ -> None
+ | i when i > 0 -> ValueSome(PatternMatch(PatternMatchKind.Substring, false, false))
+ | 0 when name.Length = searchPattern.Length -> ValueSome(PatternMatch(PatternMatchKind.Exact, false, false))
+ | 0 -> ValueSome(PatternMatch(PatternMatchKind.Prefix, false, false))
+ | _ -> ValueNone
else
// full name with dots allows for path matching, e.g.
// "f.c.so.elseif" will match "Fantomas.Core.SyntaxOak.ElseIfNode"
- patternMatcher.TryMatch $"{item.Container.FullName}.{name}" |> Option.ofNullable
+ patternMatcher.TryMatch $"{item.Container.FullName}.{name}"
+ |> ValueOption.ofNullable
- let processDocument (tryMatch: NavigableItem -> PatternMatch option) (kinds: IImmutableSet) (document: Document) =
+ let processDocument (tryMatch: NavigableItem -> PatternMatch voption) (kinds: IImmutableSet) (document: Document) =
cancellableTask {
let! ct = CancellableTask.getCancellationToken ()
let! sourceText = document.GetTextAsync ct
- let processItem (item: NavigableItem) =
- asyncMaybe { // TODO: make a flat cancellable task
-
- do! Option.guard (kinds.Contains(navigateToItemKindToRoslynKind item.Kind))
-
- let! m = tryMatch item
-
- let! sourceSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range)
- let glyph = navigateToItemKindToGlyph item.Kind
- let kind = navigateToItemKindToRoslynKind item.Kind
- let additionalInfo = formatInfo item.Container document
-
- return
- FSharpNavigateToSearchResult(
- additionalInfo,
- kind,
- patternMatchKindToNavigateToMatchKind m.Kind,
- item.Name,
- FSharpNavigableItem(glyph, ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), document, sourceSpan)
- )
- }
-
let! items = getNavigableItems document
- let! processed = items |> Seq.map processItem |> Async.Parallel
- return processed |> Array.choose id
+
+ let processed =
+ [|
+ for item in items do
+ let contains = kinds.Contains(navigateToItemKindToRoslynKind item.Kind)
+ let patternMatch = tryMatch item
+
+ match contains, patternMatch with
+ | true, ValueSome m ->
+ let sourceSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range)
+
+ match sourceSpan with
+ | ValueNone -> ()
+ | ValueSome sourceSpan ->
+ let glyph = navigateToItemKindToGlyph item.Kind
+ let kind = navigateToItemKindToRoslynKind item.Kind
+ let additionalInfo = formatInfo item.Container document
+
+ yield
+ FSharpNavigateToSearchResult(
+ additionalInfo,
+ kind,
+ patternMatchKindToNavigateToMatchKind m.Kind,
+ item.Name,
+ FSharpNavigableItem(
+ glyph,
+ ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)),
+ document,
+ sourceSpan
+ )
+ )
+ | _ -> ()
+ |]
+
+ return processed
}
interface IFSharpNavigateToSearchService with
@@ -190,7 +202,10 @@ type internal FSharpNavigateToSearchService []
let tryMatch = createMatcherFor searchPattern
let tasks =
- Seq.map (fun doc -> processDocument tryMatch kinds doc) project.Documents
+ [|
+ for doc in project.Documents do
+ yield processDocument tryMatch kinds doc
+ |]
let! results = CancellableTask.whenAll tasks
@@ -205,16 +220,10 @@ type internal FSharpNavigateToSearchService []
}
|> CancellableTask.start cancellationToken
- member _.SearchDocumentAsync
- (
- document: Document,
- searchPattern,
- kinds,
- cancellationToken
- ) : Task> =
+ member _.SearchDocumentAsync(document: Document, searchPattern, kinds, cancellationToken) =
cancellableTask {
let! result = processDocument (createMatcherFor searchPattern) kinds document
- return result |> Array.toImmutableArray
+ return Array.toImmutableArray result
}
|> CancellableTask.start cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs
index 9173dd5f863..b31b42c9822 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs
@@ -9,6 +9,7 @@ open System.Threading.Tasks
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor
open FSharp.Compiler.EditorServices
+open CancellableTasks
type internal NavigationBarSymbolItem(text, glyph, spans, childItems) =
inherit FSharpNavigationBarItem(text, glyph, spans, childItems)
@@ -20,10 +21,11 @@ type internal FSharpNavigationBarItemService [] () =
interface IFSharpNavigationBarItemService with
member _.GetItemsAsync(document, cancellationToken) : Task> =
- asyncMaybe {
- let! parseResults =
- document.GetFSharpParseResultsAsync(nameof (FSharpNavigationBarItemService))
- |> liftAsync
+ cancellableTask {
+
+ let! cancellationToken = CancellableTask.getCancellationToken ()
+
+ let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpNavigationBarItemService))
let navItems = Navigation.getNavigation parseResults.ParseTree
let! sourceText = document.GetTextAsync(cancellationToken)
@@ -31,27 +33,30 @@ type internal FSharpNavigationBarItemService [] () =
let rangeToTextSpan range =
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)
- return
- navItems.Declarations
- |> Array.choose (fun topLevelDecl ->
- rangeToTextSpan (topLevelDecl.Declaration.Range)
- |> Option.map (fun topLevelTextSpan ->
- let childItems =
- topLevelDecl.Nested
- |> Array.choose (fun decl ->
- rangeToTextSpan (decl.Range)
- |> Option.map (fun textSpan ->
- NavigationBarSymbolItem(decl.LogicalName, decl.RoslynGlyph, [| textSpan |], null)
- :> FSharpNavigationBarItem))
-
- NavigationBarSymbolItem(
- topLevelDecl.Declaration.LogicalName,
- topLevelDecl.Declaration.RoslynGlyph,
- [| topLevelTextSpan |],
- childItems
- )
- :> FSharpNavigationBarItem))
- :> IList<_>
+ if navItems.Declarations.Length = 0 then
+ return emptyResult
+ else
+
+ return
+ navItems.Declarations
+ |> Array.chooseV (fun topLevelDecl ->
+ rangeToTextSpan (topLevelDecl.Declaration.Range)
+ |> ValueOption.map (fun topLevelTextSpan ->
+ let childItems =
+ topLevelDecl.Nested
+ |> Array.chooseV (fun decl ->
+ rangeToTextSpan (decl.Range)
+ |> ValueOption.map (fun textSpan ->
+ NavigationBarSymbolItem(decl.LogicalName, decl.RoslynGlyph, [| textSpan |], null)
+ :> FSharpNavigationBarItem))
+
+ NavigationBarSymbolItem(
+ topLevelDecl.Declaration.LogicalName,
+ topLevelDecl.Declaration.RoslynGlyph,
+ [| topLevelTextSpan |],
+ childItems
+ )
+ :> FSharpNavigationBarItem))
+ :> IList<_>
}
- |> Async.map (Option.defaultValue emptyResult)
- |> RoslynHelpers.StartAsyncAsTask(cancellationToken)
+ |> CancellableTask.start cancellationToken
diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs
index 7fb51b9f83b..6e7df021a2c 100644
--- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs
+++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs
@@ -64,8 +64,8 @@ type internal FSharpAsyncQuickInfoSource
let textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range)
match textSpan with
- | None -> return None
- | Some textSpan ->
+ | ValueNone -> return None
+ | ValueSome textSpan ->
let trackingSpan =
textBuffer.CurrentSnapshot.CreateTrackingSpan(textSpan.Start, textSpan.Length, SpanTrackingMode.EdgeInclusive)
diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs
index a018bb52558..bcea39dd8ec 100644
--- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs
+++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs
@@ -6,7 +6,6 @@ open System
open System.Composition
open System.Threading
-open FSharp.Compiler
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
@@ -40,6 +39,8 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ CancellableTask.start ct
+ |> Async.AwaitTask
let! parseFileResults, checkFileResults =
document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpAddExplicitTypeToParameterRefactoring))
diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs
index 59e7288b623..07408c049b1 100644
--- a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs
+++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs
@@ -15,6 +15,7 @@ open FSharp.Compiler.Syntax
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeRefactorings
open Microsoft.CodeAnalysis.CodeActions
+open CancellableTasks
[]
type internal FSharpChangeDerefToValueRefactoring [] () =
@@ -27,6 +28,8 @@ type internal FSharpChangeDerefToValueRefactoring [] () =
let! parseResults =
document.GetFSharpParseResultsAsync(nameof (FSharpChangeDerefToValueRefactoring))
+ |> CancellableTask.start context.CancellationToken
+ |> Async.AwaitTask
|> liftAsync
let selectionRange =
diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs
index d1559708f23..6cc93b06d87 100644
--- a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs
+++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs
@@ -15,6 +15,7 @@ open FSharp.Compiler.Syntax
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeRefactorings
open Microsoft.CodeAnalysis.CodeActions
+open CancellableTasks
[]
type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring [] () =
@@ -27,6 +28,8 @@ type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring [ CancellableTask.start context.CancellationToken
+ |> Async.AwaitTask
|> liftAsync
let selectionRange =
diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs
index ac35e69af22..f96ea848c1f 100644
--- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs
+++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs
@@ -115,12 +115,15 @@ module internal BlockStructure =
| Scope.While
| Scope.For -> false
+ []
+ let ellipsis = "..."
+
let createBlockSpans isBlockStructureEnabled (sourceText: SourceText) (parsedInput: ParsedInput) =
let linetext = sourceText.Lines |> Seq.map (fun x -> x.ToString()) |> Seq.toArray
Structure.getOutliningRanges linetext parsedInput
|> Seq.distinctBy (fun x -> x.Range.StartLine)
- |> Seq.choose (fun scopeRange ->
+ |> Seq.chooseV (fun scopeRange ->
// the range of text to collapse
let textSpan =
RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, scopeRange.CollapseRange)
@@ -128,13 +131,13 @@ module internal BlockStructure =
let hintSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, scopeRange.Range)
match textSpan, hintSpan with
- | Some textSpan, Some hintSpan ->
+ | ValueSome textSpan, ValueSome hintSpan ->
let line = sourceText.Lines.GetLineFromPosition textSpan.Start
let bannerText =
- match Option.ofNullable (line.Span.Intersection textSpan) with
- | Some span -> sourceText.GetSubText(span).ToString() + "..."
- | None -> "..."
+ match ValueOption.ofNullable (line.Span.Intersection textSpan) with
+ | ValueSome span -> sourceText.GetSubText(span).ToString() + ellipsis
+ | ValueNone -> ellipsis
let blockType =
if isBlockStructureEnabled then
@@ -142,8 +145,10 @@ module internal BlockStructure =
else
FSharpBlockTypes.Nonstructural
- Some(FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope))
- | _, _ -> None)
+ ValueSome(
+ FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope)
+ )
+ | _, _ -> ValueNone)
open BlockStructure
open CancellableTasks
diff --git a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs
index fae5dde7797..2a520d72066 100644
--- a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs
+++ b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs
@@ -48,6 +48,9 @@ module TelemetryEvents =
[]
let GoToDefinitionGetSymbol = "gotodefinition/getsymbol"
+ []
+ let AnalysisSaveFileHandler = "analysis/savefilehandler"
+
// TODO: needs to be something more sophisticated in future
[]
type TelemetryThrottlingStrategy =
@@ -108,7 +111,7 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw
static member ReportFault(name, ?severity: FaultSeverity, ?e: exn) =
if TelemetryReporter.SendAdditionalTelemetry.Value then
- let faultName = String.Concat(name, "/fault")
+ let faultName = String.Concat(TelemetryReporter.eventPrefix, name, "/fault")
match severity, e with
| Some s, Some e -> TelemetryService.DefaultSession.PostFault(faultName, name, s, e)
@@ -120,7 +123,7 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw
static member ReportCustomFailure(name, ?props) =
if TelemetryReporter.SendAdditionalTelemetry.Value then
let props = defaultArg props [||]
- let name = String.Concat(name, "/failure")
+ let name = String.Concat(TelemetryReporter.eventPrefix, name, "/failure")
let event = TelemetryReporter.createEvent name props
TelemetryService.DefaultSession.PostEvent event
diff --git a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs
index 08a73dc2816..cd01e6434d7 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs
@@ -66,8 +66,8 @@ let main argv =
task.Result
match actualResolutionOption with
- | None -> Assert.True(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position")
- | Some (actualResolutionRange) ->
+ | ValueNone -> Assert.True(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position")
+ | ValueSome (actualResolutionRange) ->
let actualResolution =
sourceText
.GetSubText(RoslynHelpers.FSharpRangeToTextSpan(sourceText, actualResolutionRange))
diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs
index 71fb3b0c963..0041a7dd4bb 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs
@@ -34,7 +34,7 @@ module CompletionProviderTests =
let results =
let task =
- FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||]))
|> CancellableTask.start CancellationToken.None
task.Result |> Seq.map (fun result -> result.DisplayText)
@@ -82,7 +82,7 @@ module CompletionProviderTests =
let actual =
let task =
- FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||]))
|> CancellableTask.start CancellationToken.None
task.Result
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs
index 7f3d4d0e804..4a10dacc6c6 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs
@@ -71,7 +71,7 @@ module FindReferences =
let document =
solution.TryGetDocumentFromPath documentPath
- |> Option.defaultWith (fun _ -> failwith "Document not found")
+ |> ValueOption.defaultWith (fun _ -> failwith "Document not found")
findUsagesService
.FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context)
@@ -94,7 +94,7 @@ module FindReferences =
let document =
solution.TryGetDocumentFromPath documentPath
- |> Option.defaultWith (fun _ -> failwith "Document not found")
+ |> ValueOption.defaultWith (fun _ -> failwith "Document not found")
findUsagesService
.FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context)
@@ -116,7 +116,7 @@ module FindReferences =
let document =
solution.TryGetDocumentFromPath documentPath
- |> Option.defaultWith (fun _ -> failwith "Document not found")
+ |> ValueOption.defaultWith (fun _ -> failwith "Document not found")
findUsagesService
.FindReferencesAsync(document, getPositionOf "sharedFunc" documentPath, context)
@@ -157,7 +157,7 @@ module FindReferences =
let document =
solution2.TryGetDocumentFromPath documentPath
- |> Option.defaultWith (fun _ -> failwith "Document not found")
+ |> ValueOption.defaultWith (fun _ -> failwith "Document not found")
findUsagesService
.FindReferencesAsync(document, getPositionOf operator documentPath, context)
diff --git a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs
index 49955dedbb4..9d9432aaf13 100644
--- a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs
+++ b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs
@@ -32,7 +32,7 @@ type Worker() =
let actual =
let x =
- FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> []))
+ FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||]))
|> CancellableTask.start CancellationToken.None
x.Result