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.3 2.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-initialization Vlastnost 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-initialization Die 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-initialization No 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-initialization La 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-initialization La 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-initialization Wł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-initialization A 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 @@ - + true Microsoft.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