RFC: More general type checking for structural typings

Hi,

type person = {name: string, age: int}
let createPerson = (~name, ~age) => {name: name, age: age}

In the above code if we want to add an optional field hobbies: option<string> to the person type, then ReScript v9 compiler would guide us through all the places where we might have to add a field hobbies.
During that process we would decide whether to add a None or Some("xyx") based on our requirements.

But with this change, rescript compiler would not complain about createPerson, so I had to manually search and go through the entire codebase where we create a person type value and then add the field if it is required. By doing this I could be sure that the program would not crash. But I would not be sure that this is functionally correct since I might have missed some cases.

7 Likes

+1 on this.
although editor tooling can be used to check every reference when refactoring, I prefer current explicitness.

Maybe ReScript needs both field: int=? and field: option<int>.

1 Like

Or a compiler flag / config?

Maybe a notation {name: "test", age: 7, _} for automatically filling the optionals with None

4 Likes

Yeah this would be nice I guess.
I can then use this feature only for particular cases like ReactNative styles and would not use this for most of the other part of the codebase.

2 Likes

I think flags would only complicate things, and anyway, I think a lot of codebases would need both behaviors.

3 Likes

Maybe we should have this behavior only for @@obj annotated types, as Hongbo suggested, and keep the current behavior for the other types.

Then we could have both behaviors depending on the use case.

It would totally make sense not to be guided when adding new fields to a type with many optional fields.

3 Likes

Hi, I polished the implementation a little bit, the feature is close to be finalized.
So this new feature would be opt-in by using an attribute called @obj. For types like this:

@obj
type r = {
  x : int , 
  y : option <int>
}

let v0 = { x : 3 }
let v1 = { x : 3 , y : None}

generated JS would be:

var v0 = { x : 3 }
var v1 = { x : 3 }

The idea is that for objects annotated with obj, you don’t care about its performance in general, it is used in config patterns with lots of optionals.

Let me know if I miss anything or if any sematnics is unclear, thanks!

22 Likes

@Hongbo Thank you for this feature!!!

Thank you. Looks fantastic!

Really nice @Hongbo !

Amazing, thank you @Hongbo!

exciting! Regarding the name, I wonder if it makes sense to disambiguate from the @obj on externals, and provide something a little more descriptive, maybe like @stripUndefined ?

1 Like

Perhaps it still generates the constructor function as well? In that case it won’t break the behavior I think.

oh i see. @obj makes sense to me from that perspective :+1:

Btw I have some use cases where I don’t want the factory function to be generated. So it would be great if we can have a way not to be generated!

Wow this is such a great quality of life improvement, great job Hongbo!

@jfrolich I don’t really see the point of generating a function now to be honest.

I was thinking the function need to be created to make @obj backwards compatible, but I just realized the previous @obj is an annotation to a function instead of a record. So agree!

2 Likes

Amazing work @Hongbo!