Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue with typing application and polymorphic types. #7338

Merged
merged 2 commits into from
Mar 14, 2025

Conversation

cristianoc
Copy link
Collaborator

@cristianoc cristianoc commented Mar 13, 2025

Fixes #7323

When typing application there's a special treatment for polymorphic types, where the arity and kinds of arguments are inferred. For example: f => f(~lbl1, ~lbl2) assigns a polymorphic type 'a to f initially which is then instantated to (~lbl1:t1, ~lbl2:t2) => t3.

That same mechanism currently applies when a function is annotated to return a polymorphic type such as (string, ~wrongLabelName: int=?) => 'a, where the 'a is further instantiated to extend the function type with additional arguments. This mechanism is OK for curried function, but incorrect for uncurried functions: while e.g. 'a => 'b with curried function designates any function where the first argument is unlabeled, with uncurried function it only designates functions of arity 1. So when processing application, 'b should not be expanded further.

Two examples of problematic code that now gives type error:

let r: (string, ~wrongLabelName: int=?) => 'a = (_s, ~wrongLabelName=3) => {
  let _ = wrongLabelName
  assert(false)
}

let tst1 = r("", ~initialValue=2)  // Error
let tst2 = r("")(~initialValue=2)  // OK

and

let f  = (_, ~def=3) => assert(false)

let g1 = f(1,2)   // Error
let g2 = f(1)(2)  // OK

Fixes #7323

When typing application there's a special treatment for polymorphic types, where the arity and kinds of arguments are inferred.
For example: `f => f(~lbl1, ~lbl2)` assigns a polymorphic type `'a` to `f` initially which is then instantated to `(~lbl1:t1, ~lbl2:t2) => t3`.

That same mechanism currently applies when a function is annotated to return a polymorphic type such as `(string, ~wrongLabelName: int=?) => 'a`, where the `'a` is further instantiated to extend the function type with additional arguments.
This mechanism is OK for curried function, but incorrect for uncurried functions: while e.g. `'a => 'b` with curried function designates any function where the first argument is unlabeled, with uncurried function it only designates functions of arity 1.
So when processing application, `'b` should not be expanded further.

Two examples of problematic code that now gives type error:
```res
let r: (string, ~wrongLabelName: int=?) => 'a = (_s, ~wrongLabelName=3) => {
  let _ = wrongLabelName
  assert(false)
}

let tst1 = r("", ~initialValue=2)
let tst2 = r("")(~initialValue=2)
```

and

```res
let f  = (_, ~def=3) => assert(false)

let g1 = f(1,2)
let g2 = f(1)(2)
```
@cristianoc
Copy link
Collaborator Author

Here's a tentative fix for the issue of functions that should not pass the type checker but do.
It would be great to make sure that this does not introduce any unwanted restrictions, as the change is quite small and general. So it would be great to try it with existing projects to make sure it still compiles.
CC @cknitt

@cristianoc cristianoc requested review from cknitt and zth March 13, 2025 12:28
let ty1, ty2 =
let ty_fun = expand_head env ty_fun in
let arity_ok = List.length args < max_arity in
match ty_fun.desc with
| Tvar _ ->
| Tvar _ when (* l1 = Nolabel || *) force_tvar ->
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only change.
Obviously there's some cleanup to do -- but interested in checking that the behaviour is solid for now (and might need to re-introduce debugging code in case issues are found).

@cknitt
Copy link
Member

cknitt commented Mar 13, 2025

So it would be great to try it with existing projects to make sure it still compiles.

Thanks a lot! Will test it now.

@cknitt
Copy link
Member

cknitt commented Mar 13, 2025

Ok I can confirm that

  1. the project I tested against still compiles fine with this change
  2. let g1 = f(1,2) from your example now correctly gives an error

What about g2 from your example though?
It shouldn't compile either, but still does. Not in scope of this PR?

let f  = (_, ~def=3) => assert(false)

let g1 = f(1,2)   // Error
let g2 = f(1)(2)  // OK

@cristianoc
Copy link
Collaborator Author

Ok I can confirm that

  1. the project I tested against still compiles fine with this change
  2. let g1 = f(1,2) from your example now correctly gives an error

What about g2 from your example though? It shouldn't compile either, but still does. Not in scope of this PR?

let f  = (_, ~def=3) => assert(false)

let g1 = f(1,2)   // Error
let g2 = f(1)(2)  // OK

To see why g2 type checks, one can also give a more explicit type to f:

let f  : (int, ~def:int=?) => (int => unit)  = (_, ~def=3) => assert(false)

where assert(false) has any type 'a, so in particular it can be (int => unit).
So f(1) supplies the first argument 1 and implicitly undefined for the default def for the outer function, and f(1)(2) supplies in addition the argument 2 to the returned function.

@cristianoc
Copy link
Collaborator Author

Can't think of any issues right now. Cleaned up the code and added some tests for the expected error messages.

@cknitt
Copy link
Member

cknitt commented Mar 14, 2025

where assert(false) has any type 'a, so in particular it can be (int => unit).

Ah, yes, I somehow thought it had return type unit.

like In

let f  = (_, ~def=3) => ignore(def)

There

let g2 = f(1)(2)

correctly gives an error, too.

@cristianoc cristianoc merged commit e4d98a7 into master Mar 14, 2025
20 checks passed
@cristianoc cristianoc deleted the typing-application branch March 14, 2025 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Label name not checked and incorrect code emitted for label in external definition
2 participants