Record in list decomposed in a single step needs type hint, is this a bug?

Just noticing in multiple scenarios that the following decomposition

let ({a, b}, y) = someValue

gives an error the record field a can't be found, so I need to add an explicit type annotation to {a, b} like {a, b}: rType to make it pass.

However if the same decomposition is done in two steps, it works without the annotation.

let (x, y) = someValue
let {a, b} = x

Is this a bug that I should report, or is this a known limitation? If the latter, what’s the reason behind it?

(Rescript 11.0.0-rc.8)

Can you show an example (preferrably with a playground link) where this happens?
Normally if this happens when destructuring a tuple, the same thing would happen with a direct destructuring of the record. I can’t repro your issue.

Hi tsnobip, thanks for your reply!

I did a minimal version to demonstrate the issue:

https://rescript-idea.github.io/try?code=LYewJgrgNgpgBAERgMwJYDtUBdUnXAXjgG8AoOOLATwAd4tCTyK4BDALjgGcsAnDAOYAaZhQBGnHv3TDmAX2awGAmFgBKMAMYheYRgAoAlIQB8JDnABEloXAlXLCikrgqsAGVQ8Dxgmf1uGtq6RrYALABMhqQKpC5gIACSDERGpkwUAPSZcNrANKiwXIqqcPoAHrZUvogoGNi46AB0bp48RiUMxKy2YnKM5aTM2bkg+YUwxc6l3b39REhomDh4LapBOmAdwzlivDCsANZTcC76s3ZyVTWL9SvNrV5YHVkAVMwAKgAW8PvBemgYFA9Kxcqx0AByBhieDIEAQdBgJpDFiJZBwbAQrhwMB1dAwPQYNjoEBYH68OCgSCwOA6OBoWC9aQCDEMDBYEDcbR0OxUdiiAC0cAACvs0OVBKy4AB3bBfbisVB6KnQeDoVjAGCcb4wACy4FVTVYgrgAHkKVw6JpUMgqJLsNjqHROC4yTAAGqsKAQLVwHX66kwJpuj60eBEbqcd0AQXcAFUAKJOV6ZGKkIA

module Definition = {
  type t = {
    a: string,
    b: string,
  }
  let getRecord = () => {a: "", b: ""}
  let getList = () => (getRecord(), 42)
}

let doIt = () => {
  // compiles
  let (x, y) = Definition.getList()
  let {a, b} = x

  // compiles
  let {a, b} = Definition.getRecord()

  // breaks
  let ({a, b}, y) = Definition.getList()
  /*
  The record field a 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.a
   - Or specifying its type: let theValue: TheModule.theType = {a: VALUE}
 */
}

to be honest I don’t understand how the first example can even compile:

let doIt = () => {
  // compiles
  let (x, y) = Definition.getList()
  let {a, b} = x
}

given it wouldn’t compile when not inside a function:

let (x, y) = Definition.getList()
let {a, b} = x

About why this happens, well ReScript type system follows Hindley-Milner type inference algorithm, I’m not sure what corner case leads to this phenomenon, but you’re more than welcome to fix it if you can!

Amazing discoveries. And even this does not compile outside of the function. But it does inside:

// breaks
let {a, b} = Definition.getRecord()

let doIt = () => {
  // compiles
  let {a, b} = Definition.getRecord()
}

This feels even a bigger contradiction to me, and it only involves records, no lists.

That said, I encounter many cases where accessing a record field (or decomposition) fails to compile and needs a type hint. But the type should otherwise be known at that point. I intuitively feel that these should all compile. But I cannot wrap my head around it what exactly is happening here.

I’ll think about this further. Meanwhile, if anyone knows the reason… please do tell! :slight_smile:

1 Like

I made a hack to convert

let { record-pattern} = expr 

==>

switch expr {
  {record-pattern} =>
}

So that more type info could be used to resolve the names, the fix could be relax the rule to include tuple patterns in such transformation

3 Likes

Out of curiosity, how does this explain the difference of the record type resolution in and out of the closure?

// breaks
let {a, b} = Definition.getRecord()

let doIt = () => {
  // compiles
  let {a, b} = Definition.getRecord()
}

Is this an independent issue, or somehow related?

Toplevel is not an expression.
Another easier fix is to change the order of typing rules, that’s what I did in MoonBit.

2 Likes