diff --git a/ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/RecordExpr.cs b/ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/RecordExpr.cs index 96ed7ddf4a..4267eec7ea 100644 --- a/ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/RecordExpr.cs +++ b/ReSharper.FSharp/src/FSharp.Psi/src/Impl/Tree/RecordExpr.cs @@ -60,13 +60,8 @@ public override FSharpSymbol GetFSharpSymbol() if (symbolUse?.Symbol is FSharpField field) return field.DeclaringEntity?.Value; - // todo: cover other contexts - var sequentialExpr = SequentialExprNavigator.GetByExpression(RecordExpr.IgnoreParentParens()); - if (sequentialExpr != null && sequentialExpr.Expressions.Last() != RecordExpr) - return null; - var exprToGetBy = sequentialExpr ?? RecordExpr.IgnoreParentParens(); - - var binding = BindingNavigator.GetByExpression(exprToGetBy); + var functionExpr = GetFunctionExpression(RecordExpr); + var binding = BindingNavigator.GetByExpression(functionExpr); if (binding == null || !(binding.HeadPattern is INamedPat namedPat)) return null; @@ -79,6 +74,25 @@ public override FSharpSymbol GetFSharpSymbol() return entity.IsFSharpRecord ? entity : null; } + private static IFSharpExpression GetFunctionExpression(IFSharpExpression expression) + { + IFSharpExpression currentExpression = IfExprNavigator.GetByThenExpr(expression); + currentExpression ??= IfExprNavigator.GetByElseExpr(expression); + var matchClause = MatchClauseNavigator.GetByExpression(expression); + currentExpression = matchClause == null ? currentExpression : MatchExprNavigator.GetByClause(matchClause); + currentExpression ??= MatchLambdaExprNavigator.GetByClause(matchClause); + + var sequentialExpr = SequentialExprNavigator.GetByExpression(expression); + currentExpression ??= sequentialExpr; + if (sequentialExpr != null && sequentialExpr.Expressions.Last() != expression) + return null; + + if (currentExpression == null) + return expression; + + return GetFunctionExpression(currentExpression); + } + public override string GetName() => SharedImplUtil.MISSING_DECLARATION_NAME; diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs new file mode 100644 index 0000000000..10a26e5ef0 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs @@ -0,0 +1,9 @@ +type R = { A: int; B: int } + +let r (shouldFail : bool) : R = + if shouldFail then + failwith "" + elif true then + {}{caret} + else + failwith "" \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs.gold b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs.gold new file mode 100644 index 0000000000..73378c8f78 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Elif statement.fs.gold @@ -0,0 +1,10 @@ +type R = { A: int; B: int } + +let r (shouldFail : bool) : R = + if shouldFail then + failwith "" + elif true then + { A = {selstart}failwith "todo"{selend} + B = failwith "todo" } + else + failwith "" \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function lambda.fs b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function lambda.fs new file mode 100644 index 0000000000..735571663d --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function lambda.fs @@ -0,0 +1,5 @@ +type R = { A: int; B: int } +module foo = + let r (input : int option) : (R option) = + input + |> Option.map(fun x -> {}) \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs new file mode 100644 index 0000000000..8a14dc5544 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs @@ -0,0 +1,3 @@ +type R = { A: int; B: int } +module foo = + let r : (string option -> R) = function | Some _ -> {}{caret} | None -> {} \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs.gold b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs.gold new file mode 100644 index 0000000000..de215fc2a4 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Function statement.fs.gold @@ -0,0 +1,4 @@ +type R = { A: int; B: int } +module foo = + let r : (string option -> R) = function | Some _ -> { A = {selstart}failwith "todo"{selend} + B = failwith "todo" } | None -> {} \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs new file mode 100644 index 0000000000..921392fadc --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs @@ -0,0 +1,11 @@ +type R = { A: int; B: int } + +let r (shouldFail : bool) : R = + () + if shouldFail then + failwith "" + else + if true then + {}{caret} + else + failwith "" diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs.gold b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs.gold new file mode 100644 index 0000000000..736b62d5c3 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/If statement in function.fs.gold @@ -0,0 +1,12 @@ +type R = { A: int; B: int } + +let r (shouldFail : bool) : R = + () + if shouldFail then + failwith "" + else + if true then + { A = {selstart}failwith "todo"{selend} + B = failwith "todo" } + else + failwith "" diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs new file mode 100644 index 0000000000..059f833044 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs @@ -0,0 +1,7 @@ +type R = { A: int; B: int } + +let r (someOption : option int) : R = + () + match someOption with + | Some value -> {A = value; B = value} + | None -> {}{caret} \ No newline at end of file diff --git a/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs.gold b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs.gold new file mode 100644 index 0000000000..d3769f6ea3 --- /dev/null +++ b/ReSharper.FSharp/test/data/features/quickFixes/generateMissingRecordFields/Match statement in function.fs.gold @@ -0,0 +1,8 @@ +type R = { A: int; B: int } + +let r (someOption : option int) : R = + () + match someOption with + | Some value -> {A = value; B = value} + | None -> { A = {selstart}failwith "todo"{selend} + B = failwith "todo" } \ No newline at end of file diff --git a/ReSharper.FSharp/test/src/FSharp.Tests/QuickFixes/GenerateMissingRecordFieldsTest.fs b/ReSharper.FSharp/test/src/FSharp.Tests/QuickFixes/GenerateMissingRecordFieldsTest.fs index 6bfb957e15..64b027a6d6 100644 --- a/ReSharper.FSharp/test/src/FSharp.Tests/QuickFixes/GenerateMissingRecordFieldsTest.fs +++ b/ReSharper.FSharp/test/src/FSharp.Tests/QuickFixes/GenerateMissingRecordFieldsTest.fs @@ -23,8 +23,12 @@ type GenerateMissingRecordFieldsTest() = [] member x.``Multiline 01``() = x.DoNamedTest() [] member x.``Multiline 02``() = x.DoNamedTest() - + [] member x.``Empty function``() = x.DoNamedTest() + [] member x.``Function statement``() = x.DoNamedTest() + [] member x.``If statement in function``() = x.DoNamedTest() + [] member x.``Elif statement``() = x.DoNamedTest() + [] member x.``Match statement in function``() = x.DoNamedTest() // The quickfix should apply if the empty record is the final statement of a function binding, as that's what the // annotated return type pertains to.