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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Service/FSharpCheckerResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
10 changes: 8 additions & 2 deletions vsintegration/src/FSharp.Editor/Hints/HintService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
| _ ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) =
Expand All @@ -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

| _ -> []
29 changes: 16 additions & 13 deletions vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
67 changes: 61 additions & 6 deletions vsintegration/tests/UnitTests/InlineParameterNameHintTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

14 changes: 13 additions & 1 deletion vsintegration/tests/UnitTests/OverallHintExperienceTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down