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
Proper type instantiation discipline #1473
Conversation
f7e0640
to
d7294e9
Compare
fb7feda
to
c271744
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks reasonable, with the caveat that I'm not an expert in the current Nickel typechecker 😅
// It might make sense to accept any type for a value without definition (which would | ||
// act a bit like a function parameter). But for now, we play safe and implement a more | ||
// restrictive rule, which is that a value without a definition has type `Dyn` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a change in behaviour or was this just implicit in the previous typechecking code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a change in behavior, but I think the previous behavior was accidental, and neither officially supported nor documented. The previous behavior was indeed to let the type of fields without values be unconstrained.
This commit is the last missing piece (at least for the time being) toward a reasonable bidirectional typechecking algorithm that properly handle higher-rank types. Instantiation of polymorphic types has been ad-hoc and full of issues. Simple programs such as: ```nickel let r : { id : forall a. a -> a } = { id = fun r => r } in r.id "x" ``` or ```nickel let eval : forall a. (forall b. b -> b) -> a -> a = fun f x => f x in (eval (fun x => x) 1) : Number ``` wouldn't typecheck. Previously introduced variable levels were required before we could change that. This commit is building on the variable level to use a proper instantiation discipline from the specification of the Nickel type system located in this repository, and the original inspiration, [A Quick Look at Impredicativity]() (although type instantiation is currently predicative, the general structure of the bidirectional type system is similar). Doing so, we also move code between `check` and `infer` to better reflect the specification: `infer` is not wrapper around check + unification anymore, but it actually implements infer rules (function application, primop application, variable and annotation). Instantiation is taken care of when switching from infer mode to check mode within `subsumption` and at function application.
And fix the error expectation for `TypecheckError::VarLevelMismatch` that still had the old longer name `VariableLevelMismatch`.
Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>
4607564
to
f381e5f
Compare
Closes #584 #360 #1027
This PR is the last missing piece toward a reasonable bidirectional typechecking algorithm that properly handles higher-rank polymorphic types.
Until now, instantiation of polymorphic types has been sometimes ad-hoc and bizarrely limited. Simple programs such as:
or
wouldn't typecheck (cf related issues at the top). Variable levels introduced in #1372 were an important preliminary step before we could attack this issue.
This PR is building on #1372 to use a proper instantiation discipline, derived from Complete and Easy Bidirectional Typechecking for Higher-Rank
Polymorphism (ideally from the specification of the type system in this repository, but it's unfortunately not complete enough yet to be used as a reliable source of truth).
This commit gets rid of the ad-hoc cases of instantiation for
Op1
applications, the variable rule and others: those are now all subsumed by the subsumption rule, because they are special cases of a switch from inference mode to checking mode. Now,check
andsubsumption
are the only place where the checked type might be instantiated with rigid type variable (I suspect we might even get rid of the instantiation of the checked type insubsumption
, because it's always done bycheck
before anyway, but this has to be verified first), andsubsumption
is the only rule to instantiate an inferred type with fresh unification variables.The result is that the previously mentioned cases, plus other examples of higher-rank polymoprhic functions are now typechecking just fine.
Funnily, one test in
typecheck/fail
was actually well-typed, but didn't typecheck because of the described previous limitations. It's just been transformed to a passing test.TODO