Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
@@ -1,44 +1,85 @@
module JetBrains.ReSharper.Plugins.FSharp.Psi.Features.Util.FSharpMethodInvocationUtil

open FSharp.Compiler.SourceCodeServices
open JetBrains.ReSharper.Plugins.FSharp.Psi
open JetBrains.ReSharper.Plugins.FSharp.Psi.Impl
open JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
open JetBrains.ReSharper.Psi
open JetBrains.ReSharper.Psi.ExtensionsAPI
open JetBrains.ReSharper.Psi.Tree

let tryGetNamedArg (expr: IFSharpExpression) =
let binaryAppExpr = expr.As<IBinaryAppExpr>()
if isNull binaryAppExpr then null else

let resolveNamedArg (binaryAppExpr: IBinaryAppExpr) =
if binaryAppExpr.Operator.Reference.GetName() <> "=" then null else

let refExpr = binaryAppExpr.LeftArgument.As<IReferenceExpr>()
if isNull refExpr then null else

refExpr.Reference.Resolve().DeclaredElement.As<IParameter>()

let getMatchingParameter (initialExpr: IFSharpExpression) =
let expr = initialExpr.IgnoreInnerParens()
let tupleExpr = TupleExprNavigator.GetByExpression(expr)
let tupleExprContext = tupleExpr.IgnoreParentParens()

let appExpr = PrefixAppExprNavigator.GetByArgumentExpression(if isNull tupleExpr then expr else tupleExprContext)
if isNull appExpr then null else
let tryGetNamedArg (expr: IFSharpExpression) =
let binaryAppExpr = expr.As<IBinaryAppExpr>()
if isNull binaryAppExpr then null else
resolveNamedArg binaryAppExpr


let refExpr = appExpr.FunctionExpression.As<IReferenceExpr>()
if isNull refExpr then null else
let getMatchingParameter (expr: IFSharpExpression) =
let argsOwner =
let tupleExpr = TupleExprNavigator.GetByExpression(expr.IgnoreParentParens())
let exprContext = if isNull tupleExpr then expr else tupleExpr :> _
FSharpArgumentOwnerNavigator.GetByArgumentExpression(exprContext.IgnoreParentParens())

if isNull argsOwner then null else

let binaryAppExpr = expr.As<IBinaryAppExpr>()
// todo: recover from named args failures
if isNotNull binaryAppExpr then resolveNamedArg binaryAppExpr else

let symbolReference = argsOwner.Reference
if isNull symbolReference then null else

match box (symbolReference.GetFSharpSymbol()) with
| :? FSharpMemberOrFunctionOrValue as mfv ->

// todo: this should be cached, and kept on the FSharpArgumentsOwner
// todo: this is basically doing the same as PrefixAppExpr.Arguments, can they be merged?
let args =
argsOwner.AppliedExpressions
|> Seq.map (fun expr -> expr.IgnoreInnerParens())
|> Seq.zip mfv.CurriedParameterGroups
|> Seq.collect (fun (paramGroup, argExpr) ->
match paramGroup.Count with
| 0 -> Seq.empty
| 1 -> Seq.singleton (argExpr :?> IArgument)
| count ->
match argExpr with
| :? ITupleExpr as tupleExpr ->
Seq.init paramGroup.Count (fun i ->
if i < tupleExpr.Expressions.Count then
tupleExpr.Expressions.[i] :?> IArgument
else
null
)
| _ ->
Seq.init count (fun _ -> null)
)

match args |> Seq.tryFindIndex (fun argExpr -> expr.Equals(argExpr)) with
| None -> null
| Some paramIndex ->

use compilationContextCookie = CompilationContextCookie.OverrideOrCreate(expr.GetResolveContext())
let paramOwner = symbolReference.Resolve().DeclaredElement.As<IParametersOwner>()
if isNull paramOwner then null else

let parameter = tryGetNamedArg initialExpr
if isNotNull parameter then parameter else
let invokingExtensionMethod = mfv.IsExtensionMember && Some mfv.ApparentEnclosingEntity <> mfv.DeclaringEntity
let offset = if invokingExtensionMethod then 1 else 0
let param = paramOwner.Parameters.[paramIndex + offset]

