-
Notifications
You must be signed in to change notification settings - Fork 3
Make deriver #1
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
Open
ayc9
wants to merge
50
commits into
ocaml-ppx:main
Choose a base branch
from
ayc9:make
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Make deriver #1
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
767501b
add starter code for make deriver
ayc9 a929c74
move into new folders
ayc9 8a801f7
change function name to make_t + update tests
ayc9 cfd403c
replace `create` with `make`
ayc9 8f1564f
add tests
ayc9 381edb6
remove dependency on base + rename for readability
ayc9 8a2c7e8
redo tests using cram and remove old tests
ayc9 7c0e2d9
separate str and sig tests into new subfolders
ayc9 51efc16
rename functions and edit error msgs
ayc9 8aa7321
rename functions and edit error msgs
ayc9 4cfb1cb
Revert "rename functions and edit error msgs"
ayc9 8ce54f8
add error msg for exposing with private types + add corresponding test
ayc9 dce19b8
add test for nonrec type
ayc9 6160b73
add test for private types + edit test descriptions
ayc9 e753f41
typo
ayc9 972692d
Refactor modules
ayc9 5d5098c
Change cram test file structure
ayc9 7020222
Add changelog
ayc9 e6dd1e2
Merge branch 'ocaml-ppx:main' into make
ayc9 6412e62
Add sig for option fields + refactor
ayc9 ec829c6
Add option field test
ayc9 4fd35f7
Add unit for option signature
ayc9 408020c
Fix signature option test
ayc9 e5c5da4
Add option structure
ayc9 0aabac0
Add option structure test
ayc9 f49bf39
Fix option structure test
ayc9 71e65fb
Fix option structure + refactor
ayc9 0d37ecf
Add main annotation for signature
ayc9 4821f8c
Add option and main tests for signature
ayc9 5723fbb
Implement main annotation for structure
ayc9 7e698e5
Add main annotation tests
ayc9 de805c7
Remove labels for main fields and use fold_left
ayc9 538fee7
Update tests
ayc9 aa7af5b
Update attributes to use ppxlib instead of ppx_deriving
ayc9 b642353
Update tests
ayc9 33e46c3
Edit test names
ayc9 6dfa525
Add @default annotation
ayc9 8ce7e91
Add @default tests
ayc9 2d6ca99
Add list handling, upgrade deriver generator to V2, embed error
ayc9 9efa6e2
Add tests for list and default attr
ayc9 b44553e
Change to embed errors
ayc9 84b0da3
Generate opam file
ayc9 dc12b4a
Generate opam file
ayc9 8e8c8b6
Edit descriptions
ayc9 bc81525
Update src/make/ppx_make.ml
ayc9 02ad95d
Update src/make/ppx_make.ml
ayc9 bd8acd0
typo
ayc9 5aab935
Updates on PR comments
ayc9 bcb7a42
Remove private types check
ayc9 a1770f0
Add comments for non-deriving case
ayc9 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| 2022-01-XX | ||
| ----- | ||
|
|
||
| - Adding implementation of first standard deriver, (`make`), along with tests and changelog | ||
| - Drafting readme to detail documentation for `make` |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| (lang dune 3.0) | ||
|
|
||
| (cram enable) | ||
|
|
||
| (generate_opam_files true) | ||
|
|
||
| (name standard_derivers) | ||
|
|
||
| (source | ||
| (github ocaml-ppx/standard_derivers)) | ||
|
|
||
| (package | ||
| (name standard_derivers) | ||
| (synopsis "Standard PPX derivers") | ||
| (depends | ||
| (ppxlib (>= 0.18.0))) | ||
| (allow_empty)) |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| (library (name ppx_make) | ||
| (kind ppx_deriver) (ppx_runtime_libraries fieldslib) (libraries ppxlib) | ||
| (preprocess (pps ppxlib.metaquot))) |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,247 @@ | ||
| (* Generated code should depend on the environment in scope as little as | ||
| possible. E.g. rather than [foo = []] do [match foo with [] ->], to | ||
| eliminate the use of [=], which might be overwritten in the environment. | ||
| It is especially important to not use polymorphic comparisons. *) | ||
|
|
||
| open Ppxlib | ||
| open Ast_builder.Default | ||
|
|
||
| module Annotations = struct | ||
| let default_attr = | ||
| Attribute.declare | ||
| "standard_derivers.make.default" | ||
| Attribute.Context.label_declaration | ||
| Ast_pattern.(single_expr_payload __) | ||
| (fun expr -> expr) | ||
| ;; | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let main_attr = | ||
| Attribute.declare | ||
| "standard_derivers.make.main" | ||
| Attribute.Context.label_declaration | ||
| Ast_pattern.(pstr nil) | ||
| () | ||
| ;; | ||
|
|
||
| let find_main labels = | ||
| let main_labels, labels = List.fold_left (fun (main_labels, labels) label -> | ||
| match Attribute.get main_attr label with | ||
| | Some _ -> label::main_labels, labels | ||
| | None -> main_labels, label :: labels | ||
| ) ([], []) labels in | ||
| match main_labels with | ||
| | [] -> Ok (None, labels) | ||
| | [ main_label ] -> Ok (Some main_label, labels) | ||
| | main_labels -> Error (List.map(fun ({ pld_loc; _ }) -> | ||
| Location.error_extensionf ~loc:pld_loc "Duplicate [@deriving.%s.main] annotation" "make" | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) main_labels ) | ||
| ;; | ||
| end | ||
|
|
||
| module Check = struct | ||
| let is_derivable ~loc rec_flag tds = | ||
| match rec_flag with | ||
| | Nonrecursive -> | ||
| Error (Location.error_extensionf ~loc "nonrec is not compatible with the `make' preprocessor.") | ||
| | _ -> | ||
pitag-ha marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let is_record td = | ||
| match td.ptype_kind with | ||
| | Ptype_record _ -> true | ||
| | _ -> false | ||
| in | ||
| if not (List.exists is_record tds) | ||
| then | ||
| Error (Location.error_extensionf | ||
| ~loc | ||
| (match tds with | ||
| | [ _ ] -> "Unsupported use of make (you can only use it on records)." | ||
| | _ -> | ||
| "make can only be applied on type definitions in which at least one \ | ||
| type definition is a record.")) | ||
| else Ok () | ||
| ;; | ||
|
|
||
| let is_optional labels = List.exists (fun (name, _) -> match name with | ||
| | Optional _ -> true | ||
| | _ -> false) labels | ||
| ;; | ||
| end | ||
|
|
||
| module Construct = struct | ||
| (* Additional AST construction helpers *) | ||
|
|
||
| let apply_type ~loc ~ty_name ~tps = | ||
| ptyp_constr ~loc (Located.lident ~loc ty_name) tps | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;; | ||
|
|
||
| let lambda ~loc patterns body = | ||
| List.fold_left (fun acc (lab, pat, default) -> | ||
| pexp_fun ~loc lab default pat acc) body patterns | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;; | ||
|
|
||
| let lambda_sig ~loc arg_tys body_ty = | ||
| List.fold_left (fun acc (lab, arg_ty) -> | ||
| ptyp_arrow ~loc lab arg_ty acc) body_ty arg_tys | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ;; | ||
|
|
||
| let record ~loc pairs = | ||
| pexp_record | ||
| ~loc | ||
| (List.map (fun (name, exp) -> Located.lident ~loc name, exp) pairs) | ||
| None | ||
| ;; | ||
|
|
||
| let sig_item ~loc name typ = | ||
| psig_value ~loc (value_description ~loc ~name:(Located.mk ~loc name) ~type_:typ ~prim:[]) | ||
| ;; | ||
|
|
||
| let str_item ~loc name body = | ||
| pstr_value ~loc Nonrecursive [ value_binding ~loc ~pat:(pvar ~loc name) ~expr:body ] | ||
| ;; | ||
| end | ||
|
|
||
| module Gen_sig = struct | ||
| let label_arg label_decl = | ||
| let { pld_name = name; pld_type = ty; _ } = label_decl in | ||
| match (Attribute.get Annotations.default_attr label_decl), ty with | ||
| (* [@default _ ] -> Optional *) | ||
| | Some _, _ -> Optional name.txt, ty | ||
| (* `option` type -> Optional *) | ||
| | _, [%type: [%t? a'] option] -> Optional name.txt, a' | ||
| (* `list` type -> Optional *) | ||
| | _, [%type: [%t? _] list] -> Optional name.txt, ty | ||
| (* Regular field -> Labelled *) | ||
| | _ -> Labelled name.txt, ty | ||
| ;; | ||
|
|
||
| let create_make_sig ~loc ~ty_name ~tps label_decls = | ||
| let record = Construct.apply_type ~loc ~ty_name ~tps in | ||
| match Annotations.find_main label_decls with | ||
| | Error e -> List.map(fun e -> psig_extension ~loc (e) [] ) e | ||
| | Ok (main_arg, label_decls) -> | ||
| let types = List.map label_arg label_decls in | ||
| let add_unit types = ( | ||
| Nolabel, | ||
| Ast_helper.Typ.constr ~loc { txt = Lident "unit"; loc } [] | ||
| )::types in | ||
| let types = match main_arg with | ||
| | Some { pld_type ; _ } | ||
| -> (Nolabel, pld_type)::types | ||
| | None when Check.is_optional types -> add_unit types | ||
| | None -> types | ||
| in | ||
| let t = Construct.lambda_sig ~loc types record in | ||
| let fun_name = "make_" ^ ty_name in | ||
| [Construct.sig_item ~loc fun_name t] | ||
| ;; | ||
|
|
||
| let derive_per_td (td : type_declaration) : signature = | ||
| let { ptype_name = { txt = ty_name; loc } | ||
| ; ptype_private = private_ | ||
| ; ptype_params | ||
| ; ptype_kind | ||
| ; _ | ||
| } | ||
| = | ||
| td | ||
| in | ||
| let tps = List.map (fun (tp, _variance) -> tp) ptype_params in | ||
| match ptype_kind with | ||
| | Ptype_record label_decls -> | ||
| if private_ = Public then | ||
| let derived_item = create_make_sig ~loc ~ty_name ~tps label_decls in | ||
| derived_item | ||
ayc9 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| else | ||
| [psig_extension ~loc | ||
| (Location.error_extensionf ~loc "We cannot expose functions that explicitly create private records.") [] ] | ||
| | _ -> [] | ||
ayc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ;; | ||
|
|
||
| let generate ~ctxt (rec_flag, tds) = | ||
| let loc = Expansion_context.Deriver.derived_item_loc ctxt in | ||
| match Check.is_derivable ~loc rec_flag tds with | ||
| | Error e -> [psig_extension ~loc (e) [] ] | ||
| | Ok () -> List.concat_map (derive_per_td) tds | ||
| ;; | ||
| end | ||
|
|
||
| module Gen_struct = struct | ||
| let derive_pattern ~loc label_decl = | ||
| let { pld_name = name; pld_type = ty; _ } = label_decl in | ||
| let default_attr = (Attribute.get Annotations.default_attr label_decl) in | ||
| match default_attr, ty with | ||
| | Some default_attr, _ -> Optional name.txt, pvar ~loc name.txt, Some default_attr | ||
| | _ , [%type: [%t? _] list] -> Optional name.txt, pvar ~loc name.txt, Some (elist ~loc []) | ||
| | _, [%type: [%t? _] option] -> Optional name.txt, pvar ~loc name.txt, None | ||
| | None, _ -> Labelled name.txt, pvar ~loc name.txt, None | ||
| ;; | ||
|
|
||
| let is_optional labels = List.exists (fun (name, _, _) -> match name with | ||
| | Optional _ -> true | ||
| | _ -> false) labels | ||
| ;; | ||
|
|
||
| let create_make_fun ~loc ~record_name label_decls = | ||
| let field_labels = List.map (fun { pld_name = n; _ } -> n.txt, evar ~loc n.txt) label_decls in | ||
| match Annotations.find_main label_decls with | ||
| | Error e -> List.map(fun e -> pstr_extension ~loc (e) []) e | ||
| | Ok (main_arg, label_decls) -> | ||
| let patterns = List.map (derive_pattern ~loc) label_decls in | ||
| let add_unit patterns = (Nolabel, punit ~loc, None)::patterns in | ||
| let patterns = match main_arg with | ||
| | Some ({ pld_name = { txt = name ; _ } ; pld_loc; _ } as pld) | ||
| -> (match (Attribute.get Annotations.default_attr pld) with | ||
| | Some _ -> Error (Location.error_extensionf ~loc:pld_loc "Cannot use both @default and @main") | ||
| | None -> Ok ((Nolabel, pvar ~loc name, None)::patterns)) | ||
| | None when is_optional patterns -> Ok (add_unit patterns) | ||
| | None -> Ok patterns | ||
| in | ||
| match patterns with | ||
| | Error e -> [pstr_extension ~loc (e) []] | ||
| | Ok patterns -> | ||
| let create_record = Construct.record ~loc field_labels in | ||
| let derive_lambda = Construct.lambda ~loc patterns create_record in | ||
| let fun_name = "make_" ^ record_name in | ||
| [Construct.str_item ~loc fun_name derive_lambda] | ||
| ;; | ||
|
|
||
| let derive_per_td (td : type_declaration) : structure = | ||
| let { ptype_name = { txt = record_name; loc } | ||
| ; ptype_private = private_ | ||
| ; ptype_kind | ||
| ; _ | ||
| } | ||
| = | ||
| td | ||
| in | ||
| match ptype_kind with | ||
| | Ptype_record label_decls -> | ||
| (match private_ with | ||
| | Private -> [] | ||
| | Public -> let derived_item = create_make_fun ~loc ~record_name label_decls in | ||
| derived_item ) | ||
| | _ -> [] | ||
| ;; | ||
|
|
||
| let generate ~ctxt (rec_flag, tds) = | ||
| let loc = Expansion_context.Deriver.derived_item_loc ctxt in | ||
| match Check.is_derivable ~loc rec_flag tds with | ||
| | Error e -> [pstr_extension ~loc (e) [] ] | ||
| | Ok () -> List.concat_map (derive_per_td) tds | ||
| ;; | ||
| end | ||
|
|
||
| let make = | ||
| let attributes = | ||
| (Attribute.T Annotations.default_attr)::[Attribute.T Annotations.main_attr] | ||
| in | ||
| Deriving.add "make" | ||
| ~str_type_decl: | ||
| (Deriving.Generator.V2.make_noarg | ||
| ~attributes | ||
| Gen_struct.generate) | ||
| ~sig_type_decl: | ||
| (Deriving.Generator.V2.make_noarg | ||
| ~attributes | ||
| Gen_sig.generate) | ||
| ;; | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # This file is generated by dune, edit dune-project instead | ||
| opam-version: "2.0" | ||
| synopsis: "Standard PPX derivers" | ||
| homepage: "https://github.com/ocaml-ppx/standard_derivers" | ||
| bug-reports: "https://github.com/ocaml-ppx/standard_derivers/issues" | ||
| depends: [ | ||
| "dune" {>= "3.0"} | ||
| "ppxlib" {>= "0.18.0"} | ||
| "odoc" {with-doc} | ||
| ] | ||
| build: [ | ||
| ["dune" "subst"] {dev} | ||
| [ | ||
| "dune" | ||
| "build" | ||
| "-p" | ||
| name | ||
| "-j" | ||
| jobs | ||
| "@install" | ||
| "@runtest" {with-test} | ||
| "@doc" {with-doc} | ||
| ] | ||
| ] | ||
| dev-repo: "git+https://github.com/ocaml-ppx/standard_derivers.git" |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.