Skip to content

Pre-release quality pass - #908

Merged
balat merged 124 commits into
masterfrom
quality-pass
Sep 27, 2026
Merged

balat merged 124 commits into
masterfrom
quality-pass

Conversation

@balat

@balat balat commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

A pre-release quality pass on the whole Eliom code base. The commits are small and each does one thing. Every commit builds.

It follows #907 (latent bug fixes, merged). Unlike #907, it is meant to be behaviour-preserving, except for the last lots (Q6, Q7), which have CHANGES entries.

It is organised in lots:

  • Q1, hygiene:
    • move open Lwt.Syntax below the licence header
    • delete dead and commented-out code
    • translate the remaining French comments
    • fix stale comments
    • remove pointless aliases and exception wrappers
    • use Option, List.concat_map, Re.execp, Sys.getenv_opt and Fun.id instead of hand-written equivalents
    • remove the Obj.t in Comet
  • Q2, names and small factorisations:
    • rename French and unclear local names
    • label same-typed positional arguments
    • factor duplicated blocks
  • Q3, records for internal tuples:
    • the 25-tuple of setters in Mod_main.parse_eliom_option
    • eref', State tables and states (the phantom types of State.Ext.state are now checked)
    • eliom_params_after_action, naservice_table_content, service tables
    • cookie_info, persistent cookies, state cookies of a request
    • session groups, site timeouts, configured limits and masks
    • Comet client messages and idle policy, ppx accumulators, Mod_dom.select_nodes
  • Q4, Lwt style: let* instead of >>=, >|= and Lwt.bind, one commit per file. The handlers of Lwt.catch keep Lwt.fail: Lwt.reraise raises synchronously when the watched promise is already rejected (see Fix latent bugs found during the pre-release review #907).
  • Q5, file layout:
    • reorder client.client.ml (pure moves)
    • split it into Client_relink, Client_history and Client_call, each with an interface; Client goes from 2249 to 1488 lines and its interface is unchanged
    • split Common.get_session_info into named helpers
    • not done: moving State.Ext to its own file (it uses functions of State, which must export it, so a separate module would create a cycle) and moving the ppx Cmo module (it depends on the global settings of Ppx_eliom_utils, which uses it)
  • Q6, legacy browsers: drop the fallbacks for browsers without querySelectorAll, compareDocumentPosition, document.createEvent, addEventListener or the History API, and the Internet Explorer 8 workarounds (CHANGES entry).
  • Q7, OCaml ≥ 4.14, required anyway by ocsigenserver and wasm_of_ocaml (CHANGES entry); use String.starts_with and String.ends_with.

The public, documented API is not changed.

Testing: every commit builds, dune build @runtest passes, and the Ocsigen Start template (os_template) built against this branch was driven in a browser (wasm and JS clients): client-side navigation, back and forward, reload, RPC with a session reference, persistent reference, and Comet notifications between two browsers.

@balat
balat force-pushed the quality-pass branch 2 times, most recently from 4a5bdaa to f0a31b2 Compare September 26, 2026 16:57
@balat
balat changed the base branch from fix-latent-bugs to master September 26, 2026 17:46
A mechanical edit had put open Lwt.Syntax on the first line of 22 files,
above the licence header and the module description. Move it next to the
other opens, after those comments. The order of the opens is unchanged.
- Client_core.delay, never called.
- In ppx_eliom_server, the flush wrapper that filled global_known and
  computed a list of fresh injections, then returned the unfiltered
  list; global_known was never read.
- Service_base.Unreachable_exn, never raised (the module has no
  interface, so it was exported, but nothing uses it).
- An li element built and thrown away in Tools.menu. With xhr links,
  building it registered an unused onclick client value in the request
  data; the page no longer carries it.
- Utils.todo and two empty doc comments in the tools.
- Mod_sessiongroups.getsessgrp, the identity, never called.
- The second of two identical Process.set_ignored_params calls in
  Client_main.
Remove the remnants of the old 204-based forms (204FORMS), superseded
State and Mod_sessadmin functions, and one-line leftovers. Where a block
also explained a decision, the explanation is kept.
Remove the old noscript-based CSS preloading, the set_tracing and URL-hash
tracing hooks, the Marshal-based header encoding, and a section header
with nothing under it.
Remove camlp4-era patterns, the disabled camlp4 dump in eliomc, and
commented-out debug prints.
Also drop a commented-out record field in the Comet handler type.
- Name restore_history_dom in its comment (it said replace_page_in_history).
- Put the explanation of persist_document_head above it instead of after it.
- Drop a note about js_of_ocaml 1.1.2 and the commented-out code it
  referred to.
- State.create_volatile_table_during_session_, an undocumented alias of
  Mod_datasess.create_volatile_table_during_session used once.
- Mod_cookies.compute_session_cookies_to_send, only called through its
  alias compute_cookies_to_send; keep the latter name.
- module Opt = Option in Client, used twice.
The error promise of a client bus was derived from the Lwt.wait promise by
a bind ending in assert false, wrapped in an identity Lwt.catch, only to
give it the type of the stream elements while the resolver kept the type
exn Lwt.u. The resolver is only used with Lwt.wakeup_exn, so give both the
same type and use the pair directly.
- The Lwt.catch of Registration.typed_apply matched
  Eliom_Wrong_parameter only to fail with it, as with every other
  exception: use Lwt.fail as handler. The catch itself is needed and
  now says why: the parameter promises are already resolved, so the
  handler runs synchronously inside the bind, and the catch turns its
  exceptions into a rejected promise.
- Parameter_base.reconstruct_params and State.Ext.lwt_check_scopes spelled
  out try Lwt.return (f x) with e -> Lwt.fail e; use Lwt.wrap and
  Lwt.wrap2.
- raise (failwith ...) in Mkreg: failwith already raises.
get_secure has no optional argument, so its trailing unit parameter was
useless and every call ended with ().
Replace matches that spell out Option.map, Option.value and
Option.is_some, and the local option_map of Eliom_form. Option.value is
only used where the default is a constant or a variable, since it is
evaluated eagerly.
Replace List.concat (List.map ...), List.flatten (List.map ...), the
flatmap helper of the ppx and the recursive map_include of the tools with
List.concat_map, which applies the function in the same order.
- Re.execp instead of catching Not_found around Re.exec.
- Sys.getenv_opt instead of catching Not_found around Sys.getenv.
- Lwt_stream.flatten instead of Lwt_stream.map_list (fun x -> x).
- Node_id_set.of_list instead of a fold of Node_id_set.add.
- Fun.id instead of (fun x -> x).
Several log messages built their format as ("message" ^^ "@\n%s"). Write
the literal "message@\n%s" instead.
- string_of_result spelled out the four combinations of two booleans;
  use %b.
- make_uri special-cased the empty path for String.concat, which already
  returns "".
- A log message concatenated its text before formatting it with "%s".
- Url.has_get_args caught Not_found from String.index; use
  String.contains.
An Events channel kept its React event alive in a field of type Obj.t
option, set after building the channel through a match with an
assert false branch. The value is a bool React.event, and the fold
closure only needs a mutable cell to drop it on overflow: allocate that
cell first, as a bool React.event option ref, and build the channel with
it. No behaviour change.
The js_of_ocaml ppx drops the suffix after the last underscore, so
___eliom_appl_process_info_foo already denoted __eliom_appl_process_info.
Use the trailing underscore, as for ___eliom_server_; the generated
property name is unchanged.
Common.foundornot (Found of 'a | Notfound of 'b) was used as an option in
Route.make_naservice (always Notfound ()) and as a result carrying the
exception in Route_base.find_page_table. Use the standard types.
- Common: Vide and AVide constructors become Empty and AEmpty.
- Eliom_uri.reconstruct_relative_url_path: aremonter, aaller become up,
  down.
- Tools.menu: deplier becomes expand.
- Utils: longueur, deb, beg, endd become len, start, stop.
- Error_pages: deb becomes acc.
Mod_dom.ancessor becomes Mod_dom.ancestor (internal module), local
childrens variables become children.
- Common.make_full_state_name2 site_dir_string secure ~scope becomes
  make_full_state_name_of_sitedata ~sitedata ~secure ~scope: every caller
  passed get_site_dir_string of a sitedata.
- Mod_sessadmin.close_all_*_states2 become close_all_*_states_of_name
  (they take a full state name).
- Mod_persess.close_persistent_state2 becomes
  close_persistent_state_of_cookie, and Mod_sessiongroups.Pers.
  close_persistent_session2 becomes close_persistent_session.
- In State, fold_sub_states_aux_aux becomes prepare_sub_states_fold and
  the local f__ functions become find_or_create_cookie.
The global reference holding the reload function of the current page had
the same name as the reload_function field in which it is stashed for the
pages of the history.
The timeout setters took two adjacent positional booleans (whether to
override a value set by the configuration file, and whether the new value
comes from it), and callers wrote true true or override_configfile false.
Label them.
Every caller passed the site directory string and the two subnet masks of
the same sitedata as three positional arguments (two of them ints, easy
to swap). Take ~sitedata and compute them inside.
rebuild_class_list took three positional string lists (current classes,
classes to remove, classes to add), and one caller passed the same list
twice to remove and re-add it. Label them ~current ~removed ~added, use
List.mem, and pass ~removed:[] in that caller, which is equivalent.
Eliom was declared to build with OCaml 4.12, but two of its dependencies,
ocsigenserver and wasm_of_ocaml-compiler, require 4.14, and CI only tests
4.14 and 5. Raising the bound lets the code use the Stdlib functions of
4.13 and 4.14.
Replace the hand-written prefix and suffix tests (String.sub with a
length check, or a catch of Invalid_argument) by the Stdlib functions,
available since OCaml 4.13. This also removes the string_prefix helper of
Tools and makes remove_prefixed_param a List.filter.
Drop the parent-walking fallback of ancestor for browsers without
Node.compareDocumentPosition (Internet Explorer 8 and older).
Drop the fallbacks for browsers without querySelectorAll or classList
(Internet Explorer 8 and older), which walked the DOM and tested the
class names by hand. The has_classes helper, which returned four booleans
and was only used by these fallbacks, goes with them.
Drop the createEventObject fallback for Internet Explorer 8 and older.
copy_element worked around appendChild failing on script and style
elements, and className not being updated, in Internet Explorer 8 and
older. Append the copied children directly; the class attribute is
copied with the other attributes.
Drop the styleSheet.cssText branch for Internet Explorer 8, where setting
innerHTML on a style element failed.
Drop the code paths for browsers without history.pushState (Internet
Explorer 9 and older): the URL fragment encoding of the current page
(#!uri), its hashchange handler, and the fallbacks of Request_info that
read the path from the current URL. Process.history_api stays, as it is
exported.
It was only used by the code for browsers without the History API, and
fell back to polling location.hash for browsers without the hashchange
event.
Drop the attachEvent fallback of Internet Explorer 8 and older, and
document in CHANGES the browser features that the client side now
requires, and the removal of Mod_dom.onhashchange.
select_nodes returned a 5-tuple of node lists, and
relink_page_but_client_values passed two of them on as a pair. Both now
return a selected_nodes record. Mod_dom.select_nodes is exported, so the
change is noted in CHANGES.
The state types stored in the History API were split between two
blocks that disabled warning 39. They are now declared in one block.
Page_status was defined in the middle of the URL functions, far from
Page_status_t and the page type it builds on.
It sat between the unwrappers and the History state types, while its
only use is in init, for the handlers defined below it.
The unwrappers of client values, TyXML nodes and global data were
registered far from the one of server functions, which has to come
after call_ocaml_service. They are now registered together. Nothing
is unwrapped while the module is being initialised, so registering
them later does not change behaviour.
lock_request_handling, unlock_request_handling and wait_load_end join
the other aliases kept for the interface. The only internal use of
wait_load_end now calls Client_core directly.
Both were local to the block that installs the popstate handler, which
made that block 150 lines long. The block now only installs the
handler.
The code that binds links, forms, unique nodes, event handlers and
client attributes after a page is received depends only on Client_core
and Mod_dom. It moves, unchanged, to its own module with an interface
exporting the four functions Client uses.
The page records, Page_status, the History module and the data saved
for each state of the History API move, unchanged, to their own module.
Client opens it, since pages are used throughout, and re-exports
Page_status and History as before.
Building a request to a service, sending it, and leaving the
application (exit_to, window_open) move, unchanged, to their own module.
Client re-exports call_service, exit_to and window_open. The call of
OCaml services stays in Client, as it initialises the request data.
get_tab_cookies finds the tab cookies in the request cache after an
action, otherwise in the POST parameters or in the HTTP headers.
get_session_info reads the client process info, the expecting-process-page
flag and the cookie substitutes through get_client_process_info,
get_expecting_process_page and get_browser_cookies.
split_request_params separates the non-localised and ignored parameters
of a request, or takes them from the request cache after an action.
get_service_params removes from the GET and POST parameters the ones that
identify the non-attached service or the state of the request. It returns
a service_params record instead of a 5-tuple with nested pairs.
Common.get_sp and the registration of a service with a session, session
group or client process scope raised Failure when called outside a
request, the latter with the unhelpful message "Missing sp while
registering service". Site_information_not_available does not fit: the
site information is available during the initialisation phase, it is
the request that is missing.

Both now raise the new exception Request_information_not_available,
whose argument tells where the request was needed. For get_sp it keeps
the Lwt call stack that the Failure message carried. The documentation
of register and get_sp mentions the case, and an error during the
initialisation of a site reports it with an explanation.
@balat
balat merged commit 366c32c into master Sep 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant