Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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,84 @@
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 tryGetNamedRef (expr: IFSharpExpression) =
let binaryAppExpr = expr.As<IBinaryAppExpr>()
if isNull binaryAppExpr then null else
if isNull binaryAppExpr then None else

if binaryAppExpr.Operator.Reference.GetName() <> "=" then None else

match binaryAppExpr.LeftArgument with
| :? IReferenceExpr as refExpr -> Some refExpr
| _ -> None


let tryGetNamedArg (expr: IFSharpExpression) =
match tryGetNamedRef expr with
| None -> null
| Some refExpr -> refExpr.Reference.Resolve().DeclaredElement.As<IParameter>()


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

if binaryAppExpr.Operator.Reference.GetName() <> "=" then null else
let namedRefOpt = tryGetNamedRef expr
let namedParam =
match namedRefOpt with
| None -> null
| Some namedRef -> namedRef.Reference.Resolve().DeclaredElement.As<IParameter>()
if isNotNull namedParam then namedParam else

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

refExpr.Reference.Resolve().DeclaredElement.As<IParameter>()
let mfv =
symbolReference.TryGetFSharpSymbol()
|> Option.bind (function
| :? FSharpMemberOrFunctionOrValue as mfv -> Some mfv
| _ -> None)

let getMatchingParameter (initialExpr: IFSharpExpression) =
let expr = initialExpr.IgnoreInnerParens()
let tupleExpr = TupleExprNavigator.GetByExpression(expr)
let tupleExprContext = tupleExpr.IgnoreParentParens()
match mfv with
| None -> null
| Some mfv ->

let appExpr = PrefixAppExprNavigator.GetByArgumentExpression(if isNull tupleExpr then expr else tupleExprContext)
if isNull appExpr then null else
let paramOwner = symbolReference.Resolve().DeclaredElement.As<IParametersOwner>()
if isNull paramOwner then null else

let refExpr = appExpr.FunctionExpression.As<IReferenceExpr>()
if isNull refExpr then null else
let param =
match namedRefOpt with
| Some namedRef ->
// If this is a named argument, but FCS couldn't match it, try matching ourselves by name
paramOwner.Parameters
|> Seq.tryFind (fun param -> param.ShortName = namedRef.Reference.GetName())
| None ->

use compilationContextCookie = CompilationContextCookie.OverrideOrCreate(expr.GetResolveContext())
let args = argsOwner.ParameterArguments

let parameter = tryGetNamedArg initialExpr
if isNotNull parameter then parameter else
match args |> Seq.tryFindIndex (fun argExpr -> expr.Equals(argExpr)) with
| None -> None
| Some paramIndex ->

let method = refExpr.Reference.Resolve().DeclaredElement.As<IMethod>()
let parameters = method.Parameters
if parameters.Count = 1 then parameters.[0] else
let invokingExtensionMethod = mfv.IsExtensionMember && Some mfv.ApparentEnclosingEntity <> mfv.DeclaringEntity
let offset = if invokingExtensionMethod then 1 else 0
Some paramOwner.Parameters.[paramIndex + offset]

let index = tupleExpr.Expressions.IndexOf(expr)
if index < parameters.Count then parameters.[index] else null
// Skip unnamed parameters
match param with
| Some param when param.ShortName <> SharedImplUtil.MISSING_DECLARATION_NAME -> param
| _ -> null

