-
Notifications
You must be signed in to change notification settings - Fork 463
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
Nested record definitions #7241
Conversation
10634fe
to
2f8d81b
Compare
2f8d81b
to
3a89ab3
Compare
Turns out it's quite tricky to make these inline records actually print inline. |
let filter_params tyl = | ||
let params = | ||
List.fold_left | ||
(fun tyl ty -> | ||
let ty = repr ty in | ||
if List.memq ty tyl then Btype.newgenty (Tsubst ty) :: tyl | ||
else ty :: tyl) | ||
[] tyl | ||
in | ||
List.rev params | ||
|
||
let mark_loops_constructor_arguments = function | ||
| Cstr_tuple l -> List.iter mark_loops l | ||
| Cstr_record l -> List.iter (fun l -> mark_loops l.ld_type) l | ||
|
||
let find_inlined_type name (printing_context : printing_context option) = | ||
match printing_context with | ||
| None -> None | ||
| Some {inlined_types} -> | ||
inlined_types | ||
|> List.find_opt (fun inlined_type -> | ||
match inlined_type with | ||
| Record {type_name} -> type_name = name) | ||
|
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.
These are just moved, not changed.
compiler/ml/printtyp.ml
Outdated
| Ttuple tyl -> Otyp_tuple (tree_of_typlist ?printing_context sch tyl) | ||
| Tconstr (p, _tyl, _abbrev) | ||
when printing_context | ||
|> find_inlined_type (Path.name p) | ||
|> Option.is_some -> ( | ||
match | ||
find_inlined_type (Path.name p) printing_context |> Option.get | ||
with | ||
| Record {labels} -> | ||
Otyp_record (List.map (tree_of_label ?printing_context) labels)) |
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 what does the actual inlining in the outcome printer.
and tree_of_constraints ?printing_context params = | ||
List.fold_right | ||
(fun ty list -> | ||
let ty' = unalias ty in | ||
if proxy ty != proxy ty' then | ||
let tr = tree_of_typexp ?printing_context true ty in | ||
(tr, tree_of_typexp ?printing_context true ty') :: list | ||
else list) | ||
params [] | ||
|
||
let typexp ?printing_context sch ppf ty = | ||
!Oprint.out_type ppf (tree_of_typexp ?printing_context sch ty) | ||
|
||
let type_expr ppf ty = typexp false ppf ty | ||
|
||
and type_sch ppf ty = typexp true ppf ty | ||
|
||
and type_scheme ppf ty = | ||
reset_and_mark_loops ty; | ||
typexp true ppf ty | ||
|
||
(* Maxence *) | ||
let type_scheme_max ?(b_reset_names = true) ppf ty = | ||
if b_reset_names then reset_names (); | ||
typexp true ppf ty | ||
(* End Maxence *) | ||
|
||
let tree_of_type_scheme ty = | ||
reset_and_mark_loops ty; | ||
tree_of_typexp true ty | ||
|
||
(* Print one type declaration *) |
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.
Just moved.
tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt
Outdated
Show resolved
Hide resolved
tests/analysis_tests/tests-reanalyze/deadcode/expected/exception.txt
Outdated
Show resolved
Hide resolved
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.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Syntax Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.05
.
Benchmark suite | Current: c33f978 | Previous: 80744a5 | Ratio |
---|---|---|---|
Parse RedBlackTree.res - time/run |
2.2335862533333333 ms |
1.87223692 ms |
1.19 |
Print RedBlackTree.res - time/run |
3.43393426 ms |
2.9391152133333334 ms |
1.17 |
Print RedBlackTreeNoComments.res - time/run |
3.1161356999999996 ms |
2.693127673333333 ms |
1.16 |
Parse Napkinscript.res - time/run |
77.13125236666667 ms |
60.98575317999999 ms |
1.26 |
Print Napkinscript.res - time/run |
119.29209173333334 ms |
102.03488166 ms |
1.17 |
Parse HeroGraphic.res - time/run |
8.86553774 ms |
7.62831228 ms |
1.16 |
Print HeroGraphic.res - time/run |
14.078760946666664 ms |
12.308145193333333 ms |
1.14 |
This comment was automatically generated by workflow using github-action-benchmark.
@cristianoc type params left to solve, but I don't think that'll necessary be super complicated, so I'd be happy to get an initial review from you on the approach. |
{ | ||
name: string ; | ||
address: < street: string ;country: string > } | ||
address: ((user.address)[@res.inlineRecordReference ]) } |
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 use both res.inlineRecordReference
and res.inlineRecordDefinition
?
Presumably if one can find user.address
, one can also determine that it is an inline record definition. Or perhaps there's some convenience given by this.
Would you add some parsing and printing tests too.
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.
Thinking about parsing and printing -- this representation makes you search for ids such as user.address
.
Wondering whether going directly with a new AST representation could actually simplify this.
The compilation to usual type definitions could perhaps be done after parsing/printing, as for JSX.
On the fence on whether it's simpler to investigate this sooner or later.
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.
Reason for having an explicit attribute is because I didn't want to hijack having .
in the name meaning that it's an inline record. But perhaps that is OK, given that it's still an illegal identifier, and it only takes effect if there's also an inlined type named that available.
I think an actual AST representation makes sense as well. Not sure how to best structure it though.
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.
Reason for having an explicit attribute is because I didn't want to hijack having
.
in the name meaning that it's an inline record. But perhaps that is OK, given that it's still an illegal identifier, and it only takes effect if there's also an inlined type named that available.I think an actual AST representation makes sense as well. Not sure how to best structure it though.
Does not matter much. The observation was: if the printer is able to inline user.address
, then it should be able to find that user.address
is in fact a type definition decorated with res.inlineRecordDefinition
, which should be all the info one needs. But maybe this makes things slower or more complicated, if every type reference needs look up to see if it's inlined.
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.
Ahh I see what you mean now. Perf wise it shouldn't be much of a hit - it'll only attempt lookup in the cases when there are actual inlined types, and here we could safely look for a .
in the name before attempting as well. I'll see if I can change it to work like that real quick.
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.
@cristianoc good catch, was able to get rid of res.inlineRecordReference
here: 82fc9c8
compiler/syntax/src/res_core.ml
Outdated
~params ~start_pos p | ||
in | ||
let rec_flag = | ||
if List.length !inline_types > 0 then Asttypes.Recursive else rec_flag |
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 turning non-recursive types into recursive ones?
That would break the usual binding rules. Does one need recursion here to represent the new type, even though there's a nesting structure in inner types?
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.
Yes it does, but only if there are also inline types in the type itself. I wasn't able to make this work reliably without the rec flag when trying.
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.
Yes it does, but only if there are also inline types in the type itself. I wasn't able to make this work reliably without the rec flag when trying.
In this example:
type options = {
extra: {
superExtra: {age: int},
},
}
the parser generates type definitions in this order:
options.extra
options.extra.superExtra
options
so the order is a bit inconsistent with nested types. If deeply nested types come first, then one won't need to add rec
.
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.
Still doesn't work. I suspect this might be because it's a list of type defs we produce here in the same Pstr_type
, and not separate Pstr_type
definitions. Therefore it needs to be rec
..?
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.
Yes it'd have to be a sequence of type defs so later ones can see earlier ones.
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 would require us to change the parser a bit I think, to support returning lists of nodes everywhere for structure/signatures, rather than a single node. Can do that of course. What type of issues do you see with using the rec
flag like this?
compiler/syntax/src/res_core.ml
Outdated
Parser.leave_breadcrumb p Grammar.RecordDecl; | ||
Parser.expect Lbrace p; | ||
let rows = | ||
parse_comma_delimited_region ~grammar:Grammar.RecordDecl ~closing:Rbrace | ||
~f:parse_field_declaration_region p | ||
~f:(parse_field_declaration_region ?current_type_name_path ?inline_types) |
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.
Reanalyze gives here Call must have named argument f
in CI.
One possibility is to remove the termination analysis as reanalyze won't be supported starting from ocaml 5.3 anyway. Simplifies the coding as one does not need to respect its syntactic restrictions, the downside is one needs to be careful about not introducing infinite loops.
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.
@cristianoc sounds reasonable to turn it off. Can I turn off just the termination analysis?
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's the only thing used in tests, the termination analysis.
I think there's a single target in CI where it's active CC @cknitt
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 could go ahead as experimental feature.
There's a small comment about reanalyze restrictions.
4528601
to
c33f978
Compare
@cristianoc I made some additional changes, including supporting type params and adding a few more tests. Another look please, and then this should be ready to go as an experimental feature. |
b902d46
to
ed4cd0f
Compare
.github/workflows/ci.yml
Outdated
@@ -110,6 +110,7 @@ jobs: | |||
- os: ubuntu-24.04 | |||
ocaml_compiler: ocaml-variants.5.2.1+options,ocaml-option-static | |||
# Reanalyze does not work on OCaml 5.3.0 anymore, therefore run it on 5.2.1 | |||
# disabled for now, since Reanalyze will stop working going forward. Can likely be removed entirely soon |
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.
Since this is the only use of reanalyze, how about removing it entirely.
No point in bringing it back later, as it would immediately give an error.
CC @cknitt
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.
@cknitt you agree? I'll remove it before merging this, if so.
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.
Yes, go ahead!
Things missing:
Things considered but dropped:
Support forMake the definition explicit if it should be reused.@as
for renaming the generated type (do we want it?)First class representation in the ASTThis might be something to pick up a bit later, but for now we can let it be driven by the single attribute added.