diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md index 7f5ab7289d2..fb4f48fd425 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md @@ -1,4 +1,5 @@ ### Fixed +* Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034)) * Fix missing TailCall warning in TOp.IntegerForLoop ([PR #18399](https://github.com/dotnet/fsharp/pull/18399)) * Fix classification of `nameof` in `nameof<'T>`, `match … with nameof ident -> …`. ([Issue #10026](https://github.com/dotnet/fsharp/issues/10026), [PR #18300](https://github.com/dotnet/fsharp/pull/18300)) * Fix Realsig+ generates nested closures with incorrect Generic ([Issue #17797](https://github.com/dotnet/fsharp/issues/17797), [PR #17877](https://github.com/dotnet/fsharp/pull/17877)) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 83e172628b5..a0606be4a99 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10572,6 +10572,7 @@ and TcMethodApplication TcAdhocChecksOnLibraryMethods cenv env isInstance finalCalledMeth finalCalledMethInfo objArgs mMethExpr mItem if not finalCalledMeth.IsIndexParamArraySetter && + not finalCalledMeth.IsIndexerSetter && (finalCalledMeth.ArgSets |> List.existsi (fun i argSet -> argSet.UnnamedCalledArgs |> List.existsi (fun j ca -> ca.Position <> (i, j)))) then errorR(Deprecated(FSComp.SR.tcUnnamedArgumentsDoNotFormPrefix(), mMethExpr)) diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 07e0e95baed..5bce3a8ee39 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -658,19 +658,24 @@ type CalledMeth<'T> let nUnnamedCallerArgs = unnamedCallerArgs.Length let nUnnamedCalledArgs = unnamedCalledArgs.Length + // For an indexer setter the ParamArray slot is the second-to-last unnamed called arg + // (the last one is the setter 'value'). When a named argument matched one of the indexer + // args, fewer unnamed called args remain and the setter degenerates to the non-ParamArray + // shape - fall back to the regular indexing in that case. + let useIndexerSetterShape = isIndexerSetter && nUnnamedCalledArgs >= 2 let supportsParamArgs = allowParamArgs && nUnnamedCalledArgs >= 1 && nUnnamedCallerArgs >= nUnnamedCalledArgs-1 && let possibleParamArg = - if isIndexerSetter then + if useIndexerSetterShape then unnamedCalledArgs[nUnnamedCalledArgs-2] else unnamedCalledArgs[nUnnamedCalledArgs-1] possibleParamArg.IsParamArray && isArray1DTy g possibleParamArg.CalledArgumentType if supportsParamArgs then - if isIndexerSetter then + if useIndexerSetterShape then // Note, for an indexer setter nUnnamedCalledArgs will be at least two, and normally exactly 2 let unnamedCalledArgs2 = unnamedCalledArgs[0..unnamedCalledArgs.Length-3] @ @@ -808,6 +813,8 @@ type CalledMeth<'T> member x.IsIndexParamArraySetter = isIndexerSetter && x.UsesParamArrayConversion + member x.IsIndexerSetter = isIndexerSetter + member x.ParamArrayCalledArgOpt = x.ArgSets |> List.tryPick (fun argSet -> argSet.ParamArrayCalledArgOpt) member x.ParamArrayCallerArgs = x.ArgSets |> List.tryPick (fun argSet -> if Option.isSome argSet.ParamArrayCalledArgOpt then Some argSet.ParamArrayCallerArgs else None ) diff --git a/src/Compiler/Checking/MethodCalls.fsi b/src/Compiler/Checking/MethodCalls.fsi index 1a62bbe01bd..53c3486807c 100644 --- a/src/Compiler/Checking/MethodCalls.fsi +++ b/src/Compiler/Checking/MethodCalls.fsi @@ -271,6 +271,8 @@ type CalledMeth<'T> = member IsIndexParamArraySetter: bool + member IsIndexerSetter: bool + /// The method we're attempting to call member Method: MethInfo diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs new file mode 100644 index 00000000000..51bf0dfc291 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs @@ -0,0 +1,121 @@ +module FSharp.Compiler.ComponentTests.ErrorMessages.IndexedSetterNamedArgTests + +open Xunit +open FSharp.Test.Compiler + +[] +let ``Indexed property setter with named argument does not ICE - intrinsic``() = + FSharp """ +module Test +type T() = + member x.Item + with get (a1: obj) = 1 + and set (a1: obj) (value: int) = () + +let t = T() +t.Item(a1 = "x") <- 1 + """ + |> compile + |> shouldSucceed + +[] +let ``Indexed property setter with named argument does not ICE - named property``() = + FSharp """ +module Test +type T() = + member x.indexed1 + with get (a1: obj) = 1 + and set (a1: obj) (value: int) = () + +let t = T() +t.indexed1(a1 = "x") <- 1 + """ + |> compile + |> shouldSucceed + +[] +let ``Indexed property setter with named argument does not ICE - extension``() = + FSharp """ +module Test +type T() = + member x.indexed1 + with get (a1: obj) = 1 + and set (a1: obj) (value: int) = () + +module Extensions = + type T with + member x.indexed1 + with get (aa1: obj) = 1 + and set (aa1: obj) (value: int) = () + +open Extensions +let t = T() +t.indexed1(aa1 = "x") <- 1 + """ + |> compile + |> shouldSucceed + +[] +let ``Indexed property getter with named argument still works``() = + FSharp """ +module Test +type T() = + member x.indexed1 + with get (a1: obj) = 1 + and set (a1: obj) (value: int) = () + +let t = T() +let _ = t.indexed1(a1 = "x") + """ + |> compile + |> shouldSucceed + +[] +[] +[] +[] +let ``Indexed setter call shapes compile`` (call: string) = + FSharp $""" +module Test +type T() = + member x.indexed1 + with get (a1: obj) = 1 + and set (a1: obj) (value: int) = () + +let t = T() +{call} + """ + |> compile + |> shouldSucceed + +[] +let ``Multi-arg indexer setter with named args compiles``() = + FSharp """ +module Test +type M() = + member x.Item + with get (a: int, b: string) = 1 + and set (a: int, b: string) (v: int) = () + +let m = M() +m.[a = 1, b = "x"] <- 5 +m.[b = "x", a = 1] <- 5 +m.[1, b = "x"] <- 5 + """ + |> compile + |> shouldSucceed + +[] +let ``Default Item indexer setter with named arg compiles``() = + FSharp """ +module Test +type D() = + member x.Item + with get (k: string) = 1 + and set (k: string) (v: int) = () + +let d = D() +d.[k = "x"] <- 1 + """ + |> compile + |> shouldSucceed diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index acfeca79f35..9a0431c0677 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -294,6 +294,7 @@ +