From 9c88d40cac8ead6684da8fb696c3493b294b1115 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 15:17:06 +0200 Subject: [PATCH 1/3] Add failing tests for indexed-setter named-arg ICE (#16034) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../IndexedSetterNamedArgTests.fs | 71 +++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 2 files changed, 72 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs new file mode 100644 index 00000000000..e98b46f27e0 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs @@ -0,0 +1,71 @@ +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 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 @@ + From b89e2b2fe3859ca0726160b65f5b0ee16dfa36c9 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 15:49:19 +0200 Subject: [PATCH 2/3] Fix ICE on named-arg indexer setter (#16034) When a named argument matches one of the indexer arguments of a property setter, the remaining unnamed called args may number fewer than 2. The ParamArray detection used nUnnamedCalledArgs-2 unconditionally for indexer setters, leading to a negative index and FS0193. Gate the setter-shape indexing on nUnnamedCalledArgs >= 2 and fall back to the regular path otherwise. Also skip the unnamed-arg-prefix deprecation check for indexer setters, since the trailing 'value' arg breaks the strict (i,j) position match when an indexer arg is supplied by name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/Expressions/CheckExpressions.fs | 1 + src/Compiler/Checking/MethodCalls.fs | 11 +++++++++-- src/Compiler/Checking/MethodCalls.fsi | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 37ff7f54b66..97f82e67fbe 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 From be1ffe99c304738bc44d21fa664451c178e1c530 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 28 May 2026 16:15:16 +0200 Subject: [PATCH 3/3] Add variants, release notes for #16034 indexer-setter fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.FSharp.Compiler.Service/9.0.300.md | 1 + .../IndexedSetterNamedArgTests.fs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) 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/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs index e98b46f27e0..51bf0dfc291 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/IndexedSetterNamedArgTests.fs @@ -69,3 +69,53 @@ 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