-
Notifications
You must be signed in to change notification settings - Fork 100
Fix resolution involving deeply nested substitutions #727
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
Conversation
Fixes ocaml#690 There has been logic to fix this sort of issue for a long time, but it broke at some point. Mostly the fix is that when calculating the signatures of `With` module expressions, we always recalculate rather than using any previously calculated signature - this is because the previously calculated signature makes a round-trip from Component.* to Lang.* and Lang.ModuleType.simple_signature doesn't preserve removed items as the Components layer does. By itself this would not have fixed ocaml#690 though - The way the rest of the fix works is by ensuring that at each level of the expansion we have enough to figure out that a replacement is necessary and what it should be. For example, in issue ocaml#690, @Octachron provides the following example: ```ocaml module type S = sig module M: sig type t end type t = M.t end module type T = S with type M.t := int ``` Before this fix, the expansion of `T` would look like this: ```ocaml module M : sig end type t = M.t ``` Clearly the `M.t` can't be resolved from this. Instead, this fix works by ensuring that the expansion of `T` looks like this: ```ocaml module M: sig type t end with type t := int type t = M.t ``` From this we have enough information to replace the `M.t` with an `int` at the point we do the resolution of the RHS of type `t`. We don't really want the output to show `sig type t end with type t := int` however, so in the document layer these expressions are replaced with `sig ... end` as we had before.
ff678fd
to
c670199
Compare
$ ocamlc -c -bin-annot m.mli | ||
$ odoc compile m.cmti | ||
$ odoc link m.odoc | ||
$ odoc html-generate m.odocl --indent -o . |
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.
We have a helper script for this:
$ ocamlc -c -bin-annot m.mli | |
$ odoc compile m.cmti | |
$ odoc link m.odoc | |
$ odoc html-generate m.odocl --indent -o . | |
$ compile m.mli |
The output is slightly different because it sets the parent. If that's annoying, perhaps we can change the script and update every other tests ?
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.
We do have a helper script, true - but this way the test is a bit more self contained, and I often find this useful when I'm working on them because I find building them out of tree to be essential when developing (and debugging) these tests.
@@ -1430,14 +1430,22 @@ module Make (Syntax : SYNTAX) = struct | |||
++ Link.from_path (m :> Paths.Path.t) | |||
++ O.txt " " ++ O.keyword "end" | |||
|
|||
and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool = function |
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.
Why not render nested substitutions ? It is rendered for T
:
# Module Test
module type S = sig ... end
module type T = S with type M.t := int
but the documentation for T
doesn't mention any substitution at all:
# Module type Test.T
module M : sig ... end
type t = int
Showing the substitution is not that bad:
# Module type Test.T
module M : M.S with type t := int
type t = int
It would be even more useful in case of non-destructive substitution because the definition of t
would be in the same documentation page:
# Module type Test.T
module M : M.S with type t = int
type t = M.t
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.
Hmm, it was certainly not my intention to remove the 'with type' always, just the construct sig ... end with type ...
I'll double check.
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.
I think I misinterpreted what you mean - though I'm still not quite sure! To be clear, we've got:
module type S = sig
module M: sig
type t
end
type t = M.t
end
module type T = S with type M.t := int
You wrote that you thought this would be better:
# Module type Test.T
module M : M.S with type t := int
type t = int
I don't think you mean M.S
- I think you might have meant S.M
- but we can't do this because we can't have paths to elements within module types. Is this correct?
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.
By M.S
, I meant S.M
or even Test.S.M
, sorry for the confusion.
Hmm, it was certainly not my intention to remove the 'with type' always, just the construct
sig ... end with type ...
sig ... end with type ...
is turned into just sig ... end
but I'd expect Test.S.M with type ...
even if M
is not a module type.
but we can't do this because we can't have paths to elements within module types.
On the definition of T
, we have the path to S
and the substitution, why can't nested substitution be represented the same way ?
Perhaps we can represent it like (module type of Test.S.M) with type ...
internally ?
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.
The problem isn't M
, it's S
- S
is a module type so we can't do S.M
.
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.
That wouldn't be valid OCaml but it would be a valid reference (so can work internally). Do we require that every rendered paths should be valid OCaml paths ?
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.
Even internally it's a Path not a Reference, and in any case I strongly prefer sticking to valid OCaml than to deviate. If the language supports it some day we can do it - for example I'm keeping an eye on the transparent ascription PR as that will help with some tricker parts of handling module type of
.
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.
Alright :) Sorry for the digression.
CHANGES: Breaking changes - Remove odoc-parser into a separate repository (@jonludlam, ocaml/odoc#700) Additions - OCaml 4.13 support (@Octachron, ocaml/odoc#687, ocaml/odoc#689) - Better errors/warnings (@Julow, ocaml/odoc#692, ocaml/odoc#717, ocaml/odoc#720, ocaml/odoc#732) - ModuleType 'Alias' support (@jonludlam, ocaml/odoc#703) - Improved test suite (@lubega-simon, ocaml/odoc#697) - Improved documentation (@lubega-simon, @jonludlam, ocaml/odoc#702, ocaml/odoc#733) - Strengthen module types (@jonludlam, ocaml/odoc#731) Bugs fixed - `uwt` now can be documented (@jonludlam, ocaml/odoc#708) - Fix resolution involving deeply nested substitutions (@jonludlam, ocaml/odoc#727) - Fix off-by-one error in error reporting (@asavahista, ocaml/odoc#736)
Fixes #690
There has been logic to fix this sort of issue for a long time, but it
broke at some point.
Mostly the fix is that when calculating the signatures of
With
module expressions, we always recalculate rather than using any
previously calculated signature - this is because the previously
calculated signature makes a round-trip from Component.* to Lang.* and
Lang.ModuleType.simple_signature doesn't preserve removed items as the
Components layer does.
By itself this would not have fixed #690 though - The way the rest of
the fix works is by ensuring that at each level of the expansion we
have enough to figure out that a replacement is necessary and what it
should be. For example, in issue #690, @Octachron provides the
following example:
Before this fix, the expansion of
T
would look like this:Clearly the
M.t
can't be resolved from this. Instead, this fixworks by ensuring that the expansion of
T
looks like this:From this we have enough information to replace the
M.t
with anint
at the point we do the resolution of the RHS of typet
.We don't really want the output to show
sig type t end with type t := int
however, so in the documentlayer these expressions are replaced with
sig ... end
as we hadbefore.