From fa324a85492b4ac053153b40b9ae5ec669b87b31 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 22 Nov 2025 16:11:33 -0500 Subject: [PATCH 1/2] Add test --- .../OptionalArguments/OptionalArguments.fs | 6 ++++++ .../RefAndStructOptionalArgParity.fs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/RefAndStructOptionalArgParity.fs 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 508314ee941..54c5e9ea27c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/OptionalArguments.fs @@ -115,6 +115,12 @@ module MemberDefinitions_OptionalArguments = |> verifyCompileAndRun |> shouldSucceed + [] + let ``RefAndStructOptionalArgParity_fs`` compilation = + compilation + |> verifyCompileAndRun + |> shouldSucceed + [] let ``Optional Arguments can't be a ValueOption+StructAttribute attribute with langversion=9`` () = let source = diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/RefAndStructOptionalArgParity.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/RefAndStructOptionalArgParity.fs new file mode 100644 index 00000000000..39512aa2576 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/OptionalArguments/RefAndStructOptionalArgParity.fs @@ -0,0 +1,17 @@ +module M + +type T = + static member MRef (?x : int) = () + static member MStruct ([] ?x : int) = () + +T.MRef 3 +T.MRef () +T.MRef (x=3) +T.MRef (?x=None) +T.MRef (?x=Some 3) + +T.MStruct 3 +T.MStruct () +T.MStruct (x=3) +T.MStruct (?x=ValueNone) +T.MStruct (?x=ValueSome 3) From 669a6bbb80fce15b0f48d7bda315a744a6199e89 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 22 Nov 2025 16:22:44 -0500 Subject: [PATCH 2/2] Support explicit struct optional args --- src/Compiler/Checking/ConstraintSolver.fs | 8 ++++++ .../Checking/Expressions/CheckExpressions.fs | 26 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index 8b633577c5a..5168db730cc 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -3213,6 +3213,10 @@ and ArgsMustSubsumeOrConvert match usesTDC with | TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv) | TypeDirectedConversionUsed.No -> () + let callerTy = + let g = csenv.g + if isValueOptionTy g calledArgTy && isOptionTy g callerTy then mkValueOptionTy g (destOptionTy g callerTy) + else callerTy do! SolveTypeSubsumesTypeWithReport csenv ndeep m trace cxsln (Some calledArg.CalledArgumentType) calledArgTy callerTy if g.langVersion.SupportsFeature(LanguageFeature.WarnWhenUnitPassedToObjArg) && isUnitTy g callerTy && isObjTyAnyNullness g calledArgTy then do! WarnD(Error(FSComp.SR.tcUnitToObjSubsumption(), m)) @@ -3246,6 +3250,10 @@ and ArgsMustSubsumeOrConvertWithContextualReport match usesTDC with | TypeDirectedConversionUsed.Yes(warn, _, _) -> do! WarnD(warn csenv.DisplayEnv) | TypeDirectedConversionUsed.No -> () + let callerArgTy = + let g = csenv.g + if isValueOptionTy g calledArgTy && isOptionTy g callerArgTy then mkValueOptionTy g (destOptionTy g callerArgTy) + else callerArgTy do! SolveTypeSubsumesTypeWithWrappedContextualReport csenv ndeep m trace cxsln (Some calledArg.CalledArgumentType) calledArgTy callerArgTy (fun e -> ArgDoesNotMatchError(e :?> _, calledMeth, calledArg, callerArg)) return usesTDC } diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 527a09cf1d0..072cab259e5 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10092,7 +10092,31 @@ and TcMethodApplication_CheckArguments // This is the case where some explicit arguments have been given. let unnamedCurriedCallerArgs = unnamedCurriedCallerArgs |> List.mapSquared (fun (argExpr, argTy, mArg) -> CallerArg(argTy, mArg, false, argExpr)) - let namedCurriedCallerArgs = namedCurriedCallerArgs |> List.mapSquared (fun (id, isOpt, argExpr, argTy, mArg) -> CallerNamedArg(id, CallerArg(argTy, mArg, isOpt, argExpr))) + let namedCurriedCallerArgs = + if + g.langVersion.SupportsFeature LanguageFeature.SupportValueOptionsAsOptionalParameters + && namedCurriedCallerArgs |> List.existsSquared (fun (_, isOpt, _, _, _) -> isOpt) + then + let voptionParams = + (Set.empty, preArgumentTypeCheckingCalledMethGroup) + ||> List.fold (fun acc calledMeth -> + (acc, calledMeth.AssignedNamedArgs) + ||> List.fold (fun acc args -> + (acc, args) + ||> List.fold (fun acc arg -> + match arg.CalledArg.OptArgInfo, arg.NamedArgIdOpt with + | CalleeSide, Some argId when isValueOptionTy g arg.CalledArg.CalledArgumentType && arg.CallerArg.IsExplicitOptional -> acc |> Set.add argId.idText + | _ -> acc))) + + namedCurriedCallerArgs |> List.mapSquared (fun (id : Ident, isOpt, argExpr, argTy, mArg) -> + let argTy = + if isOpt && voptionParams |> Set.contains id.idText && isOptionTy g argTy then mkValueOptionTy g (destOptionTy g argTy) + else argTy + + CallerNamedArg(id, CallerArg(argTy, mArg, isOpt, argExpr))) + else + namedCurriedCallerArgs |> List.mapSquared (fun (id, isOpt, argExpr, argTy, mArg) -> CallerNamedArg(id, CallerArg(argTy, mArg, isOpt, argExpr))) + // Collect the information for F# 3.1 lambda propagation rule, and apply the caller's object type to the method's object type if the rule is relevant. let lambdaPropagationInfo =