未验证 提交 4e2def68 编写于 作者: K kerams 提交者: GitHub

Add parameter name hints for discriminated unions (#14262)

上级 f35fa1a1
......@@ -242,6 +242,8 @@ type FSharpSymbolUse(denv: DisplayEnv, symbol: FSharpSymbol, inst: TyparInstanti
member _.IsFromDispatchSlotImplementation = itemOcc = ItemOccurence.Implemented
member _.IsFromUse = itemOcc = ItemOccurence.Use
member _.IsFromComputationExpression =
match symbol.Item, itemOcc with
// 'seq' in 'seq { ... }' gets colored as keywords
......
......@@ -171,6 +171,9 @@ type public FSharpSymbolUse =
/// Indicates if the reference is in open statement
member IsFromOpenStatement: bool
/// Indicates if the reference is used for example at a call site
member IsFromUse: bool
/// The file name the reference occurs in
member FileName: string
......
......@@ -2163,6 +2163,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromOpenStatement
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromPattern
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromType
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsPrivateToFile
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromUse
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromAttribute()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromComputationExpression()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromDefinition()
......@@ -2171,6 +2172,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromOpenStatement()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromPattern()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromType()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsPrivateToFile()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromUse()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext DisplayContext
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext get_DisplayContext()
FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpSymbol Symbol
......@@ -19,9 +19,15 @@ module HintService =
| :? FSharpMemberOrFunctionOrValue as symbol
when hintKinds |> Set.contains HintKind.ParameterNameHint
&& InlineParameterNameHints.isValidForHint symbol ->
&& InlineParameterNameHints.isMemberOrFunctionOrValueValidForHint symbol ->
InlineParameterNameHints.getHints parseResults symbol symbolUse
InlineParameterNameHints.getHintsForMemberOrFunctionOrValue parseResults symbol symbolUse
| :? FSharpUnionCase as symbol
when hintKinds |> Set.contains HintKind.ParameterNameHint
&& InlineParameterNameHints.isUnionCaseValidForHint symbol symbolUse ->
InlineParameterNameHints.getHintsForUnionCase parseResults symbol symbolUse
// we'll be adding other stuff gradually here
| _ ->
......
......@@ -10,17 +10,27 @@ open Hints
module InlineParameterNameHints =
let private getHint (range: range, parameter: FSharpParameter) =
let private getParameterHint (range: range, parameter: FSharpParameter) =
{
Kind = HintKind.ParameterNameHint
Range = range.StartRange
Parts = [ TaggedText(TextTag.Text, $"{parameter.DisplayName} = ") ]
}
let private getFieldHint (range: range, field: FSharpField) =
{
Kind = HintKind.ParameterNameHint
Range = range.StartRange
Parts = [ TaggedText(TextTag.Text, $"{field.Name} = ") ]
}
let private doesParameterNameExist (parameter: FSharpParameter) =
parameter.DisplayName <> ""
let isValidForHint (symbol: FSharpMemberOrFunctionOrValue) =
let private doesFieldNameExist (field: FSharpField) =
not field.IsNameGenerated
let isMemberOrFunctionOrValueValidForHint (symbol: FSharpMemberOrFunctionOrValue) =
// is there a better way?
let isNotBuiltInOperator =
symbol.DeclaringEntity
......@@ -29,7 +39,12 @@ module InlineParameterNameHints =
symbol.IsFunction
&& isNotBuiltInOperator // arguably, hints for those would be rather useless
let getHints
let isUnionCaseValidForHint (symbol: FSharpUnionCase) (symbolUse: FSharpSymbolUse) =
// is the union case being used as a constructor and is it not Cons
symbolUse.IsFromUse
&& symbol.DisplayName <> "(::)"
let getHintsForMemberOrFunctionOrValue
(parseResults: FSharpParseFileResults)
(symbol: FSharpMemberOrFunctionOrValue)
(symbolUse: FSharpSymbolUse) =
......@@ -42,8 +57,31 @@ module InlineParameterNameHints =
parameters
|> Seq.zip ranges
|> Seq.where (snd >> doesParameterNameExist)
|> Seq.map getHint
|> Seq.map getParameterHint
|> Seq.toList
// this is the case at least for custom operators
| None -> []
let getHintsForUnionCase
(parseResults: FSharpParseFileResults)
(symbol: FSharpUnionCase)
(symbolUse: FSharpSymbolUse) =
let fields = Seq.toList symbol.Fields
// If a case does not use field names, don't even bother getting applied argument ranges
if fields |> List.exists doesFieldNameExist |> not then
[]
else
let ranges = parseResults.GetAllArgumentsForFunctionApplicationAtPosition symbolUse.Range.Start
// When not all field values are provided (as the user is typing), don't show anything yet
match ranges with
| Some ranges when ranges.Length = fields.Length ->
fields
|> List.zip ranges
|> List.where (snd >> doesFieldNameExist)
|> List.map getFieldHint
| _ -> []
......@@ -22,19 +22,22 @@ type internal RoslynAdapter
interface IFSharpInlineHintsService with
member _.GetInlineHintsAsync(document, _, cancellationToken) =
async {
let! sourceText = document.GetTextAsync cancellationToken |> Async.AwaitTask
let hintKinds = OptionParser.getHintKinds settings.Advanced
let! nativeHints =
HintService.getHintsForDocument
document
hintKinds
userOpName
cancellationToken
let roslynHints =
nativeHints
|> Seq.map (NativeToRoslynHintConverter.convert sourceText)
return roslynHints.ToImmutableArray()
if hintKinds.IsEmpty then
return ImmutableArray.Empty
else
let! sourceText = document.GetTextAsync cancellationToken |> Async.AwaitTask
let! nativeHints =
HintService.getHintsForDocument
document
hintKinds
userOpName
cancellationToken
let roslynHints =
nativeHints
|> Seq.map (NativeToRoslynHintConverter.convert sourceText)
return roslynHints.ToImmutableArray()
} |> RoslynHelpers.StartAsyncAsTask cancellationToken
......@@ -159,17 +159,72 @@ let wrapped = WrappedThing 42
Assert.IsEmpty(result)
[<Test>]
let ``Hints are not (yet) shown for dicrimanted unions`` () =
let ``Hints are shown for discriminated union case fields with explicit names`` () =
let code = """
type Shape =
| Square of side : float
| Circle of radius : float
| Square of side: int
| Rectangle of width: int * height: int
let a = Square 1
let b = Rectangle (1, 2)
"""
let document = getFsDocument code
let expected = [
{ Content = "side = "; Location = (5, 16) }
{ Content = "width = "; Location = (6, 20) }
{ Content = "height = "; Location = (6, 23) }
]
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
let circle = Circle 42
[<Test>]
let ``Hints for discriminated union case fields are not shown when names are generated`` () =
let code = """
type Shape =
| Triangle of side1: int * int * side3: int
| Circle of int
let c = Triangle (1, 2, 3)
let d = Circle 1
"""
let document = getFsDocument code
let result = getParameterNameHints document
let expected = [
{ Content = "side1 = "; Location = (5, 19) }
{ Content = "side3 = "; Location = (5, 25) }
]
Assert.IsEmpty(result)
let actual = getParameterNameHints document
Assert.AreEqual(expected, actual)
[<Test>]
let ``Hints for discriminated union case fields are not shown when provided arguments don't match the expected count`` () =
let code = """
type Shape =
| Triangle of side1: int * side2: int * side3: int
| Circle of int
let c = Triangle (1, 2)
"""
let document = getFsDocument code
let actual = getParameterNameHints document
Assert.IsEmpty(actual)
[<Test>]
let ``Hints for discriminated union case fields are not shown for Cons`` () =
let code = """
type X =
member _.Test() = 42 :: [42; 42]
"""
let document = getFsDocument code
let actual = getParameterNameHints document
Assert.IsEmpty(actual)
......@@ -15,12 +15,24 @@ type Song = { Artist: string; Title: string }
let whoSings song = song.Artist
let artist = whoSings { Artist = "Květy"; Title = "Je podzim" }
type Shape =
| Square of side: int
| Rectangle of width: int * height: int
let a = Square 1
let b = Rectangle (1, 2)
"""
let document = getFsDocument code
let expected = [
{ Content = ": Song"; Location = (2, 18) }
{ Content = ": string"; Location = (4, 11) }
{ Content = "song = "; Location = (4, 23) }
{ Content = ": string"; Location = (4, 11) }
{ Content = "side = "; Location = (10, 16) }
{ Content = ": Shape"; Location = (10, 6) }
{ Content = "width = "; Location = (11, 20) }
{ Content = "height = "; Location = (11, 23) }
{ Content = ": Shape"; Location = (11, 6) }
]
let actual = getAllHints document
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册