[<Language(typeof<FSharpLanguage>)>]
type FSharpMethodInvocationUtil() =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;
using FSharp.Compiler.SourceCodeServices;
using JetBrains.ReSharper.Plugins.FSharp.Psi.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl
{
public static class FSharpArgumentsOwnerUtil
{
public static IList<IArgument> CalculateParameterArguments(IFSharpReferenceOwner referenceOwner,
IEnumerable<IFSharpExpression> appliedExpressions)
{
if (!(referenceOwner.Reference?.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;

// e.g. F# extension methods with 0 parameters
if (paramGroup.Count == 0)
return EmptyList<IArgument>.Instance;

if (paramGroup.Count == 1)
return new[] {argExpr as IArgument};

var tupleExprs = argExpr is ITupleExpr tupleExpr
? (IReadOnlyList<IFSharpExpression>) tupleExpr.Expressions
: EmptyList<IFSharpExpression>.Instance;

return Enumerable.Range(0, paramGroup.Count)
.Select(i => i < tupleExprs.Count ? tupleExprs[i] as IArgument : null);
})
.ToList();
}
}
}
10 changes: 10 additions & 0 deletions ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/Attribute.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
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;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
{
internal partial class Attribute
{
private readonly CachedPsiValue<IList<IArgument>> myParameterArguments = new FileCachedPsiValue<IList<IArgument>>();

protected override FSharpSymbolReference CreateReference() =>
new CtorReference(this);

public override IFSharpIdentifier FSharpIdentifier => ReferenceName?.Identifier;

public IList<IArgument> ParameterArguments => myParameterArguments.GetValue(this, () => FSharpArgumentsOwnerUtil.CalculateParameterArguments(this, new[] { ArgExpression?.Expression }));

public IList<IArgument> Arguments => ParameterArguments.WhereNotNull().ToList();
}
}
34 changes: 34 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,34 @@
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;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Impl.Tree
{
internal partial class NewExpr
{
private readonly CachedPsiValue<IList<IArgument>> myParameterArguments = new FileCachedPsiValue<IList<IArgument>>();
public FSharpSymbolReference Reference { get; private set; }

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

public IFSharpIdentifier FSharpIdentifier => TypeName?.Identifier;

public IFSharpReferenceOwner SetName(string name) => this;

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

public IList<IArgument> ParameterArguments => myParameterArguments.GetValue(this,
() => FSharpArgumentsOwnerUtil.CalculateParameterArguments(this, new[] {ArgumentExpression}));

public IList<IArgument> Arguments => ParameterArguments.WhereNotNull().ToList();
}
}
42 changes: 35 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,54 @@
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
private readonly CachedPsiValue<IList<IArgument>> myParameterArguments = new FileCachedPsiValue<IList<IArgument>>();

public FSharpSymbolReference Reference => InvokedReferenceExpression?.Reference;

public IReferenceExpr InvokedReferenceExpression
{
get
{
var argsCount = 0;
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;
}
}

public IFSharpIdentifier FSharpIdentifier => InvokedReferenceExpression?.Identifier;

argsCount++;
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 +58,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 +80,11 @@ public IList<IExpression> Arguments
}
}

public IList<IArgument> Arguments => ParameterArguments.Where(arg => arg != null).ToList();

public IList<IArgument> ParameterArguments => myParameterArguments.GetValue(this,
() => FSharpArgumentsOwnerUtil.CalculateParameterArguments(this, AppliedExpressions));

public override IType Type()
{
var reference = InvokedFunctionReference;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using JetBrains.ReSharper.Resources.Shell;
using JetBrains.Util;
using JetBrains.Util.DataStructures;
using Microsoft.FSharp.Core;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Resolve
{
Expand All @@ -34,6 +35,9 @@ public FSharpSymbolUse GetSymbolUse() =>
public virtual FSharpSymbol GetFSharpSymbol() =>
GetSymbolUse()?.Symbol;

public FSharpOption<FSharpSymbol> TryGetFSharpSymbol() =>
OptionModule.OfObj(GetFSharpSymbol());

public override ResolveResultWithInfo ResolveWithoutCache()
{
if (!myOwner.IsValid())
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
{
}
}
13 changes: 13 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,13 @@
using System.Collections.Generic;
using JetBrains.ReSharper.Psi.Tree;

namespace JetBrains.ReSharper.Plugins.FSharp.Psi.Tree
{
public interface IFSharpArgumentsOwner : IArgumentsOwner, IFSharpReferenceOwner
{
/// List of arguments aligned with their matching parameter.
/// e.g. index #2 is the argument that matches with param #2 on the invoked reference.
/// A null element at a given index means there is no argument matching that parameter.
IList<IArgument> ParameterArguments { 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
Loading