fable-compiler / Fable

F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
http://fable.io/
MIT License
2.9k stars 295 forks source link

python: ParamObject does not generate named arguments when used in with EmitMethod #3871

Open joprice opened 2 months ago

joprice commented 2 months ago

Description

When attempting to extend a type with a method that takes keyword args, the generated code ignores the keyword arguments if EmitMethod is present. This isn't required when defining the type directly, but it is when extending an existing type.

Repro code

open Fable.Core

[<Erase>]
type IExports =
  [<ParamObject(1)>]
  [<EmitMethod("makedirs")>]
  abstract makedirs: path: string * ?exist_ok:bool -> unit

  [<ParamObject(1)>]
  abstract makedirs2: path: string * ?exist_ok:bool -> unit

type IExports with 
  [<ParamObject(1)>]
  [<EmitMethod("makedirs")>]
  member _.makedirs3(path: string, ?exist_ok:bool): unit = nativeOnly

[<ImportAll("os")>]
let os: IExports = nativeOnly

os.makedirs ("data", exist_ok = true)
os.makedirs2 ("data", exist_ok = true)
os.makedirs3 ("data", exist_ok = true)
import os
os.makedirs("data")

os.makedirs2("data", exist_ok = True)

os.makedirs("data")

https://fable.io/repl/#?code=PYBwpgdgBAYghgIwDZgHQGFgCcwChcDaAPAKJZwDOYAfALq4AuAnuFAJIkAeI2DFUAXlxQoxAApxyAWwDyCAFZgAxgwAUARgCUdYaNJSAlgwCyYBgAtgAE1UAiKXADWYKwawVb2+iMQUG5FSgHZ1d3AC4oEDgLCL8sAwgAcygAKigAfjBOAz8KAH1gRzCEYGAkKABaaigAVwgjfBFxSThZBWU1LR0fBDi4QOCXNwoAJgiomKg4hOS0zOzcgqKSssrquobGFjB2Lh4sPigAdyNzKF1m6TlFFQ0vC-0jUwtrO0HQj3uRKTApBDAsFA8qh3sMAMyqCbmWL+GYAGgyWRyfCWxVKSE0EQ2DEEUAg0QMADcwDIIEgmPhiGwpPsGABBJBIOzAT46FA4lkRDjcXj8AR4gnE0nk-AskFOIbuKB2KzROC2BFIxaFXH+GpgTS4MWg9wjaW2WUMeWKhYolX8tUarUUcUhcH6w3GqBKs2OVVYdWaIA&html=Q&css=Q

Related information

MangelMaxime commented 2 months ago

Hello,

The code in the REPL link is different compared to your snippet.

So I am unsure what is the bug you want to report.

Is it "just" the fact that EmitMethod is ignored on type augmentation?

type IExports with 
  [<ParamObject(1)>]
  [<EmitMethod("makedirs")>]
  member _.makedirs3(path: string, ?exists_ok:bool): unit = nativeOnly

Should generate makedirs instead of makedirs3?

joprice commented 2 months ago

I updated the snippet. EmitMethod works to override the name of the method, but it does not include the argument exists_ok. Try switching to javascript and you will see

import * as os from "os";

os.makedirs("data", {
    exists_ok: true,
});

os.makedirs2("data", {
    exists_ok: true,
});

os.makedirs("data", {
    exists_ok: true,
});

compared to the python

import os
os.makedirs("data")

os.makedirs2("data", exists_ok = True)

os.makedirs("data")

This is trying to show that when you don't include EmitMethod, the keyword arguments are generated for Python.

joprice commented 1 month ago

Here's another example where ParamObject is ignored:

open Fable.Core

[<Erase>]
type Process =
  abstract member start: unit -> unit
  abstract member join: unit -> unit
  abstract member is_alive: unit -> bool
  abstract member name: string

[<Erase>]
type ProcessType =
  [<EmitConstructor; ParamObject>]
  abstract Create: target: ('a -> unit) * args: 'a * ?name: string -> Process

[<Import("Process", "multiprocessing")>]
let Process: ProcessType = nativeOnly

let fn a = ()
Process.Create(target = fn, args = 1, name = "worker") |> ignore

produces

from multiprocessing import Process
from typing import Any
from fable_library_js.util import ignore

def fn(a: Any | None=None) -> None:
    pass

def _arrow2(a: int) -> None:
    fn(a)

ignore(Process())

whereas the arguments are passed in js:

import { Process } from "multiprocessing";

export function fn(a) {
}

new Process({
    target: (a) => {
        fn(a);
    },
    args: 1,
    name: "worker",
});