diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 221cb6bde01..4c284c5ab0d 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -51,6 +51,7 @@ * Fix internal error when using custom attribute with `[]` value type parameter and no `[]`. ([Issue #8353](https://github.com/dotnet/fsharp/issues/8353), [PR #19484](https://github.com/dotnet/fsharp/pull/19484)) * Fix parallel compilation of scripts ([PR #19649](https://github.com/dotnet/fsharp/pull/19649)) * Parser: fix unexpected diagnostics in debug builds, improve error messages ([PR #19730](https://github.com/dotnet/fsharp/pull/19730)) +* Fix `[] ?param` optional parameters could not be passed using the explicit `?param = expr` caller-side syntax with a `ValueOption` value. ([Issue #19711](https://github.com/dotnet/fsharp/issues/19711), [PR #19742](https://github.com/dotnet/fsharp/pull/19742)) ### Added diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 30d49e776c8..c3c416c4f13 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10135,13 +10135,21 @@ and TcMethodApplication_SplitSynArguments | _ -> let unnamedCurriedCallerArgs = unnamedCurriedCallerArgs |> List.mapSquared MakeUnnamedCallerArgInfo + let supportsValueOptionalArgs = + g.langVersion.SupportsFeature LanguageFeature.SupportValueOptionsAsOptionalParameters let namedCurriedCallerArgs = namedCurriedCallerArgs |> List.mapSquared (fun (isOpt, nm, x) -> let ty = GetNewInferenceTypeForMethodArg cenv x // #435263: compiler crash with .net optional parameters and F# optional syntax - // named optional arguments should always have option type - // STRUCT OPTIONS: if we allow struct options as optional arguments then we should relax this and rely - // on later inference to work out if this is a struct option or ref option - let ty = if isOpt then mkOptionTy denv.g ty else ty + // For LangVersion < 10 named optional arguments are constrained to option type here + // so that an early, friendly error is reported. With LangVersion >= 10 the parameter + // may be a struct-option (voption) — see issue dotnet/fsharp#19711 — so we leave the + // type as a fresh inference variable and let later unification (CalleeSide branch in + // AdjustCalledArgTypeForOptionals) pick option<_> or voption<_>. + let ty = + if isOpt && not supportsValueOptionalArgs then + mkOptionTy denv.g ty + else + ty nm, isOpt, x, ty, x.Range) (Some (unnamedCurriedCallerArgs, namedCurriedCallerArgs), None, exprTy) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs index 48b27ecb666..0af170a336a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs @@ -634,4 +634,81 @@ let main _args = |> ILVerifierModule.verifyPEFileWithSystemDlls |> run |> verifyOutputContains [|"main;18;hello;42;"|] - \ No newline at end of file + + // Regression test for https://github.com/dotnet/fsharp/issues/19711 + // `?name = expr` (explicit caller-side question-mark syntax) must accept a + // ValueOption when the declared parameter is `[] ?name`. + [] + let ``ValueOption optional parameters can be passed using ?param syntax on methods`` () = + FSharp """ +module Program +type OptionalArguments() = + member _.NoProblem (?number : int32) = + match number with + | Some v -> printfn "RSome %d" v + | None -> printfn "RNone" + member _.Problem ([] ?number : int32) = + match number with + | ValueSome v -> printfn "VSome %d" v + | ValueNone -> printfn "VNone" + +[] +let main _ = + let o = OptionalArguments() + o.NoProblem 123 + o.NoProblem (number = 123) + o.NoProblem (?number = None) + o.NoProblem (?number = Some 123) + o.NoProblem (?number = Unchecked.defaultof<_>) + o.Problem 123 + o.Problem (number = 123) + o.Problem (?number = ValueNone) + o.Problem (?number = ValueSome 123) + o.Problem (?number = Unchecked.defaultof<_>) + 0 + """ + |> asExe + |> withOptions ["--nowarn:988"] + |> compileAndRun + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "RSome 123"; "RSome 123"; "RNone"; "RSome 123"; "RNone" + "VSome 123"; "VSome 123"; "VNone"; "VSome 123"; "VNone" + ] + + [] + let ``ValueOption optional parameters can be passed using ?param syntax on constructors`` () = + FSharp """ +module Program +type X<'T>([] ?x : 'T) = + member _.M() = + match x with + | ValueSome v -> printfn "VSome %A" v + | ValueNone -> printfn "VNone" + +[] +let main _ = + X(?x = ValueSome 1).M() + X(?x = ValueNone).M() + X(?x = Unchecked.defaultof<_>).M() + 0 + """ + |> asExe + |> withOptions ["--nowarn:988"] + |> compileAndRun + |> shouldSucceed + |> withOutputContainsAllInOrder ["VSome 1"; "VNone"; "VNone"] + + [] + let ``ValueOption optional parameters via ?param syntax fail with langversion 9`` () = + FSharp """ +module Program +type X() = + static member M ([] ?x : int) = () + +X.M(?x = ValueSome 1) + """ + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnosticMessageMatches "voption"