types: settle a shared field name once the binding is inferred - #54
Merged
Conversation
Feedback from the other repo: #37 resolves a field when the base's type is known *at the access*, which left two shapes still ambiguous. Testing them splits the report in two. `Map.tryFind` is not one of them: when the map's value type is pinned, the payload resolves already. It fails only when the map itself arrives unpinned, which is the same root cause as a bare lambda parameter. The fixable shape is a base that a *later* statement pins down. `let l = c.letter` followed by `let flag = c.blank` was rejected even though `blank` determines `c`, because resolution happened at first sight rather than when HM had the answer. Such an access is now recorded and settled once the enclosing top-level binding is fully inferred. The part that makes this sound is generalization: a variable a pending obligation depends on stays monomorphic until it is settled. Block-level `let`s generalize, so without that guard the deferred variable would be generalized, each use would take its own copy, and the later resolution would reach none of them — it would type-check and mean nothing. Tests cover the three ways it must still fail: the field's type has to fit its uses, the field has to exist on the record that wins, and a value nothing pins anywhere is still an error. That last message now names both ways out. It offered only the pattern form (`case Cell { letter }:`), because the parameter form (`fun (Cell { letter }) -> …`) did not exist when it was written — and that is the one the report found reads better.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
From the other repo's feedback that #37 did not fully remove the field-name problem. Testing it splits the report in two, and one half is fixable.
Map.tryFindis not the culprit. When the map's value type is pinned, the payload already resolves:It fails only when the map itself arrives unpinned, which is the same root cause as a bare lambda parameter — nothing determines the type — rather than anything about
tryFindorOptionpayloads. There's a test for this so it stays true.The fixable shape is a base that a later statement pins down:
HM knows the answer by the end of the binding; #37 just asked too early. Such an access is now recorded and settled once the enclosing top-level binding is fully inferred.
The part worth review is generalization. A variable a pending obligation depends on stays monomorphic until settled. Block-level
lets do generalize, so without that guard the deferred variable would be generalized, each use would take its own copy, and the later resolution would reach none of them — it would type-check and mean nothing. That failure mode is silent, which is why the guard is ingeneralizerather than relying on ordering.Deferring never weakens the check. Three tests cover the ways it must still fail: the field's type has to fit every use (
l + 1wherelis thestringfield is still an error), the field has to exist on the record that wins, and a value nothing pins anywhere is still an error.The message for that last case now names both ways out, since it offered only the pattern form — the parameter form did not exist when it was written, and per the report it reads better:
Tests: 5 typecheck cases and an end-to-end one confirming the deferred access lowers to the ordinary attribute read. Full suite, clippy, fmt and the 23 doc lessons clean.