diff --git a/.gitignore b/.gitignore
index 369b31c..6b88073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/camoufox-*
+/firefox-*
/mozilla-unified
/extra-docs
/.vscode
@@ -10,3 +11,4 @@ launch.exe
*.old
__pycache__/
*.pyc
+wget-log
diff --git a/Dockerfile b/Dockerfile
index 7ff4140..00548d8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,15 +7,15 @@ COPY . /app
# Install necessary packages
RUN apt-get update && apt-get install -y \
- # Makefile utils
- build-essential make git msitools wget unzip \
+ # Mach build tools
+ build-essential make msitools wget unzip \
# Python
python3 python3-dev python3-pip \
- # Camoufox build system utils
- p7zip-full golang-go
+ # Camoufox build system tools
+ git p7zip-full golang-go aria2c
# Fetch Firefox & apply initial patches
-RUN make fetch && \
+RUN make setup-minimal && \
make mozbootstrap && \
mkdir /app/dist
@@ -23,4 +23,4 @@ RUN make fetch && \
VOLUME /root/.mozbuild
VOLUME /app/dist
-ENTRYPOINT ["python3", "./multibuild.py"]
\ No newline at end of file
+ENTRYPOINT ["python3", "./multibuild.py"]
diff --git a/Makefile b/Makefile
index 3a82ad3..4937b7f 100644
--- a/Makefile
+++ b/Makefile
@@ -2,19 +2,21 @@ include upstream.sh
export
cf_source_dir := camoufox-$(version)-$(release)
+ff_source_tarball := firefox-$(version).source.tar.xz
-debs := python3 python3-dev python3-pip p7zip-full golang-go msitools wget
-rpms := python3 python3-devel p7zip golang msitools wget
-pacman := python python-pip p7zip go msitools wget
+debs := python3 python3-dev python3-pip p7zip-full golang-go msitools wget aria2c
+rpms := python3 python3-devel p7zip golang msitools wget aria2c
+pacman := python python-pip p7zip go msitools wget aria2c
-.PHONY: help fetch clean distclean build package build-launcher check-arch revert edits run bootstrap mozbootstrap dir package-common package-linux package-macos package-windows
+.PHONY: help fetch setup setup-minimal clean distclean build package build-launcher check-arch revert edits run bootstrap mozbootstrap dir package-common package-linux package-macos package-windows
help:
@echo "Available targets:"
- @echo " fetch - Clone Firefox source code"
+ @echo " fetch - Fetch the Firefox source code"
+ @echo " setup - Setup Camoufox & local git repo for development"
@echo " bootstrap - Set up build environment"
@echo " mozbootstrap - Sets up mach"
- @echo " dir - Prepare Camoufox source directory"
+ @echo " dir - Prepare Camoufox source directory with BUILD_TARGET"
@echo " revert - Kill all working changes"
@echo " edits - Camoufox developer UI"
@echo " build-launcher - Build launcher"
@@ -27,17 +29,35 @@ help:
@echo " run - Run Camoufox"
fetch:
- git clone --depth 1 --branch $(BASE_BRANCH) --single-branch $(REMOTE_URL) $(cf_source_dir)
- cd $(cf_source_dir) && git fetch --depth 1 origin $(BASE_REVISION)
- make revert
+ aria2c -x16 -s16 -k1M -o $(ff_source_tarball) "https://archive.mozilla.org/pub/firefox/releases/$(version)/source/firefox-$(version).source.tar.xz"; \
+
+setup-minimal:
+ # Note: Only docker containers are intended to run this directly.
+ # Run this before `make dir` or `make build` to avoid setting up a local git repo.
+ if [ ! -f $(ff_source_tarball) ]; then \
+ make fetch; \
+ fi
+ # Create new cf_source_dir
+ rm -rf $(cf_source_dir)
+ mkdir -p $(cf_source_dir)
+ tar -xJf $(ff_source_tarball) -C $(cf_source_dir) --strip-components=1
+ # Copy settings & additions
+ cd $(cf_source_dir) && bash ../scripts/copy-additions.sh $(version) $(release)
+
+setup: setup-minimal
+ # Initialize local git repo for development
+ cd $(cf_source_dir) && \
+ git init -b main && \
+ git add -f -A && \
+ git commit -m "Initial commit" && \
+ git tag -a unpatched -m "Initial commit"
revert:
- cd $(cf_source_dir) && git reset --hard $(BASE_REVISION)
- python3 scripts/init-patch.py $(version) $(release)
+ cd $(cf_source_dir) && git reset --hard unpatched
dir:
@if [ ! -d $(cf_source_dir) ]; then \
- make fetch; \
+ make setup; \
fi
make clean
python3 scripts/patch.py $(version) $(release)
@@ -61,7 +81,7 @@ clean:
make revert
distclean:
- rm -rf $(cf_source_dir)
+ rm -rf $(cf_source_dir) $(ff_source_tarball)
build:
@if [ ! -f $(cf_source_dir)/_READY ]; then \
diff --git a/README.md b/README.md
index 7b241da..08eeba7 100644
--- a/README.md
+++ b/README.md
@@ -217,7 +217,7 @@ Miscellaneous (WebGl spoofing, battery status, etc)
#### Playwright support
-- Added Playwright's Juggler patches
+- A more updated version of Playwright's Juggler for the latest Firefox, maintained by me
- Various config patches to evade bot detection
#### Debloat/Optimizations
@@ -386,9 +386,7 @@ Build artifacts will now appear written under the `dist/` folder.
---
-## Development Notes
-
-### How to make a patch
+## Development Tools
This repo comes with a developer UI under scripts/developer.py:
@@ -396,26 +394,20 @@ This repo comes with a developer UI under scripts/developer.py:
make edits
```
-Patches can be added, removed, and new patches can be added through here.
+Patches can be edited, created, removed, and managed through here.
-
+
-To use, select "Reset workspace", make changes in the camoufox-\*/ folder, then select "Write workspace to patch".
+### How to make a patch
+
+1. In the developer UI, click **Reset workspace**.
+2. Make changes in the `camoufox-*/` folder as needed. You can test your changes with `make build` and `make run`.
+3. After you're done making changes, click **Write workspace to patch** and save the patch file.
### How to work on an existing patch
-1. In the developer UI, reset your workspace.
-2. Then, click "Select patches", and select all **but** the one you want to edit.
-3. Create a checkpoint. This will commit the current workspace to the local repo:
-
-```bash
-make checkpoint
-```
-
-4. In the developer UI, click "Select patches", and _apply_ the patch you would like to edit.
-
- Make your changes. Test builds can be made with `make build` and `make run`.
-
-5. After you're done editing, hit "Write workspace to patch", and overwrite the existing patch.
+1. In the developer UI, click **Edit a patch**.
+2. Select the patch you'd like to edit. Your workspace will be reset to the state of the selected patch.
+3. After you're done making changes, hit **Write workspace to patch** and overwrite the existing patch file.
---
diff --git a/additions/juggler/TargetRegistry.js b/additions/juggler/TargetRegistry.js
index ea7b2af..8c23ff8 100644
--- a/additions/juggler/TargetRegistry.js
+++ b/additions/juggler/TargetRegistry.js
@@ -368,7 +368,7 @@ class PageTarget {
onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
};
this._eventListeners = [
- helper.addObserver(this._updateModalDialogs.bind(this), 'tabmodal-dialog-loaded'),
+ helper.addObserver(this._updateModalDialogs.bind(this), 'common-dialog-loaded'),
helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
helper.addEventListener(this._linkedBrowser, 'WillChangeBrowserRemoteness', event => this._willChangeBrowserRemoteness()),
@@ -499,7 +499,7 @@ class PageTarget {
}
_updateModalDialogs() {
- const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);
+ const prompts = new Set(this._linkedBrowser.tabDialogBox.getContentDialogManager().dialogs.map(dialog => dialog.frameContentWindow.Dialog));
for (const dialog of this._dialogs.values()) {
if (!prompts.has(dialog.prompt())) {
this._dialogs.delete(dialog.id());
diff --git a/additions/juggler/content/JugglerFrameChild.jsm b/additions/juggler/content/JugglerFrameChild.jsm
index 529f6d3..47fcabb 100644
--- a/additions/juggler/content/JugglerFrameChild.jsm
+++ b/additions/juggler/content/JugglerFrameChild.jsm
@@ -8,6 +8,8 @@ const helper = new Helper();
let sameProcessInstanceNumber = 0;
+const topBrowingContextToAgents = new Map();
+
class JugglerFrameChild extends JSWindowActorChild {
constructor() {
super();
@@ -16,46 +18,66 @@ class JugglerFrameChild extends JSWindowActorChild {
}
handleEvent(aEvent) {
- if (this._agents && aEvent.type === 'DOMWillOpenModalDialog') {
- this._agents.channel.pause();
+ const agents = this._agents();
+ if (!agents)
+ return;
+ if (aEvent.type === 'DOMWillOpenModalDialog') {
+ agents.channel.pause();
return;
}
- if (this._agents && aEvent.type === 'DOMModalDialogClosed') {
- this._agents.channel.resumeSoon();
+ if (aEvent.type === 'DOMModalDialogClosed') {
+ agents.channel.resumeSoon();
return;
}
- if (this._agents && aEvent.target === this.document)
- this._agents.pageAgent.onWindowEvent(aEvent);
- if (this._agents && aEvent.target === this.document)
- this._agents.frameTree.onWindowEvent(aEvent);
+ if (aEvent.target === this.document) {
+ agents.pageAgent.onWindowEvent(aEvent);
+ agents.frameTree.onWindowEvent(aEvent);
+ }
+ }
+
+ _agents() {
+ return topBrowingContextToAgents.get(this.browsingContext.top);
}
actorCreated() {
this.actorName = `content::${this.browsingContext.browserId}/${this.browsingContext.id}/${++sameProcessInstanceNumber}`;
this._eventListeners.push(helper.addEventListener(this.contentWindow, 'load', event => {
- this._agents?.pageAgent.onWindowEvent(event);
+ this._agents()?.pageAgent.onWindowEvent(event);
}));
if (this.document.documentURI.startsWith('moz-extension://'))
return;
- this._agents = initialize(this.browsingContext, this.docShell, this);
- }
- _dispose() {
- helper.removeListeners(this._eventListeners);
- // We do not cleanup since agents are shared for all frames in the process.
+ // Child frame events will be forwarded to related top-level agents.
+ if (this.browsingContext.parent)
+ return;
- // TODO: restore the cleanup.
- // Reset transport so that all messages will be pending and will not throw any errors.
- // this._channel.resetTransport();
- // this._agents.pageAgent.dispose();
- // this._agents.frameTree.dispose();
- // this._agents = undefined;
+ let agents = topBrowingContextToAgents.get(this.browsingContext);
+ if (!agents) {
+ agents = initialize(this.browsingContext, this.docShell);
+ topBrowingContextToAgents.set(this.browsingContext, agents);
+ }
+ agents.channel.bindToActor(this);
+ agents.actor = this;
}
didDestroy() {
- this._dispose();
+ helper.removeListeners(this._eventListeners);
+
+ if (this.browsingContext.parent)
+ return;
+
+ const agents = topBrowingContextToAgents.get(this.browsingContext);
+ // The agents are already re-bound to a new actor.
+ if (agents.actor !== this)
+ return;
+
+ topBrowingContextToAgents.delete(this.browsingContext);
+
+ agents.channel.resetTransport();
+ agents.pageAgent.dispose();
+ agents.frameTree.dispose();
}
receiveMessage() { }
diff --git a/additions/juggler/content/PageAgent.js b/additions/juggler/content/PageAgent.js
index 6aee921..70dcf04 100644
--- a/additions/juggler/content/PageAgent.js
+++ b/additions/juggler/content/PageAgent.js
@@ -370,7 +370,12 @@ class PageAgent {
const unsafeObject = frame.unsafeObject(objectId);
if (!unsafeObject)
throw new Error('Object is not input!');
- const nsFiles = await Promise.all(files.map(filePath => File.createFromFileName(filePath)));
+ let nsFiles;
+ if (unsafeObject.webkitdirectory) {
+ nsFiles = await new Directory(files[0]).getFiles(true);
+ } else {
+ nsFiles = await Promise.all(files.map(filePath => File.createFromFileName(filePath)));
+ }
unsafeObject.mozSetFileArray(nsFiles);
const events = [
new (frame.domWindow().Event)('input', { bubbles: true, cancelable: true, composed: true }),
diff --git a/additions/juggler/content/main.js b/additions/juggler/content/main.js
index 022b1e3..15986bb 100644
--- a/additions/juggler/content/main.js
+++ b/additions/juggler/content/main.js
@@ -7,24 +7,10 @@ const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTr
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
-const browsingContextToAgents = new Map();
const helper = new Helper();
-function initialize(browsingContext, docShell, actor) {
- if (browsingContext.parent) {
- // For child frames, return agents from the main frame.
- return browsingContextToAgents.get(browsingContext.top);
- }
-
- let data = browsingContextToAgents.get(browsingContext);
- if (data) {
- // Rebind from one main frame actor to another one.
- data.channel.bindToActor(actor);
- return data;
- }
-
- data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false };
- browsingContextToAgents.set(browsingContext, data);
+function initialize(browsingContext, docShell) {
+ const data = { channel: undefined, pageAgent: undefined, frameTree: undefined, failedToOverrideTimezone: false };
const applySetting = {
geolocation: (geolocation) => {
@@ -84,7 +70,6 @@ function initialize(browsingContext, docShell, actor) {
data.frameTree.addBinding(worldName, name, script);
data.frameTree.setInitScripts([...contextCrossProcessCookie.initScripts, ...pageCrossProcessCookie.initScripts]);
data.channel = new SimpleChannel('', 'process-' + Services.appinfo.processID);
- data.channel.bindToActor(actor);
data.pageAgent = new PageAgent(data.channel, data.frameTree);
docShell.fileInputInterceptionEnabled = !!pageCrossProcessCookie.interceptFileChooserDialog;
diff --git a/patches/backport-rust-crates.bootstrap b/patches/backport-rust-crates.bootstrap
deleted file mode 100644
index f1003ae..0000000
--- a/patches/backport-rust-crates.bootstrap
+++ /dev/null
@@ -1,21484 +0,0 @@
-diff --git a/Cargo.lock b/Cargo.lock
-index 7242103ece..1406b76c63 100644
---- a/Cargo.lock
-+++ b/Cargo.lock
-@@ -816,21 +816,21 @@ checksum = "74428ae4f7f05f32f4448e9f42d371538196919c4834979f4f96d1fdebffcb47"
- dependencies = [
- "winapi",
- ]
-
- [[package]]
- name = "cookie"
- version = "0.16.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
- dependencies = [
-- "time 0.3.23",
-+ "time 0.3.36",
- "version_check",
- ]
-
- [[package]]
- name = "core-foundation"
- version = "0.9.3"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
- dependencies = [
- "core-foundation-sys",
-@@ -1257,20 +1257,29 @@ dependencies = [
-
- [[package]]
- name = "debugid"
- version = "0.8.0"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
- dependencies = [
- "uuid",
- ]
-
-+[[package]]
-+name = "deranged"
-+version = "0.3.11"
-+source = "registry+https://github.com/rust-lang/crates.io-index"
-+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
-+dependencies = [
-+ "powerfmt",
-+]
-+
- [[package]]
- name = "derive_arbitrary"
- version = "1.3.1"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8"
- dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- ]
-@@ -3750,21 +3759,21 @@ dependencies = [
- "once_cell",
- "proc-macro2",
- "quote",
- "regex",
- "scopeguard",
- "semver",
- "serde",
- "serde_json",
- "smallvec",
- "syn",
-- "time 0.3.23",
-+ "time 0.3.36",
- "tokio",
- "tokio-util",
- "tracing",
- "url",
- "uuid",
- "winapi",
- "windows-sys 0.52.0",
- ]
-
- [[package]]
-@@ -3880,21 +3889,21 @@ dependencies = [
-
- [[package]]
- name = "neqo-common"
- version = "0.7.2"
- source = "git+https://github.com/mozilla/neqo?tag=v0.7.2#ce5cbe4dfc2e38b238abb022c39eee4215058221"
- dependencies = [
- "enum-map",
- "env_logger",
- "log",
- "qlog",
-- "time 0.3.23",
-+ "time 0.3.36",
- "winapi",
- ]
-
- [[package]]
- name = "neqo-crypto"
- version = "0.7.2"
- source = "git+https://github.com/mozilla/neqo?tag=v0.7.2#ce5cbe4dfc2e38b238abb022c39eee4215058221"
- dependencies = [
- "bindgen 0.69.4",
- "log",
-@@ -4070,20 +4079,26 @@ dependencies = [
- "encoding_rs",
- ]
-
- [[package]]
- name = "nsstring-gtest"
- version = "0.1.0"
- dependencies = [
- "nsstring",
- ]
-
-+[[package]]
-+name = "num-conv"
-+version = "0.1.0"
-+source = "registry+https://github.com/rust-lang/crates.io-index"
-+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
-+
- [[package]]
- name = "num-derive"
- version = "0.4.0"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e"
- dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- ]
-@@ -4443,40 +4458,47 @@ dependencies = [
- [[package]]
- name = "plist"
- version = "1.3.1"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "bd39bc6cdc9355ad1dc5eeedefee696bb35c34caf21768741e81826c0bbd7225"
- dependencies = [
- "base64 0.13.999",
- "indexmap 1.9.3",
- "line-wrap",
- "serde",
-- "time 0.3.23",
-+ "time 0.3.36",
- "xml-rs",
- ]
-
-+
- [[package]]
- name = "ppv-lite86"
- version = "0.2.17"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-
- [[package]]
- name = "precomputed-hash"
- version = "0.1.1"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
-
- [[package]]
- name = "prefs_parser"
- version = "0.0.1"
-
-+[[package]]
-+name = "powerfmt"
-+version = "0.2.0"
-+source = "registry+https://github.com/rust-lang/crates.io-index"
-+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-+
- [[package]]
- name = "presser"
- version = "0.3.1"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
-
- [[package]]
- name = "prio"
- version = "0.15.3"
- source = "registry+https://github.com/rust-lang/crates.io-index"
-@@ -5622,42 +5644,46 @@ version = "0.1.45"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
- dependencies = [
- "libc",
- "wasi 0.10.0+wasi-snapshot-preview999",
- "winapi",
- ]
-
- [[package]]
- name = "time"
--version = "0.3.23"
-+version = "0.3.36"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
-+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
- dependencies = [
-+ "deranged",
- "itoa",
-+ "num-conv",
-+ "powerfmt",
- "serde",
- "time-core",
- "time-macros",
- ]
-
- [[package]]
- name = "time-core"
--version = "0.1.1"
-+version = "0.1.2"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
-+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
-
- [[package]]
- name = "time-macros"
--version = "0.2.10"
-+version = "0.2.18"
- source = "registry+https://github.com/rust-lang/crates.io-index"
--checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
-+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
- dependencies = [
-+ "num-conv",
- "time-core",
- ]
-
- [[package]]
- name = "tinystr"
- version = "0.7.4"
- source = "registry+https://github.com/rust-lang/crates.io-index"
- checksum = "d5d0e245e80bdc9b4e5356fc45a72184abbc3861992603f515270e9340f5a219"
- dependencies = [
- "displaydoc",
-@@ -6320,21 +6346,21 @@ dependencies = [
- "base64 0.21.3",
- "bytes",
- "cookie",
- "http",
- "icu_segmenter",
- "log",
- "serde",
- "serde_derive",
- "serde_json",
- "thiserror",
-- "time 0.3.23",
-+ "time 0.3.36",
- "tokio",
- "tokio-stream",
- "url",
- "warp",
- ]
-
- [[package]]
- name = "webext-storage"
- version = "0.1.0"
- source = "git+https://github.com/mozilla/application-services?rev=5fc8ee2f0f6950e36d4096983757bd046d55df9f#5fc8ee2f0f6950e36d4096983757bd046d55df9f"
-diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
-index 31ca3fcf0f..6aadf9aef9 100644
---- a/supply-chain/audits.toml
-+++ b/supply-chain/audits.toml
-@@ -1381,20 +1381,30 @@ delta = "0.14.3 -> 0.20.1"
- who = "Mike Hommey "
- criteria = "safe-to-deploy"
- delta = "2.3.2 -> 2.3.3"
-
- [[audits.debugid]]
- who = "Gabriele Svelto "
- criteria = "safe-to-deploy"
- version = "0.8.0"
- notes = "This crates was written by Sentry and I've fully audited it as Firefox crash reporting machinery relies on it."
-
-+[[audits.deranged]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+version = "0.3.11"
-+notes = """
-+This crate contains a decent bit of `unsafe` code, however all internal
-+unsafety is verified with copious assertions (many are compile-time), and
-+otherwise the unsafety is documented and left to the caller to verify.
-+"""
-+
- [[audits.derive_arbitrary]]
- who = "Mike Hommey "
- criteria = "safe-to-run"
- delta = "1.1.0 -> 1.1.1"
-
- [[audits.derive_arbitrary]]
- who = "Mike Hommey "
- criteria = "safe-to-run"
- delta = "1.1.1 -> 1.1.3"
-
-@@ -2739,20 +2749,29 @@ who = "Josh Stone "
- criteria = "safe-to-deploy"
- version = "0.4.3"
- notes = "All code written or reviewed by Josh Stone."
-
- [[audits.num-complex]]
- who = "Josh Stone "
- criteria = "safe-to-deploy"
- version = "0.4.2"
- notes = "All code written or reviewed by Josh Stone."
-
-+[[audits.num-conv]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+version = "0.1.0"
-+notes = """
-+Very straightforward, simple crate. No dependencies, unsafe, extern,
-+side-effectful std functions, etc.
-+"""
-+
- [[audits.num-derive]]
- who = "Josh Stone "
- criteria = "safe-to-deploy"
- version = "0.3.3"
- notes = "All code written or reviewed by Josh Stone."
-
- [[audits.num-derive]]
- who = "Mike Hommey "
- criteria = "safe-to-deploy"
- delta = "0.3.3 -> 0.4.0"
-@@ -3008,20 +3027,29 @@ delta = "0.1.4 -> 0.1.5"
- who = "Mike Hommey "
- criteria = "safe-to-deploy"
- delta = "0.3.25 -> 0.3.26"
-
- [[audits.plane-split]]
- who = "Nicolas Silva "
- criteria = "safe-to-deploy"
- version = "0.18.0"
- notes = "Mozilla-developed package, no unsafe code, no access to file system, network or other far reaching APIs."
-
-+[[audits.powerfmt]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+version = "0.2.0"
-+notes = """
-+A tiny bit of unsafe code to implement functionality that isn't in stable rust
-+yet, but it's all valid. Otherwise it's a pretty simple crate.
-+"""
-+
- [[audits.ppv-lite86]]
- who = "Mike Hommey "
- criteria = "safe-to-deploy"
- delta = "0.2.16 -> 0.2.17"
-
- [[audits.precomputed-hash]]
- who = "Bobby Holley "
- criteria = "safe-to-deploy"
- version = "0.1.1"
- notes = "This is a trivial crate."
-@@ -3797,50 +3825,70 @@ delta = "0.1.45 -> 0.3.17"
- [[audits.time]]
- who = "Mike Hommey "
- criteria = "safe-to-run"
- delta = "0.3.9 -> 0.3.17"
-
- [[audits.time]]
- who = "Kershaw Chang "
- criteria = "safe-to-deploy"
- delta = "0.3.17 -> 0.3.23"
-
-+[[audits.time]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+delta = "0.3.23 -> 0.3.36"
-+notes = """
-+There's a bit of new unsafe code that is self-imposed because they now assert
-+that ordinals are non-zero. All unsafe code was checked to ensure that the
-+invariants claimed were true.
-+"""
-+
- [[audits.time-core]]
- who = "Kershaw Chang "
- criteria = "safe-to-deploy"
- version = "0.1.0"
-
- [[audits.time-core]]
- who = "Mike Hommey "
- criteria = "safe-to-run"
- version = "0.1.0"
-
- [[audits.time-core]]
- who = "Kershaw Chang "
- criteria = "safe-to-deploy"
- delta = "0.1.0 -> 0.1.1"
-
-+[[audits.time-core]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+delta = "0.1.1 -> 0.1.2"
-+
- [[audits.time-macros]]
- who = "Kershaw Chang "
- criteria = "safe-to-deploy"
- version = "0.2.6"
-
- [[audits.time-macros]]
- who = "Mike Hommey "
- criteria = "safe-to-run"
- delta = "0.2.4 -> 0.2.6"
-
- [[audits.time-macros]]
- who = "Kershaw Chang "
- criteria = "safe-to-deploy"
- delta = "0.2.6 -> 0.2.10"
-
-+[[audits.time-macros]]
-+who = "Alex Franchuk "
-+criteria = "safe-to-deploy"
-+delta = "0.2.10 -> 0.2.18"
-+
- [[audits.tinystr]]
- who = "Zibi Braniecki "
- criteria = "safe-to-deploy"
- version = "0.3.4"
-
- [[audits.tinystr]]
- who = "Zibi Braniecki "
- criteria = "safe-to-deploy"
- version = "0.6.0"
-
-diff --git a/third_party/rust/deranged/.cargo-checksum.json b/third_party/rust/deranged/.cargo-checksum.json
-new file mode 100644
-index 0000000000..f29abcd86f
---- /dev/null
-+++ b/third_party/rust/deranged/.cargo-checksum.json
-@@ -0,0 +1 @@
-+{"files":{"Cargo.toml":"d1ee03b7033e382279ff580d89a70a9aaf163f977400f0899ad9624e24744e6f","LICENSE-Apache":"b8929fea28678da67251fb2daf9438f67503814211051861612441806d8edb05","LICENSE-MIT":"04620bf27e4a643dd47bf27652320c205acdb776c1f9f24bb8c3bfaba10804c5","README.md":"fc4c9482d9e5225630da44e5371d6fa3f37220e2f4da2dac076cf4cd4f9592e7","src/lib.rs":"bc4b045c160d6f28726831d83f8389d9231410ae289a99950f63436219488dbb","src/tests.rs":"235e4f158084d12b0bfe85745c444d38bb134ebe584396d0a43154260f6576a7","src/traits.rs":"e3984e763afaa23dcf8ea686b473336472953b05abebc433acb26ab5f2237257","src/unsafe_wrapper.rs":"6e57697c2cd484cd60c1a50c4f4d32cb17526447c0f387d8ea3d89a2a89db688"},"package":"b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"}
-\ No newline at end of file
-diff --git a/third_party/rust/deranged/Cargo.toml b/third_party/rust/deranged/Cargo.toml
-new file mode 100644
-index 0000000000..ff660b538c
---- /dev/null
-+++ b/third_party/rust/deranged/Cargo.toml
-@@ -0,0 +1,83 @@
-+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
-+#
-+# When uploading crates to the registry Cargo will automatically
-+# "normalize" Cargo.toml files for maximal compatibility
-+# with all versions of Cargo and also rewrite `path` dependencies
-+# to registry (e.g., crates.io) dependencies.
-+#
-+# If you are reading this file be aware that the original Cargo.toml
-+# will likely look very different (and much more reasonable).
-+# See Cargo.toml.orig for the original contents.
-+
-+[package]
-+edition = "2021"
-+rust-version = "1.67.0"
-+name = "deranged"
-+version = "0.3.11"
-+authors = ["Jacob Pratt "]
-+include = [
-+ "src/**/*",
-+ "LICENSE-*",
-+ "README.md",
-+]
-+description = "Ranged integers"
-+readme = "README.md"
-+keywords = [
-+ "integer",
-+ "int",
-+ "range",
-+]
-+license = "MIT OR Apache-2.0"
-+repository = "https://github.com/jhpratt/deranged"
-+
-+[package.metadata.docs.rs]
-+all-features = true
-+rustdoc-args = [
-+ "--cfg",
-+ "docs_rs",
-+]
-+targets = ["x86_64-unknown-linux-gnu"]
-+
-+[dependencies.num-traits]
-+version = "0.2.15"
-+optional = true
-+default-features = false
-+
-+[dependencies.powerfmt]
-+version = "0.2.0"
-+optional = true
-+default-features = false
-+
-+[dependencies.quickcheck]
-+version = "1.0.3"
-+optional = true
-+default-features = false
-+
-+[dependencies.rand]
-+version = "0.8.4"
-+optional = true
-+default-features = false
-+
-+[dependencies.serde]
-+version = "1.0.126"
-+optional = true
-+default-features = false
-+
-+[dev-dependencies.rand]
-+version = "0.8.4"
-+
-+[dev-dependencies.serde_json]
-+version = "1.0.86"
-+
-+[features]
-+alloc = []
-+default = ["std"]
-+num = ["dep:num-traits"]
-+powerfmt = ["dep:powerfmt"]
-+quickcheck = [
-+ "dep:quickcheck",
-+ "alloc",
-+]
-+rand = ["dep:rand"]
-+serde = ["dep:serde"]
-+std = ["alloc"]
-diff --git a/third_party/rust/deranged/LICENSE-Apache b/third_party/rust/deranged/LICENSE-Apache
-new file mode 100644
-index 0000000000..7646f21e37
---- /dev/null
-+++ b/third_party/rust/deranged/LICENSE-Apache
-@@ -0,0 +1,202 @@
-+
-+ Apache License
-+ Version 2.0, January 2004
-+ http://www.apache.org/licenses/
-+
-+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-+
-+ 1. Definitions.
-+
-+ "License" shall mean the terms and conditions for use, reproduction,
-+ and distribution as defined by Sections 1 through 9 of this document.
-+
-+ "Licensor" shall mean the copyright owner or entity authorized by
-+ the copyright owner that is granting the License.
-+
-+ "Legal Entity" shall mean the union of the acting entity and all
-+ other entities that control, are controlled by, or are under common
-+ control with that entity. For the purposes of this definition,
-+ "control" means (i) the power, direct or indirect, to cause the
-+ direction or management of such entity, whether by contract or
-+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
-+ outstanding shares, or (iii) beneficial ownership of such entity.
-+
-+ "You" (or "Your") shall mean an individual or Legal Entity
-+ exercising permissions granted by this License.
-+
-+ "Source" form shall mean the preferred form for making modifications,
-+ including but not limited to software source code, documentation
-+ source, and configuration files.
-+
-+ "Object" form shall mean any form resulting from mechanical
-+ transformation or translation of a Source form, including but
-+ not limited to compiled object code, generated documentation,
-+ and conversions to other media types.
-+
-+ "Work" shall mean the work of authorship, whether in Source or
-+ Object form, made available under the License, as indicated by a
-+ copyright notice that is included in or attached to the work
-+ (an example is provided in the Appendix below).
-+
-+ "Derivative Works" shall mean any work, whether in Source or Object
-+ form, that is based on (or derived from) the Work and for which the
-+ editorial revisions, annotations, elaborations, or other modifications
-+ represent, as a whole, an original work of authorship. For the purposes
-+ of this License, Derivative Works shall not include works that remain
-+ separable from, or merely link (or bind by name) to the interfaces of,
-+ the Work and Derivative Works thereof.
-+
-+ "Contribution" shall mean any work of authorship, including
-+ the original version of the Work and any modifications or additions
-+ to that Work or Derivative Works thereof, that is intentionally
-+ submitted to Licensor for inclusion in the Work by the copyright owner
-+ or by an individual or Legal Entity authorized to submit on behalf of
-+ the copyright owner. For the purposes of this definition, "submitted"
-+ means any form of electronic, verbal, or written communication sent
-+ to the Licensor or its representatives, including but not limited to
-+ communication on electronic mailing lists, source code control systems,
-+ and issue tracking systems that are managed by, or on behalf of, the
-+ Licensor for the purpose of discussing and improving the Work, but
-+ excluding communication that is conspicuously marked or otherwise
-+ designated in writing by the copyright owner as "Not a Contribution."
-+
-+ "Contributor" shall mean Licensor and any individual or Legal Entity
-+ on behalf of whom a Contribution has been received by Licensor and
-+ subsequently incorporated within the Work.
-+
-+ 2. Grant of Copyright License. Subject to the terms and conditions of
-+ this License, each Contributor hereby grants to You a perpetual,
-+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-+ copyright license to reproduce, prepare Derivative Works of,
-+ publicly display, publicly perform, sublicense, and distribute the
-+ Work and such Derivative Works in Source or Object form.
-+
-+ 3. Grant of Patent License. Subject to the terms and conditions of
-+ this License, each Contributor hereby grants to You a perpetual,
-+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-+ (except as stated in this section) patent license to make, have made,
-+ use, offer to sell, sell, import, and otherwise transfer the Work,
-+ where such license applies only to those patent claims licensable
-+ by such Contributor that are necessarily infringed by their
-+ Contribution(s) alone or by combination of their Contribution(s)
-+ with the Work to which such Contribution(s) was submitted. If You
-+ institute patent litigation against any entity (including a
-+ cross-claim or counterclaim in a lawsuit) alleging that the Work
-+ or a Contribution incorporated within the Work constitutes direct
-+ or contributory patent infringement, then any patent licenses
-+ granted to You under this License for that Work shall terminate
-+ as of the date such litigation is filed.
-+
-+ 4. Redistribution. You may reproduce and distribute copies of the
-+ Work or Derivative Works thereof in any medium, with or without
-+ modifications, and in Source or Object form, provided that You
-+ meet the following conditions:
-+
-+ (a) You must give any other recipients of the Work or
-+ Derivative Works a copy of this License; and
-+
-+ (b) You must cause any modified files to carry prominent notices
-+ stating that You changed the files; and
-+
-+ (c) You must retain, in the Source form of any Derivative Works
-+ that You distribute, all copyright, patent, trademark, and
-+ attribution notices from the Source form of the Work,
-+ excluding those notices that do not pertain to any part of
-+ the Derivative Works; and
-+
-+ (d) If the Work includes a "NOTICE" text file as part of its
-+ distribution, then any Derivative Works that You distribute must
-+ include a readable copy of the attribution notices contained
-+ within such NOTICE file, excluding those notices that do not
-+ pertain to any part of the Derivative Works, in at least one
-+ of the following places: within a NOTICE text file distributed
-+ as part of the Derivative Works; within the Source form or
-+ documentation, if provided along with the Derivative Works; or,
-+ within a display generated by the Derivative Works, if and
-+ wherever such third-party notices normally appear. The contents
-+ of the NOTICE file are for informational purposes only and
-+ do not modify the License. You may add Your own attribution
-+ notices within Derivative Works that You distribute, alongside
-+ or as an addendum to the NOTICE text from the Work, provided
-+ that such additional attribution notices cannot be construed
-+ as modifying the License.
-+
-+ You may add Your own copyright statement to Your modifications and
-+ may provide additional or different license terms and conditions
-+ for use, reproduction, or distribution of Your modifications, or
-+ for any such Derivative Works as a whole, provided Your use,
-+ reproduction, and distribution of the Work otherwise complies with
-+ the conditions stated in this License.
-+
-+ 5. Submission of Contributions. Unless You explicitly state otherwise,
-+ any Contribution intentionally submitted for inclusion in the Work
-+ by You to the Licensor shall be under the terms and conditions of
-+ this License, without any additional terms or conditions.
-+ Notwithstanding the above, nothing herein shall supersede or modify
-+ the terms of any separate license agreement you may have executed
-+ with Licensor regarding such Contributions.
-+
-+ 6. Trademarks. This License does not grant permission to use the trade
-+ names, trademarks, service marks, or product names of the Licensor,
-+ except as required for reasonable and customary use in describing the
-+ origin of the Work and reproducing the content of the NOTICE file.
-+
-+ 7. Disclaimer of Warranty. Unless required by applicable law or
-+ agreed to in writing, Licensor provides the Work (and each
-+ Contributor provides its Contributions) on an "AS IS" BASIS,
-+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-+ implied, including, without limitation, any warranties or conditions
-+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-+ PARTICULAR PURPOSE. You are solely responsible for determining the
-+ appropriateness of using or redistributing the Work and assume any
-+ risks associated with Your exercise of permissions under this License.
-+
-+ 8. Limitation of Liability. In no event and under no legal theory,
-+ whether in tort (including negligence), contract, or otherwise,
-+ unless required by applicable law (such as deliberate and grossly
-+ negligent acts) or agreed to in writing, shall any Contributor be
-+ liable to You for damages, including any direct, indirect, special,
-+ incidental, or consequential damages of any character arising as a
-+ result of this License or out of the use or inability to use the
-+ Work (including but not limited to damages for loss of goodwill,
-+ work stoppage, computer failure or malfunction, or any and all
-+ other commercial damages or losses), even if such Contributor
-+ has been advised of the possibility of such damages.
-+
-+ 9. Accepting Warranty or Additional Liability. While redistributing
-+ the Work or Derivative Works thereof, You may choose to offer,
-+ and charge a fee for, acceptance of support, warranty, indemnity,
-+ or other liability obligations and/or rights consistent with this
-+ License. However, in accepting such obligations, You may act only
-+ on Your own behalf and on Your sole responsibility, not on behalf
-+ of any other Contributor, and only if You agree to indemnify,
-+ defend, and hold each Contributor harmless for any liability
-+ incurred by, or claims asserted against, such Contributor by reason
-+ of your accepting any such warranty or additional liability.
-+
-+ END OF TERMS AND CONDITIONS
-+
-+ APPENDIX: How to apply the Apache License to your work.
-+
-+ To apply the Apache License to your work, attach the following
-+ boilerplate notice, with the fields enclosed by brackets "[]"
-+ replaced with your own identifying information. (Don't include
-+ the brackets!) The text should be enclosed in the appropriate
-+ comment syntax for the file format. We also recommend that a
-+ file or class name and description of purpose be included on the
-+ same "printed page" as the copyright notice for easier
-+ identification within third-party archives.
-+
-+ Copyright 2022 Jacob Pratt et al.
-+
-+ Licensed under the Apache License, Version 2.0 (the "License");
-+ you may not use this file except in compliance with the License.
-+ You may obtain a copy of the License at
-+
-+ http://www.apache.org/licenses/LICENSE-2.0
-+
-+ Unless required by applicable law or agreed to in writing, software
-+ distributed under the License is distributed on an "AS IS" BASIS,
-+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-+ See the License for the specific language governing permissions and
-+ limitations under the License.
-diff --git a/third_party/rust/deranged/LICENSE-MIT b/third_party/rust/deranged/LICENSE-MIT
-new file mode 100644
-index 0000000000..a11a755732
---- /dev/null
-+++ b/third_party/rust/deranged/LICENSE-MIT
-@@ -0,0 +1,19 @@
-+Copyright (c) 2022 Jacob Pratt et al.
-+
-+Permission is hereby granted, free of charge, to any person obtaining a copy
-+of this software and associated documentation files (the "Software"), to deal
-+in the Software without restriction, including without limitation the rights
-+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+copies of the Software, and to permit persons to whom the Software is
-+furnished to do so, subject to the following conditions:
-+
-+The above copyright notice and this permission notice shall be included in all
-+copies or substantial portions of the Software.
-+
-+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-+SOFTWARE.
-diff --git a/third_party/rust/deranged/README.md b/third_party/rust/deranged/README.md
-new file mode 100644
-index 0000000000..eddc4b99af
---- /dev/null
-+++ b/third_party/rust/deranged/README.md
-@@ -0,0 +1,3 @@
-+# Deranged
-+
-+This crate is a proof-of-concept implementation of ranged integers.
-diff --git a/third_party/rust/deranged/src/lib.rs b/third_party/rust/deranged/src/lib.rs
-new file mode 100644
-index 0000000000..aee3ea99f6
---- /dev/null
-+++ b/third_party/rust/deranged/src/lib.rs
-@@ -0,0 +1,1474 @@
-+#![cfg_attr(docs_rs, feature(doc_auto_cfg))]
-+#![cfg_attr(not(feature = "std"), no_std)]
-+#![deny(
-+ anonymous_parameters,
-+ clippy::all,
-+ clippy::missing_safety_doc,
-+ clippy::missing_safety_doc,
-+ clippy::undocumented_unsafe_blocks,
-+ illegal_floating_point_literal_pattern,
-+ late_bound_lifetime_arguments,
-+ patterns_in_fns_without_body,
-+ rust_2018_idioms,
-+ trivial_casts,
-+ trivial_numeric_casts,
-+ unreachable_pub,
-+ unsafe_op_in_unsafe_fn,
-+ unused_extern_crates
-+)]
-+#![warn(
-+ clippy::dbg_macro,
-+ clippy::decimal_literal_representation,
-+ clippy::get_unwrap,
-+ clippy::nursery,
-+ clippy::pedantic,
-+ clippy::todo,
-+ clippy::unimplemented,
-+ clippy::unwrap_used,
-+ clippy::use_debug,
-+ missing_copy_implementations,
-+ missing_debug_implementations,
-+ unused_qualifications,
-+ variant_size_differences
-+)]
-+#![allow(
-+ path_statements, // used for static assertions
-+ clippy::inline_always,
-+ clippy::missing_errors_doc,
-+ clippy::must_use_candidate,
-+ clippy::redundant_pub_crate,
-+)]
-+#![doc(test(attr(deny(warnings))))]
-+
-+#[cfg(test)]
-+mod tests;
-+mod traits;
-+mod unsafe_wrapper;
-+
-+#[cfg(feature = "alloc")]
-+#[allow(unused_extern_crates)]
-+extern crate alloc;
-+
-+use core::borrow::Borrow;
-+use core::cmp::Ordering;
-+use core::fmt;
-+use core::num::IntErrorKind;
-+use core::str::FromStr;
-+#[cfg(feature = "std")]
-+use std::error::Error;
-+
-+#[cfg(feature = "powerfmt")]
-+use powerfmt::smart_display;
-+
-+use crate::unsafe_wrapper::Unsafe;
-+
-+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-+pub struct TryFromIntError;
-+
-+impl fmt::Display for TryFromIntError {
-+ #[inline]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ f.write_str("out of range integral type conversion attempted")
-+ }
-+}
-+#[cfg(feature = "std")]
-+impl Error for TryFromIntError {}
-+
-+#[derive(Debug, Clone, PartialEq, Eq)]
-+pub struct ParseIntError {
-+ kind: IntErrorKind,
-+}
-+
-+impl ParseIntError {
-+ /// Outputs the detailed cause of parsing an integer failing.
-+ // This function is not const because the counterpart of stdlib isn't
-+ #[allow(clippy::missing_const_for_fn)]
-+ #[inline(always)]
-+ pub fn kind(&self) -> &IntErrorKind {
-+ &self.kind
-+ }
-+}
-+
-+impl fmt::Display for ParseIntError {
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ match self.kind {
-+ IntErrorKind::Empty => "cannot parse integer from empty string",
-+ IntErrorKind::InvalidDigit => "invalid digit found in string",
-+ IntErrorKind::PosOverflow => "number too large to fit in target type",
-+ IntErrorKind::NegOverflow => "number too small to fit in target type",
-+ IntErrorKind::Zero => "number would be zero for non-zero type",
-+ _ => "Unknown Int error kind",
-+ }
-+ .fmt(f)
-+ }
-+}
-+
-+#[cfg(feature = "std")]
-+impl Error for ParseIntError {}
-+
-+macro_rules! const_try_opt {
-+ ($e:expr) => {
-+ match $e {
-+ Some(value) => value,
-+ None => return None,
-+ }
-+ };
-+}
-+
-+macro_rules! if_signed {
-+ (true $($x:tt)*) => { $($x)*};
-+ (false $($x:tt)*) => {};
-+}
-+
-+macro_rules! if_unsigned {
-+ (true $($x:tt)*) => {};
-+ (false $($x:tt)*) => { $($x)* };
-+}
-+
-+macro_rules! article {
-+ (true) => {
-+ "An"
-+ };
-+ (false) => {
-+ "A"
-+ };
-+}
-+
-+macro_rules! unsafe_unwrap_unchecked {
-+ ($e:expr) => {{
-+ let opt = $e;
-+ debug_assert!(opt.is_some());
-+ match $e {
-+ Some(value) => value,
-+ None => core::hint::unreachable_unchecked(),
-+ }
-+ }};
-+}
-+
-+/// Informs the optimizer that a condition is always true. If the condition is false, the behavior
-+/// is undefined.
-+///
-+/// # Safety
-+///
-+/// `b` must be `true`.
-+#[inline]
-+const unsafe fn assume(b: bool) {
-+ debug_assert!(b);
-+ if !b {
-+ // Safety: The caller must ensure that `b` is true.
-+ unsafe { core::hint::unreachable_unchecked() }
-+ }
-+}
-+
-+macro_rules! impl_ranged {
-+ ($(
-+ $type:ident {
-+ mod_name: $mod_name:ident
-+ internal: $internal:ident
-+ signed: $is_signed:ident
-+ unsigned: $unsigned_type:ident
-+ optional: $optional_type:ident
-+ }
-+ )*) => {$(
-+ #[doc = concat!(
-+ article!($is_signed),
-+ " `",
-+ stringify!($internal),
-+ "` that is known to be in the range `MIN..=MAX`.",
-+ )]
-+ #[repr(transparent)]
-+ #[derive(Clone, Copy, Eq, Ord, Hash)]
-+ pub struct $type(
-+ Unsafe<$internal>,
-+ );
-+
-+ #[doc = concat!(
-+ "A `",
-+ stringify!($type),
-+ "` that is optional. Equivalent to [`Option<",
-+ stringify!($type),
-+ ">`] with niche value optimization.",
-+ )]
-+ ///
-+ #[doc = concat!(
-+ "If `MIN` is [`",
-+ stringify!($internal),
-+ "::MIN`] _and_ `MAX` is [`",
-+ stringify!($internal)
-+ ,"::MAX`] then compilation will fail. This is because there is no way to represent \
-+ the niche value.",
-+ )]
-+ ///
-+ /// This type is useful when you need to store an optional ranged value in a struct, but
-+ /// do not want the overhead of an `Option` type. This reduces the size of the struct
-+ /// overall, and is particularly useful when you have a large number of optional fields.
-+ /// Note that most operations must still be performed on the [`Option`] type, which is
-+ #[doc = concat!("obtained with [`", stringify!($optional_type), "::get`].")]
-+ #[repr(transparent)]
-+ #[derive(Clone, Copy, Eq, Hash)]
-+ pub struct $optional_type(
-+ $internal,
-+ );
-+
-+ impl $type<0, 0> {
-+ #[inline(always)]
-+ pub const fn exact() -> $type {
-+ // Safety: The value is the only one in range.
-+ unsafe { $type::new_unchecked(VALUE) }
-+ }
-+ }
-+
-+ impl $type {
-+ /// The smallest value that can be represented by this type.
-+ // Safety: `MIN` is in range by definition.
-+ pub const MIN: Self = Self::new_static::();
-+
-+ /// The largest value that can be represented by this type.
-+ // Safety: `MAX` is in range by definition.
-+ pub const MAX: Self = Self::new_static::();
-+
-+ /// Creates a ranged integer without checking the value.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The value must be within the range `MIN..=MAX`.
-+ #[inline(always)]
-+ pub const unsafe fn new_unchecked(value: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the value is in range.
-+ unsafe {
-+ $crate::assume(MIN <= value && value <= MAX);
-+ Self(Unsafe::new(value))
-+ }
-+ }
-+
-+ /// Returns the value as a primitive type.
-+ #[inline(always)]
-+ pub const fn get(self) -> $internal {
-+ ::ASSERT;
-+ // Safety: A stored value is always in range.
-+ unsafe { $crate::assume(MIN <= *self.0.get() && *self.0.get() <= MAX) };
-+ *self.0.get()
-+ }
-+
-+ #[inline(always)]
-+ pub(crate) const fn get_ref(&self) -> &$internal {
-+ ::ASSERT;
-+ let value = self.0.get();
-+ // Safety: A stored value is always in range.
-+ unsafe { $crate::assume(MIN <= *value && *value <= MAX) };
-+ value
-+ }
-+
-+ /// Creates a ranged integer if the given value is in the range `MIN..=MAX`.
-+ #[inline(always)]
-+ pub const fn new(value: $internal) -> Option {
-+ ::ASSERT;
-+ if value < MIN || value > MAX {
-+ None
-+ } else {
-+ // Safety: The value is in range.
-+ Some(unsafe { Self::new_unchecked(value) })
-+ }
-+ }
-+
-+ /// Creates a ranged integer with a statically known value. **Fails to compile** if the
-+ /// value is not in range.
-+ #[inline(always)]
-+ pub const fn new_static() -> Self {
-+ <($type, $type) as $crate::traits::StaticIsValid>::ASSERT;
-+ // Safety: The value is in range.
-+ unsafe { Self::new_unchecked(VALUE) }
-+ }
-+
-+ /// Creates a ranged integer with the given value, saturating if it is out of range.
-+ #[inline]
-+ pub const fn new_saturating(value: $internal) -> Self {
-+ ::ASSERT;
-+ if value < MIN {
-+ Self::MIN
-+ } else if value > MAX {
-+ Self::MAX
-+ } else {
-+ // Safety: The value is in range.
-+ unsafe { Self::new_unchecked(value) }
-+ }
-+ }
-+
-+ /// Expand the range that the value may be in. **Fails to compile** if the new range is
-+ /// not a superset of the current range.
-+ pub const fn expand(
-+ self,
-+ ) -> $type {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <($type, $type) as $crate::traits::ExpandIsValid>
-+ ::ASSERT;
-+ // Safety: The range is widened.
-+ unsafe { $type::new_unchecked(self.get()) }
-+ }
-+
-+ /// Attempt to narrow the range that the value may be in. Returns `None` if the value
-+ /// is outside the new range. **Fails to compile** if the new range is not a subset of
-+ /// the current range.
-+ pub const fn narrow<
-+ const NEW_MIN: $internal,
-+ const NEW_MAX: $internal,
-+ >(self) -> Option<$type> {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <($type, $type) as $crate::traits::NarrowIsValid>
-+ ::ASSERT;
-+ $type::::new(self.get())
-+ }
-+
-+ /// Converts a string slice in a given base to an integer.
-+ ///
-+ /// The string is expected to be an optional `+` or `-` sign followed by digits. Leading
-+ /// and trailing whitespace represent an error. Digits are a subset of these characters,
-+ /// depending on `radix`:
-+ ///
-+ /// - `0-9`
-+ /// - `a-z`
-+ /// - `A-Z`
-+ ///
-+ /// # Panics
-+ ///
-+ /// Panics if `radix` is not in the range `2..=36`.
-+ ///
-+ /// # Examples
-+ ///
-+ /// Basic usage:
-+ ///
-+ /// ```rust
-+ #[doc = concat!("# use deranged::", stringify!($type), ";")]
-+ #[doc = concat!(
-+ "assert_eq!(",
-+ stringify!($type),
-+ "::<5, 10>::from_str_radix(\"A\", 16), Ok(",
-+ stringify!($type),
-+ "::new_static::<10>()));",
-+ )]
-+ /// ```
-+ #[inline]
-+ pub fn from_str_radix(src: &str, radix: u32) -> Result {
-+ ::ASSERT;
-+ match $internal::from_str_radix(src, radix) {
-+ Ok(value) if value > MAX => {
-+ Err(ParseIntError { kind: IntErrorKind::PosOverflow })
-+ }
-+ Ok(value) if value < MIN => {
-+ Err(ParseIntError { kind: IntErrorKind::NegOverflow })
-+ }
-+ // Safety: If the value was out of range, it would have been caught in a
-+ // previous arm.
-+ Ok(value) => Ok(unsafe { Self::new_unchecked(value) }),
-+ Err(e) => Err(ParseIntError { kind: e.kind().clone() }),
-+ }
-+ }
-+
-+ /// Checked integer addition. Computes `self + rhs`, returning `None` if the resulting
-+ /// value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_add(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_add(rhs)))
-+ }
-+
-+ /// Unchecked integer addition. Computes `self + rhs`, assuming that the result is in
-+ /// range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self + rhs` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_add(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_add(rhs)))
-+ }
-+ }
-+
-+ /// Checked integer addition. Computes `self - rhs`, returning `None` if the resulting
-+ /// value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_sub(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_sub(rhs)))
-+ }
-+
-+ /// Unchecked integer subtraction. Computes `self - rhs`, assuming that the result is in
-+ /// range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self - rhs` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_sub(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_sub(rhs)))
-+ }
-+ }
-+
-+ /// Checked integer addition. Computes `self * rhs`, returning `None` if the resulting
-+ /// value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_mul(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_mul(rhs)))
-+ }
-+
-+ /// Unchecked integer multiplication. Computes `self * rhs`, assuming that the result is
-+ /// in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self * rhs` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_mul(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_mul(rhs)))
-+ }
-+ }
-+
-+ /// Checked integer addition. Computes `self / rhs`, returning `None` if `rhs == 0` or
-+ /// if the resulting value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_div(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_div(rhs)))
-+ }
-+
-+ /// Unchecked integer division. Computes `self / rhs`, assuming that `rhs != 0` and that
-+ /// the result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// `self` must not be zero and the result of `self / rhs` must be in the range
-+ /// `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_div(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range and that `rhs` is not
-+ // zero.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_div(rhs)))
-+ }
-+ }
-+
-+ /// Checked Euclidean division. Computes `self.div_euclid(rhs)`, returning `None` if
-+ /// `rhs == 0` or if the resulting value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_div_euclid(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_div_euclid(rhs)))
-+ }
-+
-+ /// Unchecked Euclidean division. Computes `self.div_euclid(rhs)`, assuming that
-+ /// `rhs != 0` and that the result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// `self` must not be zero and the result of `self.div_euclid(rhs)` must be in the
-+ /// range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_div_euclid(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range and that `rhs` is not
-+ // zero.
-+ unsafe {
-+ Self::new_unchecked(
-+ unsafe_unwrap_unchecked!(self.get().checked_div_euclid(rhs))
-+ )
-+ }
-+ }
-+
-+ if_unsigned!($is_signed
-+ /// Remainder. Computes `self % rhs`, statically guaranteeing that the returned value
-+ /// is in range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn rem(
-+ self,
-+ rhs: $type,
-+ ) -> $type<0, RHS_VALUE> {
-+ ::ASSERT;
-+ // Safety: The result is guaranteed to be in range due to the nature of remainder on
-+ // unsigned integers.
-+ unsafe { $type::new_unchecked(self.get() % rhs.get()) }
-+ });
-+
-+ /// Checked integer remainder. Computes `self % rhs`, returning `None` if `rhs == 0` or
-+ /// if the resulting value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_rem(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_rem(rhs)))
-+ }
-+
-+ /// Unchecked remainder. Computes `self % rhs`, assuming that `rhs != 0` and that the
-+ /// result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// `self` must not be zero and the result of `self % rhs` must be in the range
-+ /// `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_rem(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range and that `rhs` is not
-+ // zero.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_rem(rhs)))
-+ }
-+ }
-+
-+ /// Checked Euclidean remainder. Computes `self.rem_euclid(rhs)`, returning `None` if
-+ /// `rhs == 0` or if the resulting value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_rem_euclid(self, rhs: $internal) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_rem_euclid(rhs)))
-+ }
-+
-+ /// Unchecked Euclidean remainder. Computes `self.rem_euclid(rhs)`, assuming that
-+ /// `rhs != 0` and that the result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// `self` must not be zero and the result of `self.rem_euclid(rhs)` must be in the
-+ /// range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_rem_euclid(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range and that `rhs` is not
-+ // zero.
-+ unsafe {
-+ Self::new_unchecked(
-+ unsafe_unwrap_unchecked!(self.get().checked_rem_euclid(rhs))
-+ )
-+ }
-+ }
-+
-+ /// Checked negation. Computes `-self`, returning `None` if the resulting value is out
-+ /// of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_neg(self) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_neg()))
-+ }
-+
-+ /// Unchecked negation. Computes `-self`, assuming that `-self` is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `-self` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_neg(self) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe { Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_neg())) }
-+ }
-+
-+ /// Negation. Computes `self.neg()`, **failing to compile** if the result is not
-+ /// guaranteed to be in range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const fn neg(self) -> Self {
-+ ::ASSERT;
-+ ::ASSERT;
-+ // Safety: The compiler asserts that the result is in range.
-+ unsafe { self.unchecked_neg() }
-+ }
-+
-+ /// Checked shift left. Computes `self << rhs`, returning `None` if the resulting value
-+ /// is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_shl(self, rhs: u32) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_shl(rhs)))
-+ }
-+
-+ /// Unchecked shift left. Computes `self << rhs`, assuming that the result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self << rhs` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_shl(self, rhs: u32) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_shl(rhs)))
-+ }
-+ }
-+
-+ /// Checked shift right. Computes `self >> rhs`, returning `None` if
-+ /// the resulting value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_shr(self, rhs: u32) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_shr(rhs)))
-+ }
-+
-+ /// Unchecked shift right. Computes `self >> rhs`, assuming that the result is in range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self >> rhs` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_shr(self, rhs: u32) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_shr(rhs)))
-+ }
-+ }
-+
-+ if_signed!($is_signed
-+ /// Checked absolute value. Computes `self.abs()`, returning `None` if the resulting
-+ /// value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_abs(self) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_abs()))
-+ }
-+
-+ /// Unchecked absolute value. Computes `self.abs()`, assuming that the result is in
-+ /// range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self.abs()` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_abs(self) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe { Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_abs())) }
-+ }
-+
-+ /// Absolute value. Computes `self.abs()`, **failing to compile** if the result is not
-+ /// guaranteed to be in range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const fn abs(self) -> Self {
-+ ::ASSERT;
-+ ::ASSERT;
-+ // Safety: The compiler asserts that the result is in range.
-+ unsafe { self.unchecked_abs() }
-+ });
-+
-+ /// Checked exponentiation. Computes `self.pow(exp)`, returning `None` if the resulting
-+ /// value is out of range.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn checked_pow(self, exp: u32) -> Option {
-+ ::ASSERT;
-+ Self::new(const_try_opt!(self.get().checked_pow(exp)))
-+ }
-+
-+ /// Unchecked exponentiation. Computes `self.pow(exp)`, assuming that the result is in
-+ /// range.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The result of `self.pow(exp)` must be in the range `MIN..=MAX`.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline(always)]
-+ pub const unsafe fn unchecked_pow(self, exp: u32) -> Self {
-+ ::ASSERT;
-+ // Safety: The caller must ensure that the result is in range.
-+ unsafe {
-+ Self::new_unchecked(unsafe_unwrap_unchecked!(self.get().checked_pow(exp)))
-+ }
-+ }
-+
-+ /// Saturating integer addition. Computes `self + rhs`, saturating at the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_add(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_add(rhs))
-+ }
-+
-+ /// Saturating integer subtraction. Computes `self - rhs`, saturating at the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_sub(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_sub(rhs))
-+ }
-+
-+ if_signed!($is_signed
-+ /// Saturating integer negation. Computes `self - rhs`, saturating at the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_neg(self) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_neg())
-+ });
-+
-+ if_signed!($is_signed
-+ /// Saturating absolute value. Computes `self.abs()`, saturating at the numeric bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_abs(self) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_abs())
-+ });
-+
-+ /// Saturating integer multiplication. Computes `self * rhs`, saturating at the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_mul(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_mul(rhs))
-+ }
-+
-+ /// Saturating integer exponentiation. Computes `self.pow(exp)`, saturating at the
-+ /// numeric bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ pub const fn saturating_pow(self, exp: u32) -> Self {
-+ ::ASSERT;
-+ Self::new_saturating(self.get().saturating_pow(exp))
-+ }
-+
-+ /// Compute the `rem_euclid` of this type with its unsigned type equivalent
-+ // Not public because it doesn't match stdlib's "method_unsigned implemented only for signed type" tradition.
-+ // Also because this isn't implemented for normal types in std.
-+ // TODO maybe make public anyway? It is useful.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ #[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
-+ const fn rem_euclid_unsigned(
-+ rhs: $internal,
-+ range_len: $unsigned_type
-+ ) -> $unsigned_type {
-+ #[allow(unused_comparisons)]
-+ if rhs >= 0 {
-+ (rhs as $unsigned_type) % range_len
-+ } else {
-+ // Let ux refer to an n bit unsigned and ix refer to an n bit signed integer.
-+ // Can't write -ux or ux::abs() method. This gets around compilation error.
-+ // `wrapping_sub` is to handle rhs = ix::MIN since ix::MIN = -ix::MAX-1
-+ let rhs_abs = ($internal::wrapping_sub(0, rhs)) as $unsigned_type;
-+ // Largest multiple of range_len <= type::MAX is lowest if range_len * 2 > ux::MAX -> range_len >= ux::MAX / 2 + 1
-+ // Also = 0 in mod range_len arithmetic.
-+ // Sub from this large number rhs_abs (same as sub -rhs = -(-rhs) = add rhs) to get rhs % range_len
-+ // ix::MIN = -2^(n-1) so 0 <= rhs_abs <= 2^(n-1)
-+ // ux::MAX / 2 + 1 = 2^(n-1) so this subtraction will always be a >= 0 after subtraction
-+ // Thus converting rhs signed negative to equivalent positive value in mod range_len arithmetic
-+ ((($unsigned_type::MAX / range_len) * range_len) - (rhs_abs)) % range_len
-+ }
-+ }
-+
-+ /// Wrapping integer addition. Computes `self + rhs`, wrapping around the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ #[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
-+ pub const fn wrapping_add(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Forward to internal type's impl if same as type.
-+ if MIN == $internal::MIN && MAX == $internal::MAX {
-+ // Safety: std's wrapping methods match ranged arithmetic when the range is the internal datatype's range.
-+ return unsafe { Self::new_unchecked(self.get().wrapping_add(rhs)) }
-+ }
-+
-+ let inner = self.get();
-+
-+ // Won't overflow because of std impl forwarding.
-+ let range_len = MAX.abs_diff(MIN) + 1;
-+
-+ // Calculate the offset with proper handling for negative rhs
-+ let offset = Self::rem_euclid_unsigned(rhs, range_len);
-+
-+ let greater_vals = MAX.abs_diff(inner);
-+ // No wrap
-+ if offset <= greater_vals {
-+ // Safety:
-+ // if inner >= 0 -> No overflow beyond range (offset <= greater_vals)
-+ // if inner < 0: Same as >=0 with caveat:
-+ // `(signed as unsigned).wrapping_add(unsigned) as signed` is the same as
-+ // `signed::checked_add_unsigned(unsigned).unwrap()` or `wrapping_add_unsigned`
-+ // (the difference doesn't matter since it won't overflow),
-+ // but unsigned integers don't have either method so it won't compile that way.
-+ unsafe { Self::new_unchecked(
-+ ((inner as $unsigned_type).wrapping_add(offset)) as $internal
-+ ) }
-+ }
-+ // Wrap
-+ else {
-+ // Safety:
-+ // - offset < range_len by rem_euclid (MIN + ... safe)
-+ // - offset > greater_vals from if statement (offset - (greater_vals + 1) safe)
-+ //
-+ // again using `(signed as unsigned).wrapping_add(unsigned) as signed` = `checked_add_unsigned` trick
-+ unsafe { Self::new_unchecked(
-+ ((MIN as $unsigned_type).wrapping_add(
-+ offset - (greater_vals + 1)
-+ )) as $internal
-+ ) }
-+ }
-+ }
-+
-+ /// Wrapping integer subtraction. Computes `self - rhs`, wrapping around the numeric
-+ /// bounds.
-+ #[must_use = "this returns the result of the operation, without modifying the original"]
-+ #[inline]
-+ #[allow(trivial_numeric_casts)] // needed since some casts have to send unsigned -> unsigned to handle signed -> unsigned
-+ pub const fn wrapping_sub(self, rhs: $internal) -> Self {
-+ ::ASSERT;
-+ // Forward to internal type's impl if same as type.
-+ if MIN == $internal::MIN && MAX == $internal::MAX {
-+ // Safety: std's wrapping methods match ranged arithmetic when the range is the internal datatype's range.
-+ return unsafe { Self::new_unchecked(self.get().wrapping_sub(rhs)) }
-+ }
-+
-+ let inner = self.get();
-+
-+ // Won't overflow because of std impl forwarding.
-+ let range_len = MAX.abs_diff(MIN) + 1;
-+
-+ // Calculate the offset with proper handling for negative rhs
-+ let offset = Self::rem_euclid_unsigned(rhs, range_len);
-+
-+ let lesser_vals = MIN.abs_diff(inner);
-+ // No wrap
-+ if offset <= lesser_vals {
-+ // Safety:
-+ // if inner >= 0 -> No overflow beyond range (offset <= greater_vals)
-+ // if inner < 0: Same as >=0 with caveat:
-+ // `(signed as unsigned).wrapping_sub(unsigned) as signed` is the same as
-+ // `signed::checked_sub_unsigned(unsigned).unwrap()` or `wrapping_sub_unsigned`
-+ // (the difference doesn't matter since it won't overflow below 0),
-+ // but unsigned integers don't have either method so it won't compile that way.
-+ unsafe { Self::new_unchecked(
-+ ((inner as $unsigned_type).wrapping_sub(offset)) as $internal
-+ ) }
-+ }
-+ // Wrap
-+ else {
-+ // Safety:
-+ // - offset < range_len by rem_euclid (MAX - ... safe)
-+ // - offset > lesser_vals from if statement (offset - (lesser_vals + 1) safe)
-+ //
-+ // again using `(signed as unsigned).wrapping_sub(unsigned) as signed` = `checked_sub_unsigned` trick
-+ unsafe { Self::new_unchecked(
-+ ((MAX as $unsigned_type).wrapping_sub(
-+ offset - (lesser_vals + 1)
-+ )) as $internal
-+ ) }
-+ }
-+ }
-+ }
-+
-+ impl $optional_type {
-+ /// The value used as the niche. Must not be in the range `MIN..=MAX`.
-+ const NICHE: $internal = match (MIN, MAX) {
-+ ($internal::MIN, $internal::MAX) => panic!("type has no niche"),
-+ ($internal::MIN, _) => $internal::MAX,
-+ (_, _) => $internal::MIN,
-+ };
-+
-+ /// An optional ranged value that is not present.
-+ #[allow(non_upper_case_globals)]
-+ pub const None: Self = Self(Self::NICHE);
-+
-+ /// Creates an optional ranged value that is present.
-+ #[allow(non_snake_case)]
-+ #[inline(always)]
-+ pub const fn Some(value: $type) -> Self {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ Self(value.get())
-+ }
-+
-+ /// Returns the value as the standard library's [`Option`] type.
-+ #[inline(always)]
-+ pub const fn get(self) -> Option<$type> {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ if self.0 == Self::NICHE {
-+ None
-+ } else {
-+ // Safety: A stored value that is not the niche is always in range.
-+ Some(unsafe { $type::new_unchecked(self.0) })
-+ }
-+ }
-+
-+ /// Creates an optional ranged integer without checking the value.
-+ ///
-+ /// # Safety
-+ ///
-+ /// The value must be within the range `MIN..=MAX`. As the value used for niche
-+ /// value optimization is unspecified, the provided value must not be the niche
-+ /// value.
-+ #[inline(always)]
-+ pub const unsafe fn some_unchecked(value: $internal) -> Self {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ // Safety: The caller must ensure that the value is in range.
-+ unsafe { $crate::assume(MIN <= value && value <= MAX) };
-+ Self(value)
-+ }
-+
-+ /// Obtain the inner value of the struct. This is useful for comparisons.
-+ #[inline(always)]
-+ pub(crate) const fn inner(self) -> $internal {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.0
-+ }
-+
-+ #[inline(always)]
-+ pub const fn get_primitive(self) -> Option<$internal> {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ Some(const_try_opt!(self.get()).get())
-+ }
-+
-+ /// Returns `true` if the value is the niche value.
-+ #[inline(always)]
-+ pub const fn is_none(self) -> bool {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.get().is_none()
-+ }
-+
-+ /// Returns `true` if the value is not the niche value.
-+ #[inline(always)]
-+ pub const fn is_some(self) -> bool {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.get().is_some()
-+ }
-+ }
-+
-+ impl fmt::Debug for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::Debug for $optional_type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::Display for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ #[cfg(feature = "powerfmt")]
-+ impl<
-+ const MIN: $internal,
-+ const MAX: $internal,
-+ > smart_display::SmartDisplay for $type {
-+ type Metadata = <$internal as smart_display::SmartDisplay>::Metadata;
-+
-+ #[inline(always)]
-+ fn metadata(
-+ &self,
-+ f: smart_display::FormatterOptions,
-+ ) -> smart_display::Metadata<'_, Self> {
-+ ::ASSERT;
-+ self.get_ref().metadata(f).reuse()
-+ }
-+
-+ #[inline(always)]
-+ fn fmt_with_metadata(
-+ &self,
-+ f: &mut fmt::Formatter<'_>,
-+ metadata: smart_display::Metadata<'_, Self>,
-+ ) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt_with_metadata(f, metadata.reuse())
-+ }
-+ }
-+
-+ impl Default for $optional_type {
-+ #[inline(always)]
-+ fn default() -> Self {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ Self::None
-+ }
-+ }
-+
-+ impl AsRef<$internal> for $type {
-+ #[inline(always)]
-+ fn as_ref(&self) -> &$internal {
-+ ::ASSERT;
-+ &self.get_ref()
-+ }
-+ }
-+
-+ impl Borrow<$internal> for $type {
-+ #[inline(always)]
-+ fn borrow(&self) -> &$internal {
-+ ::ASSERT;
-+ &self.get_ref()
-+ }
-+ }
-+
-+ impl<
-+ const MIN_A: $internal,
-+ const MAX_A: $internal,
-+ const MIN_B: $internal,
-+ const MAX_B: $internal,
-+ > PartialEq<$type> for $type {
-+ #[inline(always)]
-+ fn eq(&self, other: &$type) -> bool {
-+ ::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.get() == other.get()
-+ }
-+ }
-+
-+ impl<
-+ const MIN_A: $internal,
-+ const MAX_A: $internal,
-+ const MIN_B: $internal,
-+ const MAX_B: $internal,
-+ > PartialEq<$optional_type> for $optional_type {
-+ #[inline(always)]
-+ fn eq(&self, other: &$optional_type) -> bool {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.inner() == other.inner()
-+ }
-+ }
-+
-+ impl<
-+ const MIN_A: $internal,
-+ const MAX_A: $internal,
-+ const MIN_B: $internal,
-+ const MAX_B: $internal,
-+ > PartialOrd<$type> for $type {
-+ #[inline(always)]
-+ fn partial_cmp(&self, other: &$type) -> Option {
-+ ::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ self.get().partial_cmp(&other.get())
-+ }
-+ }
-+
-+ impl<
-+ const MIN_A: $internal,
-+ const MAX_A: $internal,
-+ const MIN_B: $internal,
-+ const MAX_B: $internal,
-+ > PartialOrd<$optional_type> for $optional_type {
-+ #[inline]
-+ fn partial_cmp(&self, other: &$optional_type) -> Option {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ if self.is_none() && other.is_none() {
-+ Some(Ordering::Equal)
-+ } else if self.is_none() {
-+ Some(Ordering::Less)
-+ } else if other.is_none() {
-+ Some(Ordering::Greater)
-+ } else {
-+ self.inner().partial_cmp(&other.inner())
-+ }
-+ }
-+ }
-+
-+ impl<
-+ const MIN: $internal,
-+ const MAX: $internal,
-+ > Ord for $optional_type {
-+ #[inline]
-+ fn cmp(&self, other: &Self) -> Ordering {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ if self.is_none() && other.is_none() {
-+ Ordering::Equal
-+ } else if self.is_none() {
-+ Ordering::Less
-+ } else if other.is_none() {
-+ Ordering::Greater
-+ } else {
-+ self.inner().cmp(&other.inner())
-+ }
-+ }
-+ }
-+
-+ impl fmt::Binary for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::LowerHex for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::UpperHex for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::LowerExp for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::UpperExp for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl fmt::Octal for $type {
-+ #[inline(always)]
-+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ ::ASSERT;
-+ self.get().fmt(f)
-+ }
-+ }
-+
-+ impl From<$type> for $internal {
-+ #[inline(always)]
-+ fn from(value: $type) -> Self {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ value.get()
-+ }
-+ }
-+
-+ impl<
-+ const MIN: $internal,
-+ const MAX: $internal,
-+ > From<$type> for $optional_type {
-+ #[inline(always)]
-+ fn from(value: $type) -> Self {
-+ <$type as $crate::traits::RangeIsValid>::ASSERT;
-+ Self::Some(value)
-+ }
-+ }
-+
-+ impl<
-+ const MIN: $internal,
-+ const MAX: $internal,
-+ > From