Has any solution to use Es Proxy?

Hi guys.After I had defined es Proxy, when I was calling it, a compilation failure occurred.

The rescript source code:

type t
@new external proxy: ('a, 'b) => t = "Proxy"

let p = proxy(
  () => ignore()
, {
    "get": (_, p) => 
      if p === "hello" {
        Js.log("Hello Rescript!!!")
      }
  }
)

p.hello

Compiled js code:

var p = new Proxy((function (param) {
        
      }), {
      get: (function (param, p) {
          if (p === "hello") {
            console.log("Hello Rescript!!!");
            return ;
          }
          
        })
    });

p.hello // Manual compilation line

export {
  p ,
  
}

The error is reported below:

rescript: [3/3] src/index.cmj
FAILED: src/index.cmj

  We've found a bug for you!
  /root/rescript/rescript/src/index.res:14:3-7

  12 │ )
  13 │ 
  14 │ p.hello
  15 │ 

  The record field hello can't be found.
  
  If it's defined in another module or file, bring it into scope by:
  - Prefixing it with said module name: TheModule.hello
  - Or specifying its type: let theValue: TheModule.theType = {hello: VALUE}

FAILED: cannot make progress due to previous errors.
 ERROR  Command failed with exit code 2.

After manual compilation, used deno to run js code:

>> deno run -A ./lib/es6/src/index.js
$ Hello Rescript!!!

So, how to deal with this situation?

There are probably quite a few ways to do this, but I suspect you might need to use some concrete types. Here’s an idea, but others might be able to suggest improvements.

module Proxy = {
  @new external make: ('a, 'b) => 'c = "Proxy"
}

module Person = {
  type person = {name: string}
  type personWithGreeting = {name: string, greeting: string}

  let greetingHandler = {
    "get": (person: person, prop: string): string => {
      if prop == "greeting" {
        "Hello " ++ person.name
      } else {
        %raw(`Reflect.get(...arguments)`)
      }
    },
  }

  let withGreeting = (p: person): personWithGreeting => Proxy.make(p, greetingHandler)
}

let p1: Person.person = {name: "Earth"}
let p2: Person.personWithGreeting = Person.withGreeting(p1)

Js.log(p1.name) // Earth
Js.log(p2.name) // Earth
Js.log(p2.greeting) // Hello Earth

At first, thank you for the explaining code. I understand that the display has a certain fixed attribute (such as ‘greeting’) under the specified type and can be compiled. If a certain attribute member is not fixed, how to deal with it. Forgive my broken English, I don’t know if I express it clearly。Just like

rs Code:

Js.log(p2.tom)

will print

$ "Hello Tom"

rs Code:

Js.log(p2.john)

will print

$ "Hello John"

You don’t know which method will be called by user. The method name is used as a dynamic variable in the “Proxy” instance like a function parameter.

At the end, thank you again for your help.

Hi @footearth can you provide some example JS code that shows what you are trying to achieve? That will help with finding a solution.

However, here’s another example that uses JS object types instead of record types:

module Proxy = {
  @new external make: ({..}, {..}) => {..} = "Proxy"
}

let o = Proxy.make(
  Js.Obj.empty(),
  {
    "get": (_target: {..}, prop: string) => {
      if prop == "hello" {
        "Hello ReScript"
      } else {
        %raw(`Reflect.get(...arguments)`)
      }
    },
  },
)

// values have unknown types
let value1 = o["hello"]
let value2 = o["world"]

Js.log(value1) // Hello ReScript
Js.log(value2) // undefined

Thanks @kevanstannard. This is exactly what I need.
My usage scenario is to use “Proxy” to proxy React.jsx

Simple JS code like this:

const { div, p, li } = new Proxy(
  {}
, {
     get: (t, tagName) =>
        (attr, children) => React.jsx(tagName, attr, children)
  }
)

I suspect the challenge you will face here is the arbitrary destructuring to the different tags.

An idea that’s less dynamic but may have a similar syntax to your example would be to declare a module with each of the tags you are interested in.

For example:

module ReactElement = {
  let make = (
    name: string,
    props: ReactDOM.domProps,
    children: array<React.element>,
  ): React.element => {
    React.createElementVariadic(ReactDOM.stringToComponent(name), props, children)
  }
  let div = make("div")
  let p = make("p")
  let li = make("li")
}

And then you might use it like this:

let {div, p, li} = module(ReactElement)

let el = div(
  ReactDOM.domProps(~id="test", ~className="someclass", ()),
  [
    p(ReactDOM.domProps(), [React.string("Hello")]),
    p(ReactDOM.domProps(), [React.string("World")]),
  ],
)

Someone else might be able to suggest a better idea that uses your Proxy suggestion.