Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to signature file #422

Open
wants to merge 6 commits into
base: net223
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ type FSharpElementFactory(languageService: IFSharpLanguageService, sourceFile: I
let exprStatement = getExpressionStatement source
exprStatement.AttributeLists[0]

let createBindingSignature () =
let source = "module V\nval a: obj"
let moduleMember = getModuleMember source
moduleMember :?> IBindingSignature

interface IFSharpElementFactory with
member x.CreateOpenStatement(ns) =
// todo: mangle ns
Expand Down Expand Up @@ -342,6 +347,12 @@ type FSharpElementFactory(languageService: IFSharpLanguageService, sourceFile: I

| _ -> System.ArgumentOutOfRangeException() |> raise

member x.CreateParenType() : IParenTypeUsage =
let expr = getExpression "do () : (unit)"
let doExpr = expr :?> IDoExpr
let typedExpr = doExpr.Expression :?> ITypedExpr
typedExpr.TypeUsage :?> IParenTypeUsage

member x.CreateSetExpr(left: IFSharpExpression, right: IFSharpExpression) =
let source = "() <- ()"
let expr = getExpression source
Expand Down Expand Up @@ -385,3 +396,10 @@ type FSharpElementFactory(languageService: IFSharpLanguageService, sourceFile: I
moduleMember.As<ITypeDeclarationGroup>().TypeDeclarations[0] :?> IFSharpTypeDeclaration

typeDeclaration.TypeParameterDeclarationList

member x.CreateBindingSignature(bindingName: IFSharpPattern, returnType: ITypeUsage) =
assert sourceFile.IsFSharpSignatureFile
let signature = createBindingSignature ()
signature.SetHeadPattern(bindingName) |> ignore
replace signature.ReturnTypeInfo.ReturnType returnType
signature
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<Compile Include="src\Intentions\SetNameAction.fs" />
<Compile Include="src\Intentions\LetToUseAction.fs" />
<Compile Include="src\Intentions\RenameFileToMatchTypeNameAction.fs" />
<Compile Include="src\Intentions\AddFunctionToSignatureFileAction.fs" />
<Compile Include="src\QuickFixes\FSharpQuickFixBase.fs" />
<Compile Include="src\QuickFixes\RemoveUnusedOpensFix.fs" />
<Compile Include="src\QuickFixes\ReplaceUseWithLetFix.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Intentions

open FSharp.Compiler.Text
open FSharp.Compiler.Symbols
open JetBrains.ReSharper.Feature.Services.ContextActions
open JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Intentions
open JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
open JetBrains.ReSharper.Plugins.FSharp.Psi
open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl
open JetBrains.ReSharper.Psi.ExtensionsAPI
open JetBrains.ReSharper.Resources.Shell
open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
open JetBrains.ReSharper.Psi.Tree

[<RequireQualifiedAccess>]
type private ParameterNameFromPattern =
| NoNameFound
| SingleName of name: IFSharpIdentifier * attributes: string
| TupleName of ParameterNameFromPattern list

[<ContextAction(Name = "AddFunctionToSignatureFile", Group = "F#", Description = "Add function to signature file")>]
type AddFunctionToSignatureFileAction(dataProvider: FSharpContextActionDataProvider) =
inherit FSharpContextActionBase(dataProvider)

let (|ValFromImpl|_|) (symbol:FSharpSymbol) =
match symbol with
| :? FSharpMemberOrFunctionOrValue as valSymbol ->
let hasConstraints =
valSymbol.GenericParameters
|> Seq.exists (fun gp -> not (Seq.isEmpty gp.Constraints))

// Ignore constraints for now.
if hasConstraints then None else

valSymbol.SignatureLocation
|> Option.bind (fun range -> if range.FileName.EndsWith(".fs") then Some valSymbol else None)
| _ -> None

let rec tryFindParameterName (isTopLevel: bool) (p: IFSharpPattern) : ParameterNameFromPattern =
match p.IgnoreInnerParens() with
| :? ITypedPat as tp -> tryFindParameterName isTopLevel tp.Pattern
| :? ILocalReferencePat as rp -> ParameterNameFromPattern.SingleName (rp.Identifier, "")
| :? IAttribPat as ap ->
match tryFindParameterName isTopLevel ap.Pattern with
| ParameterNameFromPattern.SingleName(name, _) ->
let attributes = Seq.map (fun (al:IAttributeList) -> al.GetText()) ap.AttributeListsEnumerable |> String.concat ""
ParameterNameFromPattern.SingleName(name, attributes)
| _ ->
ParameterNameFromPattern.NoNameFound
| :? ITuplePat as tp ->
if not isTopLevel then
ParameterNameFromPattern.NoNameFound
else

Seq.map (tryFindParameterName false) tp.Patterns
|> Seq.toList
|> ParameterNameFromPattern.TupleName
| _ -> ParameterNameFromPattern.NoNameFound

let implBindingAndDecl =
let currentFSharpFile = dataProvider.PsiFile
if isNull currentFSharpFile then None else
// Don't show context action in signature file.
if currentFSharpFile.IsFSharpSigFile() then None else

let fcsService = currentFSharpFile.FcsCheckerService
if isNull fcsService || isNull fcsService.FcsProjectProvider then None else

let hasSignature = fcsService.FcsProjectProvider.HasPairFile dataProvider.SourceFile
if not hasSignature then None else

let letBindings = dataProvider.GetSelectedElement<ILetBindingsDeclaration>()
if isNull letBindings then None else
// Currently excluding recursive bindings
if letBindings.Bindings.Count <> 1 then None else
let binding = letBindings.Bindings |> Seq.exactlyOne
let refPat = binding.HeadPattern.As<IReferencePat>()
if isNull refPat || isNull refPat.Reference then None else

let moduleOrNamespaceDecl = QualifiableModuleLikeDeclarationNavigator.GetByMember(letBindings)
if isNull moduleOrNamespaceDecl then None else
let moduleOrNamespaceDeclaredElement = moduleOrNamespaceDecl.DeclaredElement
if isNull moduleOrNamespaceDeclaredElement then None else

let signatureCounterPart =
moduleOrNamespaceDeclaredElement.GetDeclarations()
|> Seq.tryPick (fun d -> if d.IsFSharpSigFile() then Some d else None)

match signatureCounterPart with
| None -> None
| Some signatureCounterPart ->

let symbolUse = refPat.GetFcsSymbolUse()
if isNull symbolUse then None else

match symbolUse.Symbol with
| ValFromImpl valSymbol ->
let text =
valSymbol.FormatLayout(symbolUse.DisplayContext)
|> Array.choose (fun (t : TaggedText) ->
match t.Tag with
| TextTag.UnknownEntity -> None
| _ -> Some t.Text)
|> String.concat ""

Some (refPat, binding, text, signatureCounterPart)
| _ -> None

override this.IsAvailable _ = Option.isSome implBindingAndDecl

override this.ExecutePsiTransaction(_solution, _progress) =
match implBindingAndDecl with
| None -> null
| Some (refPat, binding, text, signatureModuleOrNamespaceDecl) ->

use writeCookie = WriteLockCookie.Create(binding.IsPhysical())
use disableFormatter = new DisableCodeFormatter()

let factory = binding.CreateElementFactory()
let typeInfo = factory.CreateTypeUsage(text, TypeUsageContext.Return)

// Enrich the type info with the found parameters from binding.
let rec visit (index:int) (t: ITypeUsage) =
if index = binding.ParameterPatterns.Count then
match t with
| :? IFunctionTypeUsage ->
// If the return type is a function itself, the safest thing to do is to wrap it in parentheses.
// Example: `let g _ = (*) 3`
// `val g: 'a -> int -> int` is not valid, `val g: 'a -> (int -> int)` is.
let parenType = factory.CreateParenType()
replace parenType.InnerTypeUsage t
replace t parenType
| _ -> ()
else
let parameterAtIndex = tryFindParameterName true (binding.ParameterPatterns.Item(index))

match t, parameterAtIndex with
| :? IFunctionTypeUsage as ft, ParameterNameFromPattern.NoNameFound ->
visit (index + 1) ft.ReturnTypeUsage

| :? IFunctionTypeUsage as ft, ParameterNameFromPattern.SingleName (name, attributes) ->
match ft.ArgumentTypeUsage with
| :? IParameterSignatureTypeUsage as pstu ->
pstu.SetIdentifier(name) |> ignore
| _ -> ()

visit (index + 1) ft.ReturnTypeUsage

| :? IFunctionTypeUsage as ft, ParameterNameFromPattern.TupleName multipleParameterNames ->
match ft.ArgumentTypeUsage with
| :? ITupleTypeUsage as tt when tt.Items.Count = multipleParameterNames.Length ->
(multipleParameterNames, tt.Items)
||> Seq.zip
|> Seq.iter (fun (p,t) ->
match t, p with
| :? IParameterSignatureTypeUsage as pstu, ParameterNameFromPattern.SingleName (name, attributes) ->
pstu.SetIdentifier(name) |> ignore
| _ -> ()
)
| _ -> visit (index + 1) ft.ReturnTypeUsage
| _ ->
()

if not binding.ParameterPatterns.IsEmpty then
visit 0 typeInfo

let valSig =
let signatureFactory = signatureModuleOrNamespaceDecl.CreateElementFactory()
signatureFactory.CreateBindingSignature(refPat, typeInfo)

let newlineNode = NewLine(signatureModuleOrNamespaceDecl.GetLineEnding()) :> ITreeNode
addNodesAfter signatureModuleOrNamespaceDecl.LastChild [| newlineNode; valSig |] |> ignore

null

override this.Text = "Add function to signature file"
3 changes: 3 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/IFSharpElementFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public interface IFSharpElementFactory
ITypedPat CreateTypedPat(IFSharpPattern pattern, ITypeUsage typeUsage);

ITypeUsage CreateTypeUsage(string typeUsage, TypeUsageContext context);
IParenTypeUsage CreateParenType();

IReturnTypeInfo CreateReturnTypeInfo(ITypeUsage typeSignature);

Expand All @@ -62,5 +63,7 @@ public interface IFSharpElementFactory
IMemberDeclaration CreatePropertyWithAccessor(string propertyName, string accessorName, FSharpList<IParametersPatternDeclaration> args);

ITypeParameterDeclarationList CreateTypeParameterOfTypeList(FSharpList<string> names);

IBindingSignature CreateBindingSignature(IFSharpPattern bindingName, ITypeUsage returnType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Test

open System.Diagnostics.CodeAnalysis
let x{caret} ([<NotNull>] y) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Test

open System.Diagnostics.CodeAnalysis
let x{caret} ([<NotNull>] y) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
open System.Diagnostics.CodeAnalysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test
open System.Diagnostics.CodeAnalysis
val x: [<NotNull>] y: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let x{caret} ([<System.Diagnostics.CodeAnalysis.NotNull>] y : int) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let x{caret} ([<System.Diagnostics.CodeAnalysis.NotNull>] y : int) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val x: [<System.Diagnostics.CodeAnalysis.NotNull>] y: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test
open System.Diagnostics.CodeAnalysis
let x{caret} ([<NotNull>] y : int, [<SuppressMessage "Some message">] z: string) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test
open System.Diagnostics.CodeAnalysis
let x{caret} ([<NotNull>] y : int, [<SuppressMessage "Some message">] z: string) = y + 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
open System.Diagnostics.CodeAnalysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test
open System.Diagnostics.CodeAnalysis
val x: [<NotNull>] y: int * [<SuppressMessage "Some message">] z: string -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Test

let memoizeBy{caret} (g: 'a -> 'c) (f: 'a -> 'b) =
let cache =
System.Collections.Concurrent.ConcurrentDictionary<_, _>(HashIdentity.Structural)

fun x -> cache.GetOrAdd(Some(g x), lazy (f x)).Force()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NOT AVAILABLE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b c = b + c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b c = b + c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: int -> c: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b:string) c = c + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b:string) c = c + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: string -> c: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b (c: char) d = b + d + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b (c: char) d = b + d + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: int -> c: char -> d: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b : int = printfn "%s" b ; 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} b : int = printfn "%s" b ; 0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: string -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b, c) = b + c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b, c) = b + c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: int * c: int -> int
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b, _, d) = printfn "%b" d ; b + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Test

let a{caret} (b, _, d) = printfn "%b" d ; b + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module Test
val a: b: int * 'a * d: bool -> int
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<Compile Include="src\Intentions\LetToUseTest.fs" />
<Compile Include="src\Intentions\RenameFileToMatchTypeNameTest.fs" />
<Compile Include="src\Intentions\DeconstructPatternTest.fs" />
<Compile Include="src\Intentions\AddFunctionToSignatureFileActionTest.fs" />
<Compile Include="src\QuickFixes\FSharpQuickFixTestBase.fs" />
<Compile Include="src\QuickFixes\ReplaceUseWithLetTest.fs" />
<Compile Include="src\QuickFixes\RemoveUnusedSelfIdVariableFix.fs" />
Expand Down
Loading