let method = refExpr.Reference.Resolve().DeclaredElement.As<IMethod>()
let parameters = method.Parameters
if parameters.Count = 1 then parameters.[0] else
// Skip unnamed parameters
if param.ShortName = SharedImplUtil.MISSING_DECLARATION_NAME then null else param

let index = tupleExpr.Expressions.IndexOf(expr)
if index < parameters.Count then parameters.[index] else null
| _ -> null

[<Language(typeof<FSharpLanguage>)>]
type FSharpMethodInvocationUtil() =
Expand Down
24 changes: 24 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/Attribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Resolve;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
{
Expand All @@ -10,5 +13,26 @@ protected override FSharpSymbolReference CreateReference() =>
new CtorReference(this);

public override IFSharpIdentifier FSharpIdentifier => ReferenceName?.Identifier;

public IList<IArgument> Arguments
{
get
{
// todo: this is same as NewExpr.Arguments
switch (ArgExpression?.Expression.IgnoreInnerParens())
{
case IUnitExpr _:
return EmptyList<IArgument>.Instance;
case ITupleExpr tupleExpr:
return tupleExpr.Expressions.Select(arg => arg as IArgument).ToList();
case IArgument argExpr:
return new List<IArgument> { argExpr };
default:
return EmptyList<IArgument>.Instance;
}
}
}

public IList<IFSharpExpression> AppliedExpressions => new List<IFSharpExpression> {ArgExpression.Expression};
}
}
51 changes: 51 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/NewExpr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Resolve;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
{
internal partial class NewExpr
{
public FSharpSymbolReference Reference { get; private set; }

protected override void PreInit()
{
base.PreInit();
Reference = new CtorReference(this);
}

public IList<IArgument> Arguments
{
get
{
// todo: this is same as Attribute.Arguments
switch (ArgumentExpression.IgnoreInnerParens())
{
case IUnitExpr _:
return EmptyList<IArgument>.Instance;
case ITupleExpr tupleExpr:
return tupleExpr.Expressions.Select(arg => arg as IArgument).ToList();
case IArgument argExpr:
return new List<IArgument> { argExpr };
default:
return EmptyList<IArgument>.Instance;
}
}
}

public IFSharpIdentifier FSharpIdentifier => TypeName?.Identifier;

public IFSharpReferenceOwner SetName(string name)
{
throw new System.NotImplementedException();
}

public override ReferenceCollection GetFirstClassReferences() =>
new ReferenceCollection(Reference);

public IList<IFSharpExpression> AppliedExpressions => new List<IFSharpExpression> {ArgumentExpression};
}
}
77 changes: 70 additions & 7 deletions ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/PrefixAppExpr.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
using System.Collections.Generic;
using System.Linq;
using FSharp.Compiler.SourceCodeServices;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Resolve;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Util;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
{
internal partial class PrefixAppExpr
{
public FSharpSymbolReference InvokedFunctionReference
public FSharpSymbolReference Reference => InvokedReferenceExpression?.Reference;

public IReferenceExpr InvokedReferenceExpression
{
get
{
var argsCount = 0;
// todo: can we init this once then cache it?
var funExpr = (IPrefixAppExpr) this;
while (funExpr.FunctionExpression.IgnoreInnerParens() is IPrefixAppExpr appExpr)
{
funExpr = appExpr;
argsCount++;
}

if (!(funExpr.FunctionExpression.IgnoreInnerParens() is IReferenceExpr referenceExpr))
{
return null;
}

return referenceExpr;
}
}

argsCount++;
public IFSharpIdentifier FSharpIdentifier => InvokedReferenceExpression?.Identifier;

public IFSharpReferenceOwner SetName(string name) => this;

public override ReferenceCollection GetFirstClassReferences() =>
new ReferenceCollection(Reference);

public FSharpSymbolReference InvokedFunctionReference
{
get
{
var referenceExpr = InvokedReferenceExpression;
if (referenceExpr == null)
return null;

var reference = referenceExpr.Reference;
var fsSymbol = reference.GetFSharpSymbol();
Expand All @@ -35,15 +57,15 @@ public FSharpSymbolReference InvokedFunctionReference
return null;

var paramGroups = mfv.CurriedParameterGroups;
return paramGroups.Count >= argsCount ? reference : null;
return paramGroups.Count >= AppliedExpressions.Count ? reference : null;
}
}

public IList<IExpression> Arguments
public IList<IFSharpExpression> AppliedExpressions
{
get
{
var args = new List<IExpression>();
var args = new List<IFSharpExpression>();
var funExpr = (IPrefixAppExpr) this;
while (funExpr.FunctionExpression.IgnoreInnerParens() is IPrefixAppExpr appExpr)
{
Expand All @@ -57,6 +79,47 @@ public IList<IExpression> Arguments
}
}

// todo: this should be a Lazy/cache its result
public IList<IArgument> Arguments
{
get
{
if (!(InvokedFunctionReference?.GetFSharpSymbol() is FSharpMemberOrFunctionOrValue mfv))
return EmptyList<IArgument>.Instance;

var paramGroups = mfv.CurriedParameterGroups;
var isVoidReturn = paramGroups.Count == 1 && paramGroups[0].Count == 1 && paramGroups[0][0].Type.IsUnit;

if (isVoidReturn)
return EmptyArray<IArgument>.Instance;

return paramGroups
.Zip(AppliedExpressions, (paramGroup, argExpr) => (paramGroup, argExpr.IgnoreInnerParens()))
.SelectMany(pair =>
{
var (paramGroup, argExpr) = pair;

switch (paramGroup.Count)
{
case 0:
// e.g. F# extension methods with 0 parameters
return EmptyList<IArgument>.Instance;
case 1:
return new[] {argExpr as IArgument};
default:
if (!(argExpr is ITupleExpr tupleExpr))
return new[] {argExpr as IArgument};

return tupleExpr.Expressions.Count <= paramGroup.Count
? tupleExpr.Expressions.Select(expr => expr as IArgument)
: EmptyList<IArgument>.Instance;
}
})
.Where(argExpr => argExpr != null)
.ToList();
}
}

public override IType Type()
{
var reference = InvokedFunctionReference;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using JetBrains.Annotations;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public class FSharpArgumentOwnerNavigator
{
public static IFSharpArgumentsOwner GetByArgumentExpression([CanBeNull] IFSharpExpression param) =>
(IFSharpArgumentsOwner)AppLikeExprNavigator.GetByArgumentExpression(param) ??
AttributeNavigator.GetByExpression(param);
}
}
6 changes: 6 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/Tree/IAppLikeExpr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public partial interface IAppLikeExpr : IFSharpArgumentsOwner
{
}
}
2 changes: 1 addition & 1 deletion ReSharper.FSharp/src/FSharp.Psi/src/Tree/IAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public partial interface IAttribute : IFSharpReferenceOwner
public partial interface IAttribute : IFSharpArgumentsOwner
{
}
}
10 changes: 10 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/Tree/IFSharpArgumentsOwner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using JetBrains.ReSharper.Psi.Tree;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public interface IFSharpArgumentsOwner : IArgumentsOwner, IFSharpReferenceOwner
{
IList<IFSharpExpression> AppliedExpressions { get; }
}
}
6 changes: 4 additions & 2 deletions ReSharper.FSharp/src/FSharp.Psi/src/Tree/IPrefixAppExpr.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.Collections.Generic;
using System;
using JetBrains.Annotations;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Resolve;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Tree;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public partial interface IPrefixAppExpr
{
[CanBeNull] IReferenceExpr InvokedReferenceExpression { get; }

[CanBeNull] FSharpSymbolReference InvokedFunctionReference { get; }
[NotNull] IList<IExpression> Arguments { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
open System.ComponentModel

[<{selstart}DefaultValue(typeof<string>, "hi"){selend}>]
let x = ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
open System.ComponentModel

[<DefaultValue(|typeof<string>|(arg #0), |"hi"|(arg #1))>]
let x = ""

---------------------------------------------------------
(arg #0) => type
(arg #1) => value
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
open System.ComponentModel

[<{selstart}Description "hi"{selend}>]
let x = ""
Loading