From 3662dfcdf644c2a2264b08d41533a3488ced6de3 Mon Sep 17 00:00:00 2001 From: Maxime Mangel Date: Wed, 9 Aug 2023 15:58:05 +0200 Subject: [PATCH] Function decorated with [] without arguments provided should take an empty object Fix #3480 --- src/Fable.AST/Fable.fs | 11 ++++++----- src/Fable.Cli/RELEASE_NOTES.md | 4 ++++ src/Fable.Transforms/FSharp2Fable.Util.fs | 7 +++++++ src/Fable.Transforms/Fable2Babel.fs | 21 ++++++++++++++++++++- tests/Js/Main/OptionTests.fs | 7 +++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/Fable.AST/Fable.fs b/src/Fable.AST/Fable.fs index de8e12bb25..9b311cf61e 100644 --- a/src/Fable.AST/Fable.fs +++ b/src/Fable.AST/Fable.fs @@ -31,10 +31,15 @@ type EntityRef = | PrecompiledLib(p,_) -> Some p | AssemblyPath _ | CoreAssemblyName _ -> None +type Attribute = + abstract Entity: EntityRef + abstract ConstructorArgs: obj list + type MemberRefInfo = { IsInstance: bool CompiledName: string - NonCurriedArgTypes: Type list option } + NonCurriedArgTypes: Type list option + Attributes : Attribute seq } type MemberRef = | MemberRef of declaringEntity: EntityRef * info: MemberRefInfo @@ -44,10 +49,6 @@ type DeclaredType = abstract Entity: EntityRef abstract GenericArgs: Type list -type Attribute = - abstract Entity: EntityRef - abstract ConstructorArgs: obj list - type Field = abstract Name: string abstract FieldType: Type diff --git a/src/Fable.Cli/RELEASE_NOTES.md b/src/Fable.Cli/RELEASE_NOTES.md index 74a6d95796..c2d62d0e31 100644 --- a/src/Fable.Cli/RELEASE_NOTES.md +++ b/src/Fable.Cli/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### Unreleased + +* Fix #3480: Function decorated with `[]` without arguments provided should take an empty object + ### 4.1.4 * Fix #3438: Source maps diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 66853e4a94..e32499c4e5 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -1694,10 +1694,14 @@ module Util = |> Seq.mapToList (fun p -> makeType Map.empty p.Type) |> Some else None + + let fableMemberFunctionOrValue = FsMemberFunctionOrValue(memb) :> Fable.MemberFunctionOrValue + Fable.MemberRef(FsEnt.Ref(ent), { CompiledName = memb.CompiledName IsInstance = memb.IsInstanceMember NonCurriedArgTypes = nonCurriedArgTypes + Attributes = fableMemberFunctionOrValue.Attributes }) | ent -> let entRef = ent |> Option.map FsEnt.Ref @@ -1712,10 +1716,13 @@ module Util = match memb.DeclaringEntity with // We cannot retrieve compiler generated members from the entity | Some ent when not memb.IsCompilerGenerated -> + let fableMemberFunctionOrValue = FsMemberFunctionOrValue(memb) :> Fable.MemberFunctionOrValue + Fable.MemberRef(FsEnt.Ref(ent), { CompiledName = memb.CompiledName IsInstance = memb.IsInstanceMember NonCurriedArgTypes = None + Attributes = fableMemberFunctionOrValue.Attributes }) | ent -> let entRef = ent |> Option.map FsEnt.Ref diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 3b8037ef12..9ae9512700 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -1534,7 +1534,26 @@ module Util = |> Option.map (splitNamedArgs args) |> function | None -> args, None - | Some(args, []) -> args, None + | Some(args, []) -> + // Detect if the method has a ParamObject attribute + // If yes and no argument is passed, pass an empty object + // See https://github.com/fable-compiler/Fable/issues/3480 + match callInfo.MemberRef with + | Some (Fable.MemberRef (_, info)) -> + let hasParamObjectAttribute = + info.Attributes + |> Seq.tryFind (fun attr -> + attr.Entity.FullName = Atts.paramObject + ) + |> Option.isSome + + if hasParamObjectAttribute then + args, Some (makeJsObject []) + else + args, None + | _ -> + // Here detect empty named args + args, None | Some(args, namedArgs) -> let objArg = namedArgs diff --git a/tests/Js/Main/OptionTests.fs b/tests/Js/Main/OptionTests.fs index 6b31610274..1b8aa03b8f 100644 --- a/tests/Js/Main/OptionTests.fs +++ b/tests/Js/Main/OptionTests.fs @@ -62,6 +62,12 @@ type VeryOptionalClass () = type StaticClass = [] static member NamedParams(foo: string, ?bar: int) = int foo + (defaultArg bar -3) + [] + static member NamedParams(?name : string, ?age: int) = + let name = defaultArg name "John" + let age = defaultArg age 30 + + $"%s{name} is %d{age} years old" static member FSharpOptionalParam(?value: bool) = defaultArg value true static member FSharpOptionalParam2(?value: unit) = Option.isSome value static member DefaultParam([] value: bool) = value @@ -142,6 +148,7 @@ let tests = StaticClass.NamedParams(foo="5", bar=4) |> equal 9 StaticClass.NamedParams(foo="3", ?bar=Some 4) |> equal 7 StaticClass.NamedParams(foo="14") |> equal 11 + StaticClass.NamedParams() |> equal "John is 30 years old" testCase "F# optional param works" <| fun () -> let mutable f1 = fun (v: bool) ->