From e3f538cd25aa54d4c93437fb468bfc3632e101a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 7 Jun 2025 20:18:01 +0200 Subject: [PATCH 001/111] Branch out v1.19 --- Makefile | 2 +- RELEASE.md | 4 ++-- SECURITY.md | 5 ++--- .../pages/references/compatibility-and-deprecations.md | 5 ++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 24d02701acc..a067a602996 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ PREFIX ?= /usr/local TEST_FILES ?= "*_test.exs" SHARE_PREFIX ?= $(PREFIX)/share MAN_PREFIX ?= $(SHARE_PREFIX)/man -CANONICAL := main/ +# CANONICAL := main/ ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS) ELIXIRC_MIN_SIG := $(ELIXIRC) -e 'Code.put_compiler_option :infer_signatures, []' ERLC := erlc -I lib/elixir/include diff --git a/RELEASE.md b/RELEASE.md index 20fce539404..8adcda37511 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,7 +8,7 @@ ## Shipping a new version -1. Update version in /VERSION, bin/elixir, bin/elixir.bat, and bin/elixir.ps1 +1. Update version in /VERSION, bin/elixir, and bin/elixir.bat 2. Ensure /CHANGELOG.md is updated, versioned and add the current date - If this release addresses any publicly known security vulnerabilities with @@ -34,7 +34,7 @@ ### In the new branch -1. Comment the `CANONICAL=` in /Makefile +1. Comment out `CANONICAL := main/` in /Makefile 2. Update tables in /SECURITY.md and "Compatibility and Deprecations" diff --git a/SECURITY.md b/SECURITY.md index 159deb6cafe..46d87a85184 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,12 +12,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a Elixir version | Support :------------- | :----------------------------- -1.19 | Development -1.18 | Bug fixes and security patches +1.19 | Bug fixes and security patches +1.18 | Security patches only 1.17 | Security patches only 1.16 | Security patches only 1.15 | Security patches only -1.14 | Security patches only ## Announcements diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md index 84ee8250522..815fbd4890a 100644 --- a/lib/elixir/pages/references/compatibility-and-deprecations.md +++ b/lib/elixir/pages/references/compatibility-and-deprecations.md @@ -14,12 +14,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a Elixir version | Support :------------- | :----------------------------- -1.19 | Development -1.18 | Bug fixes and security patches +1.19 | Bug fixes and security patches +1.18 | Security patches only 1.17 | Security patches only 1.16 | Security patches only 1.15 | Security patches only -1.14 | Security patches only New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date). From 4425d684a0aabcdcd047db759662a1f8d6d53300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 7 Jun 2025 20:21:55 +0200 Subject: [PATCH 002/111] Update CHANGELOG --- CHANGELOG.md | 22 ------------------- .../compatibility-and-deprecations.md | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f62a2da8da..0571f4012c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,28 +8,6 @@ ## Type system improvements -### More type inference - -Elixir now performs inference of whole functions. The best way to show the new capabilities are with examples. Take the following code: - -```elixir -def add_foo_and_bar(data) do - data.foo + data.bar -end -``` - -Elixir now infers that the function expects a `map` as first argument, and the map must have the keys `.foo` and `.bar` whose values are either `integer()` or `float()`. The return type will be either `integer()` or `float()`. - -Here is another example: - -```elixir -def sum_to_string(a, b) do - Integer.to_string(a + b) -end -``` - -Even though the `+` operator works with both integers and floats, Elixir infers that `a` and `b` must be both integers, as the result of `+` is given to a function that expects an integer. The inferred type information is then used during type checking to find possible typing errors. - ### Type checking of protocol dispatch and implementations This release also adds type checking when dispatching and implementing protocols. diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md index 815fbd4890a..a97af4d4a80 100644 --- a/lib/elixir/pages/references/compatibility-and-deprecations.md +++ b/lib/elixir/pages/references/compatibility-and-deprecations.md @@ -235,4 +235,4 @@ Version | Deprecated feature | Replaced by (ava [v1.16]: https://github.com/elixir-lang/elixir/blob/v1.16/CHANGELOG.md#4-hard-deprecations [v1.17]: https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md#4-hard-deprecations [v1.18]: https://github.com/elixir-lang/elixir/blob/v1.18/CHANGELOG.md#4-hard-deprecations -[v1.19]: https://github.com/elixir-lang/elixir/blob/main/CHANGELOG.md#4-hard-deprecations +[v1.19]: https://github.com/elixir-lang/elixir/blob/v1.19/CHANGELOG.md#4-hard-deprecations From 5ff421ca276ea65d8678b794a5cdd460d5c30510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 7 Jun 2025 21:23:03 +0200 Subject: [PATCH 003/111] Disable inference as part of v1.19 release --- lib/elixir/lib/module/types.ex | 2 +- .../test/elixir/module/types/expr_test.exs | 121 ------------------ .../test/elixir/module/types/infer_test.exs | 41 ------ .../elixir/module/types/integration_test.exs | 71 ---------- .../test/elixir/module/types/pattern_test.exs | 10 -- 5 files changed, 1 insertion(+), 244 deletions(-) diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex index 3917ecf1c0f..10a8c30c10b 100644 --- a/lib/elixir/lib/module/types.ex +++ b/lib/elixir/lib/module/types.ex @@ -419,7 +419,7 @@ defmodule Module.Types do local_handler: handler, # Control if variable refinement is enabled. # It is disabled only on dynamic dispatches. - refine_vars: true + refine_vars: false } end diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 5b0bb46ebc1..2f2541db826 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -43,16 +43,6 @@ defmodule Module.Types.ExprTest do assert typecheck!([x], [:ok | x]) == dynamic(non_empty_list(term(), term())) end - test "inference" do - assert typecheck!( - [x, y, z], - ( - List.to_integer([x, y | z]) - {x, y, z} - ) - ) == dynamic(tuple([integer(), integer(), list(integer())])) - end - test "hd" do assert typecheck!([x = [123, :foo]], hd(x)) == dynamic(union(atom([:foo]), integer())) assert typecheck!([x = [123 | :foo]], hd(x)) == dynamic(integer()) @@ -127,16 +117,6 @@ defmodule Module.Types.ExprTest do end describe "funs" do - test "infers calls" do - assert typecheck!( - [x], - ( - x.(1, 2) - x - ) - ) == dynamic(fun(2)) - end - test "infers functions" do assert typecheck!(& &1) == fun([dynamic()], dynamic()) assert typecheck!(fn -> :ok end) == fun([], atom([:ok])) @@ -263,53 +243,6 @@ defmodule Module.Types.ExprTest do assert typecheck!([%x{}], x.foo_bar()) == dynamic() end - test "infers atoms" do - assert typecheck!( - [x], - ( - x.foo_bar() - x - ) - ) == dynamic(atom()) - - assert typecheck!( - [x], - ( - x.foo_bar(123) - x - ) - ) == dynamic(atom()) - - assert typecheck!( - [x], - ( - &x.foo_bar/1 - x - ) - ) == dynamic(atom()) - end - - test "infers maps" do - assert typecheck!( - [x], - ( - :foo = x.foo_bar - 123 = x.baz_bat - x - ) - ) == dynamic(open_map(foo_bar: atom([:foo]), baz_bat: integer())) - end - - test "infers args" do - assert typecheck!( - [x, y], - ( - z = Integer.to_string(x + y) - {x, y, z} - ) - ) == dynamic(tuple([integer(), integer(), binary()])) - end - test "undefined function warnings" do assert typewarn!(URI.unknown("foo")) == {dynamic(), "URI.unknown/1 is undefined or private"} @@ -535,16 +468,6 @@ defmodule Module.Types.ExprTest do end describe "binaries" do - test "inference" do - assert typecheck!( - [x, y], - ( - <> - {x, y} - ) - ) == dynamic(tuple([union(float(), integer()), integer()])) - end - test "warnings" do assert typeerror!([<>], <>) == ~l""" @@ -644,16 +567,6 @@ defmodule Module.Types.ExprTest do assert typecheck!([x], {:ok, x}) == dynamic(tuple([atom([:ok]), term()])) end - test "inference" do - assert typecheck!( - [x, y], - ( - {:ok, :error} = {x, y} - {x, y} - ) - ) == dynamic(tuple([atom([:ok]), atom([:error])])) - end - test "elem/2" do assert typecheck!(elem({:ok, 123}, 0)) == atom([:ok]) assert typecheck!(elem({:ok, 123}, 1)) == integer() @@ -1458,16 +1371,6 @@ defmodule Module.Types.ExprTest do ) == dynamic(atom([:ok, :error, :timeout])) end - test "infers type for timeout" do - assert typecheck!( - [x], - receive do - after - x -> x - end - ) == dynamic(union(integer(), atom([:infinity]))) - end - test "resets branches" do assert typecheck!( [x, timeout = :infinity], @@ -1854,16 +1757,6 @@ defmodule Module.Types.ExprTest do """ end - test "infers binary generators" do - assert typecheck!( - [x], - ( - for <<_ <- x>>, do: :ok - x - ) - ) == dynamic(binary()) - end - test ":into" do assert typecheck!([binary], for(<>, do: x)) == list(integer()) assert typecheck!([binary], for(<>, do: x, into: [])) == list(integer()) @@ -1901,20 +1794,6 @@ defmodule Module.Types.ExprTest do end ) == union(atom([:ok]), union(integer(), float())) end - - test ":reduce inference" do - assert typecheck!( - [list, x], - ( - 123 = - for _ <- list, reduce: x do - x -> x - end - - x - ) - ) == dynamic(integer()) - end end describe "info" do diff --git a/lib/elixir/test/elixir/module/types/infer_test.exs b/lib/elixir/test/elixir/module/types/infer_test.exs index 9cc4ff155a4..94c3b785c81 100644 --- a/lib/elixir/test/elixir/module/types/infer_test.exs +++ b/lib/elixir/test/elixir/module/types/infer_test.exs @@ -51,20 +51,6 @@ defmodule Module.Types.InferTest do assert types[{:fun4, 4}] == {:infer, nil, [{args, atom([:ok])}]} end - test "infer types from expressions", config do - types = - infer config do - def fun(x) do - x.foo + x.bar - end - end - - number = union(integer(), float()) - - assert types[{:fun, 1}] == - {:infer, nil, [{[dynamic(open_map(foo: number, bar: number))], dynamic(number)}]} - end - test "infer with Elixir built-in", config do types = infer config do @@ -95,33 +81,6 @@ defmodule Module.Types.InferTest do ]} end - test "infers return types from private functions", config do - types = - infer config do - def pub(x), do: priv(x) - defp priv(:ok), do: :ok - defp priv(:error), do: :error - end - - assert types[{:pub, 1}] == - {:infer, nil, [{[dynamic(atom([:ok, :error]))], dynamic(atom([:ok, :error]))}]} - - assert types[{:priv, 1}] == nil - end - - test "infers return types from super functions", config do - types = - infer config do - def pub(:ok), do: :ok - def pub(:error), do: :error - defoverridable pub: 1 - def pub(x), do: super(x) - end - - assert types[{:pub, 1}] == - {:infer, nil, [{[dynamic(atom([:ok, :error]))], dynamic(atom([:ok, :error]))}]} - end - test "infers return types even with loops", config do types = infer config do diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index 97cb75bd0c3..7c69d921b8d 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -85,77 +85,6 @@ defmodule Module.Types.IntegrationTest do ] end - test "writes exports with inferred map types" do - files = %{ - "a.ex" => """ - defmodule A do - defstruct [:x, :y, :z] - - def struct_create_with_atom_keys(x) do - infer(y = %A{x: x}) - {x, y} - end - - def map_create_with_atom_keys(x) do - infer(%{__struct__: A, x: x, y: nil, z: nil}) - x - end - - def map_update_with_atom_keys(x) do - infer(%{x | y: nil}) - x - end - - def map_update_with_unknown_keys(x, y) do - infer(%{x | y => 123}) - x - end - - defp infer(%A{x: <<_::binary>>, y: nil}) do - :ok - end - end - """ - } - - modules = compile_modules(files) - exports = read_chunk(modules[A]).exports |> Map.new() - - return = fn name, arity -> - pair = {name, arity} - %{^pair => %{sig: {:infer, nil, [{_, return}]}}} = exports - return - end - - assert return.(:struct_create_with_atom_keys, 1) == - dynamic( - tuple([ - binary(), - closed_map( - __struct__: atom([A]), - x: binary(), - y: atom([nil]), - z: atom([nil]) - ) - ]) - ) - - assert return.(:map_create_with_atom_keys, 1) == dynamic(binary()) - - assert return.(:map_update_with_atom_keys, 1) == - dynamic( - closed_map( - __struct__: atom([A]), - x: binary(), - y: term(), - z: term() - ) - ) - - assert return.(:map_update_with_unknown_keys, 2) == - dynamic(open_map()) - end - test "writes exports with inferred function types" do files = %{ "a.ex" => """ diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index 626c4c91478..ea947423e90 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -106,16 +106,6 @@ defmodule Module.Types.PatternTest do ) == uri_type end - test "refines types" do - assert typecheck!( - [x, foo = :foo, bar = 123], - ( - {^foo, ^bar} = x - x - ) - ) == dynamic(tuple([atom([:foo]), integer()])) - end - test "reports incompatible types" do assert typeerror!([x = {:ok, _}], [_ | _] = x) == ~l""" the following pattern will never match: From 7aafd6ca77d1f371bec53d53b82d12f3166a0642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 8 Jun 2025 19:13:05 +0200 Subject: [PATCH 004/111] Also download rebar3 automatically when compiling --- lib/mix/lib/mix/tasks/deps.compile.ex | 32 +++------------------------ 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/lib/mix/lib/mix/tasks/deps.compile.ex b/lib/mix/lib/mix/tasks/deps.compile.ex index 3be40eade8f..2e91535b199 100644 --- a/lib/mix/lib/mix/tasks/deps.compile.ex +++ b/lib/mix/lib/mix/tasks/deps.compile.ex @@ -227,9 +227,9 @@ defmodule Mix.Tasks.Deps.Compile do end) end - defp do_rebar3(%Mix.Dep{opts: opts} = dep, config) do - if not Mix.Rebar.available?(:rebar3) do - handle_rebar_not_found(dep) + defp do_rebar3(%Mix.Dep{opts: opts, manager: manager} = dep, config) do + if not Mix.Rebar.available?(manager) do + Mix.Tasks.Local.Rebar.run(["--force"]) end dep_path = opts[:dest] @@ -285,32 +285,6 @@ defmodule Mix.Tasks.Deps.Compile do |> Mix.Rebar.serialize_config() end - defp handle_rebar_not_found(%Mix.Dep{app: app, manager: manager}) do - shell = Mix.shell() - - shell.info( - "Could not find \"#{manager}\", which is needed to build dependency #{inspect(app)}" - ) - - install_question = - "Shall I install #{manager}? (if running non-interactively, " <> - "use \"mix local.rebar --force\")" - - if not shell.yes?(install_question) do - error_message = - "Could not find \"#{manager}\" to compile " <> - "dependency #{inspect(app)}, please ensure \"#{manager}\" is available" - - Mix.raise(error_message) - end - - Mix.Tasks.Local.Rebar.run(["--force"]) - - if not Mix.Rebar.available?(manager) do - Mix.raise("\"#{manager}\" installation failed") - end - end - defp do_make(dep, config) do command = make_command(dep) shell_cmd!(dep, config, command, [{"IS_DEP", "1"}]) From 0847e4b41a0aedf07b0eca66cca9c9a2bb6516d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Mon, 9 Jun 2025 12:07:18 +0200 Subject: [PATCH 005/111] Fix mistake in "Untracked compile-time dependencies" anti-pattern (#14563) --- lib/elixir/pages/anti-patterns/macro-anti-patterns.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/anti-patterns/macro-anti-patterns.md b/lib/elixir/pages/anti-patterns/macro-anti-patterns.md index 27b204d7d3f..5282a67f5bc 100644 --- a/lib/elixir/pages/anti-patterns/macro-anti-patterns.md +++ b/lib/elixir/pages/anti-patterns/macro-anti-patterns.md @@ -292,7 +292,7 @@ For convenience, the markup notation to generate the admonition block above is t #### Problem -This anti-pattern is the opposite of ["Compile-time dependencies"](#compile-time-dependencies) and it happens when a compile-time dependency is accidentally bypassed, making the Elixir compiler is to track dependencies and recompile files correctly. This happens when building aliases (in other words, module names) dynamically, either within a module or within a macro. +This anti-pattern is the opposite of ["Compile-time dependencies"](#compile-time-dependencies) and it happens when a compile-time dependency is accidentally bypassed, making the Elixir compiler unable to track dependencies and recompile files correctly. This happens when building aliases (in other words, module names) dynamically, either within a module or within a macro. #### Example From 2a9a4f2cab4e84fa76980ca2ba7a2dff3622937a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 9 Jun 2025 12:27:46 +0200 Subject: [PATCH 006/111] Release v1.19.0-rc.0 --- CHANGELOG.md | 2 +- VERSION | 2 +- bin/elixir | 2 +- bin/elixir.bat | 2 +- lib/elixir/pages/references/compatibility-and-deprecations.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0571f4012c7..1b834ba632c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,7 +194,7 @@ These additions offer greater transparency into the components and licenses of e This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosystem Foundation. -## v1.19.0-dev +## v1.19.0-rc.0 (2025-06-09) ### 1. Enhancements diff --git a/VERSION b/VERSION index ff32b92aad5..d3de7a2e908 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.19.0-dev +1.19.0-rc.0 diff --git a/bin/elixir b/bin/elixir index a232cf1da9a..f991e0323a7 100755 --- a/bin/elixir +++ b/bin/elixir @@ -6,7 +6,7 @@ set -e -ELIXIR_VERSION=1.19.0-dev +ELIXIR_VERSION=1.19.0-rc.0 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then cat <&2 diff --git a/bin/elixir.bat b/bin/elixir.bat index 3eec7b7119d..ded0ad23af9 100644 --- a/bin/elixir.bat +++ b/bin/elixir.bat @@ -4,7 +4,7 @@ :: SPDX-FileCopyrightText: 2021 The Elixir Team :: SPDX-FileCopyrightText: 2012 Plataformatec -set ELIXIR_VERSION=1.19.0-dev +set ELIXIR_VERSION=1.19.0-rc.0 if ""%1""=="""" if ""%2""=="""" goto documentation if /I ""%1""==""--help"" if ""%2""=="""" goto documentation diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md index a97af4d4a80..f44bf9acd0d 100644 --- a/lib/elixir/pages/references/compatibility-and-deprecations.md +++ b/lib/elixir/pages/references/compatibility-and-deprecations.md @@ -48,7 +48,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea Elixir version | Supported Erlang/OTP versions :------------- | :------------------------------- -1.19 | 26 - 27 +1.19 | 26 - 28 1.18 | 25 - 27 1.17 | 25 - 27 1.16 | 24 - 26 From 8588a8d473b13e89ced59889ab1e93dedeef6a71 Mon Sep 17 00:00:00 2001 From: Theodor-Alexandru Irimia <11174371+tirimia@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:26:53 +0200 Subject: [PATCH 007/111] Clarify why and how to start second session for tests (#14566) --- lib/elixir/pages/mix-and-otp/distributed-tasks.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/elixir/pages/mix-and-otp/distributed-tasks.md b/lib/elixir/pages/mix-and-otp/distributed-tasks.md index 681bde4b70c..7461017527a 100644 --- a/lib/elixir/pages/mix-and-otp/distributed-tasks.md +++ b/lib/elixir/pages/mix-and-otp/distributed-tasks.md @@ -124,6 +124,7 @@ Distributed tasks are exactly the same as supervised tasks. The only difference Now, let's start two named nodes again, but inside the `:kv` application: ```console +$ cd apps/kv $ iex --sname foo -S mix $ iex --sname bar -S mix ``` @@ -231,9 +232,10 @@ The first test invokes `Kernel.node/0`, which returns the name of the current no The second test checks that the code raises for unknown entries. -In order to run the first test, we need to have two nodes running. Move into `apps/kv` and let's restart the node named `bar` which is going to be used by tests. +In order to run the first test, we need to have two nodes running. Let's move into `apps/kv` and restart the node named `bar` which is going to be used by tests. This way, `bar` will not load the `:kv_server` app and leave the port available for `foo` and tests. ```console +$ cd apps/kv $ iex --sname bar -S mix ``` From 752ca8864dea0b1b8ca98f8e5a6c20e369a43e26 Mon Sep 17 00:00:00 2001 From: Tomasz Marek Sulima Date: Mon, 9 Jun 2025 23:25:47 +0300 Subject: [PATCH 008/111] Sort by call on tprof memory tests (Erlang/OTP 28) (#14565) --- lib/mix/test/mix/tasks/profile.tprof_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/test/mix/tasks/profile.tprof_test.exs b/lib/mix/test/mix/tasks/profile.tprof_test.exs index ddc5190b4b7..41973151083 100644 --- a/lib/mix/test/mix/tasks/profile.tprof_test.exs +++ b/lib/mix/test/mix/tasks/profile.tprof_test.exs @@ -84,8 +84,8 @@ defmodule Mix.Tasks.Profile.TprofTest do test "sorts based on memory per call", context do in_tmp(context.test, fn -> assert capture_io(fn -> - Tprof.run(["--type", "memory", "--sort", "per_call", "-e", @expr]) - end) =~ ~r/\n:erlang\.integer_to_binary\/1.*\nEnum\.each\/2/s + Tprof.run(["--type", "memory", "--sort", "calls", "-e", @expr]) + end) =~ ~r/Enum\.each\/2\s+1\s+.*\n:erlang\.integer_to_binary\/1\s+5\s+/s end) end From ce33663780f66fcdff7504f27d26c53e4e321afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 10 Jun 2025 09:49:55 +0200 Subject: [PATCH 009/111] Relax return type of impl_for with nil to avoid false positives --- lib/elixir/lib/protocol.ex | 2 +- .../elixir/protocol/consolidation_test.exs | 25 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/lib/elixir/lib/protocol.ex b/lib/elixir/lib/protocol.ex index ebedd282f36..ca9b6142d59 100644 --- a/lib/elixir/lib/protocol.ex +++ b/lib/elixir/lib/protocol.ex @@ -669,7 +669,7 @@ defmodule Protocol do {Descr.term(), clauses, clauses} else - {domain, clauses ++ [{[not_domain], Descr.atom([nil])}], clauses} + {domain, [{[Descr.term()], Descr.atom()}], clauses} end end diff --git a/lib/elixir/test/elixir/protocol/consolidation_test.exs b/lib/elixir/test/elixir/protocol/consolidation_test.exs index 48ab0d6a328..5364eafc447 100644 --- a/lib/elixir/test/elixir/protocol/consolidation_test.exs +++ b/lib/elixir/test/elixir/protocol/consolidation_test.exs @@ -176,31 +176,6 @@ defmodule Protocol.ConsolidationTest do assert %{{:ok, 1} => %{deprecated: "Reason", sig: _}} = exports(sample_binary()) end - test "defines signatures without fallback to any" do - exports = exports(sample_binary()) - - assert %{{:impl_for, 1} => %{sig: {:strong, domain, clauses}}} = exports - assert domain == [term()] - - assert clauses == [ - {[Of.impl(ImplStruct)], atom([Sample.Protocol.ConsolidationTest.ImplStruct])}, - {[negation(Of.impl(ImplStruct))], atom([nil])} - ] - - assert %{{:impl_for!, 1} => %{sig: {:strong, domain, clauses}}} = exports - assert domain == [Of.impl(ImplStruct)] - - assert clauses == [ - {[Of.impl(ImplStruct)], atom([Sample.Protocol.ConsolidationTest.ImplStruct])} - ] - - assert %{{:ok, 1} => %{sig: {:strong, nil, clauses}}} = exports - - assert clauses == [ - {[Of.impl(ImplStruct)], dynamic()} - ] - end - test "defines signatures with fallback to any" do exports = exports(with_any_binary()) From f8de42a053a007242b766b49355a951aa25b8b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 10 Jun 2025 09:58:02 +0200 Subject: [PATCH 010/111] Filter @compile debug_info when explicitly set to true Closes #14567. --- lib/elixir/src/elixir_erl.erl | 7 +------ lib/elixir/test/elixir/module_test.exs | 11 +++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index c4f7bb92f29..7e21533b2a7 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -182,12 +182,7 @@ dynamic_form(#{module := Module, relative_file := RelativeFile, {Def, Defmacro, Macros, Exports, Functions} = split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}), - FilteredOpts = lists:filter(fun( - {no_warn_undefined, _}) -> false; - (debug_info) -> false; - (_) -> true - end, Opts), - + FilteredOpts = proplists:delete(debug_info, proplists:delete(no_warn_undefined, Opts)), Location = {elixir_utils:characters_to_list(RelativeFile), Line}, Prefix = [{attribute, Line, file, Location}, diff --git a/lib/elixir/test/elixir/module_test.exs b/lib/elixir/test/elixir/module_test.exs index 5eedaa71246..a167086ad4b 100644 --- a/lib/elixir/test/elixir/module_test.exs +++ b/lib/elixir/test/elixir/module_test.exs @@ -413,6 +413,17 @@ defmodule ModuleTest do assert map.module == ModuleCreateDebugInfo end + test "uses the debug_info chunk when explicitly set to true" do + {:module, ModuleCreateDebugInfoTrue, binary, _} = + Module.create(ModuleCreateDebugInfoTrue, quote(do: @compile({:debug_info, true})), __ENV__) + + {:ok, {_, [debug_info: {:debug_info_v1, backend, data}]}} = + :beam_lib.chunks(binary, [:debug_info]) + + {:ok, map} = backend.debug_info(:elixir_v1, ModuleCreateDebugInfoTrue, data, []) + assert map.module == ModuleCreateDebugInfoTrue + end + test "uses the debug_info chunk even if debug_info is set to false" do {:module, ModuleCreateNoDebugInfo, binary, _} = Module.create(ModuleCreateNoDebugInfo, quote(do: @compile({:debug_info, false})), __ENV__) From c3bc849550556875ab4b742fdbc63952ab8a9d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 10 Jun 2025 10:13:50 +0200 Subject: [PATCH 011/111] Point out module must be required before macro usage in match/guard --- lib/elixir/src/elixir_rewrite.erl | 12 ++++++++++-- lib/elixir/test/elixir/kernel/expansion_test.exs | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/elixir/src/elixir_rewrite.erl b/lib/elixir/src/elixir_rewrite.erl index 04ed0206a3a..697a1b9170c 100644 --- a/lib/elixir/src/elixir_rewrite.erl +++ b/lib/elixir/src/elixir_rewrite.erl @@ -355,15 +355,23 @@ allowed_guard(Right, Arity) -> erl_internal:guard_bif(Right, Arity) orelse elixir_utils:guard_op(Right, Arity). format_error({invalid_guard, Receiver, Right, Arity, Context}) -> - io_lib:format("cannot invoke remote function ~ts.~ts/~B inside a ~ts", + io_lib:format(cannot_invoke_or_maybe_require(Receiver, Right, Arity) ++ " ~ts.~ts/~B inside a ~ts", ['Elixir.Macro':to_string(Receiver), Right, Arity, Context]); format_error({invalid_match, Receiver, Right, Arity}) -> - io_lib:format("cannot invoke remote function ~ts.~ts/~B inside a match", + io_lib:format(cannot_invoke_or_maybe_require(Receiver, Right, Arity) ++ " ~ts.~ts/~B inside a match", ['Elixir.Macro':to_string(Receiver), Right, Arity]); format_error({invalid_match_append, Arg}) -> io_lib:format("invalid argument for ++ operator inside a match, expected a literal proper list, got: ~ts", ['Elixir.Macro':to_string(Arg)]). +cannot_invoke_or_maybe_require(Receiver, Fun, Arity) -> + try + true = lists:member({Fun, Arity}, Receiver:'__info__'(macros)), + ["you must require the module", 'Elixir.Macro':to_string(Receiver), " before invoking macro"] + catch + _:_ -> "cannot invoke remote function" + end. + is_always_string({{'.', _, [Module, Function]}, _, Args}) -> is_always_string(Module, Function, length(Args)); is_always_string(Ast) -> diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index 82169bcaa4f..6ab1d955312 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -806,6 +806,15 @@ defmodule Kernel.ExpansionTest do end) end + test "in guards with macros" do + message = + ~r"you must require the moduleInteger before invoking macro Integer.is_even/1 inside a guard" + + assert_compile_error(message, fn -> + expand(quote(do: fn arg when Integer.is_even(arg) -> arg end)) + end) + end + test "in guards with bitstrings" do message = ~r"cannot invoke remote function String.Chars.to_string/1 inside a guard" From 502207c9f742b6e4881760d32520ef8212d28679 Mon Sep 17 00:00:00 2001 From: Joe Yates Date: Tue, 10 Jun 2025 11:17:04 +0200 Subject: [PATCH 012/111] Fix use of prefer with '-ing' (#14568) --- lib/elixir/lib/port.ex | 2 +- lib/elixir/src/elixir_map.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/port.ex b/lib/elixir/lib/port.ex index 424790c765b..6d1df7196b1 100644 --- a/lib/elixir/lib/port.ex +++ b/lib/elixir/lib/port.ex @@ -78,7 +78,7 @@ defmodule Port do The port can be opened through four main mechanisms. - As a short summary, prefer to using the `:spawn` and `:spawn_executable` + As a short summary, prefer to use the `:spawn` and `:spawn_executable` options mentioned below. The other two options, `:spawn_driver` and `:fd` are for advanced usage within the VM. Also consider using `System.cmd/3` if all you want is to execute a program and retrieve its return value. diff --git a/lib/elixir/src/elixir_map.erl b/lib/elixir/src/elixir_map.erl index afc2e76a249..50643944df9 100644 --- a/lib/elixir/src/elixir_map.erl +++ b/lib/elixir/src/elixir_map.erl @@ -306,6 +306,6 @@ format_error(ignored_struct_key_in_struct) -> "key :__struct__ is ignored when using structs"; format_error({deprecated_update, Struct, MapUpdate}) -> io_lib:format("the struct update syntax is deprecated:\n\n~ts\n\n" - "Instead, prefer to pattern matching on structs when the variable is first defined and " + "Instead, prefer to pattern match on structs when the variable is first defined and " "use the regular map update syntax instead:\n\n~ts\n", ['Elixir.Macro':to_string({'%', [], [Struct, MapUpdate]}), 'Elixir.Macro':to_string(MapUpdate)]). From 35818f1cdb5fedd6799b4298870b6644d3637464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 10:25:10 +0200 Subject: [PATCH 013/111] Warn when invalid fun typespec is used --- lib/elixir/lib/kernel/typespec.ex | 10 +++++++++- lib/elixir/test/elixir/kernel/warning_test.exs | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/kernel/typespec.ex b/lib/elixir/lib/kernel/typespec.ex index 485a367b123..1f3539bcf93 100644 --- a/lib/elixir/lib/kernel/typespec.ex +++ b/lib/elixir/lib/kernel/typespec.ex @@ -877,7 +877,15 @@ defmodule Kernel.Typespec do defp typespec({:fun, meta, args}, vars, caller, state) do {args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args) - {{:type, location(meta), :fun, args}, state} + + if args != [] do + IO.warn( + "fun/#{length(args)} is not valid in typespecs. Either specify fun() or use (... -> return) instead", + caller + ) + end + + {{:type, location(meta), :fun, []}, state} end defp typespec({:..., _meta, _args}, _vars, caller, _state) do diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index f118456f9d0..eb0dd65930b 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -1818,6 +1818,20 @@ defmodule Kernel.WarningTest do purge([Sample1, Sample2, Sample3]) end + test "invalid fun" do + assert_warn_eval( + [ + "nofile:2: ", + "fun/1 is not valid in typespecs. Either specify fun() or use (... -> return) instead" + ], + """ + defmodule InvalidFunType do + @type my_type :: fun(integer()) + end + """ + ) + end + test "invalid type annotations" do assert_warn_eval( [ From 342c724863113f5691dea8298ae79b0445245e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 12:08:37 +0200 Subject: [PATCH 014/111] Do no start listeners if --no-deps-check is given --- lib/mix/lib/mix/tasks/compile.ex | 2 ++ lib/mix/lib/mix/tasks/deps.loadpaths.ex | 8 +++++--- lib/mix/lib/mix/tasks/loadpaths.ex | 2 ++ lib/mix/lib/mix/tasks/run.ex | 2 ++ lib/mix/test/mix/tasks/compile_test.exs | 5 ++++- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/mix/lib/mix/tasks/compile.ex b/lib/mix/lib/mix/tasks/compile.ex index 88f296937e2..edf2f6880c4 100644 --- a/lib/mix/lib/mix/tasks/compile.ex +++ b/lib/mix/lib/mix/tasks/compile.ex @@ -61,6 +61,8 @@ defmodule Mix.Tasks.Compile do * `--erl-config` - path to an Erlang term file that will be loaded as Mix config * `--force` - forces compilation * `--list` - lists all enabled compilers + * `--listeners` - starts Mix listeners (they are started by default, + unless `--no-listeners` or `--no-deps-check` are given) * `--no-app-loading` - does not load .app resource file after compilation * `--no-archives-check` - skips checking of archives * `--no-compile` - does not actually compile, only loads code and perform checks diff --git a/lib/mix/lib/mix/tasks/deps.loadpaths.ex b/lib/mix/lib/mix/tasks/deps.loadpaths.ex index ccd084e5bea..8df70169ed4 100644 --- a/lib/mix/lib/mix/tasks/deps.loadpaths.ex +++ b/lib/mix/lib/mix/tasks/deps.loadpaths.ex @@ -75,9 +75,11 @@ defmodule Mix.Tasks.Deps.Loadpaths do Code.prepend_paths(Enum.flat_map(all, &Mix.Dep.load_paths/1), cache: true) - # For now we only allow listeners defined in dependencies, so - # we start them right after adding adding deps to the path - if "--no-listeners" not in args do + # For now we only allow listeners defined in dependencies, + # so we start them right after adding adding deps to the path, + # as long as we are sure they have been compiled + if "--listeners" in args or + ("--no-listeners" not in args and "--no-deps-check" not in args) do Mix.PubSub.start_listeners() end diff --git a/lib/mix/lib/mix/tasks/loadpaths.ex b/lib/mix/lib/mix/tasks/loadpaths.ex index 2e67bd2e5f5..a44eac90930 100644 --- a/lib/mix/lib/mix/tasks/loadpaths.ex +++ b/lib/mix/lib/mix/tasks/loadpaths.ex @@ -21,6 +21,8 @@ defmodule Mix.Tasks.Loadpaths do ## Command line options + * `--listeners` - starts Mix listeners (they are started by default, + unless `--no-listeners` or `--no-deps-check` are given) * `--no-archives-check` - does not check archives * `--no-compile` - does not compile dependencies, only check and load them * `--no-deps-check` - does not check dependencies, only load available ones diff --git a/lib/mix/lib/mix/tasks/run.ex b/lib/mix/lib/mix/tasks/run.ex index 2553c0cefd0..101e6fca4be 100644 --- a/lib/mix/lib/mix/tasks/run.ex +++ b/lib/mix/lib/mix/tasks/run.ex @@ -49,6 +49,8 @@ defmodule Mix.Tasks.Run do ## Command-line options * `--eval`, `-e` - evaluates the given code + * `--listeners` - starts Mix listeners (they are started by default, + unless `--no-listeners` or `--no-deps-check` are given) * `--require`, `-r` - executes the given pattern/file * `--parallel`, `-p` - makes all requires parallel * `--preload-modules` - preloads all modules defined in applications diff --git a/lib/mix/test/mix/tasks/compile_test.exs b/lib/mix/test/mix/tasks/compile_test.exs index cc44e77d2f4..d202102790e 100644 --- a/lib/mix/test/mix/tasks/compile_test.exs +++ b/lib/mix/test/mix/tasks/compile_test.exs @@ -425,9 +425,12 @@ defmodule Mix.Tasks.CompileTest do File.write!("src/b.erl", "-module(b).") File.write!("src/c.erl", "-module(c).") - # Ensure we can boot with compilation and listeners if desired + # Ensure we can boot without compilation and listeners if desired assert mix(["loadpaths", "--no-compile", "--no-listeners"]) == "" + # Ensure we can boot only with --no-deps-check if desired + assert mix(["loadpaths", "--no-deps-check"]) == "" + # Now setup dependencies mix(["deps.compile"]) From 59a1ad913851dca42db9afd5c02968a8e73ea5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 13:30:19 +0200 Subject: [PATCH 015/111] Allow captures to be reconstructed on type system pretty printing --- lib/elixir/lib/module/types/helpers.ex | 17 ++++++++++++++++- lib/elixir/src/elixir_fn.erl | 4 ++-- .../test/elixir/kernel/expansion_test.exs | 10 ++++++++-- .../test/elixir/module/types/expr_test.exs | 12 ++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex index b13a0e51e08..e652575a13b 100644 --- a/lib/elixir/lib/module/types/helpers.ex +++ b/lib/elixir/lib/module/types/helpers.ex @@ -141,7 +141,8 @@ defmodule Module.Types.Helpers do end) end - defp hint, do: :elixir_errors.prefix(:hint) + @doc "The hint prefix" + def hint, do: :elixir_errors.prefix(:hint) @doc """ Collect traces from variables in expression. @@ -340,6 +341,13 @@ defmodule Module.Types.Helpers do {{:., _, [mod, fun]}, meta, args} -> erl_to_ex(mod, fun, args, meta) + {:fn, meta, [{:->, _, [_args, return]}]} = expr -> + if meta[:capture] do + {:&, meta, [return]} + else + expr + end + {:&, amp_meta, [{:/, slash_meta, [{{:., dot_meta, [mod, fun]}, call_meta, []}, arity]}]} -> {mod, fun} = case :elixir_rewrite.erl_to_ex(mod, fun, arity) do @@ -385,6 +393,13 @@ defmodule Module.Types.Helpers do case end + {var, meta, context} = expr when is_atom(var) and is_atom(context) -> + if is_integer(meta[:capture]) do + {:&, meta, [meta[:capture]]} + else + expr + end + other -> other end) diff --git a/lib/elixir/src/elixir_fn.erl b/lib/elixir/src/elixir_fn.erl index d598868f90b..9a2a436d67f 100644 --- a/lib/elixir/src/elixir_fn.erl +++ b/lib/elixir/src/elixir_fn.erl @@ -131,7 +131,7 @@ capture_expr(Meta, Expr, S, E, Escaped, ArgsType) -> {expand, Fn, S, E}; {EExpr, EDict} -> EVars = validate(Meta, EDict, 1, E), - Fn = {fn, Meta, [{'->', Meta, [EVars, EExpr]}]}, + Fn = {fn, [{capture, true} | Meta], [{'->', Meta, [EVars, EExpr]}]}, {expand, Fn, S, E} end. @@ -154,7 +154,7 @@ escape({'&', Meta, [Pos]}, E, Dict) when is_integer(Pos), Pos > 0 -> {Var, Dict}; error -> Next = elixir_module:next_counter(?key(E, module)), - Var = {capture, [{counter, Next} | Meta], nil}, + Var = {capture, [{counter, Next}, {capture, Pos} | Meta], nil}, {Var, orddict:store(Pos, Var, Dict)} end; escape({'&', Meta, [Pos]}, E, _Dict) when is_integer(Pos) -> diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index 6ab1d955312..9f77db07c13 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -1212,8 +1212,14 @@ defmodule Kernel.ExpansionTest do test "keeps position meta on & variables" do assert expand(Code.string_to_quoted!("& &1")) |> clean_meta([:counter]) == - {:fn, [{:line, 1}], - [{:->, [{:line, 1}], [[{:capture, [line: 1], nil}], {:capture, [line: 1], nil}]}]} + {:fn, [capture: true, line: 1], + [ + {:->, [line: 1], + [ + [{:capture, [capture: 1, line: 1], nil}], + {:capture, [capture: 1, line: 1], nil} + ]} + ]} end test "removes no_parens when expanding 0-arity capture to fn" do diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 2f2541db826..f9ffb93abaf 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -236,6 +236,18 @@ defmodule Module.Types.ExprTest do (dynamic(map()) -> :map) """ end + + test "capture printing" do + assert typeerror!(123 = &{:ok, &1}) == """ + the following pattern will never match: + + 123 = &{:ok, &1} + + because the right-hand side has type: + + (dynamic() -> dynamic({:ok, term()})) + """ + end end describe "remotes" do From d74dff2bcfe85f2dd6f3f709bf97ae6d6c2704cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 13:39:13 +0200 Subject: [PATCH 016/111] Transform the struct update syntax into a type assertion This transforms the struct update into a type assertion, requiring the type system to be sure the expression has precisely the given struct type. The struct update syntax may still be deprecated in the future but this will provide a safer migration path and allow us to engage in more conversations with the community. --- lib/elixir/lib/module/types/expr.ex | 76 +++++++++++++++++-- lib/elixir/src/elixir_map.erl | 10 +-- .../test/elixir/module/types/expr_test.exs | 73 ++++++++++++++++++ 3 files changed, 145 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index 2aafe9ea6ce..7529035da6c 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -231,21 +231,36 @@ defmodule Module.Types.Expr do # %Struct{map | ...} # This syntax is deprecated, so we simply traverse. def of_expr( - {:%, _, [_, {:%{}, _, [{:|, _, [map, args]}]}]} = struct, + {:%, meta, [module, {:%{}, _, [{:|, _, [map, pairs]}]}]} = struct, _expected, expr, stack, context ) do - {_, context} = of_expr(map, term(), struct, stack, context) + {map_type, context} = of_expr(map, term(), struct, stack, context) context = - Enum.reduce(args, context, fn {key, value}, context when is_atom(key) -> - {_, context} = of_expr(value, term(), expr, stack, context) + if stack.mode == :traversal do context - end) + else + with {false, struct_key_type} <- map_fetch(map_type, :__struct__), + {:finite, [^module]} <- atom_fetch(struct_key_type) do + context + else + _ -> + error(__MODULE__, {:badupdate, map_type, struct, context}, meta, stack, context) + end + end - {dynamic(), context} + Enum.reduce(pairs, {map_type, context}, fn {key, value}, {acc, context} -> + # TODO: Once we support typed structs, we need to type check them here + {type, context} = of_expr(value, term(), expr, stack, context) + + case map_fetch_and_put(acc, key, type) do + {_value, acc} -> {acc, context} + _ -> {acc, context} + end + end) end # %{...} @@ -791,6 +806,55 @@ defmodule Module.Types.Expr do ## Warning formatting + def format_diagnostic({:badupdate, type, expr, context}) do + {:%, _, [module, {:%{}, _, [{:|, _, [map, _]}]}]} = expr + traces = collect_traces(map, context) + + suggestion = + case map do + {var, meta, context} when is_atom(var) and is_atom(context) -> + if capture = meta[:capture] do + "instead of using &#{capture}, you must define an anonymous function, define a variable and pattern match on \"%#{inspect(module)}{}\"" + else + "when defining the variable \"#{Macro.to_string(map)}\", you must also pattern match on \"%#{inspect(module)}{}\"" + end + + _ -> + "you must assign \"#{Macro.to_string(map)}\" to variable and pattern match on \"%#{inspect(module)}{}\"" + end + + %{ + details: %{typing_traces: traces}, + message: + IO.iodata_to_binary([ + """ + a struct for #{inspect(module)} is expected on struct update: + + #{expr_to_string(expr, collapse_structs: false) |> indent(4)} + + but got type: + + #{to_quoted_string(type) |> indent(4)} + """, + format_traces(traces), + """ + + #{hint()} #{suggestion}. Given pattern matching is enough to catch typing errors, \ + you may optionally convert the struct update into a map update. For example, \ + instead of: + + user = some_fun() + %User{user | name: "John Doe"} + + it is enough to write: + + %User{} = user = some_fun() + %{user | name: "John Doe"} + """ + ]) + } + end + def format_diagnostic({:badmap, type, expr, context}) do traces = collect_traces(expr, context) diff --git a/lib/elixir/src/elixir_map.erl b/lib/elixir/src/elixir_map.erl index 50643944df9..3b501493f38 100644 --- a/lib/elixir/src/elixir_map.erl +++ b/lib/elixir/src/elixir_map.erl @@ -18,7 +18,7 @@ expand_map(Meta, Args, S, E) -> validate_kv(Meta, EArgs, Args, E), {{'%{}', Meta, EArgs}, SE, EE}. -expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs} = Right, S, #{context := Context} = E) -> +expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, S, #{context := Context} = E) -> CleanMapArgs = delete_struct_key(Meta, MapArgs, E), {[ELeft, ERight], SE, EE} = elixir_expand:expand_args([Left, {'%{}', MapMeta, CleanMapArgs}], S, E), @@ -29,7 +29,6 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs} = Right, S, #{context := Con %% The update syntax for structs is deprecated, %% so we return only the update syntax downstream. %% TODO: Remove me on Elixir v2.0 - file_warn(MapMeta, ?key(E, file), ?MODULE, {deprecated_update, ELeft, Right}), _ = load_struct_info(Meta, ELeft, Assocs, EE), {{'%', Meta, [ELeft, ERight]}, SE, EE}; @@ -303,9 +302,4 @@ format_error({invalid_key_for_struct, Key}) -> io_lib:format("invalid key ~ts for struct, struct keys must be atoms, got: ", ['Elixir.Macro':to_string(Key)]); format_error(ignored_struct_key_in_struct) -> - "key :__struct__ is ignored when using structs"; -format_error({deprecated_update, Struct, MapUpdate}) -> - io_lib:format("the struct update syntax is deprecated:\n\n~ts\n\n" - "Instead, prefer to pattern match on structs when the variable is first defined and " - "use the regular map update syntax instead:\n\n~ts\n", - ['Elixir.Macro':to_string({'%', [], [Struct, MapUpdate]}), 'Elixir.Macro':to_string(MapUpdate)]). + "key :__struct__ is ignored when using structs". \ No newline at end of file diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index f9ffb93abaf..85bc46c85d1 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -871,6 +871,79 @@ defmodule Module.Types.ExprTest do """ end + test "updating structs" do + # When we know the type + assert typecheck!([], %Date{Date.new!(1, 1, 1) | day: 31}) == + dynamic( + closed_map( + __struct__: atom([Date]), + day: integer(), + calendar: term(), + month: term(), + year: term() + ) + ) + + # When we don't know the type of var + assert typeerror!([x], %Date{x | day: 31}) == ~l""" + a struct for Date is expected on struct update: + + %Date{x | day: 31} + + but got type: + + dynamic() + + where "x" was given the type: + + # type: dynamic() + # from: types_test.ex:LINE + x + + hint: when defining the variable "x", you must also pattern match on "%Date{}". Given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of: + + user = some_fun() + %User{user | name: "John Doe"} + + it is enough to write: + + %User{} = user = some_fun() + %{user | name: "John Doe"} + """ + + # When we don't know the type of capture + assert typeerror!([], &%Date{&1 | day: 31}) =~ ~l""" + a struct for Date is expected on struct update: + + %Date{&1 | day: 31} + + but got type: + + dynamic() + + where "capture" was given the type: + + # type: dynamic() + # from: types_test.ex:LINE + &1 + + hint: instead of using &1, you must define an anonymous function, define a variable and pattern match on "%Date{}"\ + """ + + # When we don't know the type of expression + assert typeerror!([], %Date{SomeMod.fun() | day: 31}) =~ """ + a struct for Date is expected on struct update: + + %Date{SomeMod.fun() | day: 31} + + but got type: + + dynamic() + + hint: you must assign "SomeMod.fun()" to variable and pattern match on "%Date{}".\ + """ + end + test "updating to open maps" do assert typecheck!( [key], From 9d845cf031f74aa76806fd51e655dcf13e617b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 13:59:26 +0200 Subject: [PATCH 017/111] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b834ba632c..f53a871772f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -284,7 +284,7 @@ This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosyst * [Code] The `on_undefined_variable: :warn` is deprecated. Relying on undefined variables becoming function calls will not be supported in the future * [File] Passing a callback as third argument to `File.cp/3` is deprecated, pass it as a `on_conflict: callback` option instead * [File] Passing a callback as third argument to `File.cp_r/3` is deprecated, pass it as a `on_conflict: callback` option instead - * [Kernel] The struct update syntax, such as `%URI{uri | path: "/foo/bar"}` is deprecated in favor of pattern matching on the struct when the variable is defined and then using the map update syntax `%{uri | path: "/foo/bar"}`. Thanks to the type system, pattern matching on structs can find more errors, more reliably + * [Kernel] The struct update syntax, such as `%URI{uri | path: "/foo/bar"}`, now requires the given variable (or expression) to explicitly pattern match on the struct before it can be updated. This is because, thanks to the type system, pattern matching on structs can find more errors, more reliably, and we want to promote its usage. Once pattern matching is added, you may optionally convert the struct update syntax into the map update syntax `%{uri | path: "/foo/bar"}` with no less of typing guarantees * [Kernel.ParallelCompiler] Passing `return_diagnostics: true` as an option is required on `compile`, `compile_to_path` and `require` #### Logger From 2096f4156d4dfbd7d396a6e7267f55b8314be90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 14:08:07 +0200 Subject: [PATCH 018/111] Convert only the pattern matching suggestion into a hint --- lib/elixir/lib/module/types/expr.ex | 10 ++++++---- lib/elixir/test/elixir/module/types/expr_test.exs | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index 7529035da6c..e2ced16e6f5 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -810,7 +810,7 @@ defmodule Module.Types.Expr do {:%, _, [module, {:%{}, _, [{:|, _, [map, _]}]}]} = expr traces = collect_traces(map, context) - suggestion = + fix = case map do {var, meta, context} when is_atom(var) and is_atom(context) -> if capture = meta[:capture] do @@ -839,9 +839,11 @@ defmodule Module.Types.Expr do format_traces(traces), """ - #{hint()} #{suggestion}. Given pattern matching is enough to catch typing errors, \ - you may optionally convert the struct update into a map update. For example, \ - instead of: + #{fix}. + + #{hint()} given pattern matching is enough to catch typing errors, \ + you may optionally convert the struct update into a map update. For \ + example, instead of: user = some_fun() %User{user | name: "John Doe"} diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 85bc46c85d1..a167ca86f50 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -900,7 +900,9 @@ defmodule Module.Types.ExprTest do # from: types_test.ex:LINE x - hint: when defining the variable "x", you must also pattern match on "%Date{}". Given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of: + when defining the variable "x", you must also pattern match on "%Date{}". + + hint: given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of: user = some_fun() %User{user | name: "John Doe"} @@ -927,7 +929,7 @@ defmodule Module.Types.ExprTest do # from: types_test.ex:LINE &1 - hint: instead of using &1, you must define an anonymous function, define a variable and pattern match on "%Date{}"\ + instead of using &1, you must define an anonymous function, define a variable and pattern match on "%Date{}". """ # When we don't know the type of expression @@ -940,7 +942,7 @@ defmodule Module.Types.ExprTest do dynamic() - hint: you must assign "SomeMod.fun()" to variable and pattern match on "%Date{}".\ + you must assign "SomeMod.fun()" to variable and pattern match on "%Date{}". """ end From 7e08594ca0d18ef7184c22a6134d3bd703ea74a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 11 Jun 2025 17:05:24 +0200 Subject: [PATCH 019/111] Add tests for nested struct updates too --- .../test/elixir/module/types/expr_test.exs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index a167ca86f50..076bcbf2c8b 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -872,17 +872,23 @@ defmodule Module.Types.ExprTest do end test "updating structs" do + integer_date_type = + dynamic( + closed_map( + __struct__: atom([Date]), + day: integer(), + calendar: term(), + month: term(), + year: term() + ) + ) + # When we know the type assert typecheck!([], %Date{Date.new!(1, 1, 1) | day: 31}) == - dynamic( - closed_map( - __struct__: atom([Date]), - day: integer(), - calendar: term(), - month: term(), - year: term() - ) - ) + integer_date_type + + assert typecheck!([], %Date{%Date{Date.new!(1, 1, 1) | day: 13} | day: 31}) == + integer_date_type # When we don't know the type of var assert typeerror!([x], %Date{x | day: 31}) == ~l""" From 95f982e0041f50a847e1e19263bfd6c8388b8c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 12 Jun 2025 11:36:47 +0200 Subject: [PATCH 020/111] Document bug fix on defstruct/defexception inside protocol, closes #14574 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53a871772f..c54f40b4af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -253,6 +253,7 @@ This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosyst * [DateTime] Do not truncate microseconds regardless of precision in `DateTime.diff/3` * [File] Properly handle permissions errors cascading from parent in `File.mkdir_p/1` * [Kernel] `not_a_map.key` now raises `BadMapError` for consistency with other map operations + * [Protocol] `defstruct/1` and `defexception/1` are now disabled inside `defprotocol` as to not allow defining structs/exceptions alongside a protocol * [Regex] Fix `Regex.split/2` returning too many results when the chunk being split on was empty (which can happen when using features such as `/K`) * [Stream] Ensure `Stream.transform/5` respects suspend command when its inner stream halts * [URI] Several fixes to `URI.merge/2` related to trailing slashes, trailing dots, and hostless base URIs From 9fdb835f26fa22dc3a463b82cac72ba07b24d5ed Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 14 Jun 2025 12:05:31 +0200 Subject: [PATCH 021/111] Mark inlined function call result as generated (#14581) --- lib/elixir/src/elixir_erl_pass.erl | 3 ++- .../test/elixir/fixtures/dialyzer/opaque_inline.ex | 10 ++++++++++ lib/elixir/test/elixir/kernel/dialyzer_test.exs | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex diff --git a/lib/elixir/src/elixir_erl_pass.erl b/lib/elixir/src/elixir_erl_pass.erl index 9a65052fe97..d8b10a716ac 100644 --- a/lib/elixir/src/elixir_erl_pass.erl +++ b/lib/elixir/src/elixir_erl_pass.erl @@ -628,7 +628,8 @@ translate_remote(Left, Right, Meta, Args, S) -> [TOne, TTwo] -> {{op, Ann, Right, TOne, TTwo}, SA} end; {inline_pure, Result} -> - translate(Result, Ann, S); + Generated = erl_anno:set_generated(true, Ann), + translate(Result, Generated, S); {inline_args, NewArgs} -> {TLeft, SL} = translate(Left, Ann, S), {TArgs, SA} = translate_args(NewArgs, Ann, SL), diff --git a/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex b/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex new file mode 100644 index 00000000000..ce224ee523b --- /dev/null +++ b/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex @@ -0,0 +1,10 @@ +defmodule Dialyzer.OpaqueInline do + @spec bar(MapSet.t()) :: term() + def bar(set) do + set + end + + def foo() do + bar(MapSet.new([1, 2, 3])) + end +end diff --git a/lib/elixir/test/elixir/kernel/dialyzer_test.exs b/lib/elixir/test/elixir/kernel/dialyzer_test.exs index ba5cbfb5bce..615f946b0d9 100644 --- a/lib/elixir/test/elixir/kernel/dialyzer_test.exs +++ b/lib/elixir/test/elixir/kernel/dialyzer_test.exs @@ -32,6 +32,7 @@ defmodule Kernel.DialyzerTest do :elixir_env, :elixir_erl_pass, :maps, + :sets, ArgumentError, Atom, Code, @@ -45,6 +46,7 @@ defmodule Kernel.DialyzerTest do List, Macro, Macro.Env, + MapSet, Module, Protocol, String, @@ -176,6 +178,11 @@ defmodule Kernel.DialyzerTest do assert_dialyze_no_warnings!(context) end + test "no warning on inlined calls returning opaque", context do + copy_beam!(context, Dialyzer.OpaqueInline) + assert_dialyze_no_warnings!(context) + end + defp copy_beam!(context, module) do name = "#{module}.beam" File.cp!(Path.join(context.base_dir, name), Path.join(context.outdir, name)) From f4bbf76d0b4833c928f5a0d2ec31aac5072f977d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 14 Jun 2025 12:13:07 -0700 Subject: [PATCH 022/111] Optimize empty_difference_subtype? for dynamic parts --- lib/elixir/lib/module/types/descr.ex | 33 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 5c18b28a891..ee6b7dc8b00 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -257,7 +257,7 @@ defmodule Module.Types.Descr do case descr do %{dynamic: %{optional: 1}} -> %{dynamic: %{optional: 1}} %{optional: 1} -> %{optional: 1} - _ -> %{} + _ -> @none end end @@ -464,13 +464,21 @@ defmodule Module.Types.Descr do defp iterator_difference_static(:none, map), do: map - defp empty_difference_static?(left, :term), do: not Map.has_key?(left, :optional) + # This function is designed to compute the difference during subtyping efficiently. + # Do not use it for anything else. + defp empty_difference_subtype?(%{dynamic: dyn_left} = left, %{dynamic: dyn_right} = right) do + # Dynamic will either exist on both sides or on none + empty_difference_subtype?(dyn_left, dyn_right) and + empty_difference_subtype?(Map.delete(left, :dynamic), Map.delete(right, :dynamic)) + end + + defp empty_difference_subtype?(left, :term), do: keep_optional(left) == @none - defp empty_difference_static?(left, right) do - iterator_empty_difference_static?(:maps.next(:maps.iterator(unfold(left))), unfold(right)) + defp empty_difference_subtype?(left, right) do + iterator_empty_difference_subtype?(:maps.next(:maps.iterator(unfold(left))), unfold(right)) end - defp iterator_empty_difference_static?({key, v1, iterator}, map) do + defp iterator_empty_difference_subtype?({key, v1, iterator}, map) do case map do %{^key => v2} -> value = difference(key, v1, v2) @@ -479,15 +487,14 @@ defmodule Module.Types.Descr do %{} -> empty_key?(key, v1) end and - iterator_empty_difference_static?(:maps.next(iterator), map) + iterator_empty_difference_subtype?(:maps.next(iterator), map) end - defp iterator_empty_difference_static?(:none, _map), do: true + defp iterator_empty_difference_subtype?(:none, _map), do: true # Returning 0 from the callback is taken as none() for that subtype. defp difference(:atom, v1, v2), do: atom_difference(v1, v2) defp difference(:bitmap, v1, v2), do: v1 - (v1 &&& v2) - defp difference(:dynamic, v1, v2), do: dynamic_difference(v1, v2) defp difference(:list, v1, v2), do: list_difference(v1, v2) defp difference(:map, v1, v2), do: map_difference(v1, v2) defp difference(:optional, 1, 1), do: 0 @@ -535,7 +542,6 @@ defmodule Module.Types.Descr do defp empty_key?(:map, value), do: map_empty?(value) defp empty_key?(:list, value), do: list_empty?(value) defp empty_key?(:tuple, value), do: tuple_empty?(value) - defp empty_key?(:dynamic, value), do: empty?(value) defp empty_key?(_, _value), do: false @doc """ @@ -662,7 +668,7 @@ defmodule Module.Types.Descr do end defp subtype_static?(same, same), do: true - defp subtype_static?(left, right), do: empty_difference_static?(left, right) + defp subtype_static?(left, right), do: empty_difference_subtype?(left, right) @doc """ Check if a type is equal to another. @@ -2116,13 +2122,6 @@ defmodule Module.Types.Descr do defp dynamic_intersection(left, right), do: symmetrical_intersection(unfold(left), unfold(right), &intersection/3) - defp dynamic_difference(left, right) do - case difference_static(left, right) do - value when value == @none -> 0 - value -> value - end - end - defp dynamic_to_quoted(descr, opts) do cond do descr == %{} -> From 7a28203ff5a3f4a2c6a41b4fe5f3e32d5c4f563b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 19 Jun 2025 14:56:47 +0200 Subject: [PATCH 023/111] Fix invalid warning on no parens call on true (#14593) --- lib/elixir/src/elixir_erl_pass.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/src/elixir_erl_pass.erl b/lib/elixir/src/elixir_erl_pass.erl index d8b10a716ac..1fc7bac8d4f 100644 --- a/lib/elixir/src/elixir_erl_pass.erl +++ b/lib/elixir/src/elixir_erl_pass.erl @@ -718,7 +718,7 @@ generate_struct_name_guard([Field | Rest], Acc, S) -> %% TODO: Make this a runtime error on Elixir v2.0 no_parens_remote(nil, _Key) -> {error, {badmap, nil}}; no_parens_remote(false, _Key) -> {error, {badmap, false}}; -no_parens_remote(true, _Key) -> {error, {badmap, false}}; +no_parens_remote(true, _Key) -> {error, {badmap, true}}; no_parens_remote(Atom, Fun) when is_atom(Atom) -> Message = fun() -> io_lib:format( From 711008a45665450b600825203c84fdce7673975b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 19 Jun 2025 21:00:43 +0200 Subject: [PATCH 024/111] Consistently raise UnicodeConversionError in tokenizer (#14589) --- lib/elixir/src/elixir_tokenizer.erl | 36 +++++++++++++++++++++++----- lib/elixir/test/elixir/code_test.exs | 21 ++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index e90d259454b..93b32c869c9 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -1032,18 +1032,42 @@ unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{existing_atoms_only=true} try {ok, binary_to_existing_atom(Binary, utf8)} catch - error:badarg -> {error, {?LOC(Line, Column), "unsafe atom does not exist: ", elixir_utils:characters_to_list(Binary)}} + error:badarg -> + % Check if it's a UTF-8 issue by trying to convert to list + elixir_utils:characters_to_list(Binary), + % If we get here, it's not a UTF-8 issue + {error, {?LOC(Line, Column), "unsafe atom does not exist: ", elixir_utils:characters_to_list(Binary)}} + end; +unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{}) when is_binary(Binary) -> + try + {ok, binary_to_atom(Binary, utf8)} + catch + error:badarg -> + % Try to convert using elixir_utils to get proper UnicodeConversionError + elixir_utils:characters_to_list(Binary), + % If we get here, it's not a UTF-8 issue, so it's some other badarg + {error, {?LOC(Line, Column), "invalid atom: ", elixir_utils:characters_to_list(Binary)}} end; -unsafe_to_atom(Binary, _Line, _Column, #elixir_tokenizer{}) when is_binary(Binary) -> - {ok, binary_to_atom(Binary, utf8)}; unsafe_to_atom(List, Line, Column, #elixir_tokenizer{existing_atoms_only=true}) when is_list(List) -> try {ok, list_to_existing_atom(List)} catch - error:badarg -> {error, {?LOC(Line, Column), "unsafe atom does not exist: ", List}} + error:badarg -> + % Try to convert using elixir_utils to get proper UnicodeConversionError + elixir_utils:characters_to_binary(List), + % If we get here, it's not a UTF-8 issue + {error, {?LOC(Line, Column), "unsafe atom does not exist: ", List}} end; -unsafe_to_atom(List, _Line, _Column, #elixir_tokenizer{}) when is_list(List) -> - {ok, list_to_atom(List)}. +unsafe_to_atom(List, Line, Column, #elixir_tokenizer{}) when is_list(List) -> + try + {ok, list_to_atom(List)} + catch + error:badarg -> + % Try to convert using elixir_utils to get proper UnicodeConversionError + elixir_utils:characters_to_binary(List), + % If we get here, it's not a UTF-8 issue, so it's some other badarg + {error, {?LOC(Line, Column), "invalid atom: ", List}} + end. collect_modifiers([H | T], Buffer) when ?is_downcase(H) or ?is_upcase(H) or ?is_digit(H) -> collect_modifiers(T, [H | Buffer]); diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index be0052d7513..8747520dafc 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -514,6 +514,27 @@ defmodule CodeTest do } end + test "string_to_quoted raises UnicodeConversionError for invalid UTF-8 in quoted atoms and function calls" do + invalid_utf8_cases = [ + # Quoted atom + ~S{:"\xFF"}, + ~S{:'\xFF'}, + # Quoted function call + ~S{foo."\xFF"()}, + ~S{foo.'\xFF'()} + ] + + for code <- invalid_utf8_cases do + assert_raise UnicodeConversionError, fn -> + Code.string_to_quoted!(code) + end + + assert_raise UnicodeConversionError, fn -> + Code.string_to_quoted!(code, existing_atoms_only: true) + end + end + end + @tag :requires_source test "compile source" do assert __MODULE__.__info__(:compile)[:source] == String.to_charlist(__ENV__.file) From 41151190e5fae238d7c32896ecffd52500cea740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 19 Jun 2025 21:02:02 +0200 Subject: [PATCH 025/111] Handle error result from unescape_tokens in tokenizer (#14587) --- lib/elixir/src/elixir_tokenizer.erl | 17 +++++++---- lib/elixir/test/elixir/code_test.exs | 42 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 93b32c869c9..578bc340aac 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -948,17 +948,22 @@ handle_dot([$., H | T] = Original, Line, Column, DotInfo, BaseScope, Tokens) whe InterScope end, - {ok, [UnescapedPart]} = unescape_tokens([Part], Line, Column, NewScope), + case unescape_tokens([Part], Line, Column, NewScope) of + {ok, [UnescapedPart]} -> + case unsafe_to_atom(UnescapedPart, Line, Column, NewScope) of + {ok, Atom} -> + Token = check_call_identifier(Line, Column, H, Atom, Rest), + TokensSoFar = add_token_with_eol({'.', DotInfo}, Tokens), + tokenize(Rest, NewLine, NewColumn, NewScope, [Token | TokensSoFar]); - case unsafe_to_atom(UnescapedPart, Line, Column, NewScope) of - {ok, Atom} -> - Token = check_call_identifier(Line, Column, H, Atom, Rest), - TokensSoFar = add_token_with_eol({'.', DotInfo}, Tokens), - tokenize(Rest, NewLine, NewColumn, NewScope, [Token | TokensSoFar]); + {error, Reason} -> + error(Reason, Original, NewScope, Tokens) + end; {error, Reason} -> error(Reason, Original, NewScope, Tokens) end; + {_NewLine, _NewColumn, _Parts, Rest, NewScope} -> Message = "interpolation is not allowed when calling function/macro. Found interpolation in a call starting with: ", error({?LOC(Line, Column), Message, [H]}, Rest, NewScope, Tokens); diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 8747520dafc..5be948fa38a 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -514,6 +514,48 @@ defmodule CodeTest do } end + test "string_to_quoted handles unescape errors properly" do + # Test invalid hex escape character + assert {:error, {meta, message, token}} = Code.string_to_quoted("a.'\\xg'") + + assert meta[:line] == 1 + assert meta[:column] == 3 + + assert message == + "invalid hex escape character, expected \\xHH where H is a hexadecimal digit. Syntax error after: " + + assert token == "\\x" + + # Test invalid Unicode escape character + assert {:error, {meta2, message2, token2}} = Code.string_to_quoted("a.'\\ug'") + + assert meta2[:line] == 1 + assert meta2[:column] == 3 + + assert message2 == + "invalid Unicode escape character, expected \\uHHHH or \\u{H*} where H is a hexadecimal digit. Syntax error after: " + + assert token2 == "\\u" + + # Test invalid Unicode code point (surrogate pair) + assert {:error, {meta3, message3, token3}} = Code.string_to_quoted("a.'\\u{D800}'") + + assert meta3[:line] == 1 + assert meta3[:column] == 3 + + assert message3 == "invalid or reserved Unicode code point \\u{D800}. Syntax error after: " + assert token3 == "\\u" + + # Test Unicode code point beyond valid range + assert {:error, {meta4, message4, token4}} = Code.string_to_quoted("a.'\\u{110000}'") + + assert meta4[:line] == 1 + assert meta4[:column] == 3 + + assert message4 == "invalid or reserved Unicode code point \\u{110000}. Syntax error after: " + assert token4 == "\\u" + end + test "string_to_quoted raises UnicodeConversionError for invalid UTF-8 in quoted atoms and function calls" do invalid_utf8_cases = [ # Quoted atom From 4f2868e632f6d23213e035d613773f739258c644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jun 2025 03:25:36 -0700 Subject: [PATCH 026/111] Ensure block_keyword_or_binary_operator is handled in surround context, closes #14590 --- lib/elixir/lib/code/fragment.ex | 6 +++++ lib/elixir/test/elixir/code_fragment_test.exs | 26 ++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index 255a3640cad..ca71fe21ff2 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -771,6 +771,12 @@ defmodule Code.Fragment do {{:local_or_var, acc}, offset} -> build_surround({:local_or_var, acc}, reversed, line, offset) + {{:block_keyword_or_binary_operator, acc}, offset} when acc in @textual_operators -> + build_surround({:operator, acc}, reversed, line, offset) + + {{:block_keyword_or_binary_operator, acc}, offset} when acc in @keywords -> + build_surround({:keyword, acc}, reversed, line, offset) + {{:module_attribute, ~c""}, offset} -> build_surround({:operator, ~c"@"}, reversed, line, offset) diff --git a/lib/elixir/test/elixir/code_fragment_test.exs b/lib/elixir/test/elixir/code_fragment_test.exs index cd046019674..84cc28b9466 100644 --- a/lib/elixir/test/elixir/code_fragment_test.exs +++ b/lib/elixir/test/elixir/code_fragment_test.exs @@ -562,14 +562,20 @@ defmodule CodeFragmentTest do assert CF.surround_context("안녕_세상", {1, 6}) == :none # Keywords are not local or var - for keyword <- ~w(do end after catch else rescue fn true false nil)c do - keyword_length = length(keyword) + 1 - - assert %{ - context: {:keyword, ^keyword}, + for keyword <- ~w(do end after catch else rescue fn true false nil)c, + length = length(keyword), + i <- 1..length do + assert CF.surround_context(keyword, {1, i}) == %{ + context: {:keyword, keyword}, begin: {1, 1}, - end: {1, ^keyword_length} - } = CF.surround_context(keyword, {1, 1}) + end: {1, length + 1} + } + + assert CF.surround_context(~c"Foo " ++ keyword, {1, 4 + i}) == %{ + context: {:keyword, keyword}, + begin: {1, 5}, + end: {1, length + 5} + } end end @@ -664,6 +670,12 @@ defmodule CodeFragmentTest do begin: {1, 1}, end: {1, byte_size(op) + 1} } + + assert CF.surround_context("Foo #{op}", {1, 4 + i}) == %{ + context: {:operator, String.to_charlist(op)}, + begin: {1, 5}, + end: {1, byte_size(op) + 5} + } end end From 7d3047981c56293c1467dace759e79d8b314411a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jun 2025 03:54:44 -0700 Subject: [PATCH 027/111] Distinguish source_anno from doc_anno, see #14595 --- lib/elixir/src/elixir_erl.erl | 7 ++----- lib/elixir/test/elixir/kernel/docs_test.exs | 9 +++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index 7e21533b2a7..9df9b51d0f1 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -593,15 +593,12 @@ get_moduledoc_meta(Set) -> get_docs(Set, Module, Definitions, Kind) -> [{Key, - maybe_generated(erl_anno:new(Line), Ctx), + erl_anno:new(Line), [signature_to_binary(Module, Name, Signature)], doc_value(Doc, Name), Meta#{source_annos => [?ann(DefinitionMeta)]} } || {{Name, Arity}, DefinitionMeta} <- Definitions, - {Key, Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})]. - -maybe_generated(Ann, nil) -> Ann; -maybe_generated(Ann, _Ctx) -> erl_anno:set_generated(true, Ann). + {Key, _Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})]. get_callback_docs(Set, Callbacks) -> [{Key, diff --git a/lib/elixir/test/elixir/kernel/docs_test.exs b/lib/elixir/test/elixir/kernel/docs_test.exs index 1926eece0bf..fde228722a4 100644 --- a/lib/elixir/test/elixir/kernel/docs_test.exs +++ b/lib/elixir/test/elixir/kernel/docs_test.exs @@ -430,7 +430,7 @@ defmodule Kernel.DocsTest do write_beam( defmodule ToBeUsed do defmacro __using__(_) do - quote do + quote generated: true do @doc "Hello" def foo, do: :bar end @@ -446,11 +446,12 @@ defmodule Kernel.DocsTest do {:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(WillBeUsing) - location = :erl_anno.new(line + 15) + doc_anno = :erl_anno.new(line + 15) + source_anno = :erl_anno.set_generated(true, :erl_anno.new(line + 15)) assert [ - {{:function, :foo, 0}, [generated: true, location: ^location], ["foo()"], - %{"en" => "Hello"}, %{}} + {{:function, :foo, 0}, ^doc_anno, ["foo()"], %{"en" => "Hello"}, + %{source_annos: [^source_anno]}} ] = docs end end From 274bc56f53ef25ab28f03514dcdb4707c3f23aaf Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Sun, 22 Jun 2025 20:54:12 +0200 Subject: [PATCH 028/111] Add compilers option to `Mix.install/2` (#14577) --- lib/mix/lib/mix.ex | 6 ++++-- lib/mix/test/mix_test.exs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex index a4b97a21f3a..5197d890df5 100644 --- a/lib/mix/lib/mix.ex +++ b/lib/mix/lib/mix.ex @@ -884,6 +884,7 @@ defmodule Mix do config: [], config_path: nil, consolidate_protocols: true, + compilers: [:elixir], elixir: nil, force: false, lockfile: nil, @@ -898,6 +899,7 @@ defmodule Mix do system_env = Keyword.fetch!(opts, :system_env) consolidate_protocols? = Keyword.fetch!(opts, :consolidate_protocols) start_applications? = Keyword.fetch!(opts, :start_applications) + compilers = Keyword.fetch!(opts, :compilers) id = {deps, config, system_env, consolidate_protocols?} @@ -923,7 +925,8 @@ defmodule Mix do dynamic_config = [ deps: deps, consolidate_protocols: consolidate_protocols?, - config_path: config_path + config_path: config_path, + compilers: compilers ] :ok = @@ -1093,7 +1096,6 @@ defmodule Mix do app: @mix_install_app, erlc_paths: [], elixirc_paths: [], - compilers: [:elixir], prune_code_paths: false ] ++ dynamic_config end diff --git a/lib/mix/test/mix_test.exs b/lib/mix/test/mix_test.exs index 15471b5928d..0cd19896b03 100644 --- a/lib/mix/test/mix_test.exs +++ b/lib/mix/test/mix_test.exs @@ -430,6 +430,43 @@ defmodule MixTest do System.delete_env("MIX_INSTALL_RESTORE_PROJECT_DIR") end + test "custom compilers", %{tmp_dir: tmp_dir} do + File.mkdir_p!("#{tmp_dir}/install_test/lib/mix/tasks/compile/") + + File.write!("#{tmp_dir}/install_test/lib/mix/tasks/compile/install_test.ex", """ + defmodule Mix.Tasks.Compile.InstallTest do + use Mix.Task.Compiler + + def run(_args) do + Mix.shell().info("Hello from custom compiler!") + + :noop + end + end + """) + + Mix.install( + [ + {:install_test, path: Path.join(tmp_dir, "install_test")} + ], + compilers: [:elixir, :install_test] + ) + + assert File.dir?(Path.join(tmp_dir, "installs")) + + assert Protocol.consolidated?(InstallTest.Protocol) + + assert_received {:mix_shell, :info, ["==> install_test"]} + assert_received {:mix_shell, :info, ["Compiling 3 files (.ex)"]} + assert_received {:mix_shell, :info, ["Generated install_test app"]} + assert_received {:mix_shell, :info, ["==> mix_install"]} + assert_received {:mix_shell, :info, ["Hello from custom compiler!"]} + refute_received _ + + assert List.keyfind(Application.started_applications(), :install_test, 0) + assert apply(InstallTest, :hello, []) == :world + end + test "installed?", %{tmp_dir: tmp_dir} do refute Mix.installed?() From 4f5746369b45dce5af9532309ede6af397b928a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Wed, 25 Jun 2025 19:29:47 +0200 Subject: [PATCH 029/111] Use Workload Identity Federation for Windows Trusted Signing (#14604) --- .github/workflows/release.yml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cea41b109e3..4aeec8b9486 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,6 +113,7 @@ jobs: sign: needs: [build] + environment: release strategy: fail-fast: true matrix: @@ -126,6 +127,7 @@ jobs: permissions: contents: write + id-token: write steps: - name: "Download build" @@ -133,23 +135,20 @@ jobs: with: name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }} + - name: Log in to Azure + if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: "Sign files with Trusted Signing" uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1 - if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows' + if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} with: - azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} - azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} - # AZURE_TENANT_ID and AZURE_CLIENT_ID should stay the same, - # but AZURE_CLIENT_SECRET has expiration date. When it expires go to - # App Registrations / / Certificates & secrets, - # click (+) New client secret, note the "Value" (not "Secret ID") - # and update it: - # - # $ gh --repo elixir-lang/elixir secret set AZURE_CLIENT_SECRET - azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} endpoint: https://eus.codesigning.azure.net/ - trusted-signing-account-name: trusted-signing-elixir - certificate-profile-name: Elixir + trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} + certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }} files-folder: ${{ github.workspace }} files-folder-filter: exe file-digest: SHA256 From 65dbdef7dcdca5324ecede547c6fb26a0172cdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 26 Jun 2025 13:32:32 +0200 Subject: [PATCH 030/111] Fix pry on Erlang/OTP 28 --- lib/iex/lib/iex/broker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index 56eba38da04..bcb8944521c 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -158,7 +158,7 @@ defmodule IEx.Broker do defp local_or_remote_shell() do Enum.find_value([node() | Node.list()], fn node -> try do - :erpc.call(node, IEx.Broker, :shell, []) + :erpc.call(node, :shell, :whereis, []) catch _, _ -> nil end From a450d12912c36dd1bcadc7a7ff81f4d98268e6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 26 Jun 2025 13:38:14 +0200 Subject: [PATCH 031/111] Do not send quoted expressions to Macro.dbg --- lib/iex/lib/iex/pry.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iex/lib/iex/pry.ex b/lib/iex/lib/iex/pry.ex index 463aa35e9c0..333365f65b6 100644 --- a/lib/iex/lib/iex/pry.ex +++ b/lib/iex/lib/iex/pry.ex @@ -704,7 +704,7 @@ defmodule IEx.Pry do end def dbg(ast, options, %Macro.Env{} = env) when is_list(options) do - options = quote(do: Keyword.put(unquote(options), :print_location, false)) + options = Keyword.put(options, :print_location, false) quote do IEx.Pry.pry(binding(), __ENV__) From 9e3e817e428334739c0a37eb5b99123140682f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 26 Jun 2025 13:48:55 +0200 Subject: [PATCH 032/111] Deal with undefined on :shell.whereis/0 --- lib/iex/lib/iex/broker.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index bcb8944521c..d2d9ed57140 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -158,7 +158,10 @@ defmodule IEx.Broker do defp local_or_remote_shell() do Enum.find_value([node() | Node.list()], fn node -> try do - :erpc.call(node, :shell, :whereis, []) + case :erpc.call(node, :shell, :whereis, []) do + pid when is_pid(pid) -> pid + :undefined -> nil + end catch _, _ -> nil end From 5ebe19e333089a62148170cd488a84045721f67e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 27 Jun 2025 15:29:50 +0200 Subject: [PATCH 033/111] Make sure we log all output when partition fails --- lib/mix/lib/mix/tasks/deps.partition.ex | 51 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/lib/mix/lib/mix/tasks/deps.partition.ex b/lib/mix/lib/mix/tasks/deps.partition.ex index 176bfec17f5..73fd291a07f 100644 --- a/lib/mix/lib/mix/tasks/deps.partition.ex +++ b/lib/mix/lib/mix/tasks/deps.partition.ex @@ -67,6 +67,7 @@ defmodule Mix.Tasks.Deps.Partition do ] options = [ + :exit_status, :binary, :hide, :use_stdio, @@ -164,12 +165,10 @@ defmodule Mix.Tasks.Deps.Partition do send_deps_and_server_loop([client | available], busy, deps, status) {:tcp_closed, socket} -> - shutdown_clients(available ++ busy) - Mix.raise("mix deps.partition #{inspect(socket)} closed unexpectedly") + tcp_failed!("closed unexpectedly", socket, available, busy) {:tcp_error, socket, error} -> - shutdown_clients(available ++ busy) - Mix.raise("mix deps.partition #{inspect(socket)} errored: #{inspect(error)}") + tcp_failed!("errored: #{inspect(error)}", socket, available, busy) {port, {:data, {eol, data}}} -> with %{index: index} <- @@ -189,6 +188,26 @@ defmodule Mix.Tasks.Deps.Partition do end end + defp tcp_failed!(message, socket, available, busy) do + {%{port: port} = client, busy} = pop_with(busy, &(&1.socket == socket)) + + # Let's make sure it has all been written out + # but don't wait for more than 5 seconds if it + # gets stuck for some unknown reason + receive do + {^port, {:exit_status, _}} -> :ok + after + 5_000 -> Mix.shell().error("Timed out waiting for port exit #{inspect(port)}") + end + + shutdown_clients(available ++ busy ++ [client]) + + Mix.raise( + "mix deps.partition #{inspect(socket)} #{message} " <> + "(set MIX_OS_DEPS_COMPILE_PARTITION_COUNT=1 to run in serial)" + ) + end + defp shutdown_clients(clients) do Enum.each(clients, fn %{socket: socket, port: port, index: index} -> if Mix.debug?() do @@ -201,18 +220,22 @@ defmodule Mix.Tasks.Deps.Partition do end defp close_port(port, prefix) do + try do + Port.close(port) + catch + _, _ -> :ok + end + + loop_close_port(port, prefix) + end + + defp loop_close_port(port, prefix) do receive do - {^port, {:data, {:eol, data}}} -> [prefix, data, ?\n | close_port(port, prefix)] - {^port, {:data, {:noeol, data}}} -> [data | close_port(port, prefix)] + {^port, {:data, {:eol, data}}} -> [prefix, data, ?\n | loop_close_port(port, prefix)] + {^port, {:data, {:noeol, data}}} -> [data | loop_close_port(port, prefix)] + {^port, {:exit_status, _}} -> loop_close_port(port, prefix) after - 0 -> - try do - Port.close(port) - catch - _, _ -> :ok - end - - [] + 0 -> [] end end From 592f44e2a81bb2a755140f9cb0205a3d16d0b478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 30 Jun 2025 10:55:16 +0200 Subject: [PATCH 034/111] Remove Regex warning until Erlang/OTP 28.1 --- lib/elixir/lib/kernel.ex | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index d14399fd499..d815ce47550 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3815,13 +3815,7 @@ defmodule Kernel do do_at_escape(name, doc) %{__struct__: Regex, source: source, opts: opts} = regex -> - # TODO: Remove this in Elixir v2.0 - IO.warn( - "storing and reading regexes from module attributes is deprecated, " <> - "inline the regex inside the function definition instead", - env - ) - + # TODO: Automatically deal with exported regexes case :erlang.system_info(:otp_release) < [?2, ?8] do true -> do_at_escape(name, regex) false -> quote(do: Regex.compile!(unquote(source), unquote(opts))) From a0ca37cb8f742710d301eac09052aa7b7f67ec08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 30 Jun 2025 11:57:23 +0200 Subject: [PATCH 035/111] Fix warnings on Erlang/OTP 28 --- lib/elixir/test/elixir/regex_test.exs | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index e64897b6e7c..166a3281360 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -9,22 +9,22 @@ defmodule RegexTest do doctest Regex - if System.otp_release() >= "28" do - test "module attribute" do - assert ExUnit.CaptureIO.capture_io(:stderr, fn -> - defmodule ModAttr do - @regex ~r/example/ - def regex, do: @regex - - @bare_regex :erlang.term_to_binary(@regex) - def bare_regex, do: :erlang.binary_to_term(@bare_regex) - - # We don't rewrite outside of functions - assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern - end - - assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern - end) =~ "storing and reading regexes from module attributes is deprecated" + test "module attribute" do + defmodule ModAttr do + @regex ~r/example/ + def regex, do: @regex + + @bare_regex :erlang.term_to_binary(@regex) + def bare_regex, do: :erlang.binary_to_term(@bare_regex) + + # We don't rewrite outside of functions + assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern + end + + if System.otp_release() >= "28" do + assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern + else + assert ModAttr.regex().re_pattern == ModAttr.bare_regex().re_pattern end end From db203167e2ac4cc65793c917d0ee3285336f3d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Mon, 30 Jun 2025 17:24:28 +0200 Subject: [PATCH 036/111] Replace keyword with concrete keyword lists in specs (#14611) --- lib/eex/lib/eex.ex | 19 +++-- lib/eex/lib/eex/compiler.ex | 18 +++- lib/eex/lib/eex/engine.ex | 19 ++++- lib/elixir/lib/calendar.ex | 18 +++- lib/elixir/lib/calendar/duration.ex | 17 ++++ lib/elixir/lib/code.ex | 97 ++++++++++++++++------ lib/elixir/lib/code/formatter.ex | 23 +++++ lib/elixir/lib/code/fragment.ex | 26 +++++- lib/elixir/lib/code/normalizer.ex | 10 +++ lib/elixir/lib/config.ex | 8 +- lib/elixir/lib/config/provider.ex | 11 +++ lib/elixir/lib/config/reader.ex | 12 ++- lib/elixir/lib/inspect/algebra.ex | 38 ++++++++- lib/elixir/lib/io.ex | 25 +++++- lib/elixir/lib/io/ansi/docs.ex | 20 ++++- lib/elixir/lib/json.ex | 18 +++- lib/elixir/lib/kernel.ex | 2 +- lib/elixir/lib/kernel/parallel_compiler.ex | 41 ++++++++- lib/elixir/lib/macro.ex | 13 ++- lib/elixir/lib/macro/env.ex | 50 +++++++++-- lib/elixir/lib/module.ex | 14 +++- lib/elixir/lib/module/parallel_checker.ex | 11 +++ lib/elixir/lib/module/types/descr.ex | 14 ++++ lib/elixir/lib/module/types/helpers.ex | 8 ++ lib/elixir/lib/path.ex | 8 +- lib/elixir/lib/record.ex | 11 ++- lib/elixir/lib/regex.ex | 34 +++++++- lib/elixir/lib/registry.ex | 13 ++- lib/elixir/lib/string.ex | 16 +++- lib/elixir/lib/string_io.ex | 12 ++- lib/elixir/lib/supervisor.ex | 15 +++- lib/elixir/lib/system.ex | 26 +++++- lib/elixir/lib/task/supervisor.ex | 24 ++++-- lib/elixir/lib/version.ex | 4 +- lib/ex_unit/lib/ex_unit.ex | 48 ++++++++++- lib/ex_unit/lib/ex_unit/callbacks.ex | 16 +++- lib/ex_unit/lib/ex_unit/capture_io.ex | 14 +++- lib/ex_unit/lib/ex_unit/capture_log.ex | 8 +- lib/ex_unit/lib/ex_unit/case.ex | 11 ++- lib/iex/lib/iex.ex | 74 ++++++++++++++++- lib/iex/lib/iex/broker.ex | 9 +- lib/iex/lib/iex/helpers.ex | 1 + lib/iex/lib/iex/server.ex | 10 ++- lib/iex/test/iex/autocomplete_test.exs | 22 ++++- lib/iex/test/iex/helpers_test.exs | 2 +- lib/logger/lib/logger.ex | 30 ++++++- lib/logger/lib/logger/backends/internal.ex | 25 +++++- lib/logger/lib/logger/formatter.ex | 16 +++- lib/logger/lib/logger/utils.ex | 6 ++ lib/mix/lib/mix.ex | 23 +++++ lib/mix/lib/mix/compilers/erlang.ex | 17 ++++ lib/mix/lib/mix/dep.ex | 8 ++ lib/mix/lib/mix/dep/converger.ex | 10 +++ lib/mix/lib/mix/dep/lock.ex | 10 ++- lib/mix/lib/mix/generator.ex | 16 +++- lib/mix/lib/mix/local/installer.ex | 23 ++++- lib/mix/lib/mix/project.ex | 29 +++++-- lib/mix/lib/mix/release.ex | 39 ++++++++- lib/mix/lib/mix/shell.ex | 29 ++++++- lib/mix/lib/mix/shell/io.ex | 6 ++ lib/mix/lib/mix/shell/quiet.ex | 3 + lib/mix/lib/mix/sync/lock.ex | 9 +- lib/mix/lib/mix/tasks/format.ex | 68 ++++++++++++++- lib/mix/lib/mix/tasks/profile.cprof.ex | 11 ++- lib/mix/lib/mix/tasks/profile.eprof.ex | 14 +++- lib/mix/lib/mix/tasks/profile.fprof.ex | 12 ++- lib/mix/lib/mix/tasks/profile.tprof.ex | 17 +++- lib/mix/lib/mix/tasks/run.ex | 15 +++- lib/mix/lib/mix/utils.ex | 33 ++++++-- 69 files changed, 1230 insertions(+), 149 deletions(-) diff --git a/lib/eex/lib/eex.ex b/lib/eex/lib/eex.ex index 2e3c07f5eed..6d743c1ed5c 100644 --- a/lib/eex/lib/eex.ex +++ b/lib/eex/lib/eex.ex @@ -118,6 +118,15 @@ defmodule EEx do | {:expr | :start_expr | :middle_expr | :end_expr, marker, charlist, metadata} | {:eof, metadata} + @type tokenize_opt :: + {:file, binary()} + | {:line, line} + | {:column, column} + | {:indentation, non_neg_integer} + | {:trim, boolean()} + + @type compile_opt :: tokenize_opt | {:engine, module()} | {:parser_options, Code.parser_opts()} + @doc """ Generates a function definition from the given string. @@ -220,7 +229,7 @@ defmodule EEx do "3" """ - @spec compile_string(String.t(), keyword) :: Macro.t() + @spec compile_string(String.t(), [compile_opt]) :: Macro.t() def compile_string(source, options \\ []) when is_binary(source) and is_list(options) do case tokenize(source, options) do {:ok, tokens} -> @@ -259,7 +268,7 @@ defmodule EEx do #=> "3" """ - @spec compile_file(Path.t(), keyword) :: Macro.t() + @spec compile_file(Path.t(), [compile_opt]) :: Macro.t() def compile_file(filename, options \\ []) when is_list(options) do filename = IO.chardata_to_string(filename) options = Keyword.merge([file: filename, line: 1], options) @@ -277,7 +286,7 @@ defmodule EEx do "foo baz" """ - @spec eval_string(String.t(), keyword, keyword) :: String.t() + @spec eval_string(String.t(), keyword, [compile_opt]) :: String.t() def eval_string(source, bindings \\ [], options \\ []) when is_binary(source) and is_list(bindings) and is_list(options) do compiled = compile_string(source, options) @@ -299,7 +308,7 @@ defmodule EEx do #=> "foo baz" """ - @spec eval_file(Path.t(), keyword, keyword) :: String.t() + @spec eval_file(Path.t(), keyword, [compile_opt]) :: String.t() def eval_file(filename, bindings \\ [], options \\ []) when is_list(bindings) and is_list(options) do filename = IO.chardata_to_string(filename) @@ -339,7 +348,7 @@ defmodule EEx do Note new tokens may be added in the future. """ @doc since: "1.14.0" - @spec tokenize([char()] | String.t(), opts :: keyword) :: + @spec tokenize([char()] | String.t(), [tokenize_opt]) :: {:ok, [token()]} | {:error, String.t(), metadata()} def tokenize(contents, opts \\ []) do EEx.Compiler.tokenize(contents, opts) diff --git a/lib/eex/lib/eex/compiler.ex b/lib/eex/lib/eex/compiler.ex index 44003e0970f..f401adb553d 100644 --- a/lib/eex/lib/eex/compiler.ex +++ b/lib/eex/lib/eex/compiler.ex @@ -10,6 +10,22 @@ defmodule EEx.Compiler do @h_spaces [?\s, ?\t] @all_spaces [?\s, ?\t, ?\n, ?\r] + @typedoc """ + Options for EEx compilation functions. + + These options control various aspects of EEx template compilation including + file information, parsing behavior, and the template engine to use. + """ + @type compile_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + trim: boolean(), + parser_options: Code.parser_opts(), + engine: module() + ] + @doc """ Tokenize EEx contents. """ @@ -290,7 +306,7 @@ defmodule EEx.Compiler do and the engine together by handling the tokens and invoking the engine every time a full expression or text is received. """ - @spec compile([EEx.token()], String.t(), keyword) :: Macro.t() + @spec compile([EEx.token()], String.t(), compile_opts) :: Macro.t() def compile(tokens, source, opts) do file = opts[:file] || "nofile" line = opts[:line] || 1 diff --git a/lib/eex/lib/eex/engine.ex b/lib/eex/lib/eex/engine.ex index 10b01b82b9f..8bb1858bcda 100644 --- a/lib/eex/lib/eex/engine.ex +++ b/lib/eex/lib/eex/engine.ex @@ -14,12 +14,29 @@ defmodule EEx.Engine do @type state :: term + @typedoc """ + Options passed to engine initialization. + + These are the same options passed to `EEx.Compiler.compile/3`, + allowing engines to access compilation settings and customize + their behavior accordingly. + """ + @type init_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + trim: boolean(), + parser_options: Code.parser_opts(), + engine: module() + ] + @doc """ Called at the beginning of every template. It must return the initial state. """ - @callback init(opts :: keyword) :: state + @callback init(opts :: init_opts) :: state @doc """ Called at the end of every template. diff --git a/lib/elixir/lib/calendar.ex b/lib/elixir/lib/calendar.ex index 48420bd5f08..89160389b8d 100644 --- a/lib/elixir/lib/calendar.ex +++ b/lib/elixir/lib/calendar.ex @@ -162,6 +162,22 @@ defmodule Calendar do """ @type time_zone_database :: module() + @typedoc """ + Options for formatting dates and times with `strftime/3`. + """ + @type strftime_opts :: [ + preferred_datetime: String.t(), + preferred_date: String.t(), + preferred_time: String.t(), + am_pm_names: (:am | :pm -> String.t()) | (:am | :pm, map() -> String.t()), + month_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + abbreviated_month_names: + (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + day_of_week_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()), + abbreviated_day_of_week_names: + (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()) + ] + @doc """ Returns how many days there are in the given month of the given year. """ @@ -617,7 +633,7 @@ defmodule Calendar do """ @doc since: "1.11.0" - @spec strftime(map(), String.t(), keyword()) :: String.t() + @spec strftime(map(), String.t(), strftime_opts()) :: String.t() def strftime(date_or_time_or_datetime, string_format, user_options \\ []) when is_map(date_or_time_or_datetime) and is_binary(string_format) do parse( diff --git a/lib/elixir/lib/calendar/duration.ex b/lib/elixir/lib/calendar/duration.ex index 520b39d59c4..b96e8b8f030 100644 --- a/lib/elixir/lib/calendar/duration.ex +++ b/lib/elixir/lib/calendar/duration.ex @@ -161,6 +161,22 @@ defmodule Duration do """ @type duration :: t | [unit_pair] + @typedoc """ + Options for `Duration.to_string/2`. + """ + @type to_string_opts :: [ + units: [ + year: String.t(), + month: String.t(), + week: String.t(), + day: String.t(), + hour: String.t(), + minute: String.t(), + second: String.t() + ], + separator: String.t() + ] + @microseconds_per_second 1_000_000 @doc """ @@ -436,6 +452,7 @@ defmodule Duration do """ @doc since: "1.18.0" + @spec to_string(t, to_string_opts) :: String.t() def to_string(%Duration{} = duration, opts \\ []) do units = Keyword.get(opts, :units, []) separator = Keyword.get(opts, :separator, " ") diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index 8e5c91f5da6..c098599f868 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -248,6 +248,49 @@ defmodule Code do """ @type position() :: line() | {line :: pos_integer(), column :: pos_integer()} + @typedoc """ + Options for code formatting functions. + """ + @type format_opts :: [ + file: binary(), + line: pos_integer(), + line_length: pos_integer(), + locals_without_parens: keyword(), + force_do_end_blocks: boolean(), + migrate: boolean(), + migrate_bitstring_modifiers: boolean(), + migrate_call_parens_on_pipe: boolean(), + migrate_charlists_as_sigils: boolean(), + migrate_unless: boolean() + ] + + @typedoc """ + Options for parsing functions that convert strings to quoted expressions. + """ + @type parser_opts :: [ + file: binary(), + line: pos_integer(), + column: pos_integer(), + indentation: non_neg_integer(), + columns: boolean(), + unescape: boolean(), + existing_atoms_only: boolean(), + token_metadata: boolean(), + literal_encoder: (term(), Macro.metadata() -> term()), + static_atoms_encoder: (atom() -> term()), + emit_warnings: boolean() + ] + + @typedoc """ + Options for environment evaluation functions like eval_string/3 and eval_quoted/3. + """ + @type env_eval_opts :: [ + file: binary(), + line: pos_integer(), + module: module(), + prune_binding: boolean() + ] + @boolean_compiler_options [ :docs, :debug_info, @@ -560,7 +603,7 @@ defmodule Code do [a: 1, b: 2] """ - @spec eval_string(List.Chars.t(), binding, Macro.Env.t() | keyword) :: {term, binding} + @spec eval_string(List.Chars.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding} def eval_string(string, binding \\ [], opts \\ []) def eval_string(string, binding, %Macro.Env{} = env) do @@ -615,7 +658,8 @@ defmodule Code do """ @doc since: "1.15.0" - @spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]} + @spec with_diagnostics([log: boolean()], (-> result)) :: + {result, [diagnostic(:warning | :error)]} when result: term() def with_diagnostics(opts \\ [], fun) do value = :erlang.get(:elixir_code_diagnostics) @@ -648,7 +692,7 @@ defmodule Code do Defaults to `true`. """ @doc since: "1.15.0" - @spec print_diagnostic(diagnostic(:warning | :error), keyword()) :: :ok + @spec print_diagnostic(diagnostic(:warning | :error), snippet: boolean()) :: :ok def print_diagnostic(diagnostic, opts \\ []) do read_snippet? = Keyword.get(opts, :snippet, true) :elixir_errors.print_diagnostic(diagnostic, read_snippet?) @@ -1035,7 +1079,7 @@ defmodule Code do address the deprecation warnings. """ @doc since: "1.6.0" - @spec format_string!(binary, keyword) :: iodata + @spec format_string!(binary, format_opts) :: iodata def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do line_length = Keyword.get(opts, :line_length, 98) @@ -1060,7 +1104,7 @@ defmodule Code do available options. """ @doc since: "1.6.0" - @spec format_file!(binary, keyword) :: iodata + @spec format_file!(binary, format_opts) :: iodata def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do string = File.read!(file) formatted = format_string!(string, [file: file, line: 1] ++ opts) @@ -1098,7 +1142,7 @@ defmodule Code do [a: 1, b: 2] """ - @spec eval_quoted(Macro.t(), binding, Macro.Env.t() | keyword) :: {term, binding} + @spec eval_quoted(Macro.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding} def eval_quoted(quoted, binding \\ [], env_or_opts \\ []) do {value, binding, _env} = eval_verify(:eval_quoted, [quoted, binding, env_for_eval(env_or_opts)]) @@ -1129,8 +1173,15 @@ defmodule Code do * `:line` - the line on which the script starts * `:module` - the module to run the environment on + + * `:prune_binding` - (since v1.14.2) prune binding to keep only + variables read or written by the evaluated code. Note that + variables used by modules are always pruned, even if later used + by the modules. You can submit to the `:on_module` tracer event + and access the variables used by the module from its environment. """ @doc since: "1.14.0" + @spec env_for_eval(Macro.Env.t() | env_eval_opts) :: Macro.Env.t() def env_for_eval(env_or_opts), do: :elixir.env_for_eval(env_or_opts) @doc """ @@ -1144,15 +1195,11 @@ defmodule Code do ## Options - * `:prune_binding` - (since v1.14.2) prune binding to keep only - variables read or written by the evaluated code. Note that - variables used by modules are always pruned, even if later used - by the modules. You can submit to the `:on_module` tracer event - and access the variables used by the module from its environment. + It accepts the same options as `env_for_eval/1`. """ @doc since: "1.14.0" - @spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), keyword) :: + @spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), env_eval_opts) :: {term, binding, Macro.Env.t()} def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ []) when is_list(binding) do @@ -1263,7 +1310,7 @@ defmodule Code do {:error, {[line: 1, column: 4], "syntax error before: ", "\"3\""}} """ - @spec string_to_quoted(List.Chars.t(), keyword) :: + @spec string_to_quoted(List.Chars.t(), parser_opts) :: {:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}} def string_to_quoted(string, opts \\ []) when is_list(opts) do file = Keyword.get(opts, :file, "nofile") @@ -1290,7 +1337,7 @@ defmodule Code do Check `string_to_quoted/2` for options information. """ - @spec string_to_quoted!(List.Chars.t(), keyword) :: Macro.t() + @spec string_to_quoted!(List.Chars.t(), parser_opts) :: Macro.t() def string_to_quoted!(string, opts \\ []) when is_list(opts) do file = Keyword.get(opts, :file, "nofile") line = Keyword.get(opts, :line, 1) @@ -1341,7 +1388,7 @@ defmodule Code do """ @doc since: "1.13.0" - @spec string_to_quoted_with_comments(List.Chars.t(), keyword) :: + @spec string_to_quoted_with_comments(List.Chars.t(), parser_opts) :: {:ok, Macro.t(), list(map())} | {:error, {location :: keyword, term, term}} def string_to_quoted_with_comments(string, opts \\ []) when is_list(opts) do charlist = to_charlist(string) @@ -1371,7 +1418,7 @@ defmodule Code do Check `string_to_quoted/2` for options information. """ @doc since: "1.13.0" - @spec string_to_quoted_with_comments!(List.Chars.t(), keyword) :: {Macro.t(), list(map())} + @spec string_to_quoted_with_comments!(List.Chars.t(), parser_opts) :: {Macro.t(), list(map())} def string_to_quoted_with_comments!(string, opts \\ []) do charlist = to_charlist(string) @@ -1456,6 +1503,9 @@ defmodule Code do ## Options + This function accepts all options supported by `format_string!/2` for controlling + code formatting, plus these additional options: + * `:comments` - the list of comments associated with the quoted expression. Defaults to `[]`. It is recommended that both `:token_metadata` and `:literal_encoder` options are given to `string_to_quoted_with_comments/2` @@ -1466,17 +1516,14 @@ defmodule Code do `string_to_quoted/2`, setting this option to `false` will prevent it from escaping the sequences twice. Defaults to `true`. - * `:locals_without_parens` - a keyword list of name and arity - pairs that should be kept without parens whenever possible. - The arity may be the atom `:*`, which implies all arities of - that name. The formatter already includes a list of functions - and this option augments this list. - - * `:syntax_colors` - a keyword list of colors the output is colorized. - See `Inspect.Opts` for more information. + See `format_string!/2` for the full list of formatting options including + `:file`, `:line`, `:line_length`, `:locals_without_parens`, `:force_do_end_blocks`, + `:syntax_colors`, and all migration options like `:migrate_charlists_as_sigils`. """ @doc since: "1.13.0" - @spec quoted_to_algebra(Macro.t(), keyword) :: Inspect.Algebra.t() + @spec quoted_to_algebra(Macro.t(), [ + Code.Formatter.to_algebra_opt() | Code.Normalizer.normalize_opt() + ]) :: Inspect.Algebra.t() def quoted_to_algebra(quoted, opts \\ []) do quoted |> Code.Normalizer.normalize(opts) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 170edf71299..a4417282d04 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -155,9 +155,32 @@ defmodule Code.Formatter do @do_end_keywords [:rescue, :catch, :else, :after] + @typedoc """ + Options for `to_algebra/2`. + + These options include all the standard code formatting options plus + additional context like comments and syntax highlighting colors. + """ + @type to_algebra_opt :: + {:comments, [term()]} + | {:syntax_colors, keyword()} + | {:sigils, keyword()} + | {:file, binary()} + | {:line, pos_integer()} + | {:line_length, pos_integer()} + | {:locals_without_parens, keyword()} + | {:force_do_end_blocks, boolean()} + | {:migrate, boolean()} + | {:migrate_bitstring_modifiers, boolean()} + | {:migrate_call_parens_on_pipe, boolean()} + | {:migrate_charlists_as_sigils, boolean()} + | {:migrate_unless, boolean()} + | {atom(), term()} + @doc """ Converts the quoted expression into an algebra document. """ + @spec to_algebra(Macro.t(), [to_algebra_opt]) :: Inspect.Algebra.t() def to_algebra(quoted, opts \\ []) do comments = Keyword.get(opts, :comments, []) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index ca71fe21ff2..fff274a34ec 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -11,6 +11,26 @@ defmodule Code.Fragment do @type position :: {line :: pos_integer(), column :: pos_integer()} + @typedoc """ + Options for cursor context functions. + + Currently, these options are not used but reserved for future extensibility. + """ + @type cursor_opts :: [] + + @typedoc """ + Options for converting code fragments to quoted expressions. + """ + @type container_cursor_to_quoted_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + columns: boolean(), + token_metadata: boolean(), + literal_encoder: (term(), Macro.metadata() -> term()), + trailing_fragment: String.t() + ] + @doc ~S""" Returns the list of lines in the given string, preserving their line endings. @@ -172,7 +192,7 @@ defmodule Code.Fragment do references, and more. """ @doc since: "1.13.0" - @spec cursor_context(List.Chars.t(), keyword()) :: + @spec cursor_context(List.Chars.t(), cursor_opts()) :: {:alias, charlist} | {:alias, inside_alias, charlist} | {:block_keyword_or_binary_operator, charlist} @@ -662,7 +682,7 @@ defmodule Code.Fragment do of examples and their return values. """ @doc since: "1.13.0" - @spec surround_context(List.Chars.t(), position(), keyword()) :: + @spec surround_context(List.Chars.t(), position(), cursor_opts()) :: %{begin: position, end: position, context: context} | :none when context: {:alias, charlist} @@ -1215,7 +1235,7 @@ defmodule Code.Fragment do """ @doc since: "1.13.0" - @spec container_cursor_to_quoted(List.Chars.t(), keyword()) :: + @spec container_cursor_to_quoted(List.Chars.t(), container_cursor_to_quoted_opts()) :: {:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}} def container_cursor_to_quoted(fragment, opts \\ []) do {trailing_fragment, opts} = Keyword.pop(opts, :trailing_fragment) diff --git a/lib/elixir/lib/code/normalizer.ex b/lib/elixir/lib/code/normalizer.ex index c3dd67a3af9..e45a5aee6a8 100644 --- a/lib/elixir/lib/code/normalizer.ex +++ b/lib/elixir/lib/code/normalizer.ex @@ -10,10 +10,20 @@ defmodule Code.Normalizer do is_binary(x) or is_atom(x) + @typedoc """ + Options for `normalize/2`. + """ + @type normalize_opt :: + {:line, pos_integer() | nil} + | {:escape, boolean()} + | {:locals_without_parens, keyword()} + | {atom(), term()} + @doc """ Wraps literals in the quoted expression to conform to the AST format expected by the formatter. """ + @spec normalize(Macro.t(), [normalize_opt]) :: Macro.t() def normalize(quoted, opts \\ []) do line = Keyword.get(opts, :line, nil) escape = Keyword.get(opts, :escape, true) diff --git a/lib/elixir/lib/config.ex b/lib/elixir/lib/config.ex index b18fbd271c3..409512b84f2 100644 --- a/lib/elixir/lib/config.ex +++ b/lib/elixir/lib/config.ex @@ -98,6 +98,12 @@ defmodule Config do (assembled with `mix release`). """ + @type config_opts :: [ + imports: [Path.t()] | :disabled, + env: atom(), + target: atom() + ] + @opts_key {__MODULE__, :opts} @config_key {__MODULE__, :config} @imports_key {__MODULE__, :imports} @@ -306,7 +312,7 @@ defmodule Config do end @doc false - @spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled} + @spec __eval__!(Path.t(), binary(), config_opts) :: {keyword, [Path.t()] | :disabled} def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do env = Keyword.get(opts, :env) target = Keyword.get(opts, :target) diff --git a/lib/elixir/lib/config/provider.ex b/lib/elixir/lib/config/provider.ex index 756d583e074..b039a2233c7 100644 --- a/lib/elixir/lib/config/provider.ex +++ b/lib/elixir/lib/config/provider.ex @@ -111,6 +111,16 @@ defmodule Config.Provider do """ @type config_path :: {:system, binary(), binary()} | binary() + @typedoc """ + Options for `init/3`. + """ + @type init_opts :: [ + extra_config: config(), + prune_runtime_sys_config_after_boot: boolean(), + reboot_system_after_config: boolean(), + validate_compile_env: [{atom(), [atom()], term()}] + ] + @doc """ Invoked when initializing a config provider. @@ -196,6 +206,7 @@ defmodule Config.Provider do @reboot_mode_key :config_provider_reboot_mode @doc false + @spec init([{module(), term()}], config_path(), init_opts()) :: config() def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do validate_config_path!(config_path) providers = for {provider, init} <- providers, do: {provider, provider.init(init)} diff --git a/lib/elixir/lib/config/reader.ex b/lib/elixir/lib/config/reader.ex index 1997bb1cb0e..3a259d3602e 100644 --- a/lib/elixir/lib/config/reader.ex +++ b/lib/elixir/lib/config/reader.ex @@ -46,6 +46,12 @@ defmodule Config.Reader do @behaviour Config.Provider + @type config_opts :: [ + imports: [Path.t()] | :disabled, + env: atom(), + target: atom() + ] + @impl true def init(opts) when is_list(opts) do {path, opts} = Keyword.pop!(opts, :path) @@ -68,7 +74,7 @@ defmodule Config.Reader do Accepts the same options as `read!/2`. """ @doc since: "1.11.0" - @spec eval!(Path.t(), binary, keyword) :: keyword + @spec eval!(Path.t(), binary, config_opts) :: keyword def eval!(file, contents, opts \\ []) when is_binary(file) and is_binary(contents) and is_list(opts) do Config.__eval__!(Path.expand(file), contents, opts) |> elem(0) @@ -90,7 +96,7 @@ defmodule Config.Reader do """ @doc since: "1.9.0" - @spec read!(Path.t(), keyword) :: keyword + @spec read!(Path.t(), config_opts) :: keyword def read!(file, opts \\ []) when is_binary(file) and is_list(opts) do file = Path.expand(file) Config.__eval__!(file, File.read!(file), opts) |> elem(0) @@ -104,7 +110,7 @@ defmodule Config.Reader do option cannot be disabled in `read_imports!/2`. """ @doc since: "1.9.0" - @spec read_imports!(Path.t(), keyword) :: {keyword, [Path.t()]} + @spec read_imports!(Path.t(), config_opts) :: {keyword, [Path.t()]} def read_imports!(file, opts \\ []) when is_binary(file) and is_list(opts) do if opts[:imports] == :disabled do raise ArgumentError, ":imports must be a list of paths" diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 9c0175865dd..776dfbcd4bc 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -115,11 +115,28 @@ defmodule Inspect.Opts do width: non_neg_integer | :infinity } + @typedoc """ + Options for building an `Inspect.Opts` struct with `new/1`. + """ + @type new_opt :: + {:base, :decimal | :binary | :hex | :octal} + | {:binaries, :infer | :as_binaries | :as_strings} + | {:charlists, :infer | :as_lists | :as_charlists} + | {:custom_options, keyword} + | {:inspect_fun, (any, t -> Inspect.Algebra.t())} + | {:limit, non_neg_integer | :infinity} + | {:pretty, boolean} + | {:printable_limit, non_neg_integer | :infinity} + | {:safe, boolean} + | {:structs, boolean} + | {:syntax_colors, [{color_key, IO.ANSI.ansidata()}]} + | {:width, non_neg_integer | :infinity} + @doc """ Builds an `Inspect.Opts` struct. """ @doc since: "1.13.0" - @spec new(keyword()) :: t + @spec new([new_opt()]) :: t def new(opts) do struct(%Inspect.Opts{inspect_fun: default_inspect_fun()}, opts) end @@ -324,6 +341,14 @@ defmodule Inspect.Algebra do quote do: {:doc_color, unquote(doc), unquote(color)} end + @typedoc """ + Options for container documents. + """ + @type container_opts :: [ + separator: String.t(), + break: :strict | :flex | :maybe + ] + @docs [ :doc_break, :doc_collapse, @@ -440,7 +465,14 @@ defmodule Inspect.Algebra do updated options from inspection. """ @doc since: "1.6.0" - @spec container_doc(t, [term], t, Inspect.Opts.t(), (term, Inspect.Opts.t() -> t), keyword()) :: + @spec container_doc( + t, + [term], + t, + Inspect.Opts.t(), + (term, Inspect.Opts.t() -> t), + container_opts() + ) :: t def container_doc(left, collection, right, inspect_opts, fun, opts \\ []) do container_doc_with_opts(left, collection, right, inspect_opts, fun, opts) |> elem(0) @@ -496,7 +528,7 @@ defmodule Inspect.Algebra do t, Inspect.Opts.t(), (term, Inspect.Opts.t() -> t), - keyword() + container_opts() ) :: {t, Inspect.Opts.t()} def container_doc_with_opts(left, collection, right, inspect_opts, fun, opts \\ []) diff --git a/lib/elixir/lib/io.ex b/lib/elixir/lib/io.ex index e5c012ffd35..123441bd279 100644 --- a/lib/elixir/lib/io.ex +++ b/lib/elixir/lib/io.ex @@ -128,6 +128,22 @@ defmodule IO do @type nodata :: {:error, term} | :eof @type chardata :: String.t() | maybe_improper_list(char | chardata, String.t() | []) + @type inspect_opts :: [Inspect.Opts.new_opt() | {:label, term}] + + @typedoc """ + Stacktrace information as keyword options for `warn/2`. + + At least `:file` is required. Other options are optional and used + to provide more precise location information. + """ + @type warn_stacktrace_opts :: [ + file: String.t(), + line: pos_integer(), + column: pos_integer(), + module: module(), + function: {atom(), arity()} + ] + defguardp is_device(term) when is_atom(term) or is_pid(term) defguardp is_iodata(data) when is_list(data) or is_binary(data) @@ -346,7 +362,10 @@ defmodule IO do #=> my_app.ex:4: MyApp.main/1 """ - @spec warn(chardata | String.Chars.t(), Exception.stacktrace() | keyword() | Macro.Env.t()) :: + @spec warn( + chardata | String.Chars.t(), + Exception.stacktrace() | warn_stacktrace_opts() | Macro.Env.t() + ) :: :ok def warn(message, stacktrace_info) @@ -476,7 +495,7 @@ defmodule IO do after: [2, 4, 6] """ - @spec inspect(item, keyword) :: item when item: var + @spec inspect(item, inspect_opts) :: item when item: var def inspect(item, opts \\ []) do inspect(:stdio, item, opts) end @@ -486,7 +505,7 @@ defmodule IO do See `inspect/2` for a full list of options. """ - @spec inspect(device, item, keyword) :: item when item: var + @spec inspect(device, item, inspect_opts) :: item when item: var def inspect(device, item, opts) when is_device(device) and is_list(opts) do label = if label = opts[:label], do: [to_chardata(label), ": "], else: [] opts = Inspect.Opts.new(opts) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index f47d5365415..0291c62dfda 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -5,6 +5,20 @@ defmodule IO.ANSI.Docs do @moduledoc false + @type print_opts :: [ + enabled: boolean(), + doc_bold: [IO.ANSI.ansicode()], + doc_code: [IO.ANSI.ansicode()], + doc_headings: [IO.ANSI.ansicode()], + doc_metadata: [IO.ANSI.ansicode()], + doc_quote: [IO.ANSI.ansicode()], + doc_inline_code: [IO.ANSI.ansicode()], + doc_table_heading: [IO.ANSI.ansicode()], + doc_title: [IO.ANSI.ansicode()], + doc_underline: [IO.ANSI.ansicode()], + width: pos_integer() + ] + @bullet_text_unicode "• " @bullet_text_ascii "* " @bullets [?*, ?-, ?+] @@ -52,7 +66,7 @@ defmodule IO.ANSI.Docs do See `default_options/0` for docs on the supported options. """ - @spec print_headings([String.t()], keyword) :: :ok + @spec print_headings([String.t()], print_opts) :: :ok def print_headings(headings, options \\ []) do # It's possible for some of the headings to contain newline characters (`\n`), so in order to prevent it from # breaking the output from `print_headings/2`, as `print_headings/2` tries to pad the whole heading, we first split @@ -77,7 +91,7 @@ defmodule IO.ANSI.Docs do See `default_options/0` for docs on the supported options. """ - @spec print_metadata(map, keyword) :: :ok + @spec print_metadata(map, print_opts) :: :ok def print_metadata(metadata, options \\ []) when is_map(metadata) do options = Keyword.merge(default_options(), options) print_each_metadata(metadata, options) && IO.write("\n") @@ -115,7 +129,7 @@ defmodule IO.ANSI.Docs do It takes a set of `options` defined in `default_options/0`. """ - @spec print(term(), String.t(), keyword) :: :ok + @spec print(term(), String.t(), print_opts) :: :ok def print(doc, format, options \\ []) def print(doc, "text/markdown", options) when is_binary(doc) and is_list(options) do diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex index 8b143965f9e..6f431d616a9 100644 --- a/lib/elixir/lib/json.ex +++ b/lib/elixir/lib/json.ex @@ -328,6 +328,22 @@ defmodule JSON do | {:invalid_byte, non_neg_integer(), byte()} | {:unexpected_sequence, non_neg_integer(), binary()} + @typedoc """ + Decoders for customizing JSON decoding behavior. + """ + @type decoders :: [ + array_start: (term() -> term()), + array_push: (term(), term() -> term()), + array_finish: (term(), term() -> {term(), term()}), + object_start: (term() -> term()), + object_push: (term(), term(), term() -> term()), + object_finish: (term(), term() -> {term(), term()}), + float: (String.t() -> term()), + integer: (String.t() -> term()), + string: (String.t() -> term()), + null: term() + ] + @doc ~S""" Decodes the given JSON. @@ -381,7 +397,7 @@ defmodule JSON do For streaming decoding, see Erlang's [`:json`](`:json`) module. """ - @spec decode(binary(), term(), keyword()) :: + @spec decode(binary(), term(), decoders()) :: {term(), term(), binary()} | {:error, decode_error_reason()} def decode(binary, acc, decoders) when is_binary(binary) and is_list(decoders) do decoders = Keyword.put_new(decoders, :null, nil) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index d815ce47550..642bb837494 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -2456,7 +2456,7 @@ defmodule Kernel do See the "Deriving" section of the documentation of the `Inspect` protocol for more information. """ - @spec inspect(Inspect.t(), keyword) :: String.t() + @spec inspect(Inspect.t(), [Inspect.Opts.new_opt()]) :: String.t() def inspect(term, opts \\ []) when is_list(opts) do opts = Inspect.Opts.new(opts) diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex index 0621817f3f4..cf483bc08a4 100644 --- a/lib/elixir/lib/kernel/parallel_compiler.ex +++ b/lib/elixir/lib/kernel/parallel_compiler.ex @@ -16,6 +16,38 @@ defmodule Kernel.ParallelCompiler do @type warning() :: {file :: Path.t(), Code.position(), message :: String.t()} @type error() :: {file :: Path.t(), Code.position(), message :: String.t()} + @typedoc """ + Options for parallel compilation functions. + """ + @type compile_opts :: [ + after_compile: (-> term()), + each_file: (Path.t() -> term()), + each_long_compilation: (Path.t() -> term()) | (Path.t(), pid() -> term()), + each_long_verification: (module() -> term()) | (module(), pid() -> term()), + each_module: (Path.t(), module(), binary() -> term()), + each_cycle: ([module()], [Code.diagnostic(:warning)] -> + {:compile, [module()], [Code.diagnostic(:warning)]} + | {:runtime, [module()], [Code.diagnostic(:warning)]}), + long_compilation_threshold: pos_integer(), + long_verification_threshold: pos_integer(), + verification: boolean(), + profile: :time, + dest: Path.t(), + beam_timestamp: term(), + return_diagnostics: boolean(), + max_concurrency: pos_integer() + ] + + @typedoc """ + Options for requiring files in parallel. + """ + @type require_opts :: [ + each_file: (Path.t() -> term()), + each_module: (Path.t(), module(), binary() -> term()), + max_concurrency: pos_integer(), + return_diagnostics: boolean() + ] + @doc """ Starts a task for parallel compilation. """ @@ -185,7 +217,7 @@ defmodule Kernel.ParallelCompiler do """ @doc since: "1.6.0" - @spec compile([Path.t()], keyword()) :: + @spec compile([Path.t()], compile_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def compile(files, options \\ []) when is_list(options) do @@ -198,7 +230,7 @@ defmodule Kernel.ParallelCompiler do See `compile/2` for more information. """ @doc since: "1.6.0" - @spec compile_to_path([Path.t()], Path.t(), keyword()) :: + @spec compile_to_path([Path.t()], Path.t(), compile_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def compile_to_path(files, path, options \\ []) when is_binary(path) and is_list(options) do @@ -233,9 +265,12 @@ defmodule Kernel.ParallelCompiler do Setting this option to 1 will compile files sequentially. Defaults to the number of schedulers online, or at least 2. + * `:return_diagnostics` - when `true`, returns structured diagnostics + as maps instead of the legacy format. Defaults to `false`. + """ @doc since: "1.6.0" - @spec require([Path.t()], keyword()) :: + @spec require([Path.t()], require_opts()) :: {:ok, [atom], [warning] | info()} | {:error, [error] | [Code.diagnostic(:error)], [warning] | info()} def require(files, options \\ []) when is_list(options) do diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 30cce303e71..5b9ce6e4f3b 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -197,6 +197,15 @@ defmodule Macro do @typedoc "A captured remote function in the format of &Mod.fun/arity" @type captured_remote_function :: fun + @type escape_opts :: [ + unquote: boolean(), + prune_metadata: boolean() + ] + + @type inspect_atom_opts :: [ + escape: (binary(), char() -> binary()) + ] + @doc """ Breaks a pipeline expression into a list. @@ -835,7 +844,7 @@ defmodule Macro do bound), while `quote/2` produces syntax trees for expressions. """ - @spec escape(term, keyword) :: t() + @spec escape(term, escape_opts) :: t() def escape(expr, opts \\ []) do unquote = Keyword.get(opts, :unquote, false) kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :none @@ -2399,7 +2408,7 @@ defmodule Macro do """ @doc since: "1.14.0" - @spec inspect_atom(:literal | :key | :remote_call, atom, keyword) :: binary + @spec inspect_atom(:literal | :key | :remote_call, atom, inspect_atom_opts) :: binary def inspect_atom(source_format, atom, opts \\ []) def inspect_atom(:literal, atom, _opts) when is_nil(atom) or is_boolean(atom) do diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index f48ca1a31bd..8e0016431bb 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -70,6 +70,42 @@ defmodule Macro.Env do @typep tracers :: [module] @typep versioned_vars :: %{optional(variable) => var_version :: non_neg_integer} + @type define_import_opts :: [ + trace: boolean(), + emit_warnings: boolean(), + info_callback: (atom() -> [{atom(), arity()}]), + only: :functions | :macros | [{atom(), arity()}], + except: [{atom(), arity()}], + warn: boolean() + ] + + @type define_alias_opts :: [ + trace: boolean(), + as: atom(), + warn: boolean() + ] + + @type define_require_opts :: [ + trace: boolean(), + as: atom(), + warn: boolean() + ] + + @type expand_alias_opts :: [ + trace: boolean() + ] + + @type expand_import_opts :: [ + allow_locals: boolean(), + check_deprecations: boolean(), + trace: boolean() + ] + + @type expand_require_opts :: [ + check_deprecations: boolean(), + trace: boolean() + ] + @type t :: %{ __struct__: __MODULE__, aliases: aliases, @@ -331,7 +367,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_require(t, Macro.metadata(), module) :: {:ok, t} + @spec define_require(t, Macro.metadata(), module, define_require_opts) :: {:ok, t} def define_require(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -391,7 +427,8 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_import(t, Macro.metadata(), module, keyword) :: {:ok, t} | {:error, String.t()} + @spec define_import(t, Macro.metadata(), module, define_import_opts) :: + {:ok, t} | {:error, String.t()} def define_import(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -441,7 +478,8 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec define_alias(t, Macro.metadata(), module, keyword) :: {:ok, t} | {:error, String.t()} + @spec define_alias(t, Macro.metadata(), module, define_alias_opts) :: + {:ok, t} | {:error, String.t()} def define_alias(env, meta, module, opts \\ []) when is_list(meta) and is_atom(module) and is_list(opts) do {trace, opts} = Keyword.pop(opts, :trace, true) @@ -487,7 +525,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_alias(t, keyword, [atom()], keyword) :: + @spec expand_alias(t, keyword, [atom()], expand_alias_opts) :: {:alias, atom()} | :error def expand_alias(env, meta, list, opts \\ []) when is_list(meta) and is_list(list) and is_list(opts) do @@ -527,7 +565,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_import(t, keyword, atom(), arity(), keyword) :: + @spec expand_import(t, keyword, atom(), arity(), expand_import_opts) :: {:macro, module(), (Macro.metadata(), args :: [Macro.t()] -> Macro.t())} | {:function, module(), atom()} | {:error, :not_found | {:conflict, module()} | {:ambiguous, [module()]}} @@ -583,7 +621,7 @@ defmodule Macro.Env do """ @doc since: "1.17.0" - @spec expand_require(t, keyword, module(), atom(), arity(), keyword) :: + @spec expand_require(t, keyword, module(), atom(), arity(), expand_require_opts) :: {:macro, module(), (Macro.metadata(), args :: [Macro.t()] -> Macro.t())} | :error def expand_require(env, meta, module, name, arity, opts \\ []) diff --git a/lib/elixir/lib/module.ex b/lib/elixir/lib/module.ex index 067668ddc70..8f8549b8649 100644 --- a/lib/elixir/lib/module.ex +++ b/lib/elixir/lib/module.ex @@ -687,6 +687,16 @@ defmodule Module do @type definition :: {atom, arity} @type def_kind :: :def | :defp | :defmacro | :defmacrop + @type create_opts :: [ + file: binary(), + line: pos_integer(), + generated: boolean() + ] + + @type get_definition_opts :: [ + skip_clauses: boolean() + ] + @extra_error_msg_defines? "Use Kernel.function_exported?/3 and Kernel.macro_exported?/3 " <> "to check for public functions and macros instead" @@ -918,7 +928,7 @@ defmodule Module do when defining the module, while `Kernel.defmodule/2` automatically uses the environment it is invoked at. """ - @spec create(module, Macro.t(), Macro.Env.t() | keyword) :: {:module, module, binary, term} + @spec create(module, Macro.t(), Macro.Env.t() | create_opts) :: {:module, module, binary, term} def create(module, quoted, opts) def create(module, quoted, %Macro.Env{} = env) when is_atom(module) do @@ -1423,7 +1433,7 @@ defmodule Module do only an interest in fetching the kind and the metadata """ - @spec get_definition(module, definition, keyword) :: + @spec get_definition(module, definition, get_definition_opts) :: {:v1, def_kind, meta :: keyword, [{meta :: keyword, arguments :: [Macro.t()], guards :: [Macro.t()], Macro.t()}]} | nil diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 5841a8b6718..2ffdbf69490 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -11,9 +11,20 @@ defmodule Module.ParallelChecker do @type warning() :: term() @type mode() :: :erlang | :elixir | :protocol + @typedoc """ + Options for `start_link/1`. + """ + @type start_link_opts :: [ + {:max_concurrency, pos_integer()} + | {:long_verification_threshold, pos_integer()} + | {:each_long_verification, (module() -> term()) | (module(), pid() -> term())} + | {atom(), term()} + ] + @doc """ Initializes the parallel checker process. """ + @spec start_link(start_link_opts()) :: {:ok, cache()} def start_link(opts \\ []) do :proc_lib.start_link(__MODULE__, :init, [opts]) end diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index ee6b7dc8b00..26ee9fe6164 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -51,6 +51,13 @@ defmodule Module.Types.Descr do # Type definitions + @typedoc """ + Options for `to_quoted/2`. + """ + @type to_quoted_opts :: [ + collapse_structs: boolean() + ] + defguard is_descr(descr) when is_map(descr) or descr == :term defp descr_key?(:term, _key), do: true @@ -552,6 +559,7 @@ defmodule Module.Types.Descr do * `:collapse_structs` - do not show struct fields that match their default type """ + @spec to_quoted(term(), to_quoted_opts) :: Macro.t() def to_quoted(descr, opts \\ []) do if term_type?(descr) do {:term, [], []} @@ -620,7 +628,13 @@ defmodule Module.Types.Descr do @doc """ Converts a descr to its quoted string representation. + + ## Options + + * `:collapse_structs` - do not show struct fields that match + their default type """ + @spec to_quoted(term(), to_quoted_opts) :: String.t() def to_quoted_string(descr, opts \\ []) do descr |> to_quoted(opts) diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex index e652575a13b..8e879e409ab 100644 --- a/lib/elixir/lib/module/types/helpers.ex +++ b/lib/elixir/lib/module/types/helpers.ex @@ -6,6 +6,13 @@ defmodule Module.Types.Helpers do # AST and enumeration helpers. @moduledoc false + @typedoc """ + Options for `expr_to_string/2`. + """ + @type expr_to_string_opts :: [ + collapse_structs: boolean() + ] + ## AST helpers @doc """ @@ -272,6 +279,7 @@ defmodule Module.Types.Helpers do We also undo some macro expressions done by the Kernel module. """ + @spec expr_to_string(Macro.t(), expr_to_string_opts) :: String.t() def expr_to_string(expr, opts \\ []) do string = prewalk_expr_to_string(expr, opts) diff --git a/lib/elixir/lib/path.ex b/lib/elixir/lib/path.ex index cf7b3ae75ab..46461f116c2 100644 --- a/lib/elixir/lib/path.ex +++ b/lib/elixir/lib/path.ex @@ -22,6 +22,8 @@ defmodule Path do """ @type t :: IO.chardata() + @type relative_to_opts :: [force: boolean()] + @doc """ Converts the given path to an absolute one. @@ -401,7 +403,7 @@ defmodule Path do Path.relative_to("../foo", "/usr/local") #=> "../foo" """ - @spec relative_to(t, t, keyword) :: binary + @spec relative_to(t, t, relative_to_opts) :: binary def relative_to(path, cwd, opts \\ []) when is_list(opts) do os_type = major_os_type() split_path = split(path) @@ -479,7 +481,7 @@ defmodule Path do Check `relative_to/3` for the supported options. """ - @spec relative_to_cwd(t, keyword) :: binary + @spec relative_to_cwd(t, relative_to_opts) :: binary def relative_to_cwd(path, opts \\ []) when is_list(opts) do case :file.get_cwd() do {:ok, base} -> relative_to(path, IO.chardata_to_string(base), opts) @@ -801,7 +803,7 @@ defmodule Path do Path.wildcard("projects/*/ebin/**/*.{beam,app}") """ - @spec wildcard(t, keyword) :: [binary] + @spec wildcard(t, match_dot: boolean()) :: [binary] def wildcard(glob, opts \\ []) when is_list(opts) do mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard diff --git a/lib/elixir/lib/record.ex b/lib/elixir/lib/record.ex index 2b1ce2f3559..15d378e27b2 100644 --- a/lib/elixir/lib/record.ex +++ b/lib/elixir/lib/record.ex @@ -45,6 +45,13 @@ defmodule Record do a module by calling `Code.fetch_docs/1`. """ + @type extract_opts :: [ + from: binary(), + from_lib: binary(), + includes: [binary()], + macros: keyword() + ] + @doc """ Extracts record information from an Erlang file. @@ -102,7 +109,7 @@ defmodule Record do ] """ - @spec extract(name :: atom, keyword) :: keyword + @spec extract(name :: atom, extract_opts) :: keyword def extract(name, opts) when is_atom(name) and is_list(opts) do Record.Extractor.extract(name, opts) end @@ -119,7 +126,7 @@ defmodule Record do Accepts the same options as listed for `Record.extract/2`. """ - @spec extract_all(keyword) :: [{name :: atom, keyword}] + @spec extract_all(extract_opts) :: [{name :: atom, keyword}] def extract_all(opts) when is_list(opts) do Record.Extractor.extract_all(opts) end diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 2327f43ed99..a94f32057e6 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -164,6 +164,11 @@ defmodule Regex do @type t :: %__MODULE__{re_pattern: term, source: binary, opts: [term]} + @type named_captures_opts :: [ + return: :binary | :index, + offset: non_neg_integer() + ] + defmodule CompileError do @moduledoc """ An exception raised when a regular expression could not be compiled. @@ -321,7 +326,7 @@ defmodule Regex do ["d", ""] """ - @spec run(t, binary, [term]) :: nil | [binary] | [{integer, integer}] + @spec run(t, binary, capture_opts) :: nil | [binary] | [{integer, integer}] def run(regex, string, options \\ []) def run(%Regex{} = regex, string, options) when is_binary(string) do @@ -343,6 +348,8 @@ defmodule Regex do * `:return` - when set to `:index`, returns byte index and match length. Defaults to `:binary`. + * `:offset` - (since v1.12.0) specifies the starting offset to match in the given string. + Defaults to zero. ## Examples @@ -363,7 +370,7 @@ defmodule Regex do You can then use `binary_part/3` to fetch the relevant part from the given string. """ - @spec named_captures(t, String.t(), keyword) :: map | nil + @spec named_captures(t, String.t(), named_captures_opts) :: map | nil def named_captures(regex, string, options \\ []) when is_binary(string) do names = names(regex) options = Keyword.put(options, :capture, names) @@ -545,7 +552,7 @@ defmodule Regex do [["cd"], ["ce"]] """ - @spec scan(t(), String.t(), [term()]) :: [[String.t()]] | [[{integer(), integer()}]] + @spec scan(t(), String.t(), capture_opts) :: [[String.t()]] | [[{integer(), integer()}]] def scan(regex, string, options \\ []) def scan(%Regex{} = regex, string, options) when is_binary(string) do @@ -573,6 +580,25 @@ defmodule Regex do end end + @typedoc """ + Options for regex functions that capture matches. + """ + @type capture_opts :: [ + return: :binary | :index, + capture: :all | :first | :all_but_first | :none | :all_names | [binary() | atom()], + offset: non_neg_integer() + ] + + @typedoc """ + Options for `split/3`. + """ + @type split_opts :: [ + parts: pos_integer() | :infinity, + trim: boolean(), + on: :first | :all | :all_but_first | :none | :all_names | [atom() | integer()], + include_captures: boolean() + ] + @doc """ Splits the given target based on the given pattern and in the given number of parts. @@ -626,7 +652,7 @@ defmodule Regex do ["a", "b", "c"] """ - @spec split(t, String.t(), [term]) :: [String.t()] + @spec split(t, String.t(), split_opts) :: [String.t()] def split(regex, string, options \\ []) def split(%Regex{}, "", opts) do diff --git a/lib/elixir/lib/registry.ex b/lib/elixir/lib/registry.ex index 17370964701..d35f82bc12a 100644 --- a/lib/elixir/lib/registry.ex +++ b/lib/elixir/lib/registry.ex @@ -242,6 +242,11 @@ defmodule Registry do {:register, registry, key, registry_partition :: pid, value} | {:unregister, registry, key, registry_partition :: pid} + @typedoc """ + Options used for `dispatch/4`. + """ + @type dispatch_opts :: [parallel: boolean()] + ## Via callbacks @doc false @@ -483,9 +488,15 @@ defmodule Registry do See the module documentation for examples of using the `dispatch/3` function for building custom dispatching or a pubsub system. + + ## Options + + * `:parallel` - if `true`, the dispatching is done in parallel + across all partitions. Defaults to `false`. + """ @doc since: "1.4.0" - @spec dispatch(registry, key, dispatcher, keyword) :: :ok + @spec dispatch(registry, key, dispatcher, dispatch_opts) :: :ok when dispatcher: (entries :: [{pid, value}] -> term) | {module(), atom(), [term()]} def dispatch(registry, key, mfa_or_fun, opts \\ []) when is_atom(registry) and is_function(mfa_or_fun, 1) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 626e7d259b3..73d59f31901 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -298,6 +298,15 @@ defmodule String do | [nonempty_binary] | (compiled_search_pattern :: :binary.cp()) + @type split_opts :: [ + parts: pos_integer() | :infinity, + trim: boolean() + ] + + @type splitter_opts :: [trim: boolean()] + + @type replace_opts :: [global: boolean()] + @conditional_mappings [:greek, :turkic] @doc """ @@ -502,7 +511,8 @@ defmodule String do ["a", "b", " c "] """ - @spec split(t, pattern | Regex.t(), keyword) :: [t] + @spec split(t, pattern, split_opts()) :: [t] + @spec split(t, Regex.t(), Regex.split_opts()) :: [t] def split(string, pattern, options \\ []) def split(string, %Regex{} = pattern, options) when is_binary(string) and is_list(options) do @@ -607,7 +617,7 @@ defmodule String do ["1", "2", "3", "4"] """ - @spec splitter(t, pattern, keyword) :: Enumerable.t() + @spec splitter(t, pattern, splitter_opts) :: Enumerable.t() def splitter(string, pattern, options \\ []) def splitter(string, "", options) when is_binary(string) and is_list(options) do @@ -1616,7 +1626,7 @@ defmodule String do "é" """ - @spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), keyword) :: t + @spec replace(t, pattern | Regex.t(), t | (t -> t | iodata), replace_opts) :: t def replace(subject, pattern, replacement, options \\ []) when is_binary(subject) and (is_binary(replacement) or is_function(replacement, 1)) and diff --git a/lib/elixir/lib/string_io.ex b/lib/elixir/lib/string_io.ex index 550633a7af8..d52ff1c238b 100644 --- a/lib/elixir/lib/string_io.ex +++ b/lib/elixir/lib/string_io.ex @@ -17,6 +17,11 @@ defmodule StringIO do """ + @type open_opts :: [ + capture_prompt: boolean(), + encoding: :unicode | :latin1 + ] + # We're implementing the GenServer behaviour instead of using the # `use GenServer` macro, because we don't want the `child_spec/1` # function as it doesn't make sense to be started under a supervisor. @@ -59,7 +64,7 @@ defmodule StringIO do """ @doc since: "1.7.0" - @spec open(binary, keyword, (pid -> res)) :: {:ok, res} when res: var + @spec open(binary, open_opts, (pid -> res)) :: {:ok, res} when res: var def open(string, options, function) when is_binary(string) and is_list(options) and is_function(function, 1) do {:ok, pid} = GenServer.start(__MODULE__, {self(), string, options}, []) @@ -83,7 +88,8 @@ defmodule StringIO do If options are provided, the result will be `{:ok, pid}`, returning the IO device created. The option `:capture_prompt`, when set to `true`, causes prompts (which are specified as arguments to `IO.get*` functions) to be - included in the device's output. + included in the device's output. See `options/3` for the list of supported + options. If a function is provided, the device will be created and sent to the function. When the function returns, the device will be closed. The final @@ -111,7 +117,7 @@ defmodule StringIO do {:ok, {"", "The input was foo"}} """ - @spec open(binary, keyword) :: {:ok, pid} + @spec open(binary, open_opts) :: {:ok, pid} @spec open(binary, (pid -> res)) :: {:ok, res} when res: var def open(string, options_or_function \\ []) diff --git a/lib/elixir/lib/supervisor.ex b/lib/elixir/lib/supervisor.ex index 6829ee90c21..a3cb0318545 100644 --- a/lib/elixir/lib/supervisor.ex +++ b/lib/elixir/lib/supervisor.ex @@ -659,6 +659,19 @@ defmodule Supervisor do @typedoc since: "1.16.0" @type module_spec :: {module(), args :: term()} | module() + @typedoc """ + Options for overriding child specification fields. + """ + @type child_spec_overrides :: [ + id: atom() | term(), + start: {module(), atom(), [term()]}, + restart: restart(), + shutdown: shutdown(), + type: type(), + modules: [module()] | :dynamic, + significant: boolean() + ] + @doc """ Starts a supervisor with the given children. @@ -896,7 +909,7 @@ defmodule Supervisor do #=> start: {Agent, :start_link, [fn -> :ok end]}} """ - @spec child_spec(child_spec() | module_spec(), keyword()) :: child_spec() + @spec child_spec(child_spec() | module_spec(), child_spec_overrides()) :: child_spec() def child_spec(module_or_map, overrides) def child_spec({_, _, _, _, _, _} = tuple, _overrides) do diff --git a/lib/elixir/lib/system.ex b/lib/elixir/lib/system.ex index d2881e5b0b5..13d7e93298f 100644 --- a/lib/elixir/lib/system.ex +++ b/lib/elixir/lib/system.ex @@ -113,6 +113,28 @@ defmodule System do | :sigusr1 | :sigusr2 + @type cmd_opts :: [ + into: Collectable.t(), + lines: pos_integer(), + cd: Path.t(), + env: [{binary(), binary() | nil}], + arg0: binary(), + stderr_to_stdout: boolean(), + use_stdio: boolean(), + parallelism: boolean() + ] + + @type shell_opts :: [ + into: Collectable.t(), + lines: pos_integer(), + cd: Path.t(), + env: [{binary(), binary() | nil}], + stderr_to_stdout: boolean(), + use_stdio: boolean(), + parallelism: boolean(), + close_stdin: boolean() + ] + @vm_signals [:sigquit, :sigterm, :sigusr1] @os_signals [:sighup, :sigabrt, :sigalrm, :sigusr2, :sigchld, :sigstop, :sigtstp] @signals @vm_signals ++ @os_signals @@ -941,7 +963,7 @@ defmodule System do immediately terminate. Defaults to false. """ @doc since: "1.12.0" - @spec shell(binary, keyword) :: {Collectable.t(), exit_status :: non_neg_integer} + @spec shell(binary, shell_opts) :: {Collectable.t(), exit_status :: non_neg_integer} def shell(command, opts \\ []) when is_binary(command) do command |> String.trim() |> do_shell(opts) end @@ -1101,7 +1123,7 @@ defmodule System do If you desire to execute a trusted command inside a shell, with pipes, redirecting and so on, please check `shell/2`. """ - @spec cmd(binary, [binary], keyword) :: {Collectable.t(), exit_status :: non_neg_integer} + @spec cmd(binary, [binary], cmd_opts) :: {Collectable.t(), exit_status :: non_neg_integer} def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do assert_no_null_byte!(command, "System.cmd/3") diff --git a/lib/elixir/lib/task/supervisor.ex b/lib/elixir/lib/task/supervisor.ex index d8b3f5ac80a..59b63f680e0 100644 --- a/lib/elixir/lib/task/supervisor.ex +++ b/lib/elixir/lib/task/supervisor.ex @@ -87,6 +87,18 @@ defmodule Task.Supervisor do @typedoc since: "1.17.0" @type async_stream_option :: Task.async_stream_option() | {:shutdown, Supervisor.shutdown()} + @typedoc """ + Options for `async/3`, `async/5`, `async_nolink/3`, and `async_nolink/5` functions. + """ + @type async_opts :: [ + shutdown: :brutal_kill | timeout() + ] + + @type start_child_opts :: [ + restart: :temporary | :transient | :permanent, + shutdown: :brutal_kill | timeout() + ] + @doc false def child_spec(opts) when is_list(opts) do id = @@ -175,7 +187,7 @@ defmodule Task.Supervisor do The tasks must trap exits for the timeout to have an effect. """ - @spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t() + @spec async(Supervisor.supervisor(), (-> any), async_opts) :: Task.t() def async(supervisor, fun, options \\ []) do async(supervisor, :erlang, :apply, [fun, []], options) end @@ -197,7 +209,7 @@ defmodule Task.Supervisor do The tasks must trap exits for the timeout to have an effect. """ - @spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t() + @spec async(Supervisor.supervisor(), module, atom, [term], async_opts) :: Task.t() def async(supervisor, module, fun, args, options \\ []) do async(supervisor, :link, module, fun, args, options) end @@ -284,7 +296,7 @@ defmodule Task.Supervisor do end """ - @spec async_nolink(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t() + @spec async_nolink(Supervisor.supervisor(), (-> any), async_opts) :: Task.t() def async_nolink(supervisor, fun, options \\ []) do async_nolink(supervisor, :erlang, :apply, [fun, []], options) end @@ -303,7 +315,7 @@ defmodule Task.Supervisor do as the `:restart` option (the default), as `async_nolink/5` keeps a direct reference to the task which is lost if the task is restarted. """ - @spec async_nolink(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t() + @spec async_nolink(Supervisor.supervisor(), module, atom, [term], async_opts) :: Task.t() def async_nolink(supervisor, module, fun, args, options \\ []) do async(supervisor, :nolink, module, fun, args, options) end @@ -523,7 +535,7 @@ defmodule Task.Supervisor do The task must trap exits for the timeout to have an effect. """ - @spec start_child(Supervisor.supervisor(), (-> any), keyword) :: + @spec start_child(Supervisor.supervisor(), (-> any), start_child_opts) :: DynamicSupervisor.on_start_child() def start_child(supervisor, fun, options \\ []) do restart = options[:restart] @@ -538,7 +550,7 @@ defmodule Task.Supervisor do Similar to `start_child/3` except the task is specified by the given `module`, `fun` and `args`. """ - @spec start_child(Supervisor.supervisor(), module, atom, [term], keyword) :: + @spec start_child(Supervisor.supervisor(), module, atom, [term], start_child_opts) :: DynamicSupervisor.on_start_child() def start_child(supervisor, module, fun, args, options \\ []) when is_atom(fun) and is_list(args) do diff --git a/lib/elixir/lib/version.ex b/lib/elixir/lib/version.ex index 8d98380cee1..f96420bffef 100644 --- a/lib/elixir/lib/version.ex +++ b/lib/elixir/lib/version.ex @@ -118,6 +118,8 @@ defmodule Version do @type build :: String.t() | nil @type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build} + @type match_opts :: [allow_pre: boolean()] + defmodule Requirement do @moduledoc """ A struct that holds version requirement information. @@ -296,7 +298,7 @@ defmodule Version do ** (Version.InvalidRequirementError) invalid requirement: "== == 1.0.0" """ - @spec match?(version, requirement, keyword) :: boolean + @spec match?(version, requirement, match_opts) :: boolean def match?(version, requirement, opts \\ []) def match?(version, requirement, opts) when is_binary(requirement) do diff --git a/lib/ex_unit/lib/ex_unit.ex b/lib/ex_unit/lib/ex_unit.ex index bc2ea55fac1..4c1baecfef5 100644 --- a/lib/ex_unit/lib/ex_unit.ex +++ b/lib/ex_unit/lib/ex_unit.ex @@ -93,6 +93,50 @@ defmodule ExUnit do @type test_id :: {module, name :: atom} + @typedoc """ + Configuration options for ExUnit. + + See `configure/1` for detailed documentation of each option. + """ + @type configure_opts :: [ + {:assert_receive_timeout, non_neg_integer()} + | {:autorun, boolean()} + | {:capture_log, boolean() | [level: Logger.level()]} + | {:colors, + [ + enabled: boolean(), + success: atom(), + invalid: atom(), + skipped: atom(), + failure: atom(), + error_info: atom(), + extra_info: atom(), + location_info: [atom()], + diff_insert: atom(), + diff_insert_whitespace: IO.ANSI.ansidata(), + diff_delete: atom(), + diff_delete_whitespace: IO.ANSI.ansidata() + ]} + | {:exclude, keyword()} + | {:exit_status, non_neg_integer()} + | {:failures_manifest_path, String.t()} + | {:formatters, [module()]} + | {:include, keyword()} + | {:max_cases, pos_integer()} + | {:max_failures, pos_integer() | :infinity} + | {:only_test_ids, [test_id()]} + | {:rand_algorithm, atom()} + | {:refute_receive_timeout, non_neg_integer()} + | {:seed, non_neg_integer()} + | {:slowest, non_neg_integer()} + | {:slowest_modules, non_neg_integer()} + | {:stacktrace_depth, non_neg_integer()} + | {:timeout, pos_integer()} + | {:trace, boolean()} + | {:test_location_relative_path, String.t()} + | {atom(), term()} + ] + defmodule Test do @moduledoc """ A struct that keeps information about the test. @@ -218,7 +262,7 @@ defmodule ExUnit do If you want to run tests manually, you can set the `:autorun` option to `false` and use `run/0` to run tests. """ - @spec start(Keyword.t()) :: :ok + @spec start(configure_opts()) :: :ok def start(options \\ []) do {:ok, _} = Application.ensure_all_started(:ex_unit) @@ -357,7 +401,7 @@ defmodule ExUnit do and these options can then be used in places such as custom formatters. These other options will be ignored by ExUnit itself. """ - @spec configure(Keyword.t()) :: :ok + @spec configure(configure_opts()) :: :ok def configure(options) when is_list(options) do Enum.each(options, fn {k, v} -> Application.put_env(:ex_unit, k, v) diff --git a/lib/ex_unit/lib/ex_unit/callbacks.ex b/lib/ex_unit/lib/ex_unit/callbacks.ex index 622a02658cc..5fad529b552 100644 --- a/lib/ex_unit/lib/ex_unit/callbacks.ex +++ b/lib/ex_unit/lib/ex_unit/callbacks.ex @@ -169,6 +169,12 @@ defmodule ExUnit.Callbacks do end """ + @type child_spec_overrides :: [ + restart: :permanent | :transient | :temporary, + shutdown: :brutal_kill | timeout(), + type: :worker | :supervisor + ] + @doc false def __register__(module) do Module.put_attribute(module, :ex_unit_describe, nil) @@ -560,7 +566,7 @@ defmodule ExUnit.Callbacks do more about these keys in [the `Task` module](`Task#module-ancestor-and-caller-tracking`). """ @doc since: "1.5.0" - @spec start_supervised(Supervisor.child_spec() | module | {module, term}, keyword) :: + @spec start_supervised(Supervisor.child_spec() | module | {module, term}, child_spec_overrides) :: Supervisor.on_start_child() def start_supervised(child_spec_or_module, opts \\ []) do sup = @@ -581,7 +587,8 @@ defmodule ExUnit.Callbacks do not started properly. """ @doc since: "1.6.0" - @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: pid + @spec start_supervised!(Supervisor.child_spec() | module | {module, term}, child_spec_overrides) :: + pid def start_supervised!(child_spec_or_module, opts \\ []) do case start_supervised(child_spec_or_module, opts) do {:ok, pid} -> @@ -627,7 +634,10 @@ defmodule ExUnit.Callbacks do > *by the test supervisor* in reverse order, ensuring graceful termination. """ @doc since: "1.14.0" - @spec start_link_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) :: + @spec start_link_supervised!( + Supervisor.child_spec() | module | {module, term}, + child_spec_overrides + ) :: pid def start_link_supervised!(child_spec_or_module, opts \\ []) do pid = start_supervised!(child_spec_or_module, opts) diff --git a/lib/ex_unit/lib/ex_unit/capture_io.ex b/lib/ex_unit/lib/ex_unit/capture_io.ex index 30e437a43ff..f506b6a4bf5 100644 --- a/lib/ex_unit/lib/ex_unit/capture_io.ex +++ b/lib/ex_unit/lib/ex_unit/capture_io.ex @@ -28,6 +28,12 @@ defmodule ExUnit.CaptureIO do """ + @type capture_io_opts :: [ + input: String.t(), + capture_prompt: boolean(), + encoding: :unicode | :latin1 + ] + @doc """ Captures IO generated when evaluating `fun`. @@ -152,7 +158,7 @@ defmodule ExUnit.CaptureIO do See `capture_io/1` for more information. """ - @spec capture_io(atom() | pid() | String.t() | keyword(), (-> any())) :: String.t() + @spec capture_io(atom() | pid() | String.t() | capture_io_opts, (-> any())) :: String.t() def capture_io(device_pid_input_or_options, fun) def capture_io(device_or_pid, fun) @@ -172,7 +178,7 @@ defmodule ExUnit.CaptureIO do See `capture_io/1` for more information. """ - @spec capture_io(atom() | pid(), String.t() | keyword(), (-> any())) :: String.t() + @spec capture_io(atom() | pid(), String.t() | capture_io_opts, (-> any())) :: String.t() def capture_io(device_or_pid, input_or_options, fun) when (is_atom(device_or_pid) or is_pid(device_or_pid)) and (is_binary(input_or_options) or is_list(input_or_options)) and is_function(fun, 0) do @@ -209,7 +215,7 @@ defmodule ExUnit.CaptureIO do See `with_io/1` for more information. """ @doc since: "1.13.0" - @spec with_io(atom() | pid() | String.t() | keyword(), (-> any())) :: {any(), String.t()} + @spec with_io(atom() | pid() | String.t() | capture_io_opts, (-> any())) :: {any(), String.t()} def with_io(device_pid_input_or_options, fun) def with_io(device, fun) when is_atom(device) and is_function(fun, 0) do @@ -234,7 +240,7 @@ defmodule ExUnit.CaptureIO do See `with_io/1` for more information. """ @doc since: "1.13.0" - @spec with_io(atom() | pid(), String.t() | keyword(), (-> any())) :: {any(), String.t()} + @spec with_io(atom() | pid(), String.t() | capture_io_opts, (-> any())) :: {any(), String.t()} def with_io(device_or_pid, input_or_options, fun) def with_io(device, input, fun) diff --git a/lib/ex_unit/lib/ex_unit/capture_log.ex b/lib/ex_unit/lib/ex_unit/capture_log.ex index e885a10df91..e8178a9b7b4 100644 --- a/lib/ex_unit/lib/ex_unit/capture_log.ex +++ b/lib/ex_unit/lib/ex_unit/capture_log.ex @@ -43,6 +43,10 @@ defmodule ExUnit.CaptureLog do @compile {:no_warn_undefined, Logger} + @type capture_log_opts :: [ + level: Logger.level() | nil + ] + @doc """ Captures Logger messages generated when evaluating `fun`. @@ -74,7 +78,7 @@ defmodule ExUnit.CaptureLog do To get the result of the evaluation along with the captured log, use `with_log/2`. """ - @spec capture_log(keyword, (-> any)) :: String.t() + @spec capture_log(capture_log_opts, (-> any)) :: String.t() def capture_log(opts \\ [], fun) do {_, log} = with_log(opts, fun) log @@ -98,7 +102,7 @@ defmodule ExUnit.CaptureLog do """ @doc since: "1.13.0" - @spec with_log(keyword, (-> result)) :: {result, log :: String.t()} when result: any + @spec with_log(capture_log_opts, (-> result)) :: {result, log :: String.t()} when result: any def with_log(opts \\ [], fun) when is_list(opts) do opts = if opts[:level] == :warn do diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex index 38b59cf2e5c..69103257b05 100644 --- a/lib/ex_unit/lib/ex_unit/case.ex +++ b/lib/ex_unit/lib/ex_unit/case.ex @@ -338,6 +338,11 @@ defmodule ExUnit.Case do """ @type env :: module() | Macro.Env.t() + + @type register_attribute_opts :: [ + accumulate: boolean(), + persist: boolean() + ] @compile {:no_warn_undefined, [IEx.Pry]} @reserved [:module, :file, :line, :test, :async, :registered, :describe] @@ -767,7 +772,7 @@ defmodule ExUnit.Case do """ @doc since: "1.3.0" - @spec register_attribute(env, atom, keyword) :: :ok + @spec register_attribute(env, atom, register_attribute_opts) :: :ok def register_attribute(env, name, opts \\ []) def register_attribute(%{module: mod}, name, opts), do: register_attribute(mod, name, opts) @@ -809,7 +814,7 @@ defmodule ExUnit.Case do """ @doc since: "1.10.0" - @spec register_describe_attribute(env, atom, keyword) :: :ok + @spec register_describe_attribute(env, atom, register_attribute_opts) :: :ok def register_describe_attribute(env, name, opts \\ []) def register_describe_attribute(%{module: mod}, name, opts) do @@ -849,7 +854,7 @@ defmodule ExUnit.Case do """ @doc since: "1.10.0" - @spec register_module_attribute(env, atom, keyword) :: :ok + @spec register_module_attribute(env, atom, register_attribute_opts) :: :ok def register_module_attribute(env, name, opts \\ []) def register_module_attribute(%{module: mod}, name, opts) do diff --git a/lib/iex/lib/iex.ex b/lib/iex/lib/iex.ex index 046d648a416..fc5cf9b7b91 100644 --- a/lib/iex/lib/iex.ex +++ b/lib/iex/lib/iex.ex @@ -390,6 +390,78 @@ defmodule IEx do """ + @typedoc """ + Color settings used by the IEx shell. + """ + @type colors_opts :: [ + enabled: boolean(), + eval_result: IO.ANSI.ansidata(), + eval_info: IO.ANSI.ansidata(), + eval_error: IO.ANSI.ansidata(), + eval_interrupt: IO.ANSI.ansidata(), + stack_info: IO.ANSI.ansidata(), + blame_diff: IO.ANSI.ansidata(), + ls_directory: IO.ANSI.ansidata(), + ls_device: IO.ANSI.ansidata(), + doc_code: IO.ANSI.ansidata(), + doc_inline_code: IO.ANSI.ansidata(), + doc_headings: IO.ANSI.ansidata(), + doc_title: IO.ANSI.ansidata(), + doc_bold: IO.ANSI.ansidata(), + doc_underline: IO.ANSI.ansidata(), + syntax_colors: syntax_colors_opts() | false + ] + + @typedoc """ + Syntax coloring settings for inspected expressions. + """ + @type syntax_colors_opts :: [ + atom: IO.ANSI.ansidata(), + binary: IO.ANSI.ansidata(), + boolean: IO.ANSI.ansidata(), + charlist: IO.ANSI.ansidata(), + list: IO.ANSI.ansidata(), + map: IO.ANSI.ansidata(), + nil: IO.ANSI.ansidata(), + number: IO.ANSI.ansidata(), + string: IO.ANSI.ansidata(), + tuple: IO.ANSI.ansidata(), + variable: IO.ANSI.ansidata(), + call: IO.ANSI.ansidata(), + operator: IO.ANSI.ansidata(), + reset: IO.ANSI.ansidata() + ] + + @typedoc """ + Inspect settings used by the IEx shell. + """ + @type inspect_opts :: [ + base: :binary | :decimal | :octal | :hex, + binaries: :infer | :as_binaries | :as_strings, + charlists: :infer | :as_charlists | :as_lists, + custom_options: keyword(), + inspect_fun: (term(), Inspect.Opts.t() -> Inspect.Algebra.t()), + limit: pos_integer() | :infinity, + pretty: boolean(), + printable_limit: pos_integer() | :infinity, + safe: boolean(), + structs: boolean(), + syntax_colors: syntax_colors_opts(), + width: pos_integer() | :infinity + ] + + @type configure_opts :: [ + auto_reload: boolean(), + alive_prompt: String.t(), + colors: colors_opts(), + default_prompt: String.t(), + dot_iex: String.t() | nil, + history_size: integer(), + inspect: inspect_opts(), + parser: {module(), atom(), [any()]}, + width: pos_integer() + ] + @doc """ Configures IEx. @@ -521,7 +593,7 @@ defmodule IEx do in-memory modules when they get invalidated by a concurrent compilation happening in the Operating System. """ - @spec configure(keyword()) :: :ok + @spec configure(configure_opts) :: :ok def configure(options) do IEx.Config.configure(options) end diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index d2d9ed57140..c9b6d552d5c 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -9,6 +9,13 @@ defmodule IEx.Broker do @type take_ref :: {takeover_ref :: reference(), server_ref :: reference()} @type shell :: pid | nil + @typedoc """ + Options for `take_over/3`. + """ + @type take_over_opts :: [ + evaluator: pid() + ] + use GenServer ## Shell API @@ -58,7 +65,7 @@ defmodule IEx.Broker do @doc """ Client requests a takeover. """ - @spec take_over(binary, iodata, keyword) :: + @spec take_over(binary, iodata, take_over_opts) :: {:ok, server :: pid, group_leader :: pid, counter :: integer} | {:error, :no_iex | :refused | atom()} def take_over(location, whereami, opts) do diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex index 80860f60bb4..9b9db7ad745 100644 --- a/lib/iex/lib/iex/helpers.ex +++ b/lib/iex/lib/iex/helpers.ex @@ -113,6 +113,7 @@ defmodule IEx.Helpers do * `:force` - when `true`, forces the application to recompile """ + @spec recompile(force: boolean()) :: :ok | :error | :noop def recompile(options \\ []) do cond do not mix_started?() -> diff --git a/lib/iex/lib/iex/server.ex b/lib/iex/lib/iex/server.ex index 5ab6d8eff04..fe0d938ee03 100644 --- a/lib/iex/lib/iex/server.ex +++ b/lib/iex/lib/iex/server.ex @@ -14,6 +14,14 @@ defmodule IEx.Server do """ + @type run_opts :: [ + prefix: String.t(), + env: Macro.Env.t(), + binding: keyword(), + on_eof: :stop_evaluator | :halt, + register: boolean() + ] + @doc false defstruct parser_state: [], counter: 1, @@ -35,7 +43,7 @@ defmodule IEx.Server do """ @doc since: "1.8.0" - @spec run(keyword) :: :ok + @spec run(run_opts) :: :ok def run(opts) when is_list(opts) do if Keyword.get(opts, :register, true) do IEx.Broker.register(self()) diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs index 99b526f7efa..39d454f50ef 100644 --- a/lib/iex/test/iex/autocomplete_test.exs +++ b/lib/iex/test/iex/autocomplete_test.exs @@ -62,7 +62,16 @@ defmodule IEx.AutocompleteTest do assert expand(~c"t String") == {:yes, ~c"", [~c"String", ~c"StringIO"]} assert expand(~c"t String.") == - {:yes, ~c"", [~c"codepoint/0", ~c"grapheme/0", ~c"pattern/0", ~c"t/0"]} + {:yes, ~c"", + [ + ~c"codepoint/0", + ~c"grapheme/0", + ~c"pattern/0", + ~c"replace_opts/0", + ~c"split_opts/0", + ~c"splitter_opts/0", + ~c"t/0" + ]} assert expand(~c"t String.grap") == {:yes, ~c"heme", []} assert expand(~c"t String.grap") == {:yes, ~c"heme", []} @@ -85,7 +94,16 @@ defmodule IEx.AutocompleteTest do assert expand(~c"t(String") == {:yes, ~c"", [~c"String", ~c"StringIO"]} assert expand(~c"t(String.") == - {:yes, ~c"", [~c"codepoint/0", ~c"grapheme/0", ~c"pattern/0", ~c"t/0"]} + {:yes, ~c"", + [ + ~c"codepoint/0", + ~c"grapheme/0", + ~c"pattern/0", + ~c"replace_opts/0", + ~c"split_opts/0", + ~c"splitter_opts/0", + ~c"t/0" + ]} assert expand(~c"t(String.grap") == {:yes, ~c"heme", []} end diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs index 10d9ecf5735..77f35f5529e 100644 --- a/lib/iex/test/iex/helpers_test.exs +++ b/lib/iex/test/iex/helpers_test.exs @@ -967,7 +967,7 @@ defmodule IEx.HelpersTest do describe "t" do test "prints when there is no type information or the type is private" do - assert capture_io(fn -> t(IEx) end) == "No type information for IEx was found\n" + assert capture_io(fn -> t(List) end) == "No type information for List was found\n" assert capture_io(fn -> t(Enum.doesnt_exist()) end) == "No type information for Enum.doesnt_exist was found or " <> diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex index c240ab85578..1e604e0d84b 100644 --- a/lib/logger/lib/logger.ex +++ b/lib/logger/lib/logger.ex @@ -507,6 +507,30 @@ defmodule Logger do @type report :: map() | keyword() @type message :: :unicode.chardata() | String.Chars.t() | report() @type metadata :: keyword() + + @type configure_opts :: [ + level: level(), + translator_inspect_opts: Inspect.Opts.t(), + sync_threshold: non_neg_integer(), + discard_threshold: non_neg_integer(), + truncate: non_neg_integer() | :infinity, + utc_log: boolean() + ] + + @type formatter_opts :: [ + colors: [ + enabled: boolean(), + debug: atom(), + info: atom(), + warning: atom(), + error: atom() + ], + format: String.t() | {module(), atom()}, + metadata: :all | [atom()], + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] + @new_erlang_levels [:emergency, :alert, :critical, :warning, :notice] @levels [:error, :info, :debug] ++ @new_erlang_levels @metadata :logger_level @@ -552,7 +576,7 @@ defmodule Logger do instead. """ @doc since: "1.15.0" - @spec default_formatter(keyword) :: {module, :logger.formatter_config()} + @spec default_formatter(formatter_opts) :: {module, :logger.formatter_config()} def default_formatter(overrides \\ []) when is_list(overrides) do Application.get_env(:logger, :default_formatter, []) |> Keyword.merge(overrides) @@ -712,7 +736,7 @@ defmodule Logger do :translator_inspect_opts ] @backend_options [:sync_threshold, :discard_threshold, :truncate, :utc_log] - @spec configure(keyword) :: :ok + @spec configure(configure_opts) :: :ok def configure(options) do for {k, v} <- options do cond do @@ -973,7 +997,7 @@ defmodule Logger do anonymous functions to `bare_log/3` and they will only be evaluated if there is something to be logged. """ - @spec bare_log(level, message | (-> message | {message, keyword}), keyword) :: :ok + @spec bare_log(level, message | (-> message | {message, keyword}), metadata) :: :ok def bare_log(level, message_or_fun, metadata \\ []) do level = elixir_level_to_erlang_level(level) diff --git a/lib/logger/lib/logger/backends/internal.ex b/lib/logger/lib/logger/backends/internal.ex index afef580a6e5..4af6c2e117b 100644 --- a/lib/logger/lib/logger/backends/internal.ex +++ b/lib/logger/lib/logger/backends/internal.ex @@ -8,13 +8,32 @@ defmodule Logger.Backends.Internal do @name __MODULE__ @type backend :: :gen_event.handler() + @typedoc """ + Configuration options for Logger backends. + + These are the built-in backend options that can be configured at runtime. + """ + @type backend_config_opts :: [ + sync_threshold: pos_integer(), + discard_threshold: pos_integer() | :infinity, + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] + + @typedoc """ + Options for `add/2` and `remove/2` operations. + """ + @type add_remove_opts :: [ + flush: boolean() + ] + @doc """ Apply runtime configuration to all backends. See the module doc for more information. """ @backend_options [:sync_threshold, :discard_threshold, :truncate, :utc_log] - @spec configure(keyword) :: :ok + @spec configure(backend_config_opts) :: :ok def configure(options) do ensure_started() Logger.Backends.Config.configure(Keyword.take(options, @backend_options)) @@ -65,7 +84,7 @@ defmodule Logger.Backends.Internal do {:ok, _pid} = Logger.add_backend(MyBackend, flush: true) """ - @spec add(backend, keyword) :: Supervisor.on_start_child() + @spec add(backend, add_remove_opts) :: Supervisor.on_start_child() def add(backend, opts \\ []) do ensure_started() _ = if opts[:flush], do: Logger.flush() @@ -91,7 +110,7 @@ defmodule Logger.Backends.Internal do to `Logger` are processed before the backend is removed """ - @spec remove(backend, keyword) :: :ok | {:error, term} + @spec remove(backend, add_remove_opts) :: :ok | {:error, term} def remove(backend, opts \\ []) do ensure_started() _ = if opts[:flush], do: Logger.flush() diff --git a/lib/logger/lib/logger/formatter.ex b/lib/logger/lib/logger/formatter.ex index 858449bc27c..1caed9da74d 100644 --- a/lib/logger/lib/logger/formatter.ex +++ b/lib/logger/lib/logger/formatter.ex @@ -99,6 +99,20 @@ defmodule Logger.Formatter do @type date_time_ms :: {date, time_ms} @type pattern :: :date | :level | :levelpad | :message | :metadata | :node | :time + + @type new_opts :: [ + colors: [ + enabled: boolean(), + debug: atom(), + info: atom(), + warning: atom(), + error: atom() + ], + format: String.t() | {module(), atom()}, + metadata: :all | [atom()], + truncate: pos_integer() | :infinity, + utc_log: boolean() + ] @valid_patterns [:time, :date, :message, :level, :node, :metadata, :levelpad] @default_pattern "\n$time $metadata[$level] $message\n" @replacement "�" @@ -152,7 +166,7 @@ defmodule Logger.Formatter do The color of the message can also be configured per message via the `:ansi_color` metadata. """ - @spec new(keyword) :: formatter when formatter: term + @spec new(new_opts) :: formatter when formatter: term def new(options \\ []) do template = compile(options[:format]) colors = colors(options[:colors] || []) diff --git a/lib/logger/lib/logger/utils.ex b/lib/logger/lib/logger/utils.ex index 5492740714d..302e4829e8c 100644 --- a/lib/logger/lib/logger/utils.ex +++ b/lib/logger/lib/logger/utils.ex @@ -127,6 +127,12 @@ defmodule Logger.Utils do For information about format scanning and how to consume them, check `:io_lib.scan_format/2`. """ + @spec scan_inspect( + atom() | binary() | charlist(), + list(), + non_neg_integer() | :infinity, + Inspect.Opts.t() + ) :: [:io_lib.format_spec()] def scan_inspect(format, args, truncate, opts \\ %Inspect.Opts{}) def scan_inspect(format, args, truncate, opts) when is_atom(format) do diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex index 5197d890df5..ee3701f686f 100644 --- a/lib/mix/lib/mix.ex +++ b/lib/mix/lib/mix.ex @@ -424,6 +424,25 @@ defmodule Mix do """ + @typedoc """ + Options for `install/2`. + + See the `install/2` function documentation for detailed information about each option. + """ + @type install_opts :: [ + force: boolean(), + verbose: boolean(), + consolidate_protocols: boolean(), + elixir: String.t(), + system_env: Enum.t(), + config: keyword(), + config_path: String.t() | atom(), + lockfile: String.t() | atom(), + start_applications: boolean(), + compilers: [atom()], + runtime_config: keyword() + ] + @mix_install_project Mix.InstallProject @mix_install_app :mix_install @mix_install_app_string Atom.to_string(@mix_install_app) @@ -845,6 +864,10 @@ defmodule Mix do if `:force` is enabled. """ @doc since: "1.12.0" + @spec install( + [atom() | {atom(), String.t()} | {atom(), String.t(), keyword()} | {atom(), keyword()}], + install_opts() + ) :: :ok def install(deps, opts \\ []) def install(deps, opts) when is_list(deps) and is_list(opts) do diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index ab94b0a03de..1ffa1425770 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -7,6 +7,15 @@ defmodule Mix.Compilers.Erlang do @manifest_vsn 1 + @typedoc """ + Options for `compile/6`. + """ + @type compile_opts :: [ + force: boolean(), + parallel: MapSet.t(Path.t()), + preload: (-> term()) + ] + @doc """ Compiles the given `mappings`. @@ -58,6 +67,14 @@ defmodule Mix.Compilers.Erlang do `{:error, errors, warnings}` in case of error. This function returns `{status, diagnostics}` as specified in `Mix.Task.Compiler`. """ + @spec compile( + Path.t(), + [{Path.t(), Path.t()}], + atom(), + atom(), + compile_opts, + (Path.t(), Path.t() -> {:ok, term(), [term()]} | {:error, [term()], [term()]}) + ) :: {Mix.Task.Compiler.status(), [Mix.Task.Compiler.Diagnostic.t()]} def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_atom(src_ext) and is_atom(dest_ext) and is_list(opts) do force = opts[:force] diff --git a/lib/mix/lib/mix/dep.ex b/lib/mix/lib/mix/dep.ex index 1dd953b4cd9..8887ec86f54 100644 --- a/lib/mix/lib/mix/dep.ex +++ b/lib/mix/lib/mix/dep.ex @@ -79,6 +79,13 @@ defmodule Mix.Dep do system_env: keyword } + @typedoc """ + Options for `filter_by_name/3`. + """ + @type filter_by_name_opts :: [ + include_children: boolean() + ] + @doc """ Returns loaded dependencies from the cache for the current environment. @@ -197,6 +204,7 @@ defmodule Mix.Dep do Raises if any of the names are missing. """ + @spec filter_by_name([atom() | String.t()], [t()], filter_by_name_opts) :: [t()] def filter_by_name(given, all_deps, opts \\ []) do # Ensure all apps are atoms apps = to_app_names(given) diff --git a/lib/mix/lib/mix/dep/converger.ex b/lib/mix/lib/mix/dep/converger.ex index 1d036c49822..c29073f53f7 100644 --- a/lib/mix/lib/mix/dep/converger.ex +++ b/lib/mix/lib/mix/dep/converger.ex @@ -8,6 +8,14 @@ defmodule Mix.Dep.Converger do @moduledoc false + @typedoc """ + Options for `converge/1` and `converge/4`. + """ + @type converge_opts :: [ + env: atom(), + target: atom() + ] + @doc """ Topologically sorts the given dependencies. """ @@ -72,6 +80,7 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ + @spec converge(converge_opts) :: [Mix.Dep.t()] def converge(opts \\ []) do converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0) end @@ -89,6 +98,7 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ + @spec converge(term(), map() | nil, converge_opts, function()) :: {[Mix.Dep.t()], term(), map()} def converge(acc, lock, opts, callback) do {deps, acc, lock} = all(acc, lock, opts, callback) if remote = Mix.RemoteConverger.get(), do: remote.post_converge() diff --git a/lib/mix/lib/mix/dep/lock.ex b/lib/mix/lib/mix/dep/lock.ex index 9f031024782..a8ec7192f56 100644 --- a/lib/mix/lib/mix/dep/lock.ex +++ b/lib/mix/lib/mix/dep/lock.ex @@ -9,6 +9,14 @@ defmodule Mix.Dep.Lock do @moduledoc false + @typedoc """ + Options for `write/2`. + """ + @type write_opts :: [ + file: Path.t(), + check_locked: boolean() + ] + @doc """ Reads the lockfile, returns a map containing each app name and its current lock information. @@ -30,7 +38,7 @@ defmodule Mix.Dep.Lock do @doc """ Receives a map and writes it as the latest lock. """ - @spec write(map(), keyword) :: :ok + @spec write(map(), write_opts) :: :ok def write(map, opts \\ []) do lockfile = opts[:file] || lockfile() diff --git a/lib/mix/lib/mix/generator.ex b/lib/mix/lib/mix/generator.ex index 1e8af77ef60..e04e4ca30dd 100644 --- a/lib/mix/lib/mix/generator.ex +++ b/lib/mix/lib/mix/generator.ex @@ -7,6 +7,12 @@ defmodule Mix.Generator do Conveniences for working with paths and generating content. """ + @type generator_opts :: [ + force: boolean(), + quiet: boolean(), + format_elixir: boolean() + ] + @doc ~S""" Creates a file with the given contents. @@ -17,6 +23,7 @@ defmodule Mix.Generator do * `:force` - forces creation without a shell prompt * `:quiet` - does not log command output + * `:format_elixir` (since v1.18.0) - if `true`, apply formatter to the generated file ## Examples @@ -25,7 +32,7 @@ defmodule Mix.Generator do true """ - @spec create_file(Path.t(), iodata, keyword) :: boolean() + @spec create_file(Path.t(), iodata, generator_opts) :: boolean() def create_file(path, contents, opts \\ []) when is_binary(path) do log(:green, :creating, Path.relative_to_cwd(path), opts) @@ -62,7 +69,7 @@ defmodule Mix.Generator do true """ - @spec create_directory(Path.t(), keyword) :: true + @spec create_directory(Path.t(), quiet: boolean()) :: true def create_directory(path, options \\ []) when is_binary(path) do log(:green, "creating", Path.relative_to_cwd(path), options) File.mkdir_p!(path) @@ -79,6 +86,7 @@ defmodule Mix.Generator do * `:force` - forces copying without a shell prompt * `:quiet` - does not log command output + * `:format_elixir` (since v1.18.0) - if `true`, apply formatter to the generated file ## Examples @@ -88,7 +96,7 @@ defmodule Mix.Generator do """ @doc since: "1.9.0" - @spec copy_file(Path.t(), Path.t(), keyword) :: boolean() + @spec copy_file(Path.t(), Path.t(), generator_opts) :: boolean() def copy_file(source, target, options \\ []) do create_file(target, File.read!(source), options) end @@ -116,7 +124,7 @@ defmodule Mix.Generator do """ @doc since: "1.9.0" - @spec copy_template(Path.t(), Path.t(), keyword, keyword) :: boolean() + @spec copy_template(Path.t(), Path.t(), keyword, generator_opts) :: boolean() def copy_template(source, target, assigns, options \\ []) do create_file(target, EEx.eval_file(source, assigns: assigns), options) end diff --git a/lib/mix/lib/mix/local/installer.ex b/lib/mix/lib/mix/local/installer.ex index b5180d87168..10d8d0938ce 100644 --- a/lib/mix/lib/mix/local/installer.ex +++ b/lib/mix/lib/mix/local/installer.ex @@ -23,10 +23,27 @@ defmodule Mix.Local.Installer do | {:url, url :: binary} | {:fetcher, dep_spec :: tuple} + @typedoc """ + Options for installer functions. + + These options are used by various installer functions to control behavior + during installation, parsing, and uninstallation. + """ + @type installer_opts :: [ + app: String.t(), + submodules: boolean(), + sparse: String.t(), + force: boolean(), + sha512: String.t(), + organization: String.t(), + repo: String.t(), + timeout: pos_integer() + ] + @doc """ Checks that the `install_spec` and `opts` are supported by the respective module. """ - @callback check_install_spec(install_spec, opts :: keyword) :: :ok | {:error, String.t()} + @callback check_install_spec(install_spec, opts :: installer_opts) :: :ok | {:error, String.t()} @doc """ Returns a list of already installed version of the same artifact. @@ -37,7 +54,7 @@ defmodule Mix.Local.Installer do Builds a local artifact either from a remote dependency or for the current project. """ - @callback build(install_spec, opts :: Keyword.t()) :: Path.t() + @callback build(install_spec, opts :: installer_opts) :: Path.t() @doc """ The installation itself. @@ -175,7 +192,7 @@ defmodule Mix.Local.Installer do @doc """ Receives `argv` and `opts` from options parsing and returns an `install_spec`. """ - @spec parse_args([String.t()], keyword) :: install_spec | {:error, String.t()} + @spec parse_args([String.t()], installer_opts) :: install_spec | {:error, String.t()} def parse_args(argv, opts) def parse_args([], _opts) do diff --git a/lib/mix/lib/mix/project.ex b/lib/mix/lib/mix/project.ex index 72f648967ec..7c89b2ba35e 100644 --- a/lib/mix/lib/mix/project.ex +++ b/lib/mix/lib/mix/project.ex @@ -158,6 +158,22 @@ defmodule Mix.Project do """ + @type build_structure_opts :: [ + symlink_ebin: boolean(), + source: String.t() + ] + + @typedoc """ + Options for dependency traversal functions. + + These options control how dependency trees are traversed and filtered + in functions like `deps_scms/1`, `deps_paths/1`, and `deps_tree/1`. + """ + @type deps_traversal_opts :: [ + depth: pos_integer(), + parents: [atom()] + ] + @doc false defmacro __using__(_) do quote do @@ -540,7 +556,7 @@ defmodule Mix.Project do """ @doc since: "1.10.0" - @spec deps_scms(keyword) :: %{optional(atom) => Mix.SCM.t()} + @spec deps_scms(deps_traversal_opts) :: %{optional(atom) => Mix.SCM.t()} def deps_scms(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{scm: scm} -> scm end) end @@ -561,7 +577,7 @@ defmodule Mix.Project do #=> %{foo: "deps/foo", bar: "custom/path/dep"} """ - @spec deps_paths(keyword) :: %{optional(atom) => Path.t()} + @spec deps_paths(deps_traversal_opts) :: %{optional(atom) => Path.t()} def deps_paths(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{opts: opts} -> opts[:dest] end) end @@ -583,7 +599,7 @@ defmodule Mix.Project do """ @doc since: "1.15.0" - @spec deps_tree(keyword) :: %{optional(atom) => [atom]} + @spec deps_tree(deps_traversal_opts) :: %{optional(atom) => [atom]} def deps_tree(opts \\ []) when is_list(opts) do traverse_deps(opts, fn %{deps: deps} -> Enum.map(deps, & &1.app) end) end @@ -843,8 +859,11 @@ defmodule Mix.Project do * `:symlink_ebin` - symlink ebin instead of copying it + * `:source` - the source directory to copy from. + Defaults to the current working directory. + """ - @spec build_structure(keyword, keyword) :: :ok + @spec build_structure(keyword, build_structure_opts) :: :ok def build_structure(config \\ config(), opts \\ []) do source = opts[:source] || File.cwd!() target = app_path(config) @@ -878,7 +897,7 @@ defmodule Mix.Project do `opts` are the same options that can be passed to `build_structure/2`. """ - @spec ensure_structure(keyword, keyword) :: :ok + @spec ensure_structure(keyword, build_structure_opts) :: :ok def ensure_structure(config \\ config(), opts \\ []) do if File.exists?(app_path(config)) do :ok diff --git a/lib/mix/lib/mix/release.ex b/lib/mix/lib/mix/release.ex index dc066d64f89..ff74c480797 100644 --- a/lib/mix/lib/mix/release.ex +++ b/lib/mix/lib/mix/release.ex @@ -68,6 +68,23 @@ defmodule Mix.Release do steps: [(t -> t) | :assemble, ...] } + @typedoc """ + Options for stripping BEAM files. + """ + @type strip_beam_opts :: [ + keep: [String.t()], + compress: boolean() + ] + + @typedoc """ + Erlang/OTP sys.config structure. + + A list of tuples where each tuple contains an application name and its + configuration as a keyword list. This is the standard format for Erlang + application configuration. + """ + @type sys_config :: [{application(), keyword()}] + @default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent] @safe_modes [:permanent, :temporary, :transient] @unsafe_modes [:load, :none] @@ -445,7 +462,7 @@ defmodule Mix.Release do In case there are no config providers, it doesn't change `sys_config`. """ - @spec make_sys_config(t, keyword(), Config.Provider.config_path()) :: + @spec make_sys_config(t, sys_config(), Config.Provider.config_path()) :: :ok | {:error, String.t()} def make_sys_config(release, sys_config, config_provider_path) do {sys_config, runtime_config?} = @@ -900,8 +917,26 @@ defmodule Mix.Release do The exact chunks that are kept are not documented and may change in future versions. + + ## Options + + * `:keep` - a list of additional chunk names (as strings) to keep in the + stripped BEAM file. These will be added to the significant chunks + determined by `:beam_lib.significant_chunks/0` + + * `:compress` - when `true`, the resulting BEAM file will be compressed + using gzip. Defaults to `false` + + ## Examples + + # Strip with default options + Mix.Release.strip_beam(beam_binary) + + # Keep additional chunks and compress + Mix.Release.strip_beam(beam_binary, keep: ["Docs", "ChunkName"], compress: true) + """ - @spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()} + @spec strip_beam(binary(), strip_beam_opts()) :: {:ok, binary()} | {:error, :beam_lib, term()} def strip_beam(binary, options \\ []) when is_list(options) do chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1) all_chunks = Enum.uniq(@additional_chunks ++ :beam_lib.significant_chunks() ++ chunks_to_keep) diff --git a/lib/mix/lib/mix/shell.ex b/lib/mix/lib/mix/shell.ex index 75770ef7a8a..03f7bf31b52 100644 --- a/lib/mix/lib/mix/shell.ex +++ b/lib/mix/lib/mix/shell.ex @@ -7,6 +7,29 @@ defmodule Mix.Shell do Defines `Mix.Shell` contract. """ + @type cmd_opts :: [ + {:print_app, boolean()} + | {:stderr_to_stdout, boolean()} + | {:quiet, boolean()} + | {:env, [{String.t(), String.t()}]} + | {:cd, String.t()} + | {atom(), term()} + ] + + @type yes_opts :: [ + {:default, :yes | :no} + | {atom(), term()} + ] + + @type stream_cmd_opts :: [ + {:cd, String.t()} + | {:stderr_to_stdout, boolean()} + | {:use_stdio, boolean()} + | {:env, [{String.t(), String.t()}]} + | {:quiet, boolean()} + | {atom(), term()} + ] + @doc false defstruct [:callback] @@ -48,7 +71,7 @@ defmodule Mix.Shell do All the built-in shells support these. """ - @callback cmd(command :: String.t(), options :: keyword) :: integer + @callback cmd(command :: String.t(), options :: cmd_opts) :: integer @doc """ Prompts the user for input. @@ -69,7 +92,7 @@ defmodule Mix.Shell do * `:default` - `:yes` or `:no` (the default is `:yes`) """ - @callback yes?(message :: binary, options :: keyword) :: boolean + @callback yes?(message :: binary, options :: yes_opts) :: boolean @doc """ Prints the current application to the shell if @@ -117,7 +140,7 @@ defmodule Mix.Shell do * `:quiet` - overrides the callback to no-op """ - @spec cmd(String.t() | {String.t(), [String.t()]}, keyword, (binary -> term)) :: + @spec cmd(String.t() | {String.t(), [String.t()]}, stream_cmd_opts, (binary -> term)) :: exit_status :: non_neg_integer def cmd(command, options \\ [], callback) when is_function(callback, 1) do callback = diff --git a/lib/mix/lib/mix/shell/io.ex b/lib/mix/lib/mix/shell/io.ex index 57aabfc5524..0988272954c 100644 --- a/lib/mix/lib/mix/shell/io.ex +++ b/lib/mix/lib/mix/shell/io.ex @@ -11,6 +11,11 @@ defmodule Mix.Shell.IO do @behaviour Mix.Shell + @typedoc """ + Options for `yes?/2`. + """ + @type yes_opts :: [default: :yes | :no] + @doc """ Prints the current application to the shell if it was not printed yet. @@ -72,6 +77,7 @@ defmodule Mix.Shell.IO do end """ + @spec yes?(String.t(), yes_opts) :: boolean() def yes?(message, options \\ []) do default = Keyword.get(options, :default, :yes) diff --git a/lib/mix/lib/mix/shell/quiet.ex b/lib/mix/lib/mix/shell/quiet.ex index 6c5f0c0dbbf..22fec04943f 100644 --- a/lib/mix/lib/mix/shell/quiet.ex +++ b/lib/mix/lib/mix/shell/quiet.ex @@ -52,11 +52,14 @@ defmodule Mix.Shell.Quiet do the prompt instead. Defaults to `:yes`. """ + @spec yes?(String.t(), Mix.Shell.IO.yes_opts()) :: boolean() defdelegate yes?(message, options \\ []), to: Mix.Shell.IO @doc """ Executes the given command quietly without outputting anything. """ + @spec cmd(String.t() | {String.t(), [String.t()]}, Mix.Shell.stream_cmd_opts()) :: + exit_status :: non_neg_integer def cmd(command, opts \\ []) do Mix.Shell.cmd(command, opts, fn data -> data end) end diff --git a/lib/mix/lib/mix/sync/lock.ex b/lib/mix/lib/mix/sync/lock.ex index 4705f7602e9..9f13b853435 100644 --- a/lib/mix/lib/mix/sync/lock.ex +++ b/lib/mix/lib/mix/sync/lock.ex @@ -73,6 +73,13 @@ defmodule Mix.Sync.Lock do @probe_data_size byte_size(@probe_data) @probe_timeout_ms 5_000 + @typedoc """ + Options for `with_lock/3`. + """ + @type with_lock_opts :: [ + on_taken: (String.t() -> any()) + ] + @doc """ Acquires a lock identified by the given key. @@ -95,7 +102,7 @@ defmodule Mix.Sync.Lock do is successfully acquired by this process. """ - @spec with_lock(iodata(), (-> term()), keyword()) :: term() + @spec with_lock(iodata(), (-> term()), with_lock_opts()) :: term() def with_lock(key, fun, opts \\ []) do opts = Keyword.validate!(opts, [:on_taken]) diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index db05232bcb3..2abd3feced3 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -223,15 +223,55 @@ defmodule Mix.Tasks.Format do ins: [text: :green, space: :green_background] ] + @typedoc """ + Options passed to plugin `features/1` callback. + + These are the same formatter options from `.formatter.exs` configuration, + allowing plugins to access formatting settings when determining their capabilities. + """ + @type features_opts :: [ + inputs: [String.t()], + plugins: [module()], + subdirectories: [String.t()], + import_deps: [atom()], + export: keyword(), + locals_without_parens: keyword(), + line_length: pos_integer(), + normalize_bitstring_modifiers: boolean(), + normalize_charlists_as_binaries: boolean() + ] + + @typedoc """ + Options passed to plugin `format/2` callback. + + These options include context-specific information about what is being formatted + (sigil, extension, file) along with all the standard formatter configuration options. + """ + @type format_opts :: [ + {:sigil, atom()} + | {:modifiers, charlist()} + | {:extension, String.t()} + | {:file, String.t()} + | {:inputs, [String.t()]} + | {:plugins, [module()]} + | {:subdirectories, [String.t()]} + | {:import_deps, [atom()]} + | {:export, keyword()} + | {:locals_without_parens, keyword()} + | {:line_length, pos_integer()} + | {:normalize_bitstring_modifiers, boolean()} + | {:normalize_charlists_as_binaries, boolean()} + ] + @doc """ Returns which features this plugin should plug into. """ - @callback features(Keyword.t()) :: [sigils: [atom()], extensions: [binary()]] + @callback features(features_opts) :: [sigils: [atom()], extensions: [binary()]] @doc """ Receives a string to be formatted with options and returns said string. """ - @callback format(String.t(), Keyword.t()) :: String.t() + @callback format(String.t(), format_opts) :: String.t() @impl true def run(all_args) do @@ -356,6 +396,16 @@ defmodule Mix.Tasks.Format do plugins end + @typedoc """ + Options for `formatter_for_file/2`. + """ + @type formatter_for_file_opts :: [ + deps_paths: %{atom() => String.t()}, + dot_formatter: String.t(), + plugin_loader: ([module()] -> [module()]), + root: String.t() + ] + @doc """ Returns a formatter function and the formatter options to be used for the given file. @@ -389,6 +439,8 @@ defmodule Mix.Tasks.Format do * `:root` - use the given root as the current working directory. """ @doc since: "1.13.0" + @spec formatter_for_file(String.t(), formatter_for_file_opts) :: + {(String.t() -> String.t()), keyword()} def formatter_for_file(file, opts \\ []) do cwd = Keyword.get_lazy(opts, :root, &File.cwd!/0) {dot_formatter, formatter_opts} = eval_dot_formatter(cwd, opts) @@ -809,8 +861,18 @@ defmodule Mix.Tasks.Format do end) end + @typedoc """ + Options for `text_diff_format/3`. + """ + @type text_diff_format_opts :: [ + after: non_neg_integer(), + before: non_neg_integer(), + color: boolean(), + line: pos_integer() + ] + @doc false - @spec text_diff_format(String.t(), String.t()) :: iolist() + @spec text_diff_format(String.t(), String.t(), text_diff_format_opts) :: iolist() def text_diff_format(old, new, opts \\ []) def text_diff_format(code, code, _opts), do: [] diff --git a/lib/mix/lib/mix/tasks/profile.cprof.ex b/lib/mix/lib/mix/tasks/profile.cprof.ex index 62abd3489a1..5eaf752d1c9 100644 --- a/lib/mix/lib/mix/tasks/profile.cprof.ex +++ b/lib/mix/lib/mix/tasks/profile.cprof.ex @@ -95,6 +95,15 @@ defmodule Mix.Tasks.Profile.Cprof do (for example, 2147483647 in a 32-bit host). """ + @typedoc """ + Options for the cprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + limit: non_neg_integer(), + module: module() + ] + @switches [ parallel: :boolean, require: :keep, @@ -172,7 +181,7 @@ defmodule Mix.Tasks.Profile.Cprof do * `:module` - filters out any results not pertaining to the given module """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {return_value, num_matched_functions, analysis_result} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.eprof.ex b/lib/mix/lib/mix/tasks/profile.eprof.ex index 52099b7247a..e4c134a01de 100644 --- a/lib/mix/lib/mix/tasks/profile.eprof.ex +++ b/lib/mix/lib/mix/tasks/profile.eprof.ex @@ -101,6 +101,18 @@ defmodule Mix.Tasks.Profile.Eprof do `Mix.Tasks.Profile.Cprof` that uses [`:cprof`](`:cprof`) and has a low performance degradation effect. """ + @typedoc """ + Options for the eprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + calls: non_neg_integer(), + time: non_neg_integer(), + sort: :time | :calls, + warmup: boolean(), + set_on_spawn: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -189,7 +201,7 @@ defmodule Mix.Tasks.Profile.Eprof do * `:set_on_spawn` - if newly spawned processes should be measured (default: `true`) """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {return_value, results} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/profile.fprof.ex b/lib/mix/lib/mix/tasks/profile.fprof.ex index 8a7f4cd4cbc..5fdf1840ac1 100644 --- a/lib/mix/lib/mix/tasks/profile.fprof.ex +++ b/lib/mix/lib/mix/tasks/profile.fprof.ex @@ -114,6 +114,16 @@ defmodule Mix.Tasks.Profile.Fprof do this should provide more realistic insights into bottlenecks. """ + @typedoc """ + Options for the fprof profiler. + """ + @type profile_opts :: [ + callers: boolean(), + details: boolean(), + sort: :acc | :own, + trace_to_file: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -184,7 +194,7 @@ defmodule Mix.Tasks.Profile.Fprof do usage for larger workloads. """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:runtime_tools) Mix.ensure_application!(:tools) diff --git a/lib/mix/lib/mix/tasks/profile.tprof.ex b/lib/mix/lib/mix/tasks/profile.tprof.ex index 163c9448cf1..1a266621589 100644 --- a/lib/mix/lib/mix/tasks/profile.tprof.ex +++ b/lib/mix/lib/mix/tasks/profile.tprof.ex @@ -144,6 +144,21 @@ defmodule Mix.Tasks.Profile.Tprof do which is more limited but has a lower footprint. """ + @typedoc """ + Options for the tprof profiler. + """ + @type profile_opts :: [ + matching: {module() | :_, atom() | :_, arity() | :_}, + type: :time | :memory | :calls, + calls: non_neg_integer(), + time: non_neg_integer(), + memory: non_neg_integer(), + sort: :calls | :per_call | :time | :memory, + report: :process | :total, + warmup: boolean(), + set_on_spawn: boolean() + ] + @switches [ parallel: :boolean, require: :keep, @@ -260,7 +275,7 @@ defmodule Mix.Tasks.Profile.Tprof do * `:set_on_spawn` - if newly spawned processes should be measured (default: `true`) """ - @spec profile((-> result), keyword()) :: result when result: any() + @spec profile((-> result), profile_opts()) :: result when result: any() def profile(fun, opts \\ []) when is_function(fun, 0) do Mix.ensure_application!(:tools) {type, return_value, results} = profile_and_analyse(fun, opts) diff --git a/lib/mix/lib/mix/tasks/run.ex b/lib/mix/lib/mix/tasks/run.ex index 101e6fca4be..043b18a74f9 100644 --- a/lib/mix/lib/mix/tasks/run.ex +++ b/lib/mix/lib/mix/tasks/run.ex @@ -65,6 +65,19 @@ defmodule Mix.Tasks.Run do """ + @typedoc """ + Options for the internal `run/5` function. + + These options are typically parsed from command-line arguments. + """ + @type run_opts :: [ + {:parallel_require, String.t()} + | {:eval, String.t()} + | {:require, String.t()} + | {:parallel, boolean()} + | {:config, String.t()} + ] + @impl true def run(args) do {opts, head} = @@ -99,7 +112,7 @@ defmodule Mix.Tasks.Run do @doc false @spec run( OptionParser.argv(), - keyword, + run_opts, OptionParser.argv(), (String.t() -> term()), (String.t() -> term()) diff --git a/lib/mix/lib/mix/utils.ex b/lib/mix/lib/mix/utils.ex index edf04d21221..3e95e7207c0 100644 --- a/lib/mix/lib/mix/utils.ex +++ b/lib/mix/lib/mix/utils.ex @@ -276,7 +276,7 @@ defmodule Mix.Utils do Returns the name of the file written to, or "-" if the output was to STDOUT. This function is made public mostly for testing. """ - @spec write_according_to_opts!(Path.t(), iodata(), keyword) :: Path.t() + @spec write_according_to_opts!(Path.t(), iodata(), write_opts) :: Path.t() def write_according_to_opts!(default_file_spec, contents, opts) do file_spec = Keyword.get(opts, :output, default_file_spec) @@ -305,7 +305,7 @@ defmodule Mix.Utils do If the `:output` option is `-` then prints to standard output, see write_according_to_opts!/3 for details. """ - @spec write_json_tree!(Path.t(), [node], (node -> {formatted_node, [node]}), keyword) :: + @spec write_json_tree!(Path.t(), [node], (node -> {formatted_node, [node]}), write_opts) :: Path.t() when node: term() def write_json_tree!(default_file_spec, nodes, callback, opts \\ []) do @@ -336,13 +336,36 @@ defmodule Mix.Utils do @type formatted_node :: {name :: String.Chars.t(), edge_info :: String.Chars.t()} + @typedoc """ + Options for `write_according_to_opts!/3`, `write_json_tree!/4`, and `write_dot_graph!/5`. + """ + @type write_opts :: [ + output: String.t() + ] + + @typedoc """ + Options for `print_tree/3`. + """ + @type print_tree_opts :: [ + format: String.t() + ] + + @typedoc """ + Options for `read_path/2`. + """ + @type read_path_opts :: [ + timeout: pos_integer(), + sha512: String.t() + ] + @doc """ Prints the given tree according to the callback. The callback will be invoked for each node and it must return a `{printed, children}` tuple. """ - @spec print_tree([node], (node -> {formatted_node, [node]}), keyword) :: :ok when node: term() + @spec print_tree([node], (node -> {formatted_node, [node]}), print_tree_opts) :: :ok + when node: term() def print_tree(nodes, callback, opts \\ []) do pretty? = case Keyword.get(opts, :format) do @@ -414,7 +437,7 @@ defmodule Mix.Utils do String.t(), [node], (node -> {formatted_node, [node]}), - keyword + write_opts ) :: Path.t() when node: term() def write_dot_graph!(default_file_spec, title, nodes, callback, opts \\ []) do @@ -632,7 +655,7 @@ defmodule Mix.Utils do seconds """ - @spec read_path(String.t(), keyword) :: + @spec read_path(String.t(), read_path_opts) :: {:ok, binary} | :badpath | {:remote, String.t()} From fa2c96ffc3172a4a380b198b8c2b3b1eafebe200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 1 Jul 2025 10:43:24 +0200 Subject: [PATCH 037/111] Remove specs which are pass through and from private modules --- lib/eex/lib/eex.ex | 7 ++- lib/eex/lib/eex/compiler.ex | 18 +------- lib/eex/lib/eex/engine.ex | 23 +++------- lib/elixir/lib/code.ex | 44 +++++++++++-------- lib/elixir/lib/code/formatter.ex | 24 +---------- lib/elixir/lib/code/normalizer.ex | 11 +---- lib/elixir/lib/io/ansi/docs.ex | 2 +- lib/elixir/lib/module/types/descr.ex | 11 ----- lib/elixir/lib/module/types/helpers.ex | 12 ++---- lib/iex/lib/iex/broker.ex | 9 +--- lib/mix/lib/mix/compilers/erlang.ex | 8 +++- lib/mix/lib/mix/dep.ex | 8 ---- lib/mix/lib/mix/dep/converger.ex | 10 ----- lib/mix/lib/mix/release.ex | 3 +- lib/mix/lib/mix/tasks/format.ex | 59 +++----------------------- 15 files changed, 60 insertions(+), 189 deletions(-) diff --git a/lib/eex/lib/eex.ex b/lib/eex/lib/eex.ex index 6d743c1ed5c..38a29e65328 100644 --- a/lib/eex/lib/eex.ex +++ b/lib/eex/lib/eex.ex @@ -125,7 +125,11 @@ defmodule EEx do | {:indentation, non_neg_integer} | {:trim, boolean()} - @type compile_opt :: tokenize_opt | {:engine, module()} | {:parser_options, Code.parser_opts()} + @type compile_opt :: + tokenize_opt + | {:engine, module()} + | {:parser_options, Code.parser_opts()} + | {atom(), term()} @doc """ Generates a function definition from the given string. @@ -137,6 +141,7 @@ defmodule EEx do template. The supported `options` are described [in the module docs](#module-options). + Additional options are passed to the underlying engine. ## Examples diff --git a/lib/eex/lib/eex/compiler.ex b/lib/eex/lib/eex/compiler.ex index f401adb553d..44003e0970f 100644 --- a/lib/eex/lib/eex/compiler.ex +++ b/lib/eex/lib/eex/compiler.ex @@ -10,22 +10,6 @@ defmodule EEx.Compiler do @h_spaces [?\s, ?\t] @all_spaces [?\s, ?\t, ?\n, ?\r] - @typedoc """ - Options for EEx compilation functions. - - These options control various aspects of EEx template compilation including - file information, parsing behavior, and the template engine to use. - """ - @type compile_opts :: [ - file: String.t(), - line: pos_integer(), - column: pos_integer(), - indentation: non_neg_integer(), - trim: boolean(), - parser_options: Code.parser_opts(), - engine: module() - ] - @doc """ Tokenize EEx contents. """ @@ -306,7 +290,7 @@ defmodule EEx.Compiler do and the engine together by handling the tokens and invoking the engine every time a full expression or text is received. """ - @spec compile([EEx.token()], String.t(), compile_opts) :: Macro.t() + @spec compile([EEx.token()], String.t(), keyword) :: Macro.t() def compile(tokens, source, opts) do file = opts[:file] || "nofile" line = opts[:line] || 1 diff --git a/lib/eex/lib/eex/engine.ex b/lib/eex/lib/eex/engine.ex index 8bb1858bcda..12234f048c8 100644 --- a/lib/eex/lib/eex/engine.ex +++ b/lib/eex/lib/eex/engine.ex @@ -14,29 +14,16 @@ defmodule EEx.Engine do @type state :: term - @typedoc """ - Options passed to engine initialization. - - These are the same options passed to `EEx.Compiler.compile/3`, - allowing engines to access compilation settings and customize - their behavior accordingly. - """ - @type init_opts :: [ - file: String.t(), - line: pos_integer(), - column: pos_integer(), - indentation: non_neg_integer(), - trim: boolean(), - parser_options: Code.parser_opts(), - engine: module() - ] - @doc """ Called at the beginning of every template. + It receives the options during compilation, including the + ones managed by EEx, such as `:line` and `:file`, as well + as custom engine options. + It must return the initial state. """ - @callback init(opts :: init_opts) :: state + @callback init(opts :: keyword) :: state @doc """ Called at the end of every template. diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index c098599f868..af738268409 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -251,18 +251,27 @@ defmodule Code do @typedoc """ Options for code formatting functions. """ - @type format_opts :: [ - file: binary(), - line: pos_integer(), - line_length: pos_integer(), - locals_without_parens: keyword(), - force_do_end_blocks: boolean(), - migrate: boolean(), - migrate_bitstring_modifiers: boolean(), - migrate_call_parens_on_pipe: boolean(), - migrate_charlists_as_sigils: boolean(), - migrate_unless: boolean() - ] + @type format_opt :: + {:file, binary()} + | {:line, pos_integer()} + | {:line_length, pos_integer()} + | {:locals_without_parens, keyword()} + | {:force_do_end_blocks, boolean()} + | {:migrate, boolean()} + | {:migrate_bitstring_modifiers, boolean()} + | {:migrate_call_parens_on_pipe, boolean()} + | {:migrate_charlists_as_sigils, boolean()} + | {:migrate_unless, boolean()} + | {atom(), term()} + + @typedoc """ + Options for `quoted_to_algebra/2`. + """ + @type quoted_to_algebra_opt :: + {:line, pos_integer() | nil} + | {:escape, boolean()} + | {:locals_without_parens, keyword()} + | {:comments, [term()]} @typedoc """ Options for parsing functions that convert strings to quoted expressions. @@ -1079,7 +1088,7 @@ defmodule Code do address the deprecation warnings. """ @doc since: "1.6.0" - @spec format_string!(binary, format_opts) :: iodata + @spec format_string!(binary, [format_opt]) :: iodata def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do line_length = Keyword.get(opts, :line_length, 98) @@ -1104,7 +1113,7 @@ defmodule Code do available options. """ @doc since: "1.6.0" - @spec format_file!(binary, format_opts) :: iodata + @spec format_file!(binary, [format_opt]) :: iodata def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do string = File.read!(file) formatted = format_string!(string, [file: file, line: 1] ++ opts) @@ -1516,14 +1525,13 @@ defmodule Code do `string_to_quoted/2`, setting this option to `false` will prevent it from escaping the sequences twice. Defaults to `true`. - See `format_string!/2` for the full list of formatting options including + See `format_string!/2` for the full list of formatting options including `:file`, `:line`, `:line_length`, `:locals_without_parens`, `:force_do_end_blocks`, `:syntax_colors`, and all migration options like `:migrate_charlists_as_sigils`. """ @doc since: "1.13.0" - @spec quoted_to_algebra(Macro.t(), [ - Code.Formatter.to_algebra_opt() | Code.Normalizer.normalize_opt() - ]) :: Inspect.Algebra.t() + @spec quoted_to_algebra(Macro.t(), [format_opt() | quoted_to_algebra_opt()]) :: + Inspect.Algebra.t() def quoted_to_algebra(quoted, opts \\ []) do quoted |> Code.Normalizer.normalize(opts) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index a4417282d04..70ea70ae0e8 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -155,32 +155,10 @@ defmodule Code.Formatter do @do_end_keywords [:rescue, :catch, :else, :after] - @typedoc """ - Options for `to_algebra/2`. - - These options include all the standard code formatting options plus - additional context like comments and syntax highlighting colors. - """ - @type to_algebra_opt :: - {:comments, [term()]} - | {:syntax_colors, keyword()} - | {:sigils, keyword()} - | {:file, binary()} - | {:line, pos_integer()} - | {:line_length, pos_integer()} - | {:locals_without_parens, keyword()} - | {:force_do_end_blocks, boolean()} - | {:migrate, boolean()} - | {:migrate_bitstring_modifiers, boolean()} - | {:migrate_call_parens_on_pipe, boolean()} - | {:migrate_charlists_as_sigils, boolean()} - | {:migrate_unless, boolean()} - | {atom(), term()} - @doc """ Converts the quoted expression into an algebra document. """ - @spec to_algebra(Macro.t(), [to_algebra_opt]) :: Inspect.Algebra.t() + @spec to_algebra(Macro.t(), keyword()) :: Inspect.Algebra.t() def to_algebra(quoted, opts \\ []) do comments = Keyword.get(opts, :comments, []) diff --git a/lib/elixir/lib/code/normalizer.ex b/lib/elixir/lib/code/normalizer.ex index e45a5aee6a8..a4aa9492ec1 100644 --- a/lib/elixir/lib/code/normalizer.ex +++ b/lib/elixir/lib/code/normalizer.ex @@ -10,20 +10,11 @@ defmodule Code.Normalizer do is_binary(x) or is_atom(x) - @typedoc """ - Options for `normalize/2`. - """ - @type normalize_opt :: - {:line, pos_integer() | nil} - | {:escape, boolean()} - | {:locals_without_parens, keyword()} - | {atom(), term()} - @doc """ Wraps literals in the quoted expression to conform to the AST format expected by the formatter. """ - @spec normalize(Macro.t(), [normalize_opt]) :: Macro.t() + @spec normalize(Macro.t(), keyword()) :: Macro.t() def normalize(quoted, opts \\ []) do line = Keyword.get(opts, :line, nil) escape = Keyword.get(opts, :escape, true) diff --git a/lib/elixir/lib/io/ansi/docs.ex b/lib/elixir/lib/io/ansi/docs.ex index 0291c62dfda..fe3a04bc1e4 100644 --- a/lib/elixir/lib/io/ansi/docs.ex +++ b/lib/elixir/lib/io/ansi/docs.ex @@ -44,7 +44,7 @@ defmodule IO.ANSI.Docs do Values for the color settings are strings with comma-separated ANSI values. """ - @spec default_options() :: keyword + @spec default_options() :: print_opts def default_options do [ enabled: true, diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 26ee9fe6164..a27b6975048 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -49,15 +49,6 @@ defmodule Module.Types.Descr do @empty_intersection [0, @none] @empty_difference [0, []] - # Type definitions - - @typedoc """ - Options for `to_quoted/2`. - """ - @type to_quoted_opts :: [ - collapse_structs: boolean() - ] - defguard is_descr(descr) when is_map(descr) or descr == :term defp descr_key?(:term, _key), do: true @@ -559,7 +550,6 @@ defmodule Module.Types.Descr do * `:collapse_structs` - do not show struct fields that match their default type """ - @spec to_quoted(term(), to_quoted_opts) :: Macro.t() def to_quoted(descr, opts \\ []) do if term_type?(descr) do {:term, [], []} @@ -634,7 +624,6 @@ defmodule Module.Types.Descr do * `:collapse_structs` - do not show struct fields that match their default type """ - @spec to_quoted(term(), to_quoted_opts) :: String.t() def to_quoted_string(descr, opts \\ []) do descr |> to_quoted(opts) diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex index 8e879e409ab..aea8c0b6074 100644 --- a/lib/elixir/lib/module/types/helpers.ex +++ b/lib/elixir/lib/module/types/helpers.ex @@ -6,13 +6,6 @@ defmodule Module.Types.Helpers do # AST and enumeration helpers. @moduledoc false - @typedoc """ - Options for `expr_to_string/2`. - """ - @type expr_to_string_opts :: [ - collapse_structs: boolean() - ] - ## AST helpers @doc """ @@ -278,8 +271,11 @@ defmodule Module.Types.Helpers do translating inlined Erlang calls back to Elixir. We also undo some macro expressions done by the Kernel module. + + ## Options + + * `:collapse_structs` - when false, show structs full representation """ - @spec expr_to_string(Macro.t(), expr_to_string_opts) :: String.t() def expr_to_string(expr, opts \\ []) do string = prewalk_expr_to_string(expr, opts) diff --git a/lib/iex/lib/iex/broker.ex b/lib/iex/lib/iex/broker.ex index c9b6d552d5c..d2d9ed57140 100644 --- a/lib/iex/lib/iex/broker.ex +++ b/lib/iex/lib/iex/broker.ex @@ -9,13 +9,6 @@ defmodule IEx.Broker do @type take_ref :: {takeover_ref :: reference(), server_ref :: reference()} @type shell :: pid | nil - @typedoc """ - Options for `take_over/3`. - """ - @type take_over_opts :: [ - evaluator: pid() - ] - use GenServer ## Shell API @@ -65,7 +58,7 @@ defmodule IEx.Broker do @doc """ Client requests a takeover. """ - @spec take_over(binary, iodata, take_over_opts) :: + @spec take_over(binary, iodata, keyword) :: {:ok, server :: pid, group_leader :: pid, counter :: integer} | {:error, :no_iex | :refused | atom()} def take_over(location, whereami, opts) do diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index 1ffa1425770..27d02037fde 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -11,9 +11,11 @@ defmodule Mix.Compilers.Erlang do Options for `compile/6`. """ @type compile_opts :: [ + all_warnings: boolean(), force: boolean(), parallel: MapSet.t(Path.t()), - preload: (-> term()) + preload: (-> term()), + verbose: boolean() ] @doc """ @@ -27,6 +29,8 @@ defmodule Mix.Compilers.Erlang do ## Options + * `:all_warnings` - a flag to disable showing all previous warnings + * `:force` - forces compilation regardless of modification times * `:parallel` - a mapset of files to compile in parallel @@ -34,6 +38,8 @@ defmodule Mix.Compilers.Erlang do * `:preload` - any code that must be preloaded if any pending entry needs to be compiled + * `:verbose` - a flag to enable verbose printing + ## Examples For example, a simple compiler for Lisp Flavored Erlang diff --git a/lib/mix/lib/mix/dep.ex b/lib/mix/lib/mix/dep.ex index 8887ec86f54..1dd953b4cd9 100644 --- a/lib/mix/lib/mix/dep.ex +++ b/lib/mix/lib/mix/dep.ex @@ -79,13 +79,6 @@ defmodule Mix.Dep do system_env: keyword } - @typedoc """ - Options for `filter_by_name/3`. - """ - @type filter_by_name_opts :: [ - include_children: boolean() - ] - @doc """ Returns loaded dependencies from the cache for the current environment. @@ -204,7 +197,6 @@ defmodule Mix.Dep do Raises if any of the names are missing. """ - @spec filter_by_name([atom() | String.t()], [t()], filter_by_name_opts) :: [t()] def filter_by_name(given, all_deps, opts \\ []) do # Ensure all apps are atoms apps = to_app_names(given) diff --git a/lib/mix/lib/mix/dep/converger.ex b/lib/mix/lib/mix/dep/converger.ex index c29073f53f7..1d036c49822 100644 --- a/lib/mix/lib/mix/dep/converger.ex +++ b/lib/mix/lib/mix/dep/converger.ex @@ -8,14 +8,6 @@ defmodule Mix.Dep.Converger do @moduledoc false - @typedoc """ - Options for `converge/1` and `converge/4`. - """ - @type converge_opts :: [ - env: atom(), - target: atom() - ] - @doc """ Topologically sorts the given dependencies. """ @@ -80,7 +72,6 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ - @spec converge(converge_opts) :: [Mix.Dep.t()] def converge(opts \\ []) do converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0) end @@ -98,7 +89,6 @@ defmodule Mix.Dep.Converger do See `Mix.Dep.Loader.children/1` for options. """ - @spec converge(term(), map() | nil, converge_opts, function()) :: {[Mix.Dep.t()], term(), map()} def converge(acc, lock, opts, callback) do {deps, acc, lock} = all(acc, lock, opts, callback) if remote = Mix.RemoteConverger.get(), do: remote.post_converge() diff --git a/lib/mix/lib/mix/release.ex b/lib/mix/lib/mix/release.ex index ff74c480797..d31a9b9b1f9 100644 --- a/lib/mix/lib/mix/release.ex +++ b/lib/mix/lib/mix/release.ex @@ -921,8 +921,7 @@ defmodule Mix.Release do ## Options * `:keep` - a list of additional chunk names (as strings) to keep in the - stripped BEAM file. These will be added to the significant chunks - determined by `:beam_lib.significant_chunks/0` + stripped BEAM file beyond those required by Erlang/Elixir * `:compress` - when `true`, the resulting BEAM file will be compressed using gzip. Defaults to `false` diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index 2abd3feced3..dbf76e483c8 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -223,55 +223,19 @@ defmodule Mix.Tasks.Format do ins: [text: :green, space: :green_background] ] - @typedoc """ - Options passed to plugin `features/1` callback. - - These are the same formatter options from `.formatter.exs` configuration, - allowing plugins to access formatting settings when determining their capabilities. - """ - @type features_opts :: [ - inputs: [String.t()], - plugins: [module()], - subdirectories: [String.t()], - import_deps: [atom()], - export: keyword(), - locals_without_parens: keyword(), - line_length: pos_integer(), - normalize_bitstring_modifiers: boolean(), - normalize_charlists_as_binaries: boolean() - ] - - @typedoc """ - Options passed to plugin `format/2` callback. - - These options include context-specific information about what is being formatted - (sigil, extension, file) along with all the standard formatter configuration options. - """ - @type format_opts :: [ - {:sigil, atom()} - | {:modifiers, charlist()} - | {:extension, String.t()} - | {:file, String.t()} - | {:inputs, [String.t()]} - | {:plugins, [module()]} - | {:subdirectories, [String.t()]} - | {:import_deps, [atom()]} - | {:export, keyword()} - | {:locals_without_parens, keyword()} - | {:line_length, pos_integer()} - | {:normalize_bitstring_modifiers, boolean()} - | {:normalize_charlists_as_binaries, boolean()} - ] - @doc """ Returns which features this plugin should plug into. + + It receives all options specified in `.formatter.exs`. """ - @callback features(features_opts) :: [sigils: [atom()], extensions: [binary()]] + @callback features(keyword) :: [sigils: [atom()], extensions: [binary()]] @doc """ Receives a string to be formatted with options and returns said string. + + It receives all options specified in `.formatter.exs`. """ - @callback format(String.t(), format_opts) :: String.t() + @callback format(String.t(), keyword) :: String.t() @impl true def run(all_args) do @@ -861,18 +825,7 @@ defmodule Mix.Tasks.Format do end) end - @typedoc """ - Options for `text_diff_format/3`. - """ - @type text_diff_format_opts :: [ - after: non_neg_integer(), - before: non_neg_integer(), - color: boolean(), - line: pos_integer() - ] - @doc false - @spec text_diff_format(String.t(), String.t(), text_diff_format_opts) :: iolist() def text_diff_format(old, new, opts \\ []) def text_diff_format(code, code, _opts), do: [] From d8bb1c40095c3a26faf673a5b2c72755e0bccddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Tue, 1 Jul 2025 15:53:34 +0200 Subject: [PATCH 038/111] Add missing erlang compiler options (#14614) Document options on leex and yecc compilers --- lib/mix/lib/mix/tasks/compile.erlang.ex | 3 ++- lib/mix/lib/mix/tasks/compile.leex.ex | 1 + lib/mix/lib/mix/tasks/compile.yecc.ex | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/mix/lib/mix/tasks/compile.erlang.ex b/lib/mix/lib/mix/tasks/compile.erlang.ex index 2140b4090ac..2261c180cb0 100644 --- a/lib/mix/lib/mix/tasks/compile.erlang.ex +++ b/lib/mix/lib/mix/tasks/compile.erlang.ex @@ -8,7 +8,7 @@ defmodule Mix.Tasks.Compile.Erlang do @recursive true @manifest "compile.erlang" - @switches [force: :boolean, all_warnings: :boolean] + @switches [force: :boolean, verbose: :boolean, all_warnings: :boolean] @moduledoc """ Compiles Erlang source files. @@ -23,6 +23,7 @@ defmodule Mix.Tasks.Compile.Erlang do * `--all-warnings` (`--no-all-warnings`) - prints all warnings, including previous compilations (default is true except on errors) * `--force` - forces compilation regardless of modification times + * `--verbose` - prints verbose output ## Configuration diff --git a/lib/mix/lib/mix/tasks/compile.leex.ex b/lib/mix/lib/mix/tasks/compile.leex.ex index 3eebff22444..deef8dec163 100644 --- a/lib/mix/lib/mix/tasks/compile.leex.ex +++ b/lib/mix/lib/mix/tasks/compile.leex.ex @@ -30,6 +30,7 @@ defmodule Mix.Tasks.Compile.Leex do * `--all-warnings` (`--no-all-warnings`) - prints all warnings, including previous compilations (default is true except on errors) * `--force` - forces compilation regardless of modification times + * `--verbose` - prints verbose output ## Configuration diff --git a/lib/mix/lib/mix/tasks/compile.yecc.ex b/lib/mix/lib/mix/tasks/compile.yecc.ex index 373565e1152..46650336ea0 100644 --- a/lib/mix/lib/mix/tasks/compile.yecc.ex +++ b/lib/mix/lib/mix/tasks/compile.yecc.ex @@ -8,7 +8,7 @@ defmodule Mix.Tasks.Compile.Yecc do @recursive true @manifest "compile.yecc" - @switches [force: :boolean, all_warnings: :boolean] + @switches [force: :boolean, verbose: :boolean, all_warnings: :boolean] # These options can't be controlled with :yecc_options. @forced_opts [report: true, return: true] @@ -30,6 +30,7 @@ defmodule Mix.Tasks.Compile.Yecc do * `--all-warnings` (`--no-all-warnings`) - prints all warnings, including previous compilations (default is true except on errors) * `--force` - forces compilation regardless of modification times + * `--verbose` - prints verbose output ## Configuration From 24e63ccc4722e2d4cccc66a03b2efb9aa603ef30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Tue, 1 Jul 2025 22:34:21 +0200 Subject: [PATCH 039/111] Fix parallel option type in Mix.Compilers.Erlang.compile/6 spec (#14615) compile.yecc and compile.leex tasks call it with parallel: true --- lib/mix/lib/mix/compilers/erlang.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index 27d02037fde..84af2218ea4 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -13,7 +13,7 @@ defmodule Mix.Compilers.Erlang do @type compile_opts :: [ all_warnings: boolean(), force: boolean(), - parallel: MapSet.t(Path.t()), + parallel: boolean() | MapSet.t(Path.t()), preload: (-> term()), verbose: boolean() ] From fcea4b4755200b193c2c0078e82d4ac51e21597b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 3 Jul 2025 10:24:19 +0200 Subject: [PATCH 040/111] Handle filesystem errors in iex helpers (#14618) `File.cd` and `File.ls` can return any posix error code --- lib/iex/lib/iex/helpers.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex index 9b9db7ad745..cf470754feb 100644 --- a/lib/iex/lib/iex/helpers.ex +++ b/lib/iex/lib/iex/helpers.ex @@ -1035,6 +1035,9 @@ defmodule IEx.Helpers do {:error, :enoent} -> IO.puts(IEx.color(:eval_error, "No directory #{directory}")) + + {:error, reason} -> + IO.puts(IEx.color(:eval_error, :file.format_error(reason))) end dont_display_result() @@ -1079,6 +1082,9 @@ defmodule IEx.Helpers do {:error, :enotdir} -> IO.puts(IEx.color(:eval_info, Path.absname(path))) + + {:error, reason} -> + IO.puts(IEx.color(:eval_error, :file.format_error(reason))) end dont_display_result() From a5b9c5b5db61d70b71c3272dbf7ddd84d14f532f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 3 Jul 2025 12:21:36 +0200 Subject: [PATCH 041/111] Add required field back to struct info Closes #14616. Closes #14617. Closes #14500. --- lib/elixir/lib/kernel/utils.ex | 2 +- lib/elixir/lib/macro.ex | 28 ++++++++++++++++++++++----- lib/elixir/lib/module.ex | 13 ++++++++++--- lib/elixir/test/elixir/macro_test.exs | 17 ++++++++-------- lib/elixir/test/elixir/map_test.exs | 2 +- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/elixir/lib/kernel/utils.ex b/lib/elixir/lib/kernel/utils.ex index 7fc379e66ec..c126ef10cf9 100644 --- a/lib/elixir/lib/kernel/utils.ex +++ b/lib/elixir/lib/kernel/utils.ex @@ -217,7 +217,7 @@ defmodule Kernel.Utils do case enforce_keys -- :maps.keys(struct) do [] -> mapper = fn {key, val} -> - %{field: key, default: val} + %{field: key, default: val, required: :lists.member(key, enforce_keys)} end :ets.insert(set, {{:elixir, :struct}, :lists.map(mapper, fields)}) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 5b9ce6e4f3b..b09a418f7e1 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -861,21 +861,39 @@ defmodule Macro do end @doc """ - Extracts the struct information (equivalent to calling - `module.__info__(:struct)`). + Extracts the struct information. This is useful when a struct needs to be expanded at compilation time and the struct being expanded may or may - not have been compiled. This function is also capable of - expanding structs defined under the module being compiled. + not have been compiled (including structs in the defined + under the module being compiled). For compiled modules, + it will invoke `module.__info__(:struct)`. + Calling this function also adds an export dependency on the given struct. It will raise `ArgumentError` if the struct is not available. + + ## Compatibility considerations + + This function currently returns both `:required` and `:default` + entries for each field. While this naming is inconsistent + (a required field should not have a default), this is done for + backwards compatibility purposes. + + In future releases, Elixir may introduce truly required struct + fields, and therefore only one of required or default will be + present. Your code should prepare for such scenario accordingly. """ @doc since: "1.18.0" @spec struct_info!(module(), Macro.Env.t()) :: - [%{field: atom(), required: boolean(), default: term()}] + [ + %{ + required(:field) => atom(), + optional(:required) => boolean(), + optional(:default) => term() + } + ] def struct_info!(module, env) when is_atom(module) do case :elixir_map.maybe_load_struct_info([line: env.line], module, [], true, env) do {:ok, info} -> info diff --git a/lib/elixir/lib/module.ex b/lib/elixir/lib/module.ex index 8f8549b8649..19ca9fe9b6a 100644 --- a/lib/elixir/lib/module.ex +++ b/lib/elixir/lib/module.ex @@ -681,7 +681,6 @@ defmodule Module do This function is generated for all modules. It's similar to `module_info/1` but includes some additional Elixir-specific information, such as struct and macro information. For documentation, see `c:Module.__info__/1`. - ''' @type definition :: {atom, arity} @@ -721,7 +720,8 @@ defmodule Module do * `:module` - the module atom name - * `:struct` - (since v1.14.0) if the module defines a struct and if so each field in order + * `:struct` - (since v1.14.0) if the module defines a struct and if so each field in order. + See `Macro.struct_info!/2` for more information """ @callback __info__(:attributes) :: keyword() @@ -731,7 +731,14 @@ defmodule Module do @callback __info__(:md5) :: binary() @callback __info__(:module) :: module() @callback __info__(:struct) :: - list(%{required(:field) => atom(), optional(:default) => term()}) | nil + [ + %{ + required(:field) => atom(), + optional(:required) => boolean(), + optional(:default) => term() + } + ] + | nil @doc """ Returns information about module attributes used by Elixir. diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index e53111362cd..c3f752eea40 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -1645,31 +1645,32 @@ defmodule MacroTest do test "struct_info!/2 expands structs multiple levels deep" do defmodule StructBang do + @enforce_keys [:b] defstruct [:a, :b] assert Macro.struct_info!(StructBang, __ENV__) == [ - %{field: :a, default: nil}, - %{field: :b, default: nil} + %{field: :a, default: nil, required: false}, + %{field: :b, default: nil, required: true} ] def within_function do assert Macro.struct_info!(StructBang, __ENV__) == [ - %{field: :a, default: nil}, - %{field: :b, default: nil} + %{field: :a, default: nil, required: false}, + %{field: :b, default: nil, required: true} ] end defmodule Nested do assert Macro.struct_info!(StructBang, __ENV__) == [ - %{field: :a, default: nil}, - %{field: :b, default: nil} + %{field: :a, default: nil, required: false}, + %{field: :b, default: nil, required: true} ] end end assert Macro.struct_info!(StructBang, __ENV__) == [ - %{field: :a, default: nil}, - %{field: :b, default: nil} + %{field: :a, default: nil, required: false}, + %{field: :b, default: nil, required: true} ] end diff --git a/lib/elixir/test/elixir/map_test.exs b/lib/elixir/test/elixir/map_test.exs index dd1dd6c5d83..c3ee4356674 100644 --- a/lib/elixir/test/elixir/map_test.exs +++ b/lib/elixir/test/elixir/map_test.exs @@ -401,7 +401,7 @@ defmodule MapTest do end info = Macro.struct_info!(WithBitstring, __ENV__) - assert info == [%{default: <<255, 127::7>>, field: :bitstring}] + assert info == [%{default: <<255, 127::7>>, field: :bitstring, required: false}] end test "defstruct can only be used once in a module" do From f4037a31babb24f9cf2d7fa23ca0ed733789920d Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Fri, 20 Jun 2025 14:20:08 +0200 Subject: [PATCH 042/111] Simplified tuple definitions by removing negations (#14596) --- lib/elixir/lib/module/types/descr.ex | 373 +++++------------- .../test/elixir/module/types/descr_test.exs | 12 +- 2 files changed, 117 insertions(+), 268 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index a27b6975048..2c41ec14ef2 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -30,7 +30,7 @@ defmodule Module.Types.Descr do @atom_top {:negation, :sets.new(version: 2)} @map_top [{:open, %{}, []}] @non_empty_list_top [{:term, :term, []}] - @tuple_top [{:open, [], []}] + @tuple_top [{:open, []}] @map_empty [{:closed, %{}, []}] @none %{} @@ -46,7 +46,7 @@ defmodule Module.Types.Descr do @not_non_empty_list Map.delete(@term, :list) @not_list Map.replace!(@not_non_empty_list, :bitmap, @bit_top - @bit_empty_list) - @empty_intersection [0, @none] + @empty_intersection [0, @none, []] @empty_difference [0, []] defguard is_descr(descr) when is_map(descr) or descr == :term @@ -191,9 +191,6 @@ defmodule Module.Types.Descr do * For gradual tuple types: processes both dynamic and static components separately, then combines them. - Internally it uses `tuple_reduce/4` with concatenation as the join function - and a transform that is simply the identity. - The list of arguments can be flattened into a broad domain by calling: |> Enum.zip_with(fn types -> Enum.reduce(types, &union/2) end) @@ -209,13 +206,8 @@ defmodule Module.Types.Descr do end end - # Call tuple_reduce to build the simple union of tuples that come from each map literal. - # Thus, initial is `[]`, join is concatenation, and the transform of a map literal - # with no negations is just to keep the map literal as is. defp tuple_elim_negations_static(%{tuple: dnf} = descr, transform) when map_size(descr) == 1 do - tuple_reduce(dnf, [], &Kernel.++/2, fn :closed, elements -> - [transform.(elements)] - end) + Enum.map(dnf, fn {:closed, elements} -> transform.(elements) end) end defp tuple_elim_negations_static(descr, _transform) when descr == %{}, do: [] @@ -527,19 +519,18 @@ defmodule Module.Types.Descr do not Map.has_key?(descr, :atom) and not Map.has_key?(descr, :bitmap) and not Map.has_key?(descr, :optional) and + not Map.has_key?(descr, :tuple) and (not Map.has_key?(descr, :map) or map_empty?(descr.map)) and (not Map.has_key?(descr, :list) or list_empty?(descr.list)) and - (not Map.has_key?(descr, :tuple) or tuple_empty?(descr.tuple)) and (not Map.has_key?(descr, :fun) or fun_empty?(descr.fun)) end end - # For atom, bitmap, and optional, if the key is present, + # For atom, bitmap, tuple, and optional, if the key is present, # then they are not empty, defp empty_key?(:fun, value), do: fun_empty?(value) defp empty_key?(:map, value), do: map_empty?(value) defp empty_key?(:list, value), do: list_empty?(value) - defp empty_key?(:tuple, value), do: tuple_empty?(value) defp empty_key?(_, _value), do: false @doc """ @@ -2962,6 +2953,7 @@ defmodule Module.Types.Descr do defp tuple_descr(tag, fields) do case tuple_descr(fields, [], false) do + :empty -> %{} {fields, true} -> %{dynamic: %{tuple: tuple_new(tag, Enum.reverse(fields))}} {_, false} -> %{tuple: tuple_new(tag, fields)} end @@ -2972,9 +2964,29 @@ defmodule Module.Types.Descr do end defp tuple_descr([value | rest], acc, dynamic?) do - case :maps.take(:dynamic, value) do - :error -> tuple_descr(rest, [value | acc], dynamic?) - {dynamic, _static} -> tuple_descr(rest, [dynamic | acc], true) + # Check if the static part is empty + static_empty? = + case value do + # Has dynamic component, check static separately + %{dynamic: _} -> false + _ -> empty?(value) + end + + if static_empty? do + :empty + else + case :maps.take(:dynamic, value) do + :error -> + tuple_descr(rest, [value | acc], dynamic?) + + {dynamic, _static} -> + # Check if dynamic component is empty + if empty?(dynamic) do + :empty + else + tuple_descr(rest, [dynamic | acc], true) + end + end end end @@ -2982,16 +2994,16 @@ defmodule Module.Types.Descr do {acc, dynamic?} end - defp tuple_new(tag, elements), do: [{tag, elements, []}] + defp tuple_new(tag, elements), do: [{tag, elements}] defp tuple_intersection(dnf1, dnf2) do - for {tag1, elements1, negs1} <- dnf1, - {tag2, elements2, negs2} <- dnf2, + for {tag1, elements1} <- dnf1, + {tag2, elements2} <- dnf2, reduce: [] do acc -> case tuple_literal_intersection(tag1, elements1, tag2, elements2) do {tag, elements} -> - entry = {tag, elements, negs1 ++ negs2} + entry = {tag, elements} case :lists.member(entry, acc) do true -> acc @@ -3002,10 +3014,6 @@ defmodule Module.Types.Descr do acc end end - |> case do - [] -> 0 - acc -> acc - end end defp tuple_literal_intersection(tag1, elements1, tag2, elements2) do @@ -3041,29 +3049,68 @@ defmodule Module.Types.Descr do end defp tuple_difference(dnf1, dnf2) do - Enum.reduce(dnf2, dnf1, fn {tag2, elements2, negs2}, dnf1 -> - Enum.reduce(dnf1, [], fn {tag1, elements1, negs1}, acc -> - # Prune negations that have no values in common - acc = - case tuple_literal_intersection(tag1, elements1, tag2, elements2) do - :empty -> [{tag1, elements1, negs1}] ++ acc - _ -> [{tag1, elements1, [{tag2, elements2} | negs1]}] ++ acc - end - - Enum.reduce(negs2, acc, fn {neg_tag2, neg_elements2}, inner_acc -> - case tuple_literal_intersection(tag1, elements1, neg_tag2, neg_elements2) do - :empty -> inner_acc - {tag, fields} -> [{tag, fields, negs1} | inner_acc] - end - end) + Enum.reduce(dnf2, dnf1, fn {tag2, elements2}, dnf1 -> + Enum.reduce(dnf1, [], fn {tag1, elements1}, acc -> + tuple_eliminate_single_negation(tag1, elements1, {tag2, elements2}) ++ acc end) end) - |> case do - [] -> 0 - acc -> acc + end + + defp tuple_eliminate_single_negation(tag, elements, {neg_tag, neg_elements}) do + n = length(elements) + m = length(neg_elements) + + # Scenarios where the difference is guaranteed to be empty: + # 1. When removing larger tuples from a fixed-size positive tuple + # 2. When removing smaller tuples from larger tuples + if (tag == :closed and n < m) or (neg_tag == :closed and n > m) do + [{tag, elements}] + else + tuple_elim_content([], tag, elements, neg_elements) ++ + tuple_elim_size(n, m, tag, elements, neg_tag) + end + end + + # Eliminates negations according to tuple content. + defp tuple_elim_content(_acc, _tag, _elements, []), do: [] + + # Subtracts each element of a negative tuple to build a new tuple with the difference. + # Example: {number(), atom()} and not {float(), :foo} contains types {integer(), :foo} + # as well as {float(), atom() and not :foo} + # Same process as tuple_elements_empty? + defp tuple_elim_content(acc, tag, elements, [neg_type | neg_elements]) do + {ty, elements} = List.pop_at(elements, 0, term()) + diff = difference(ty, neg_type) + + res = tuple_elim_content([ty | acc], tag, elements, neg_elements) + + if empty?(diff) do + res + else + [{tag, Enum.reverse(acc, [diff | elements])} | res] end end + # Eliminates negations according to size + # Example: {integer(), ...} and not {term(), term(), ...} contains {integer()} + # The tuples to consider are all those of size n to m - 1, and if the negative tuple is + # closed, we also need to consider tuples of size greater than m + 1. + defp tuple_elim_size(_, _, :closed, _, _), do: [] + + defp tuple_elim_size(n, m, :open, elements, neg_tag) do + n..(m - 1)//1 + |> Enum.reduce([], fn i, acc -> + [{:closed, tuple_fill(elements, i)} | acc] + end) + |> Kernel.++( + if neg_tag == :open do + [] + else + [{:open, tuple_fill(elements, m + 1)}] + end + ) + end + defp tuple_union(dnf1, dnf2) do # Union is just concatenation, but we rely on some optimization strategies to # avoid the list to grow when possible @@ -3079,14 +3126,14 @@ defmodule Module.Types.Descr do end end - defp maybe_optimize_tuple_union({tag1, pos1, []} = tuple1, {tag2, pos2, []} = tuple2) do + defp maybe_optimize_tuple_union({tag1, pos1} = tuple1, {tag2, pos2} = tuple2) do case tuple_union_optimization_strategy(tag1, pos1, tag2, pos2) do :all_equal -> tuple1 {:one_index_difference, index, v1, v2} -> new_pos = List.replace_at(pos1, index, union(v1, v2)) - {tag1, new_pos, []} + {tag1, new_pos} :left_subtype_of_right -> tuple2 @@ -3167,9 +3214,8 @@ defmodule Module.Types.Descr do defp tuple_to_quoted(dnf, opts) do dnf - |> tuple_simplify() |> tuple_fusion() - |> Enum.map(&tuple_each_to_quoted(&1, opts)) + |> Enum.map(&tuple_literal_to_quoted(&1, opts)) end # Given a dnf of tuples, fuses the tuple unions when possible, @@ -3181,14 +3227,9 @@ defmodule Module.Types.Descr do # 2. Group tuples by size and tag # 3. Try fusions for each group until no fusion is found # 4. Merge the groups back into a dnf - {without_negs, with_negs} = Enum.split_with(dnf, fn {_tag, _elems, negs} -> negs == [] end) - - without_negs = - without_negs - |> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end) - |> Enum.flat_map(fn {_, tuples} -> tuple_non_negated_fuse(tuples) end) - - without_negs ++ with_negs + dnf + |> Enum.group_by(fn {tag, elems} -> {tag, length(elems)} end) + |> Enum.flat_map(fn {_, tuples} -> tuple_non_negated_fuse(tuples) end) end defp tuple_non_negated_fuse(tuples) do @@ -3208,20 +3249,6 @@ defmodule Module.Types.Descr do end end - defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}, opts) do - case negative_tuples do - [] -> - tuple_literal_to_quoted({tag, positive_tuple}, opts) - - _ -> - negative_tuples - |> non_empty_map_or(&tuple_literal_to_quoted(&1, opts)) - |> Kernel.then( - &{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}, opts), {:not, [], [&1]}]} - ) - end - end - defp tuple_literal_to_quoted({:closed, []}, _opts), do: {:{}, [], []} defp tuple_literal_to_quoted({tag, elements}, opts) do @@ -3242,56 +3269,6 @@ defmodule Module.Types.Descr do end end - # Check if a tuple represented in DNF is empty - defp tuple_empty?(dnf) do - Enum.all?(dnf, fn {tag, pos, negs} -> tuple_empty?(tag, pos, negs) end) - end - - # No negations, so not empty unless there's an empty type - defp tuple_empty?(_, pos, []), do: Enum.any?(pos, &empty?/1) - # Open empty negation makes it empty - defp tuple_empty?(_, _, [{:open, []} | _]), do: true - # Open positive can't be emptied by a single closed negative - defp tuple_empty?(:open, pos, [{:closed, _}]), do: Enum.any?(pos, &empty?/1) - - defp tuple_empty?(tag, elements, [{neg_tag, neg_elements} | negs]) do - n = length(elements) - m = length(neg_elements) - - # Scenarios where the difference is guaranteed to be empty: - # 1. When removing larger tuples from a fixed-size positive tuple - # 2. When removing smaller tuples from larger tuples - if (tag == :closed and n < m) or (neg_tag == :closed and n > m) do - tuple_empty?(tag, elements, negs) - else - tuple_elements_empty?([], tag, elements, neg_elements, negs) and - tuple_compatibility(n, m, tag, elements, neg_tag, negs) - end - end - - # Recursively check elements for emptiness - defp tuple_elements_empty?(_, _, _, [], _), do: true - - defp tuple_elements_empty?(acc, tag, elements, [neg_type | neg_elements], negs) do - # Handles the case where {tag, elements} is an open tuple, like {:open, []} - {ty, elements} = List.pop_at(elements, 0, term()) - diff = difference(ty, neg_type) - - (empty?(diff) or tuple_empty?(tag, Enum.reverse(acc, [diff | elements]), negs)) and - tuple_elements_empty?([ty | acc], tag, elements, neg_elements, negs) - end - - # Determines if the set difference is empty when: - # - Positive tuple: {tag, elements} of size n - # - Negative tuple: open or closed tuples of size m - defp tuple_compatibility(n, m, tag, elements, neg_tag, negs) do - # The tuples to consider are all those of size n to m - 1, and if the negative tuple is - # closed, we also need to consider tuples of size greater than m + 1. - tag == :closed or - (Enum.all?(n..(m - 1)//1, &tuple_empty?(:closed, tuple_fill(elements, &1), negs)) and - (neg_tag == :open or tuple_empty?(:open, tuple_fill(elements, m + 1), negs))) - end - @doc """ Fetches the type of the value returned by accessing `index` on `tuple` with the assumption that the descr is exclusively a tuple (or dynamic). @@ -3370,43 +3347,23 @@ defmodule Module.Types.Descr do defp tuple_fetch_static(descr, index) when is_integer(index) do case descr do - :term -> - {true, term()} - - %{tuple: tuple} -> - tuple_get(tuple, index) - |> pop_optional_static() - - %{} -> - {false, none()} + :term -> {true, term()} + %{tuple: tuple} -> tuple_get(tuple, index) |> pop_optional_static() + %{} -> {false, none()} end end defp tuple_get(dnf, index) do Enum.reduce(dnf, none(), fn - # Optimization: if there are no negatives, just return the type at that index. - {tag, elements, []}, acc -> - Enum.at(elements, index, tag_to_type(tag)) |> union(acc) - - {tag, elements, negs}, acc -> - {fst, snd} = tuple_pop_index(tag, elements, index) - - case tuple_split_negative(negs, index) do - :empty -> - acc - - negative -> - negative - |> pair_make_disjoint() - |> pair_eliminate_negations_fst(fst, snd) - |> union(acc) - end + {tag, elements}, acc -> Enum.at(elements, index, tag_to_type(tag)) |> union(acc) end) end @doc """ Returns all of the values that are part of a tuple. """ + def tuple_values(descr) when descr == %{}, do: :badtuple + def tuple_values(descr) do case :maps.take(:dynamic, descr) do :error -> @@ -3427,100 +3384,16 @@ defmodule Module.Types.Descr do end defp process_tuples_values(dnf) do - tuple_reduce(dnf, none(), &union/2, fn tag, elements -> + Enum.reduce(dnf, none(), fn {tag, elements}, acc -> cond do Enum.any?(elements, &empty?/1) -> none() tag == :open -> term() tag == :closed -> Enum.reduce(elements, none(), &union/2) end + |> union(acc) end) end - defp tuple_reduce(dnf, initial, join, transform) do - Enum.reduce(dnf, initial, fn {tag, elements, negs}, acc -> - join.(acc, tuple_reduce(tag, elements, negs, initial, join, transform)) - end) - end - - defp tuple_reduce(tag, elements, [], _init, _join, transform), do: transform.(tag, elements) - defp tuple_reduce(_tag, _elements, [{:open, []} | _], initial, _join, _transform), do: initial - - defp tuple_reduce(tag, elements, [{neg_tag, neg_elements} | negs], initial, join, transform) do - n = length(elements) - m = length(neg_elements) - - if (tag == :closed and n < m) or (neg_tag == :closed and n > m) do - tuple_reduce(tag, elements, negs, initial, join, transform) - else - # Those two functions eliminate the negations, transforming into - # a union of tuples to compute their values. - elim_content([], tag, elements, neg_elements, negs, initial, join, transform) - |> join.(elim_size(n, m, tag, elements, neg_tag, negs, initial, join, transform)) - end - end - - # Eliminates negations according to tuple content. - # This means that there are no more neg_elements to subtract -- end the recursion. - defp elim_content(_acc, _tag, _elements, [], _, initial, _join, _transform), do: initial - - # Subtracts each element of a negative tuple to build a new tuple with the difference. - # Example: {number(), atom()} and not {float(), :foo} contains types {integer(), :foo} - # as well as {float(), atom() and not :foo} - # Same process as tuple_elements_empty? - defp elim_content(acc, tag, elements, [neg_type | neg_elements], negs, init, join, transform) do - {ty, elements} = List.pop_at(elements, 0, term()) - diff = difference(ty, neg_type) - - if empty?(diff) do - init - else - tuple_reduce(tag, Enum.reverse(acc, [diff | elements]), negs, init, join, transform) - end - |> join.(elim_content([ty | acc], tag, elements, neg_elements, negs, init, join, transform)) - end - - # Eliminates negations according to size - # Example: {integer(), ...} and not {term(), term(), ...} contains {integer()} - defp elim_size(_, _, :closed, _, _, _, initial, _join, _transfo), do: initial - - defp elim_size(n, m, tag, elements, neg_tag, negs, initial, join, transform) do - n..(m - 1)//1 - |> Enum.reduce(initial, fn i, acc -> - tuple_reduce(:closed, tuple_fill(elements, i), negs, initial, join, transform) - |> join.(acc) - end) - |> join.( - if neg_tag == :open do - initial - else - tuple_reduce(tag, tuple_fill(elements, m + 1), negs, initial, join, transform) - end - ) - end - - defp tuple_pop_index(tag, elements, index) do - case List.pop_at(elements, index) do - {nil, _} -> {tag_to_type(tag), %{tuple: [{tag, elements, []}]}} - {type, rest} -> {type, %{tuple: [{tag, rest, []}]}} - end - end - - defp tuple_split_negative(negs, index) do - Enum.reduce_while(negs, [], fn - {:open, []}, _acc -> {:halt, :empty} - {tag, elements}, acc -> {:cont, [tuple_pop_index(tag, elements, index) | acc]} - end) - end - - # Use heuristics to simplify a tuple dnf for pretty printing. - defp tuple_simplify(dnf) do - for {tag, elements, negs} <- dnf, - not tuple_empty?([{tag, elements, negs}]) do - n = length(elements) - {tag, elements, Enum.reject(negs, &tuple_empty_negation?(tag, n, &1))} - end - end - @doc """ Delete an element from the tuple. @@ -3571,25 +3444,7 @@ defmodule Module.Types.Descr do # Takes a static map type and removes an index from it. defp tuple_delete_static(%{tuple: dnf}, index) do - Enum.reduce(dnf, none(), fn - # Optimization: if there are no negatives, we can directly remove the element - {tag, elements, []}, acc -> - union(acc, %{tuple: tuple_new(tag, List.delete_at(elements, index))}) - - {tag, elements, negs}, acc -> - {fst, snd} = tuple_pop_index(tag, elements, index) - - case tuple_split_negative(negs, index) do - :empty -> - acc - - negative -> - negative - |> pair_make_disjoint() - |> pair_eliminate_negations_snd(fst, snd) - |> union(acc) - end - end) + %{tuple: Enum.map(dnf, fn {tag, elements} -> {tag, List.delete_at(elements, index)} end)} end # If there is no map part to this static type, there is nothing to delete. @@ -3653,31 +3508,21 @@ defmodule Module.Types.Descr do defp tuple_insert_static(descr, index, type) do Map.update!(descr, :tuple, fn dnf -> - Enum.map(dnf, fn {tag, elements, negs} -> - {tag, List.insert_at(elements, index, type), - Enum.map(negs, fn {neg_tag, neg_elements} -> - {neg_tag, List.insert_at(neg_elements, index, type)} - end)} + Enum.map(dnf, fn {tag, elements} -> + {tag, List.insert_at(elements, index, type)} end) end) end - # Remove useless negations, which denote tuples of incompatible sizes. - defp tuple_empty_negation?(tag, n, {neg_tag, neg_elements}) do - m = length(neg_elements) - (tag == :closed and n < m) or (neg_tag == :closed and n > m) - end - defp tuple_of_size_at_least(n) when is_integer(n) and n >= 0 do - tuple_descr(:open, List.duplicate(term(), n)) + %{tuple: tuple_new(:open, List.duplicate(term(), n))} end defp tuple_of_size_at_least_static?(descr, index) do case descr do %{tuple: dnf} -> Enum.all?(dnf, fn - {_, elements, []} -> length(elements) >= index - entry -> subtype?(%{tuple: [entry]}, tuple_of_size_at_least(index)) + {_, elements} -> length(elements) >= index end) %{} -> diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index 76c1e14021e..772da3e660e 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -1113,6 +1113,8 @@ defmodule Module.Types.DescrTest do test "tuple_fetch" do assert tuple_fetch(term(), 0) == :badtuple assert tuple_fetch(integer(), 0) == :badtuple + assert tuple_fetch(tuple([none(), atom()]), 1) == :badtuple + assert tuple_fetch(tuple([none()]), 0) == :badtuple assert tuple_fetch(tuple([integer(), atom()]), 0) == {false, integer()} assert tuple_fetch(tuple([integer(), atom()]), 1) == {false, atom()} @@ -1124,7 +1126,7 @@ defmodule Module.Types.DescrTest do assert tuple_fetch(tuple([integer(), atom()]), -1) == :badindex assert tuple_fetch(empty_tuple(), 0) == :badindex - assert difference(tuple(), tuple()) |> tuple_fetch(0) == :badindex + assert difference(tuple(), tuple()) |> tuple_fetch(0) == :badtuple assert tuple([atom()]) |> difference(empty_tuple()) |> tuple_fetch(0) == {false, atom()} @@ -1149,7 +1151,7 @@ defmodule Module.Types.DescrTest do assert tuple([integer(), atom(), integer()]) |> difference(tuple([integer(), term(), integer()])) - |> tuple_fetch(1) == :badindex + |> tuple_fetch(1) == :badtuple assert tuple([integer(), atom(), integer()]) |> difference(tuple([integer(), term(), atom()])) @@ -1177,6 +1179,7 @@ defmodule Module.Types.DescrTest do assert tuple_delete_at(empty_tuple(), 0) == :badindex assert tuple_delete_at(integer(), 0) == :badtuple assert tuple_delete_at(term(), 0) == :badtuple + assert tuple_delete_at(tuple([none()]), 0) == :badtuple # Test deleting an element from a closed tuple assert tuple_delete_at(tuple([integer(), atom(), boolean()]), 1) == @@ -1195,8 +1198,8 @@ defmodule Module.Types.DescrTest do dynamic(tuple([integer()])) # Test deleting from a union of tuples - assert tuple_delete_at(union(tuple([integer(), atom()]), tuple([float(), binary()])), 1) == - union(tuple([integer()]), tuple([float()])) + assert tuple_delete_at(union(tuple([integer(), atom()]), tuple([float(), binary()])), 1) + |> equal?(tuple([union(integer(), float())])) # Test deleting from an intersection of tuples assert intersection(tuple([integer(), atom()]), tuple([term(), boolean()])) @@ -1287,6 +1290,7 @@ defmodule Module.Types.DescrTest do test "tuple_values" do assert tuple_values(integer()) == :badtuple + assert tuple_values(tuple([none()])) == :badtuple assert tuple_values(tuple([])) == none() assert tuple_values(tuple()) == term() assert tuple_values(open_tuple([integer()])) == term() From e73dd0c17d2fce329cab0ad5d14700edcd4e09e3 Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Thu, 3 Jul 2025 11:39:54 +0200 Subject: [PATCH 043/111] Perf optimizations and inferred intersections (#14605) --- lib/elixir/lib/module/types/apply.ex | 4 +- lib/elixir/lib/module/types/descr.ex | 135 +++++++++++++----- lib/elixir/lib/module/types/expr.ex | 8 +- .../test/elixir/module/types/descr_test.exs | 73 ++++++---- .../test/elixir/module/types/expr_test.exs | 31 ++-- .../elixir/module/types/integration_test.exs | 4 +- 6 files changed, 181 insertions(+), 74 deletions(-) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index b03889546d6..2b3be0bd3bf 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -487,7 +487,7 @@ defmodule Module.Types.Apply do {union(type, fun_from_non_overlapping_clauses(clauses)), fallback?, context} {{:infer, _, clauses}, context} when length(clauses) <= @max_clauses -> - {union(type, fun_from_overlapping_clauses(clauses)), fallback?, context} + {union(type, fun_from_inferred_clauses(clauses)), fallback?, context} {_, context} -> {type, true, context} @@ -705,7 +705,7 @@ defmodule Module.Types.Apply do result = case info do {:infer, _, clauses} when length(clauses) <= @max_clauses -> - fun_from_overlapping_clauses(clauses) + fun_from_inferred_clauses(clauses) _ -> dynamic(fun(arity)) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 2c41ec14ef2..772e63e85db 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -46,8 +46,8 @@ defmodule Module.Types.Descr do @not_non_empty_list Map.delete(@term, :list) @not_list Map.replace!(@not_non_empty_list, :bitmap, @bit_top - @bit_empty_list) - @empty_intersection [0, @none, []] - @empty_difference [0, []] + @empty_intersection [0, @none, [], :fun_bottom] + @empty_difference [0, [], :fun_bottom] defguard is_descr(descr) when is_map(descr) or descr == :term @@ -135,16 +135,17 @@ defmodule Module.Types.Descr do @doc """ Creates a function from overlapping function clauses. """ - def fun_from_overlapping_clauses(args_clauses) do + def fun_from_inferred_clauses(args_clauses) do domain_clauses = Enum.reduce(args_clauses, [], fn {args, return}, acc -> - pivot_overlapping_clause(args_to_domain(args), return, acc) + domain = args |> Enum.map(&upper_bound/1) |> args_to_domain() + pivot_overlapping_clause(domain, upper_bound(return), acc) end) funs = for {domain, return} <- domain_clauses, args <- domain_to_args(domain), - do: fun(args, return) + do: fun(args, dynamic(return)) Enum.reduce(funs, &intersection/2) end @@ -198,19 +199,19 @@ defmodule Module.Types.Descr do def domain_to_args(descr) do case :maps.take(:dynamic, descr) do :error -> - tuple_elim_negations_static(descr, &Function.identity/1) + unwrap_domain_tuple(descr, fn {:closed, elems} -> elems end) {dynamic, static} -> - tuple_elim_negations_static(static, &Function.identity/1) ++ - tuple_elim_negations_static(dynamic, fn elems -> Enum.map(elems, &dynamic/1) end) + unwrap_domain_tuple(static, fn {:closed, elems} -> elems end) ++ + unwrap_domain_tuple(dynamic, fn {:closed, elems} -> Enum.map(elems, &dynamic/1) end) end end - defp tuple_elim_negations_static(%{tuple: dnf} = descr, transform) when map_size(descr) == 1 do - Enum.map(dnf, fn {:closed, elements} -> transform.(elements) end) + defp unwrap_domain_tuple(%{tuple: dnf} = descr, transform) when map_size(descr) == 1 do + Enum.map(dnf, transform) end - defp tuple_elim_negations_static(descr, _transform) when descr == %{}, do: [] + defp unwrap_domain_tuple(descr, _transform) when descr == %{}, do: [] defp domain_to_flat_args(domain, arity) do case domain_to_args(domain) do @@ -1173,6 +1174,7 @@ defmodule Module.Types.Descr do static_arrows == [] -> # TODO: We need to validate this within the theory + arguments = Enum.map(arguments, &upper_bound/1) {:ok, dynamic(fun_apply_static(arguments, dynamic_arrows, false))} true -> @@ -1327,9 +1329,9 @@ defmodule Module.Types.Descr do if subtype?(rets_reached, result), do: result, else: union(result, rets_reached) end - defp aux_apply(result, input, returns_reached, [{dom, ret} | arrow_intersections]) do + defp aux_apply(result, input, returns_reached, [{args, ret} | arrow_intersections]) do # Calculate the part of the input not covered by this arrow's domain - dom_subtract = difference(input, args_to_domain(dom)) + dom_subtract = difference(input, args_to_domain(args)) # Refine the return type by intersecting with this arrow's return type ret_refine = intersection(returns_reached, ret) @@ -1426,7 +1428,7 @@ defmodule Module.Types.Descr do # determines emptiness. length(neg_arguments) == positive_arity and subtype?(args_to_domain(neg_arguments), positive_domain) and - phi_starter(neg_arguments, negation(neg_return), positives) + phi_starter(neg_arguments, neg_return, positives) end) end end @@ -1464,27 +1466,75 @@ defmodule Module.Types.Descr do # # See [Castagna and Lanvin (2024)](https://arxiv.org/abs/2408.14345), Theorem 4.2. defp phi_starter(arguments, return, positives) do - n = length(arguments) - # Arity mismatch: if there is one positive function with a different arity, - # then it cannot be a subtype of the (arguments->type) functions. - if Enum.any?(positives, fn {args, _ret} -> length(args) != n end) do - false + # Optimization: When all positive functions have non-empty domains, + # we can simplify the phi function check to a direct subtyping test. + # This avoids the expensive recursive phi computation by checking only that applying the + # input to the positive intersection yields a subtype of the return + if all_non_empty_domains?([{arguments, return} | positives]) do + fun_apply_static(arguments, [positives], false) + |> subtype?(return) else - arguments = Enum.map(arguments, &{false, &1}) - phi(arguments, {false, return}, positives) + n = length(arguments) + # Arity mismatch: functions with different arities cannot be subtypes + # of the target function type (arguments -> return) + if Enum.any?(positives, fn {args, _ret} -> length(args) != n end) do + false + else + # Initialize memoization cache for the recursive phi computation + arguments = Enum.map(arguments, &{false, &1}) + {result, _cache} = phi(arguments, {false, negation(return)}, positives, %{}) + result + end end end - defp phi(args, {b, t}, []) do - Enum.any?(args, fn {bool, typ} -> bool and empty?(typ) end) or (b and empty?(t)) + defp phi(args, {b, t}, [], cache) do + {Enum.any?(args, fn {bool, typ} -> bool and empty?(typ) end) or (b and empty?(t)), cache} end - defp phi(args, {b, ret}, [{arguments, return} | rest_positive]) do - phi(args, {true, intersection(ret, return)}, rest_positive) and - Enum.all?(Enum.with_index(arguments), fn {type, index} -> - List.update_at(args, index, fn {_, arg} -> {true, difference(arg, type)} end) - |> phi({b, ret}, rest_positive) - end) + defp phi(args, {b, ret}, [{arguments, return} | rest_positive], cache) do + # Create cache key from function arguments + cache_key = {args, {b, ret}, [{arguments, return} | rest_positive]} + + case Map.get(cache, cache_key) do + nil -> + # Compute result and cache it + {result1, cache} = phi(args, {true, intersection(ret, return)}, rest_positive, cache) + + if not result1 do + # Store false result in cache + cache = Map.put(cache, cache_key, false) + {false, cache} + else + # This doesn't stop if one intermediate result is false? + {result2, cache} = + Enum.with_index(arguments) + |> Enum.reduce_while({true, cache}, fn {type, index}, {acc_result, acc_cache} -> + {new_result, new_cache} = + List.update_at(args, index, fn {_, arg} -> {true, difference(arg, type)} end) + |> phi({b, ret}, rest_positive, acc_cache) + + if new_result do + {:cont, {acc_result and new_result, new_cache}} + else + {:halt, {false, new_cache}} + end + end) + + result = result1 and result2 + # Store result in cache + cache = Map.put(cache, cache_key, result) + {result, cache} + end + + cached_result -> + # Return cached result + {cached_result, cache} + end + end + + defp all_non_empty_domains?(positives) do + Enum.all?(positives, fn {args, _ret} -> not empty?(args_to_domain(args)) end) end defp fun_union(bdd1, bdd2) do @@ -1831,6 +1881,10 @@ defmodule Module.Types.Descr do # b) If only the last type differs, subtracts it # 3. Base case: adds dnf2 type to negations of dnf1 type # The result may be larger than the initial dnf1, which is maintained in the accumulator. + defp list_difference(_, dnf) when dnf == @non_empty_list_top do + 0 + end + defp list_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn {t2, last2, negs2}, acc_dnf1 -> last2 = list_tail_unfold(last2) @@ -1858,6 +1912,8 @@ defmodule Module.Types.Descr do end) end + defp list_empty?(@non_empty_list_top), do: false + defp list_empty?(dnf) do Enum.all?(dnf, fn {list_type, last_type, negs} -> last_type = list_tail_unfold(last_type) @@ -2118,9 +2174,6 @@ defmodule Module.Types.Descr do defp dynamic_to_quoted(descr, opts) do cond do - descr == %{} -> - [] - # We check for :term literally instead of using term_type? # because we check for term_type? in to_quoted before we # compute the difference(dynamic, static). @@ -2130,6 +2183,9 @@ defmodule Module.Types.Descr do single = indivisible_bitmap(descr, opts) -> [single] + empty?(descr) -> + [] + true -> case non_term_type_to_quoted(descr, opts) do {:none, _meta, []} = none -> [none] @@ -2398,6 +2454,10 @@ defmodule Module.Types.Descr do if empty?(type), do: throw(:empty), else: type end + defp map_difference(_, dnf) when dnf == @map_top do + 0 + end + defp map_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn # Optimization: we are removing an open map with one field. @@ -3048,10 +3108,15 @@ defmodule Module.Types.Descr do zip_non_empty_intersection!(rest1, rest2, [non_empty_intersection!(type1, type2) | acc]) end + defp tuple_difference(_, dnf) when dnf == @tuple_top do + 0 + end + defp tuple_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn {tag2, elements2}, dnf1 -> Enum.reduce(dnf1, [], fn {tag1, elements1}, acc -> - tuple_eliminate_single_negation(tag1, elements1, {tag2, elements2}) ++ acc + tuple_eliminate_single_negation(tag1, elements1, {tag2, elements2}) + |> tuple_union(acc) end) end) end @@ -3066,8 +3131,10 @@ defmodule Module.Types.Descr do if (tag == :closed and n < m) or (neg_tag == :closed and n > m) do [{tag, elements}] else - tuple_elim_content([], tag, elements, neg_elements) ++ + tuple_union( + tuple_elim_content([], tag, elements, neg_elements), tuple_elim_size(n, m, tag, elements, neg_tag) + ) end end diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex index e2ced16e6f5..6893035530c 100644 --- a/lib/elixir/lib/module/types/expr.ex +++ b/lib/elixir/lib/module/types/expr.ex @@ -355,7 +355,7 @@ defmodule Module.Types.Expr do add_inferred(acc, args, body) end) - {fun_from_overlapping_clauses(acc), context} + {fun_from_inferred_clauses(acc), context} end end @@ -476,7 +476,11 @@ defmodule Module.Types.Expr do {args_types, context} = Enum.map_reduce(args, context, &of_expr(&1, @pending, &1, stack, &2)) - Apply.fun_apply(fun_type, args_types, call, stack, context) + if stack.mode == :traversal do + {dynamic(), context} + else + Apply.fun_apply(fun_type, args_types, call, stack, context) + end end def of_expr({{:., _, [callee, key_or_fun]}, meta, []} = call, expected, expr, stack, context) diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index 772da3e660e..b8f54851a75 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -668,6 +668,11 @@ defmodule Module.Types.DescrTest do assert subtype?(f, fun([none(), integer()], term())) assert subtype?(fun([none(), number()], atom()), f) assert subtype?(fun([tuple(), number()], atom()), f) + + # (none, float -> atom) is not a subtype of (none, integer -> atom) + # because float has an empty intersection with integer. + # it's only possible to find this out by doing the + # intersection one by one. refute subtype?(fun([none(), float()], atom()), f) refute subtype?(fun([pid(), float()], atom()), f) # A function with the wrong arity is refused @@ -761,54 +766,72 @@ defmodule Module.Types.DescrTest do intersection(fun([integer()], atom()), fun([float()], binary())) end - test "fun_from_overlapping_clauses" do + test "fun_from_inferred_clauses" do # No overlap - assert fun_from_overlapping_clauses([{[integer()], atom()}, {[float()], binary()}]) + assert fun_from_inferred_clauses([{[integer()], atom()}, {[float()], binary()}]) |> equal?( - fun_from_non_overlapping_clauses([{[integer()], atom()}, {[float()], binary()}]) + intersection( + fun_from_non_overlapping_clauses([{[integer()], atom()}, {[float()], binary()}]), + fun([number()], dynamic()) + ) ) # Subsets - assert fun_from_overlapping_clauses([{[integer()], atom()}, {[number()], binary()}]) + assert fun_from_inferred_clauses([{[integer()], atom()}, {[number()], binary()}]) |> equal?( - fun_from_non_overlapping_clauses([ - {[integer()], union(atom(), binary())}, - {[float()], binary()} - ]) + intersection( + fun_from_non_overlapping_clauses([ + {[integer()], union(atom(), binary())}, + {[float()], binary()} + ]), + fun([number()], dynamic()) + ) ) - assert fun_from_overlapping_clauses([{[number()], binary()}, {[integer()], atom()}]) + assert fun_from_inferred_clauses([{[number()], binary()}, {[integer()], atom()}]) |> equal?( - fun_from_non_overlapping_clauses([ - {[integer()], union(atom(), binary())}, - {[float()], binary()} - ]) + intersection( + fun_from_non_overlapping_clauses([ + {[integer()], union(atom(), binary())}, + {[float()], binary()} + ]), + fun([number()], dynamic()) + ) ) # Partial - assert fun_from_overlapping_clauses([ + assert fun_from_inferred_clauses([ {[union(integer(), pid())], atom()}, {[union(float(), pid())], binary()} ]) |> equal?( - fun_from_non_overlapping_clauses([ - {[integer()], atom()}, - {[float()], binary()}, - {[pid()], union(atom(), binary())} - ]) + intersection( + fun_from_non_overlapping_clauses([ + {[integer()], atom()}, + {[float()], binary()}, + {[pid()], union(atom(), binary())} + ]), + fun([union(number(), pid())], dynamic()) + ) ) # Difference - assert fun_from_overlapping_clauses([ + assert fun_from_inferred_clauses([ {[integer(), union(pid(), atom())], atom()}, {[number(), pid()], binary()} ]) |> equal?( - fun_from_non_overlapping_clauses([ - {[float(), pid()], binary()}, - {[integer(), atom()], atom()}, - {[integer(), pid()], union(atom(), binary())} - ]) + intersection( + fun_from_non_overlapping_clauses([ + {[float(), pid()], binary()}, + {[integer(), atom()], atom()}, + {[integer(), pid()], union(atom(), binary())} + ]), + fun_from_non_overlapping_clauses([ + {[integer(), union(pid(), atom())], dynamic()}, + {[number(), pid()], dynamic()} + ]) + ) ) end end diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 076bcbf2c8b..c6b97fd692b 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -118,24 +118,37 @@ defmodule Module.Types.ExprTest do describe "funs" do test "infers functions" do - assert typecheck!(& &1) == fun([dynamic()], dynamic()) - assert typecheck!(fn -> :ok end) == fun([], atom([:ok])) + assert typecheck!(& &1) |> equal?(fun([term()], dynamic())) + + assert typecheck!(fn -> :ok end) |> equal?(fun([], dynamic(atom([:ok])))) assert typecheck!(fn <<"ok">>, {} -> :ok <<"error">>, {} -> :error [_ | _], %{} -> :list - end) == + end) + |> equal?( intersection( fun( - [dynamic(non_empty_list(term(), term())), dynamic(open_map())], - atom([:list]) + [non_empty_list(term(), term()), open_map()], + dynamic(atom([:list])) ), fun( - [dynamic(binary()), dynamic(tuple([]))], - atom([:ok, :error]) + [binary(), tuple([])], + dynamic(atom([:ok, :error])) ) ) + ) + end + + test "application" do + assert typecheck!( + [map], + (fn + %{a: a} = data -> %{data | b: a} + data -> data + end).(map) + ) == dynamic() end test "bad function" do @@ -233,7 +246,7 @@ defmodule Module.Types.ExprTest do but function has type: - (dynamic(map()) -> :map) + (map() -> dynamic(:map)) """ end @@ -245,7 +258,7 @@ defmodule Module.Types.ExprTest do because the right-hand side has type: - (dynamic() -> dynamic({:ok, term()})) + (term() -> dynamic({:ok, term()})) """ end end diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index 7c69d921b8d..cb74df09b37 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -109,8 +109,8 @@ defmodule Module.Types.IntegrationTest do assert return.(:captured, 0) |> equal?( fun_from_non_overlapping_clauses([ - {[dynamic(binary())], atom([:ok, :error])}, - {[dynamic(non_empty_list(term(), term()))], atom([:list])} + {[binary()], dynamic(atom([:ok, :error]))}, + {[non_empty_list(term(), term())], dynamic(atom([:list]))} ]) ) end From 5c7687bf6ffbd4d43bc04acd4af1362bb9e098a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 3 Jul 2025 15:58:17 +0200 Subject: [PATCH 044/111] Apply further fn optimizations and fixes (#14619) Closes #14598 --- lib/elixir/lib/module/types/descr.ex | 110 ++++++++---------- .../test/elixir/module/types/descr_test.exs | 12 +- .../test/elixir/module/types/expr_test.exs | 10 ++ 3 files changed, 64 insertions(+), 68 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 772e63e85db..4cf6cb1fabb 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -46,8 +46,8 @@ defmodule Module.Types.Descr do @not_non_empty_list Map.delete(@term, :list) @not_list Map.replace!(@not_non_empty_list, :bitmap, @bit_top - @bit_empty_list) - @empty_intersection [0, @none, [], :fun_bottom] - @empty_difference [0, [], :fun_bottom] + @empty_intersection [0, []] + @empty_difference [0, []] defguard is_descr(descr) when is_map(descr) or descr == :term @@ -398,12 +398,20 @@ defmodule Module.Types.Descr do # Returning 0 from the callback is taken as none() for that subtype. defp intersection(:atom, v1, v2), do: atom_intersection(v1, v2) defp intersection(:bitmap, v1, v2), do: v1 &&& v2 - defp intersection(:dynamic, v1, v2), do: dynamic_intersection(v1, v2) defp intersection(:list, v1, v2), do: list_intersection(v1, v2) defp intersection(:map, v1, v2), do: map_intersection(v1, v2) defp intersection(:optional, 1, 1), do: 1 defp intersection(:tuple, v1, v2), do: tuple_intersection(v1, v2) - defp intersection(:fun, v1, v2), do: fun_intersection(v1, v2) + + defp intersection(:fun, v1, v2) do + bdd = fun_intersection(v1, v2) + if bdd == :fun_bottom, do: 0, else: bdd + end + + defp intersection(:dynamic, v1, v2) do + descr = dynamic_intersection(v1, v2) + if descr == @none, do: 0, else: descr + end @doc """ Computes the difference between two types. @@ -490,7 +498,11 @@ defmodule Module.Types.Descr do defp difference(:map, v1, v2), do: map_difference(v1, v2) defp difference(:optional, 1, 1), do: 0 defp difference(:tuple, v1, v2), do: tuple_difference(v1, v2) - defp difference(:fun, v1, v2), do: fun_difference(v1, v2) + + defp difference(:fun, v1, v2) do + bdd = fun_difference(v1, v2) + if bdd == :fun_bottom, do: 0, else: bdd + end @doc """ Compute the negation of a type. @@ -1159,7 +1171,7 @@ defmodule Module.Types.Descr do with {:ok, domain, static_arrows, dynamic_arrows} <- fun_normalize_both(fun_static, fun_dynamic, arity) do cond do - empty?(args_domain) -> + Enum.any?(arguments, &empty?/1) -> {:badarg, domain_to_flat_args(domain, arity)} not subtype?(args_domain, domain) -> @@ -1170,26 +1182,21 @@ defmodule Module.Types.Descr do end static? -> - {:ok, fun_apply_static(arguments, static_arrows, false)} + {:ok, fun_apply_static(arguments, static_arrows)} static_arrows == [] -> # TODO: We need to validate this within the theory arguments = Enum.map(arguments, &upper_bound/1) - {:ok, dynamic(fun_apply_static(arguments, dynamic_arrows, false))} + {:ok, dynamic(fun_apply_static(arguments, dynamic_arrows))} true -> # For dynamic cases, combine static and dynamic results - {static_args, dynamic_args, maybe_empty?} = - if args_dynamic? do - {Enum.map(arguments, &upper_bound/1), Enum.map(arguments, &lower_bound/1), true} - else - {arguments, arguments, false} - end + arguments = Enum.map(arguments, &upper_bound/1) {:ok, union( - fun_apply_static(static_args, static_arrows, false), - dynamic(fun_apply_static(dynamic_args, dynamic_arrows, maybe_empty?)) + fun_apply_static(arguments, static_arrows), + dynamic(fun_apply_static(arguments, dynamic_arrows)) )} end end @@ -1289,26 +1296,12 @@ defmodule Module.Types.Descr do :badfun end - defp fun_apply_static(arguments, arrows, maybe_empty?) do + defp fun_apply_static(arguments, arrows) do type_args = args_to_domain(arguments) - # Optimization: short-circuits when inner loop is none() or outer loop is term() - if maybe_empty? and empty?(type_args) do - Enum.reduce_while(arrows, none(), fn intersection_of_arrows, acc -> - Enum.reduce_while(intersection_of_arrows, term(), fn - {_dom, _ret}, acc when acc == @none -> {:halt, acc} - {_dom, ret}, acc -> {:cont, intersection(acc, ret)} - end) - |> case do - :term -> {:halt, :term} - inner -> {:cont, union(inner, acc)} - end - end) - else - Enum.reduce(arrows, none(), fn intersection_of_arrows, acc -> - aux_apply(acc, type_args, term(), intersection_of_arrows) - end) - end + Enum.reduce(arrows, none(), fn intersection_of_arrows, acc -> + aux_apply(acc, type_args, term(), intersection_of_arrows) + end) end # Helper function for function application that handles the application of @@ -1471,7 +1464,7 @@ defmodule Module.Types.Descr do # This avoids the expensive recursive phi computation by checking only that applying the # input to the positive intersection yields a subtype of the return if all_non_empty_domains?([{arguments, return} | positives]) do - fun_apply_static(arguments, [positives], false) + fun_apply_static(arguments, [positives]) |> subtype?(return) else n = length(arguments) @@ -1496,45 +1489,44 @@ defmodule Module.Types.Descr do # Create cache key from function arguments cache_key = {args, {b, ret}, [{arguments, return} | rest_positive]} - case Map.get(cache, cache_key) do - nil -> + case cache do + %{^cache_key => value} -> + value + + %{} -> # Compute result and cache it {result1, cache} = phi(args, {true, intersection(ret, return)}, rest_positive, cache) if not result1 do - # Store false result in cache cache = Map.put(cache, cache_key, false) {false, cache} else - # This doesn't stop if one intermediate result is false? - {result2, cache} = - Enum.with_index(arguments) - |> Enum.reduce_while({true, cache}, fn {type, index}, {acc_result, acc_cache} -> - {new_result, new_cache} = - List.update_at(args, index, fn {_, arg} -> {true, difference(arg, type)} end) - |> phi({b, ret}, rest_positive, acc_cache) - - if new_result do - {:cont, {acc_result and new_result, new_cache}} - else - {:halt, {false, new_cache}} - end + {_index, result2, cache} = + Enum.reduce_while(arguments, {0, true, cache}, fn + type, {index, acc_result, acc_cache} -> + {new_result, new_cache} = + args + |> List.update_at(index, fn {_, arg} -> {true, difference(arg, type)} end) + |> phi({b, ret}, rest_positive, acc_cache) + + if new_result do + {:cont, {index + 1, acc_result and new_result, new_cache}} + else + {:halt, {index + 1, false, new_cache}} + end end) result = result1 and result2 - # Store result in cache cache = Map.put(cache, cache_key, result) {result, cache} end - - cached_result -> - # Return cached result - {cached_result, cache} end end defp all_non_empty_domains?(positives) do - Enum.all?(positives, fn {args, _ret} -> not empty?(args_to_domain(args)) end) + Enum.all?(positives, fn {args, _ret} -> + Enum.all?(args, fn arg -> not empty?(arg) end) + end) end defp fun_union(bdd1, bdd2) do @@ -2392,10 +2384,6 @@ defmodule Module.Types.Descr do :empty -> acc end end - |> case do - [] -> 0 - acc -> acc - end end # Intersects two map literals; throws if their intersection is empty. diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index b8f54851a75..c1346661be9 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -913,16 +913,14 @@ defmodule Module.Types.DescrTest do assert fun_apply(fun([dynamic()], term()), [dynamic()]) == {:ok, term()} assert fun_apply(fun([integer()], dynamic()), [integer()]) == {:ok, dynamic()} - assert fun_apply(fun([dynamic()], integer()), [dynamic()]) - |> elem(1) - |> equal?(integer()) + assert fun_apply(fun([dynamic()], integer()), [dynamic()]) == + {:ok, union(integer(), dynamic())} - assert fun_apply(fun([dynamic(), atom()], float()), [dynamic(), atom()]) - |> elem(1) - |> equal?(float()) + assert fun_apply(fun([dynamic(), atom()], float()), [dynamic(), atom()]) == + {:ok, union(float(), dynamic())} fun = fun([dynamic(integer())], atom()) - assert fun_apply(fun, [dynamic(integer())]) |> elem(1) |> equal?(atom()) + assert fun_apply(fun, [dynamic(integer())]) == {:ok, union(atom(), dynamic())} assert fun_apply(fun, [dynamic(number())]) == {:ok, dynamic()} assert fun_apply(fun, [integer()]) == {:ok, dynamic()} assert fun_apply(fun, [float()]) == {:badarg, [dynamic(integer())]} diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index c6b97fd692b..473a674ca58 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -149,6 +149,16 @@ defmodule Module.Types.ExprTest do data -> data end).(map) ) == dynamic() + + assert typecheck!( + [], + [true, false] + |> Enum.random() + |> then(fn + true -> :ok + _ -> :error + end) + ) == dynamic(atom([:ok, :error])) end test "bad function" do From aa6964547d97277bf9fc5b5e89abdef705f3bec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 4 Jul 2025 09:34:28 +0200 Subject: [PATCH 045/111] Fix return type of phi, closes #14621 --- lib/elixir/lib/module/types/descr.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 4cf6cb1fabb..b14625f80ad 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -1491,7 +1491,7 @@ defmodule Module.Types.Descr do case cache do %{^cache_key => value} -> - value + {value, cache} %{} -> # Compute result and cache it From 248a71e2fc9333341d45e0ee594961552742b8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Jul 2025 09:46:08 +0200 Subject: [PATCH 046/111] Fix logger docs Closes #14628. Closes #14629. --- lib/logger/lib/logger.ex | 2 +- lib/logger/lib/logger/formatter.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex index 1e604e0d84b..5591671e8c8 100644 --- a/lib/logger/lib/logger.ex +++ b/lib/logger/lib/logger.ex @@ -12,7 +12,7 @@ defmodule Logger do Overall, you will find that `Logger`: - * Provides all 7 syslog levels + * Provides all 8 syslog levels (although debug, info, warning, and error are the most commonly used). * Supports both message-based and structural logging. diff --git a/lib/logger/lib/logger/formatter.ex b/lib/logger/lib/logger/formatter.ex index 1caed9da74d..2ed22d88af3 100644 --- a/lib/logger/lib/logger/formatter.ex +++ b/lib/logger/lib/logger/formatter.ex @@ -8,7 +8,7 @@ defmodule Logger.Formatter do @moduledoc ~S""" Conveniences and built-in formatter for logs. - This modules defines a suitable `:logger` formatter which formats + This module defines a suitable `:logger` formatter which formats messages and reports as Elixir terms and also provides additional functionality, such as timezone conversion, truncation, and coloring. This formatter is used by default by `Logger` and you can configure it From 9619116cde6c92fa4e7e6ad7eb6973d1a953aac0 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Tue, 8 Jul 2025 09:00:58 +0200 Subject: [PATCH 047/111] Prevent mix test from overriding :failures_manifest_path option (#14632) Introduced in 99be673 --- lib/mix/lib/mix/tasks/test.ex | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/mix/lib/mix/tasks/test.ex b/lib/mix/lib/mix/tasks/test.ex index ad32028f915..23d2432fd24 100644 --- a/lib/mix/lib/mix/tasks/test.ex +++ b/lib/mix/lib/mix/tasks/test.ex @@ -502,7 +502,6 @@ defmodule Mix.Tasks.Test do @impl true def run(args) do {opts, files} = OptionParser.parse!(args, strict: @switches, aliases: [b: :breakpoints]) - opts = put_manifest_file(opts) if not Mix.Task.recursing?() do do_run(opts, args, files) @@ -576,6 +575,7 @@ defmodule Mix.Tasks.Test do Mix.Task.run("compile", args -- ["--warnings-as-errors"]) project = Mix.Project.config() + {partitions, opts} = Keyword.pop(opts, :partitions) partitioned? = is_integer(partitions) and partitions > 1 @@ -667,7 +667,7 @@ defmodule Mix.Tasks.Test do catch kind, reason -> # Also mark the whole suite as failed - file = Keyword.fetch!(opts, :failures_manifest_path) + file = get_manifest_path(opts) ExUnit.Filters.fail_all!(file) :erlang.raise(kind, reason, __STACKTRACE__) else @@ -871,7 +871,10 @@ defmodule Mix.Tasks.Test do defp merge_helper_opts(opts) do # The only options that are additive from app env are the excludes value = List.wrap(Application.get_env(:ex_unit, :exclude, [])) - Keyword.update(opts, :exclude, value, &Enum.uniq(&1 ++ value)) + + opts + |> Keyword.update(:exclude, value, &Enum.uniq(&1 ++ value)) + |> Keyword.put_new_lazy(:failures_manifest_path, fn -> get_manifest_path([]) end) end defp default_opts(opts) do @@ -918,16 +921,14 @@ defmodule Mix.Tasks.Test do @manifest_file_name ".mix_test_failures" - defp put_manifest_file(opts) do - Keyword.put_new_lazy( - opts, - :failures_manifest_path, - fn -> Path.join(Mix.Project.manifest_path(), @manifest_file_name) end - ) + defp get_manifest_path(opts) do + opts[:failures_manifest_path] || + Application.get_env(:ex_unit, :failures_manifest_path) || + Path.join(Mix.Project.manifest_path(), @manifest_file_name) end defp manifest_opts(opts) do - manifest_file = Keyword.fetch!(opts, :failures_manifest_path) + manifest_file = get_manifest_path(opts) if opts[:failed] do if opts[:stale] do From b878f37577ac10201e47bfebc13cd03e0af636ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 10 Jul 2025 09:30:26 +0200 Subject: [PATCH 048/111] Improve error message for protocols with no implementation, closes #14364 --- lib/elixir/lib/module/types/apply.ex | 20 ++++++-- .../elixir/module/types/integration_test.exs | 46 ++++++++++++++++++- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 2b3be0bd3bf..7fc02f5d719 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -1031,11 +1031,21 @@ defmodule Module.Types.Apply do Code.ensure_loaded?(mod) and Keyword.has_key?(mod.module_info(:attributes), :__protocol__) -> - # Protocol errors can be very verbose, so we collapse structs - """ - but expected a type that implements the #{inspect(mod)} protocol, it must be one of: - #{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)} - """ + if function_exported?(mod, :__protocol__, 1) and + mod.__protocol__(:impls) == {:consolidated, []} do + """ + but the protocol was not yet implemented for any type and therefore will always fail. \ + This error typically happens within libraries that define protocols and will disappear as \ + soon as there is one implementation. If you expect the protocol to be implemented later on, \ + you can define an implementation specific for development/test. + """ + else + # Protocol errors can be very verbose, so we collapse structs + """ + but expected a type that implements the #{inspect(mod)} protocol, it must be one of: + #{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)} + """ + end true -> """ diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index cb74df09b37..be9ff32c2b8 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -384,7 +384,7 @@ defmodule Module.Types.IntegrationTest do assert_no_warnings(files) end - test "mismatched impl" do + test "mismatched implementation" do files = %{ "a.ex" => """ defprotocol Itself do @@ -420,6 +420,50 @@ defmodule Module.Types.IntegrationTest do assert_warnings(files, warnings) end + @tag :require_ast + test "no implementation" do + files = %{ + "a.ex" => """ + defprotocol NoImplProtocol do + def callback(data) + end + """, + "b.ex" => """ + defmodule NoImplProtocol.Caller do + def run do + NoImplProtocol.callback(:hello) + end + end + """ + } + + warnings = [ + """ + warning: incompatible types given to NoImplProtocol.callback/1: + + NoImplProtocol.callback(:hello) + + given types: + + -:hello- + + but the protocol was not yet implemented for any type and therefore will always fail. \ + This error typically happens within libraries that define protocols and will disappear as \ + soon as there is one implementation. If you expect the protocol to be implemented later on, \ + you can define an implementation specific for development/test. + + typing violation found at: + │ + 3 │ NoImplProtocol.callback(:hello) + │ ~ + │ + └─ b.ex:3:20: NoImplProtocol.Caller.run/0 + """ + ] + + assert_warnings(files, warnings, consolidate_protocols: true) + end + @tag :require_ast test "String.Chars protocol dispatch" do files = %{ From fff2cf0d81299f8c9a6e3ebfac5f658639873ba3 Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Wed, 9 Jul 2025 13:09:07 -0500 Subject: [PATCH 049/111] Standardize "Examples" heading section levels in docs (#14638) * Convert "Examples" 3rd level headings to 2nd level when not under a 2nd level * Convert 1st level "Examples" heading to 2nd level --- lib/elixir/lib/calendar/date.ex | 4 ++-- lib/elixir/lib/calendar/datetime.ex | 6 +++--- lib/elixir/lib/calendar/naive_datetime.ex | 4 ++-- lib/elixir/lib/calendar/time.ex | 4 ++-- lib/elixir/lib/kernel.ex | 2 +- lib/elixir/lib/list.ex | 4 ++-- lib/elixir/lib/version.ex | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/calendar/date.ex b/lib/elixir/lib/calendar/date.ex index 0fb91ebce07..e931e088010 100644 --- a/lib/elixir/lib/calendar/date.ex +++ b/lib/elixir/lib/calendar/date.ex @@ -321,7 +321,7 @@ defmodule Date do @doc """ Converts the given date to a string according to its calendar. - ### Examples + ## Examples iex> Date.to_string(~D[2000-02-28]) "2000-02-28" @@ -399,7 +399,7 @@ defmodule Date do or other calendars in which the days also start at midnight. Attempting to convert dates from other calendars will raise an `ArgumentError`. - ### Examples + ## Examples iex> Date.to_iso8601(~D[2000-02-28]) "2000-02-28" diff --git a/lib/elixir/lib/calendar/datetime.ex b/lib/elixir/lib/calendar/datetime.ex index b945c0ab99b..04dfbe91c74 100644 --- a/lib/elixir/lib/calendar/datetime.ex +++ b/lib/elixir/lib/calendar/datetime.ex @@ -1046,7 +1046,7 @@ defmodule DateTime do its abbreviation, which means information is lost when converting to such format. - ### Examples + ## Examples iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET", ...> hour: 23, minute: 0, second: 7, microsecond: {0, 0}, @@ -1390,7 +1390,7 @@ defmodule DateTime do custom (but relatively common) representation which appends the time zone abbreviation and full name to the datetime. - ### Examples + ## Examples iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET", ...> hour: 23, minute: 0, second: 7, microsecond: {0, 0}, @@ -1636,7 +1636,7 @@ defmodule DateTime do not currently supported in Elixir's standard library but it is available by third-party libraries. - ### Examples + ## Examples iex> dt = DateTime.from_naive!(~N[2018-11-15 10:00:00], "Europe/Copenhagen", FakeTimeZoneDatabase) iex> dt |> DateTime.add(3600, :second, FakeTimeZoneDatabase) diff --git a/lib/elixir/lib/calendar/naive_datetime.ex b/lib/elixir/lib/calendar/naive_datetime.ex index 76e1337b97b..a9cd09b9249 100644 --- a/lib/elixir/lib/calendar/naive_datetime.ex +++ b/lib/elixir/lib/calendar/naive_datetime.ex @@ -761,7 +761,7 @@ defmodule NaiveDateTime do For readability, this function follows the RFC3339 suggestion of removing the "T" separator between the date and time components. - ### Examples + ## Examples iex> NaiveDateTime.to_string(~N[2000-02-28 23:00:13]) "2000-02-28 23:00:13" @@ -908,7 +908,7 @@ defmodule NaiveDateTime do Only supports converting naive datetimes which are in the ISO calendar, attempting to convert naive datetimes from other calendars will raise. - ### Examples + ## Examples iex> NaiveDateTime.to_iso8601(~N[2000-02-28 23:00:13]) "2000-02-28T23:00:13" diff --git a/lib/elixir/lib/calendar/time.ex b/lib/elixir/lib/calendar/time.ex index e3b4fce96c3..e9502e588f3 100644 --- a/lib/elixir/lib/calendar/time.ex +++ b/lib/elixir/lib/calendar/time.ex @@ -225,7 +225,7 @@ defmodule Time do @doc """ Converts the given `time` to a string. - ### Examples + ## Examples iex> Time.to_string(~T[23:00:00]) "23:00:00" @@ -334,7 +334,7 @@ defmodule Time do format, for human readability. It also supports the "basic" format through passing the `:basic` option. - ### Examples + ## Examples iex> Time.to_iso8601(~T[23:00:13]) "23:00:13" diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 642bb837494..20c78c2f1e6 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -2815,7 +2815,7 @@ defmodule Kernel do This is most commonly used in pipelines, using the `|>/2` operator, allowing you to pipe a value to a function outside of its first argument. - ### Examples + ## Examples iex> 1 |> then(fn x -> x * 2 end) 2 diff --git a/lib/elixir/lib/list.ex b/lib/elixir/lib/list.ex index efad0d2e1bb..e964099fd63 100644 --- a/lib/elixir/lib/list.ex +++ b/lib/elixir/lib/list.ex @@ -915,7 +915,7 @@ defmodule List do If `prefix` is an empty list, it returns `true`. - ### Examples + ## Examples iex> List.starts_with?([1, 2, 3], [1, 2]) true @@ -945,7 +945,7 @@ defmodule List do If `suffix` is an empty list, it returns `true`. - ### Examples + ## Examples iex> List.ends_with?([1, 2, 3], [2, 3]) true diff --git a/lib/elixir/lib/version.ex b/lib/elixir/lib/version.ex index f96420bffef..36ce18d30dc 100644 --- a/lib/elixir/lib/version.ex +++ b/lib/elixir/lib/version.ex @@ -442,7 +442,7 @@ defmodule Version do If `string` is an invalid requirement, a `Version.InvalidRequirementError` is raised. - # Examples + ## Examples iex> Version.parse_requirement!("== 2.0.1") Version.parse_requirement!("== 2.0.1") @@ -485,7 +485,7 @@ defmodule Version do @doc """ Converts the given version to a string. - ### Examples + ## Examples iex> Version.to_string(%Version{major: 1, minor: 2, patch: 3}) "1.2.3" From a244323815164933a91992c929aeae3b7066ab91 Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Wed, 9 Jul 2025 13:08:47 -0500 Subject: [PATCH 050/111] Correct grammar in structural sorting order section (#14639) It is not required by the Elixir developer, but it is not required for them to know this by heart. The former indicates that it is the Elixir developers who are not requiring this, the latter expresses that they do not need to know this by heart. --- lib/elixir/lib/kernel.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 20c78c2f1e6..7c8e8545494 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -231,7 +231,7 @@ defmodule Kernel do Finally, note there is an overall structural sorting order, called "Term Ordering", defined below. This order is provided for reference - purposes, it is not required by Elixir developers to know it by heart. + purposes, it is not required for Elixir developers to know it by heart. ### Term ordering From bd68532c27434b159ba414c22ef891ded18b6054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 10 Jul 2025 10:21:34 +0200 Subject: [PATCH 051/111] Remove explicit mentions to elixirc, as it isn't used in practice --- .../pages/getting-started/introduction.md | 2 +- .../getting-started/module-attributes.md | 24 ++++++---- .../getting-started/modules-and-functions.md | 45 ++++--------------- 3 files changed, 25 insertions(+), 46 deletions(-) diff --git a/lib/elixir/pages/getting-started/introduction.md b/lib/elixir/pages/getting-started/introduction.md index 3c31ea7a250..c642addcdce 100644 --- a/lib/elixir/pages/getting-started/introduction.md +++ b/lib/elixir/pages/getting-started/introduction.md @@ -57,4 +57,4 @@ $ elixir simple.exs Hello world from Elixir ``` -Later on we will learn [how to compile Elixir code](modules-and-functions.md) and how to create and work within Elixir projects using the Mix build tool. For now, let's move on to learn the basic data types in the language. +`iex` and `elixir` are all we need to learn the main language concepts. There is a separate guide named ["Mix and OTP guide"](../mix-and-otp/introduction-to-mix.md) that explores how to actually create, manage, and test full-blown Elixir projects. For now, let's move on to learn the basic data types in the language. diff --git a/lib/elixir/pages/getting-started/module-attributes.md b/lib/elixir/pages/getting-started/module-attributes.md index 5c556aced43..e1d824b8231 100644 --- a/lib/elixir/pages/getting-started/module-attributes.md +++ b/lib/elixir/pages/getting-started/module-attributes.md @@ -32,7 +32,7 @@ In the example above, we are defining the module documentation by using the modu `@moduledoc` and `@doc` are by far the most used attributes, and we expect you to use them a lot. Elixir treats documentation as first-class and provides many functions to access documentation. We will cover them [in their own chapter](writing-documentation.md). -Let's go back to the `Math` module defined in the previous chapters, add some documentation and save it to the `math.ex` file: +Documentation is only accessible from compiled modules. So in order to give it a try, let's once again define the `Math` module, but this time within a file named `math.ex`: ```elixir defmodule Math do @@ -53,23 +53,29 @@ defmodule Math do end ``` -Elixir promotes the use of Markdown with heredocs to write readable documentation. Heredocs are multi-line strings, they start and end with triple double-quotes, keeping the formatting of the inner text. We can access the documentation of any compiled module directly from IEx: +Elixir promotes the use of Markdown with heredocs to write readable documentation. Heredocs are multi-line strings, they start and end with triple double-quotes, keeping the formatting of the inner text. -```console -$ elixirc math.ex -$ iex +Now let's compile it. Start `iex` and then invoke [the `c/2` helper](`IEx.Helpers.c/2`): + +```elixir +iex> c("math.ex", ".") +[Math] ``` +And now we can access them: + ```elixir -iex> h Math # Access the docs for the module Math +iex> h Math # Docs for module Math ... -iex> h Math.sum # Access the docs for the sum function +iex> h Math.sum # Docs for the sum function ... ``` -We also provide a tool called [ExDoc](https://github.com/elixir-lang/ex_doc) which is used to generate HTML pages from the documentation. +When we compiled the module, you may have noticed Elixir created a `Elixir.Math.beam` file. That's the bytecode for the module and that's where the documentation is stored. + +In our day to day, Elixir developers use the `Mix` build tool to compile code and projects like [ExDoc](https://github.com/elixir-lang/ex_doc) to generate HTML and EPUB pages from the documentation. -You can take a look at the docs for `Module` for a complete list of supported attributes. Elixir also uses attributes to annotate our code with [typespecs](../references/typespecs.md). +Take a look at the docs for `Module` for a complete list of supported attributes. ## As temporary storage diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index 25263746508..bfccc32ce7c 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -25,38 +25,13 @@ iex> Math.sum(1, 2) 3 ``` -In this chapter we will define our own modules, with different levels of complexity. As our examples get longer in size, it can be tricky to type them all in the shell. It's about time for us to learn how to compile Elixir code and also how to run Elixir scripts. +In this chapter we will define our own modules, with different levels of complexity. As our examples get longer in size, it can be tricky to type them all in the shell, so we will resort more frequently to scripting. -## Compilation +## Scripting -Most of the time it is convenient to write modules into files so they can be compiled and reused. Let's assume we have a file named `math.ex` with the following contents: +Elixir has two file extensions `.ex` (Elixir) and `.exs` (Elixir scripts). Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. -```elixir -defmodule Math do - def sum(a, b) do - a + b - end -end -``` - -This file can be compiled using `elixirc`: - -```console -$ elixirc math.ex -``` - -This will generate a file named `Elixir.Math.beam` containing the bytecode for the defined module. If we start `iex` again, our module definition will be available (provided that `iex` is started in the same directory the bytecode file is in): - -```elixir -iex> Math.sum(1, 2) -3 -``` - -## Scripting mode - -In addition to the Elixir file extension `.ex`, Elixir also supports `.exs` files for scripting. Elixir treats both files exactly the same way, the only difference is in intention. `.ex` files are meant to be compiled while `.exs` files are used for scripting. This convention is followed by projects like `mix`. - -For instance, we can create a file called `math.exs`: +Let's create a file named `math.exs`: ```elixir defmodule Math do @@ -74,15 +49,13 @@ And execute it as: $ elixir math.exs ``` -Because we used `elixir` instead of `elixirc`, the module was compiled and loaded into memory, but no `.beam` file was written to disk. - -Elixir projects are usually organized into three directories: +You can also load the file within `iex` by running: - * `_build` - contains compilation artifacts - * `lib` - contains Elixir code (usually `.ex` files) - * `test` - contains tests (usually `.exs` files) +```console +$ iex math.exs +``` -When working on actual projects, the build tool called `mix` will be responsible for compiling and setting up the proper paths for you. For learning and convenience purposes, we recommend you to write the following code into script files and execute them as shown above. +And then have direct access to the `Math` module. ## Function definition From c03caa18cff75a4b0a17efb04591f1ecd26494eb Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Thu, 10 Jul 2025 02:36:20 -0500 Subject: [PATCH 052/111] Use backticks around literals in documentation (#14633) --- lib/elixir/lib/code.ex | 8 ++++---- lib/elixir/lib/code/fragment.ex | 4 ++-- lib/elixir/lib/inspect/algebra.ex | 2 +- lib/elixir/lib/kernel/parallel_compiler.ex | 4 ++-- lib/elixir/lib/regex.ex | 6 +++--- lib/elixir/lib/system.ex | 2 +- lib/elixir/pages/getting-started/structs.md | 2 +- lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md | 2 +- lib/ex_unit/lib/ex_unit.ex | 2 +- lib/ex_unit/lib/ex_unit/case.ex | 2 +- lib/iex/lib/iex/info.ex | 2 +- lib/mix/lib/mix.ex | 2 +- lib/mix/lib/mix/shell.ex | 4 ++-- lib/mix/lib/mix/tasks/archive.build.ex | 2 +- lib/mix/lib/mix/tasks/compile.app.ex | 4 ++-- lib/mix/lib/mix/tasks/deps.ex | 2 +- lib/mix/lib/mix/tasks/format.ex | 2 +- lib/mix/lib/mix/tasks/release.ex | 12 ++++++------ lib/mix/lib/mix/tasks/test.ex | 4 ++-- 19 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex index af738268409..b0463e48d41 100644 --- a/lib/elixir/lib/code.ex +++ b/lib/elixir/lib/code.ex @@ -725,7 +725,7 @@ defmodule Code do * `:line` - the line the string starts, used for error reporting * `:line_length` - the line length to aim for when formatting - the document. Defaults to 98. This value indicates when an expression + the document. Defaults to `98`. This value indicates when an expression should be broken over multiple lines but it is not guaranteed to do so. See the "Line length" section below for more information @@ -1227,14 +1227,14 @@ defmodule Code do Defaults to `"nofile"`. * `:line` - the starting line of the string being parsed. - Defaults to 1. + Defaults to `1`. * `:column` - (since v1.11.0) the starting column of the string being parsed. - Defaults to 1. + Defaults to `1`. * `:indentation` - (since v1.19.0) the indentation for the string being parsed. This is useful when the code parsed is embedded within another document. - Defaults to 0. + Defaults to `0`. * `:columns` - when `true`, attach a `:column` key to the quoted metadata. Defaults to `false`. diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index fff274a34ec..e1323c290ef 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -1213,10 +1213,10 @@ defmodule Code.Fragment do Defaults to `"nofile"`. * `:line` - the starting line of the string being parsed. - Defaults to 1. + Defaults to `1`. * `:column` - the starting column of the string being parsed. - Defaults to 1. + Defaults to `1`. * `:columns` - when `true`, attach a `:column` key to the quoted metadata. Defaults to `false`. diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 776dfbcd4bc..62e039c938c 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -46,7 +46,7 @@ defmodule Inspect.Opts do * `:limit` - limits the number of items that are inspected for tuples, bitstrings, maps, lists and any other collection of items, with the exception of printable strings and printable charlists which use the `:printable_limit` option. - It accepts a positive integer or `:infinity`. It defaults to 100 since + It accepts a positive integer or `:infinity`. It defaults to `100` since `Elixir v1.19.0`, as it has better defaults to deal with nested collections. * `:pretty` - if set to `true` enables pretty printing. Defaults to `false`. diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex index cf483bc08a4..925b8237717 100644 --- a/lib/elixir/lib/kernel/parallel_compiler.ex +++ b/lib/elixir/lib/kernel/parallel_compiler.ex @@ -213,7 +213,7 @@ defmodule Kernel.ParallelCompiler do * `:max_concurrency` - the maximum number of files to compile in parallel. Setting this option to 1 will compile files sequentially. - Defaults to the number of schedulers online, or at least 2. + Defaults to the number of schedulers online, or at least `2`. """ @doc since: "1.6.0" @@ -263,7 +263,7 @@ defmodule Kernel.ParallelCompiler do * `:max_concurrency` - the maximum number of files to compile in parallel. Setting this option to 1 will compile files sequentially. - Defaults to the number of schedulers online, or at least 2. + Defaults to the number of schedulers online, or at least `2`. * `:return_diagnostics` - when `true`, returns structured diagnostics as maps instead of the legacy format. Defaults to `false`. diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index a94f32057e6..8ca5a226d34 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -306,7 +306,7 @@ defmodule Regex do * `:capture` - what to capture in the result. See the ["Captures" section](#module-captures) to see the possible capture values. * `:offset` - (since v1.12.0) specifies the starting offset to match in the given string. - Defaults to zero. + Defaults to `0`. ## Examples @@ -349,7 +349,7 @@ defmodule Regex do * `:return` - when set to `:index`, returns byte index and match length. Defaults to `:binary`. * `:offset` - (since v1.12.0) specifies the starting offset to match in the given string. - Defaults to zero. + Defaults to `0`. ## Examples @@ -523,7 +523,7 @@ defmodule Regex do * `:capture` - what to capture in the result. See the ["Captures" section](#module-captures) to see the possible capture values. * `:offset` - (since v1.12.0) specifies the starting offset to match in the given string. - Defaults to zero. + Defaults to `0`. ## Examples diff --git a/lib/elixir/lib/system.ex b/lib/elixir/lib/system.ex index 13d7e93298f..ab00c5a958f 100644 --- a/lib/elixir/lib/system.ex +++ b/lib/elixir/lib/system.ex @@ -960,7 +960,7 @@ defmodule System do * `:close_stdin` (since v1.14.1) - if the stdin should be closed on Unix systems, forcing any command that waits on stdin to - immediately terminate. Defaults to false. + immediately terminate. Defaults to `false`. """ @doc since: "1.12.0" @spec shell(binary, shell_opts) :: {Collectable.t(), exit_status :: non_neg_integer} diff --git a/lib/elixir/pages/getting-started/structs.md b/lib/elixir/pages/getting-started/structs.md index 73fb42e9fe1..7654131b42e 100644 --- a/lib/elixir/pages/getting-started/structs.md +++ b/lib/elixir/pages/getting-started/structs.md @@ -113,7 +113,7 @@ iex> %Product{} %Product{name: nil} ``` -You can define a structure combining both fields with explicit default values, and implicit `nil` values. In this case you must first specify the fields which implicitly default to nil: +You can define a structure combining both fields with explicit default values, and implicit `nil` values. In this case you must first specify the fields which implicitly default to `nil`: ```elixir iex> defmodule User do diff --git a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md index 3a2a7389109..8ae44d38962 100644 --- a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md +++ b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md @@ -147,7 +147,7 @@ port = String.to_integer(System.get_env("PORT") || "4040") {Task, fn -> KVServer.accept(port) end} ``` -Insert these changes in your code and now you may start your application using the following command `PORT=4321 mix run --no-halt`, notice how we are passing the port as a variable, but still defaults to 4040 if none is given. +Insert these changes in your code and now you may start your application using the following command `PORT=4321 mix run --no-halt`, notice how we are passing the port as a variable, but still defaults to `4040` if none is given. Now that the server is part of the supervision tree, it should start automatically when we run the application. Start your server, now passing the port, and once again use the `telnet` client to make sure that everything still works: diff --git a/lib/ex_unit/lib/ex_unit.ex b/lib/ex_unit/lib/ex_unit.ex index 4c1baecfef5..b323e0f58e5 100644 --- a/lib/ex_unit/lib/ex_unit.ex +++ b/lib/ex_unit/lib/ex_unit.ex @@ -331,7 +331,7 @@ defmodule ExUnit do filter. See the "Filters" section in the documentation for `ExUnit.Case`; * `:exit_status` - specifies an alternate exit status to use when the test - suite fails. Defaults to 2; + suite fails. Defaults to `2`; * `:failures_manifest_path` - specifies a path to the file used to store failures between runs; diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex index 69103257b05..d2cea8e82cd 100644 --- a/lib/ex_unit/lib/ex_unit/case.ex +++ b/lib/ex_unit/lib/ex_unit/case.ex @@ -187,7 +187,7 @@ defmodule ExUnit.Case do * `:skip` - skips the test with the given reason - * `:timeout` - customizes the test timeout in milliseconds (defaults to 60000). + * `:timeout` - customizes the test timeout in milliseconds (defaults to `60_000`). Accepts `:infinity` as a timeout value. * `:tmp_dir` - (since v1.11.0) see the "Tmp Dir" section below diff --git a/lib/iex/lib/iex/info.ex b/lib/iex/lib/iex/info.ex index aaf05a57b0a..332d7ade11a 100644 --- a/lib/iex/lib/iex/info.ex +++ b/lib/iex/lib/iex/info.ex @@ -458,7 +458,7 @@ defimpl IEx.Info, for: Range do description = """ This is a struct representing a range of numbers. It is commonly defined using the `first..last//step` syntax. The step is not - required and defaults to 1. + required and defaults to `1`. """ [ diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex index ee3701f686f..52a169aa02d 100644 --- a/lib/mix/lib/mix.ex +++ b/lib/mix/lib/mix.ex @@ -846,7 +846,7 @@ defmodule Mix do ## Environment variables The `MIX_INSTALL_DIR` environment variable configures the directory that - caches all `Mix.install/2`. It defaults to the "mix/install" folder in the + caches all `Mix.install/2`. It defaults to the `mix/install` folder in the default user cache of your operating system. You can use `install_project_dir/0` to access the directory of an existing install (alongside other installs): diff --git a/lib/mix/lib/mix/shell.ex b/lib/mix/lib/mix/shell.ex index 03f7bf31b52..d3e544b8c2d 100644 --- a/lib/mix/lib/mix/shell.ex +++ b/lib/mix/lib/mix/shell.ex @@ -131,9 +131,9 @@ defmodule Mix.Shell do * `:cd` *(since v1.11.0)* - the directory to run the command in - * `:stderr_to_stdout` - redirects stderr to stdout, defaults to true, unless use_stdio is set to false + * `:stderr_to_stdout` - redirects stderr to stdout, defaults to `true`, unless `:use_stdio` is set to `false` - * `:use_stdio` - controls whether the command should use stdin / stdout / stdrr, defaults to true + * `:use_stdio` - controls whether the command should use `stdin` / `stdout` / `stdrr`, defaults to `true` * `:env` - a list of environment variables, defaults to `[]` diff --git a/lib/mix/lib/mix/tasks/archive.build.ex b/lib/mix/lib/mix/tasks/archive.build.ex index 2330889e564..9d9a763a7b8 100644 --- a/lib/mix/lib/mix/tasks/archive.build.ex +++ b/lib/mix/lib/mix/tasks/archive.build.ex @@ -36,7 +36,7 @@ defmodule Mix.Tasks.Archive.Build do ## Command line options * `-o` - specifies output file name. - If there is a `mix.exs`, defaults to "APP-VERSION.ez". + If there is a `mix.exs`, defaults to `APP-VERSION.ez`. * `-i` - specifies the input directory to archive. If there is a `mix.exs`, defaults to the current application build. diff --git a/lib/mix/lib/mix/tasks/compile.app.ex b/lib/mix/lib/mix/tasks/compile.app.ex index 92b272e04b2..7383b4a8802 100644 --- a/lib/mix/lib/mix/tasks/compile.app.ex +++ b/lib/mix/lib/mix/tasks/compile.app.ex @@ -100,8 +100,8 @@ defmodule Mix.Tasks.Compile.App do * `:reliable_dir_mtime` - this task relies on the operating system changing the mtime on a directory whenever a file is added or removed. - You can set this option to false if your system does not provide - reliable mtimes. Defaults to false on Windows. + You can set this option to `false` if your system does not provide + reliable mtimes. Defaults to `false` on Windows. ## Phases diff --git a/lib/mix/lib/mix/tasks/deps.ex b/lib/mix/lib/mix/tasks/deps.ex index ce7080eac42..f17cd078cc2 100644 --- a/lib/mix/lib/mix/tasks/deps.ex +++ b/lib/mix/lib/mix/tasks/deps.ex @@ -114,7 +114,7 @@ defmodule Mix.Tasks.Deps do * `:runtime` - whether the dependency is part of runtime applications. If the `:applications` key is not provided in `def application` in your `mix.exs` file, Mix will automatically include all dependencies as a runtime - application, except if `runtime: false` is given. Defaults to true. + application, except if `runtime: false` is given. Defaults to `true`. * `:system_env` - an enumerable of key-value tuples of binaries to be set as environment variables when loading or compiling the dependency diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index dbf76e483c8..59b937e482b 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -80,7 +80,7 @@ defmodule Mix.Tasks.Format do * `--stdin-filename` - path to the file being formatted on stdin. This is useful if you are using plugins to support custom filetypes such as `.heex`. Without passing this flag, it is assumed that the code being - passed via stdin is valid Elixir code. Defaults to "stdin.exs". + passed via stdin is valid Elixir code. Defaults to `stdin.exs`. * `--migrate` - enables the `:migrate` option, which should be able to automatically fix some deprecation warnings but changes the AST. diff --git a/lib/mix/lib/mix/tasks/release.ex b/lib/mix/lib/mix/tasks/release.ex index 57fed44b5f8..b9cc0caa4bf 100644 --- a/lib/mix/lib/mix/tasks/release.ex +++ b/lib/mix/lib/mix/tasks/release.ex @@ -176,11 +176,11 @@ defmodule Mix.Tasks.Release do in daemon mode so it automatically restarts the system in case of crashes. See the generated `releases/RELEASE_VSN/env.sh` file. - The daemon will write all of its standard output to the "tmp/log/" + The daemon will write all of its standard output to the `tmp/log/` directory in the release root. You can watch the log file by doing `tail -f tmp/log/erlang.log.1` or similar. Once files get too large, the index suffix will be incremented. A developer can also attach - to the standard input of the daemon by invoking "to_erl tmp/pipe/" + to the standard input of the daemon by invoking `to_erl tmp/pipe/` from the release root. However, note that attaching to the system should be done with extreme care, since the usual commands for exiting an Elixir system, such as hitting Ctrl+C twice or Ctrl+\\, @@ -441,12 +441,12 @@ defmodule Mix.Tasks.Release do ] * `:rel_templates_path` - the path to find template files that are copied to - the release, such as "vm.args.eex", "remote.vm.args.eex", "env.sh.eex" - (or "env.bat.eex"), and "overlays". Defaults to "rel" in the project root. + the release, such as `vm.args.eex`, `remote.vm.args.eex`, `env.sh.eex` + (or `env.bat.eex`), and `overlays`. Defaults to `"rel"` in the project root. * `:overlays` - a list of directories with extra files to be copied as is to the release. The "overlays" directory at `:rel_templates_path` - is always included in this list by default (typically at "rel/overlays"). + is always included in this list by default (typically at `"rel/overlays"`). See the "Overlays" section for more information. * `:steps` - a list of steps to execute when assembling the release. See @@ -479,7 +479,7 @@ defmodule Mix.Tasks.Release do the release is assembled. This can be easily done by placing such files in the `rel/overlays` directory. Any file in there is copied as is to the release root. For example, if you have placed a - "rel/overlays/Dockerfile" file, the "Dockerfile" will be copied as + `rel/overlays/Dockerfile` file, the "Dockerfile" will be copied as is to the release root. If you want to specify extra overlay directories, you can do so diff --git a/lib/mix/lib/mix/tasks/test.ex b/lib/mix/lib/mix/tasks/test.ex index 23d2432fd24..b7cb8cb4f73 100644 --- a/lib/mix/lib/mix/tasks/test.ex +++ b/lib/mix/lib/mix/tasks/test.ex @@ -212,7 +212,7 @@ defmodule Mix.Tasks.Test do test suite) as errors and returns an exit status of 1 if the test suite would otherwise pass. If the test suite fails and also include warnings as errors, the exit status returned will be the value of the `--exit-status` option, which - defaults to 2, plus one. Therefore in the default case, this will be exit status 3. + defaults to `2`, plus one. Therefore in the default case, this will be exit status `3`. Note that failures reported by `--warnings-as-errors` cannot be retried with the `--failed` flag. @@ -237,7 +237,7 @@ defmodule Mix.Tasks.Test do It is expected that all test paths contain a `test_helper.exs` file * `:test_pattern` - a pattern to find potential test files. - Defaults to `*.{ex,exs}`. + Defaults to `"*.{ex,exs}"`. In Elixir versions earlier than 1.19.0, this option defaulted to `*_test.exs`, but to allow better warnings for misnamed test files, it since matches any From c3ec4006071122cc95842ebb643075f4dc840a7d Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Thu, 10 Jul 2025 02:35:53 -0500 Subject: [PATCH 053/111] Use thin space (U+2009) as a separator instead of _ and in regular English language (#14635) --- lib/elixir/lib/calendar/date.ex | 4 ++-- lib/elixir/lib/calendar/datetime.ex | 4 ++-- lib/elixir/lib/calendar/naive_datetime.ex | 4 ++-- lib/elixir/lib/calendar/time.ex | 4 ++-- lib/elixir/pages/anti-patterns/code-anti-patterns.md | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/elixir/lib/calendar/date.ex b/lib/elixir/lib/calendar/date.ex index e931e088010..b4e365bea46 100644 --- a/lib/elixir/lib/calendar/date.ex +++ b/lib/elixir/lib/calendar/date.ex @@ -633,7 +633,7 @@ defmodule Date do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> Date.convert(~D[2000-01-01], Calendar.Holocene) @@ -667,7 +667,7 @@ defmodule Date do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> Date.convert!(~D[2000-01-01], Calendar.Holocene) diff --git a/lib/elixir/lib/calendar/datetime.ex b/lib/elixir/lib/calendar/datetime.ex index 04dfbe91c74..201696886fb 100644 --- a/lib/elixir/lib/calendar/datetime.ex +++ b/lib/elixir/lib/calendar/datetime.ex @@ -1922,7 +1922,7 @@ defmodule DateTime do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT", @@ -1969,7 +1969,7 @@ defmodule DateTime do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT", diff --git a/lib/elixir/lib/calendar/naive_datetime.ex b/lib/elixir/lib/calendar/naive_datetime.ex index a9cd09b9249..00b4da3e409 100644 --- a/lib/elixir/lib/calendar/naive_datetime.ex +++ b/lib/elixir/lib/calendar/naive_datetime.ex @@ -1261,7 +1261,7 @@ defmodule NaiveDateTime do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> NaiveDateTime.convert(~N[2000-01-01 13:30:15], Calendar.Holocene) @@ -1327,7 +1327,7 @@ defmodule NaiveDateTime do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> NaiveDateTime.convert!(~N[2000-01-01 13:30:15], Calendar.Holocene) diff --git a/lib/elixir/lib/calendar/time.ex b/lib/elixir/lib/calendar/time.ex index e9502e588f3..0ae043c658d 100644 --- a/lib/elixir/lib/calendar/time.ex +++ b/lib/elixir/lib/calendar/time.ex @@ -781,7 +781,7 @@ defmodule Time do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> Time.convert(~T[13:30:15], Calendar.Holocene) @@ -837,7 +837,7 @@ defmodule Time do ## Examples Imagine someone implements `Calendar.Holocene`, a calendar based on the - Gregorian calendar that adds exactly 10,000 years to the current Gregorian + Gregorian calendar that adds exactly 10 000 years to the current Gregorian year: iex> Time.convert!(~T[13:30:15], Calendar.Holocene) diff --git a/lib/elixir/pages/anti-patterns/code-anti-patterns.md b/lib/elixir/pages/anti-patterns/code-anti-patterns.md index 921717d946c..d4bfebc0fed 100644 --- a/lib/elixir/pages/anti-patterns/code-anti-patterns.md +++ b/lib/elixir/pages/anti-patterns/code-anti-patterns.md @@ -144,7 +144,7 @@ end #### Problem -An `Atom` is an Elixir basic type whose value is its own name. Atoms are often useful to identify resources or express the state, or result, of an operation. Creating atoms dynamically is not an anti-pattern by itself. However, atoms are not garbage collected by the Erlang Virtual Machine, so values of this type live in memory during a software's entire execution lifetime. The Erlang VM limits the number of atoms that can exist in an application by default to *1_048_576*, which is more than enough to cover all atoms defined in a program, but attempts to serve as an early limit for applications which are "leaking atoms" through dynamic creation. +An `Atom` is an Elixir basic type whose value is its own name. Atoms are often useful to identify resources or express the state, or result, of an operation. Creating atoms dynamically is not an anti-pattern by itself. However, atoms are not garbage collected by the Erlang Virtual Machine, so values of this type live in memory during a software's entire execution lifetime. The Erlang VM limits the number of atoms that can exist in an application by default to *1 048 576*, which is more than enough to cover all atoms defined in a program, but attempts to serve as an early limit for applications which are "leaking atoms" through dynamic creation. For these reasons, creating atoms dynamically can be considered an anti-pattern when the developer has no control over how many atoms will be created during the software execution. This unpredictable scenario can expose the software to unexpected behavior caused by excessive memory usage, or even by reaching the maximum number of *atoms* possible. From cb49bfb6e4b7fb669067a5be435bcbdcb0042c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 10 Jul 2025 17:15:21 +0200 Subject: [PATCH 054/111] Add local_for_callback option to Macro.Env.expand_import (#14620) --- lib/elixir/lib/macro/env.ex | 26 ++++++++++++++++++++------ lib/elixir/src/elixir_dispatch.erl | 8 +++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index 8e0016431bb..edae8a4cc5d 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -96,7 +96,8 @@ defmodule Macro.Env do ] @type expand_import_opts :: [ - allow_locals: boolean(), + allow_locals: + boolean() | (Macro.metadata(), atom(), arity(), t() -> function() | false), check_deprecations: boolean(), trace: boolean() ] @@ -555,8 +556,15 @@ defmodule Macro.Env do ## Options - * `:allow_locals` - when set to `false`, it does not attempt to capture - local macros defined in the current module in `env` + * `:allow_locals` - controls how local macros are resolved. + Defaults to `true`. + + - When `false`, does not attempt to capture local macros defined in the + current module in `env` + - When `true`, uses a default resolver that looks for public macros in + the current module + - When a function, uses the function as a custom local resolver. The function + must have the signature: `(meta, name, arity, env) -> function() | false` * `:check_deprecations` - when set to `false`, does not check for deprecations when expanding macros @@ -580,10 +588,16 @@ defmodule Macro.Env do trace = Keyword.get(opts, :trace, true) module = env.module + # When allow_locals is a callback, we don't need to pass module macros as extra + # because the callback will handle local macro resolution extra = - case allow_locals and function_exported?(module, :__info__, 1) do - true -> [{module, module.__info__(:macros)}] - false -> [] + if is_function(allow_locals, 4) do + [] + else + case allow_locals and function_exported?(module, :__info__, 1) do + true -> [{module, module.__info__(:macros)}] + false -> [] + end end case :elixir_dispatch.expand_import(meta, name, arity, env, extra, allow_locals, trace) do diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index e6769e2ba80..ea52a61df9b 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -172,7 +172,13 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> do_expand_import(Dispatch, Meta, Name, Arity, Module, E, Trace); _ -> - Local = AllowLocals andalso elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E), + Local = case AllowLocals of + false -> false; + true -> elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E); + Fun when is_function(Fun, 4) -> + %% If we have a custom local resolver, use it. + Fun(Meta, Name, Arity, E) + end, case Dispatch of %% There is a local and an import. This is a conflict unless From ea77a68daa6dfbc61d125d71a9bf85dea321871d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 10 Jul 2025 17:30:36 +0200 Subject: [PATCH 055/111] Add tests for allow_locals option --- lib/elixir/lib/macro/env.ex | 8 ++++---- lib/elixir/src/elixir_dispatch.erl | 4 +--- lib/elixir/test/elixir/macro/env_test.exs | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index edae8a4cc5d..af8d79b7796 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -558,13 +558,13 @@ defmodule Macro.Env do * `:allow_locals` - controls how local macros are resolved. Defaults to `true`. - + - When `false`, does not attempt to capture local macros defined in the current module in `env` - When `true`, uses a default resolver that looks for public macros in the current module - - When a function, uses the function as a custom local resolver. The function - must have the signature: `(meta, name, arity, env) -> function() | false` + - When a function, it will be invoked to lazily compute a local function + (or return false). It has signature `(-> function() | false)` * `:check_deprecations` - when set to `false`, does not check for deprecations when expanding macros @@ -591,7 +591,7 @@ defmodule Macro.Env do # When allow_locals is a callback, we don't need to pass module macros as extra # because the callback will handle local macro resolution extra = - if is_function(allow_locals, 4) do + if is_function(allow_locals, 0) do [] else case allow_locals and function_exported?(module, :__info__, 1) do diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl index ea52a61df9b..346097e9a76 100644 --- a/lib/elixir/src/elixir_dispatch.erl +++ b/lib/elixir/src/elixir_dispatch.erl @@ -175,9 +175,7 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) -> Local = case AllowLocals of false -> false; true -> elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E); - Fun when is_function(Fun, 4) -> - %% If we have a custom local resolver, use it. - Fun(Meta, Name, Arity, E) + Fun when is_function(Fun, 0) -> Fun() end, case Dispatch of diff --git a/lib/elixir/test/elixir/macro/env_test.exs b/lib/elixir/test/elixir/macro/env_test.exs index 3138f542519..07e6a5b9546 100644 --- a/lib/elixir/test/elixir/macro/env_test.exs +++ b/lib/elixir/test/elixir/macro/env_test.exs @@ -207,6 +207,25 @@ defmodule Macro.EnvTest do assert fun.([generated: true], [quote(do: hello())]) == quote(generated: true, do: hello()) end + defmacro allow_locals_example, do: :ok + + test "allow_locals" do + {:macro, Macro.EnvTest, fun} = + expand_import(env(), meta(), :allow_locals_example, 0) + + assert fun.([], []) == :ok + + assert expand_import(env(), meta(), :allow_locals_example, 0, allow_locals: false) == + {:error, :not_found} + + assert expand_import(env(), meta(), :allow_locals_example, 0, + allow_locals: fn -> send(self(), false) end + ) == + {:error, :not_found} + + assert_received false + end + test "with tracing and deprecations" do message = "MacroEnvMacros.my_deprecated_macro/1 is deprecated" From 26855eda9f8428b20c7b05bf3c9f82182f514023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Thu, 10 Jul 2025 20:48:30 +0200 Subject: [PATCH 056/111] Update `allow_local` option spec (#14642) --- lib/elixir/lib/macro/env.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/elixir/lib/macro/env.ex b/lib/elixir/lib/macro/env.ex index af8d79b7796..94f0212cc32 100644 --- a/lib/elixir/lib/macro/env.ex +++ b/lib/elixir/lib/macro/env.ex @@ -96,8 +96,7 @@ defmodule Macro.Env do ] @type expand_import_opts :: [ - allow_locals: - boolean() | (Macro.metadata(), atom(), arity(), t() -> function() | false), + allow_locals: boolean() | (-> function() | false), check_deprecations: boolean(), trace: boolean() ] From 8d2775051d1cbfd13ad2b1ee7386d10dd8518f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Jul 2025 11:05:32 +0200 Subject: [PATCH 057/111] Update ... to an operator in Code.Fragment --- lib/elixir/lib/code/fragment.ex | 2 +- lib/elixir/test/elixir/code_fragment_test.exs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index e1323c290ef..06714048bb4 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -335,7 +335,7 @@ defmodule Code.Fragment do end defp identifier_to_cursor_context([?., ?., ?: | _], n, _), do: {{:unquoted_atom, ~c".."}, n + 3} - defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:local_or_var, ~c"..."}, n + 3} + defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:operator, ~c"..."}, n + 3} defp identifier_to_cursor_context([?., ?: | _], n, _), do: {{:unquoted_atom, ~c"."}, n + 2} defp identifier_to_cursor_context([?., ?. | _], n, _), do: {{:operator, ~c".."}, n + 2} diff --git a/lib/elixir/test/elixir/code_fragment_test.exs b/lib/elixir/test/elixir/code_fragment_test.exs index 84cc28b9466..f4ae21cc996 100644 --- a/lib/elixir/test/elixir/code_fragment_test.exs +++ b/lib/elixir/test/elixir/code_fragment_test.exs @@ -127,8 +127,6 @@ defmodule CodeFragmentTest do assert CF.cursor_context("hello(\t") == {:local_call, ~c"hello"} assert CF.cursor_context("hello(\n") == {:local_call, ~c"hello"} assert CF.cursor_context("hello(\r\n") == {:local_call, ~c"hello"} - assert CF.cursor_context("...(") == {:local_call, ~c"..."} - assert CF.cursor_context("...(\s") == {:local_call, ~c"..."} end test "dot_arity" do @@ -326,6 +324,11 @@ defmodule CodeFragmentTest do assert CF.cursor_context("<~> ") == {:operator_call, ~c"<~>"} assert CF.cursor_context(":: ") == {:operator_call, ~c"::"} + assert CF.cursor_context("...(") == {:operator_call, ~c"..."} + assert CF.cursor_context("...(\s") == {:operator_call, ~c"..."} + assert CF.cursor_context("+(") == {:operator_call, ~c"+"} + assert CF.cursor_context("++(\s") == {:operator_call, ~c"++"} + assert CF.cursor_context("+/") == {:operator_arity, ~c"+"} assert CF.cursor_context("++/") == {:operator_arity, ~c"++"} assert CF.cursor_context("!/") == {:operator_arity, ~c"!"} From 2b8ee38660767f9524e305ac49e00e92dac56fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Jul 2025 11:12:18 +0200 Subject: [PATCH 058/111] Tag / as an operator in fragments, closes #14643 --- lib/elixir/lib/code/fragment.ex | 3 ++- lib/elixir/test/elixir/code_fragment_test.exs | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index 06714048bb4..7e5323c5fad 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -302,7 +302,8 @@ defmodule Code.Fragment do {{:local_or_var, acc}, count} -> {{:local_arity, acc}, count} {{:dot, base, acc}, count} -> {{:dot_arity, base, acc}, count} {{:operator, acc}, count} -> {{:operator_arity, acc}, count} - {_, _} -> {:none, 0} + {{:sigil, _}, _} -> {:none, 0} + {_, _} -> {{:operator, ~c"/"}, 1} end end diff --git a/lib/elixir/test/elixir/code_fragment_test.exs b/lib/elixir/test/elixir/code_fragment_test.exs index f4ae21cc996..497ca3ae0bf 100644 --- a/lib/elixir/test/elixir/code_fragment_test.exs +++ b/lib/elixir/test/elixir/code_fragment_test.exs @@ -302,6 +302,7 @@ defmodule CodeFragmentTest do end test "operators" do + assert CF.cursor_context("/") == {:operator, ~c"/"} assert CF.cursor_context("+") == {:operator, ~c"+"} assert CF.cursor_context("++") == {:operator, ~c"++"} assert CF.cursor_context("!") == {:operator, ~c"!"} @@ -361,6 +362,15 @@ defmodule CodeFragmentTest do assert CF.cursor_context("~r/") == :none assert CF.cursor_context("~r<") == :none + assert CF.cursor_context("~r''") == :none + assert CF.cursor_context("~r' '") == :none + assert CF.cursor_context("~r'foo'") == :none + + # The slash is used in sigils, arities, and operators, so there is ambiguity + assert CF.cursor_context("~r//") == {:operator, ~c"/"} + assert CF.cursor_context("~r/ /") == {:operator, ~c"/"} + assert CF.cursor_context("~r/foo/") == {:local_arity, ~c"foo"} + assert CF.cursor_context("~R") == {:sigil, ~c"R"} assert CF.cursor_context("~R/") == :none assert CF.cursor_context("~R<") == :none From f87fbc2833d83ec2c2c54ece51806153e57392ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Jul 2025 12:56:46 +0200 Subject: [PATCH 059/111] Clarify function types --- lib/elixir/pages/references/gradual-set-theoretic-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir/pages/references/gradual-set-theoretic-types.md b/lib/elixir/pages/references/gradual-set-theoretic-types.md index b33ba852d0f..b63097ee682 100644 --- a/lib/elixir/pages/references/gradual-set-theoretic-types.md +++ b/lib/elixir/pages/references/gradual-set-theoretic-types.md @@ -97,7 +97,7 @@ If you give it an integer, it negates it. If you give it a boolean, it negates i We can say this function has the type `(integer() -> integer())` because it is capable of receiving an integer and returning an integer. In this case, `(integer() -> integer())` is a set that represents all functions that can receive an integer and return an integer. Even though this function can receive other arguments and return other values, it is still part of the `(integer() -> integer())` set. -This function also has the type `(boolean() -> boolean())`, because it receives booleans and returns booleans. Therefore, we can say the overall type of the function is `(integer() -> integer()) and (boolean() -> boolean())`. The intersection means the function belongs to both sets. +This function also has the type `(boolean() -> boolean())`, because it also receives booleans and returns booleans. If you pass the function above to another function that expects `(boolean() -> boolean())`, type checking will succeed. Therefore, we can say the overall type of the function is `(integer() -> integer()) and (boolean() -> boolean())`. The intersection means the function belongs to both sets. At this point, you may ask, why not a union? As a real-world example, take a t-shirt with green and yellow stripes. We can say the t-shirt belongs to the set of "t-shirts with green color". We can also say the t-shirt belongs to the set of "t-shirts with yellow color". Let's see the difference between unions and intersections: @@ -105,7 +105,7 @@ At this point, you may ask, why not a union? As a real-world example, take a t-s * `(t_shirts_with_green() and t_shirts_with_yellow())` - contains t-shirts with both green and yellow (and maybe other colors) -Since the t-shirt has both colors, we say it belongs to the intersection of both sets. The same way that a function that goes from `(integer() -> integer())` and `(boolean() -> boolean())` is also an intersection. In practice, it does not make sense to define the union of two functions in Elixir, so the compiler will always point to the right direction. +Since the t-shirt has both colors, we could say it belongs to the union of green and yellow t-shirts, but doing so would not capture the fact it is both green and yellow. Therefore it is more precise to say it belongs to the intersection of both sets. The same way that a function that goes from `(integer() -> integer())` and `(boolean() -> boolean())` is also an intersection. In practice, it is not useful to define the union of two functions in Elixir, so the compiler will point you to the right direction if you specify the wrong one. ## The `dynamic()` type From 855df4fc7708814eb1d5f747560135b1f8771925 Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Fri, 11 Jul 2025 15:03:26 +0200 Subject: [PATCH 060/111] Domain keys in map (#14478) - Introduced tests for union, intersection, and difference operations involving domain key types. - Validated subtype relationships and intersection results for maps with domain keys. - Enhanced map fetch and delete functionalities to handle domain key types. - Ensured correct behavior of dynamic types with domain keys in various scenarios. --- lib/elixir/lib/module/types/descr.ex | 793 ++++++++++++++++-- .../test/elixir/module/types/descr_test.exs | 394 ++++++++- 2 files changed, 1079 insertions(+), 108 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index b14625f80ad..d7c73d87c98 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -26,6 +26,23 @@ defmodule Module.Types.Descr do @bit_top (1 <<< 7) - 1 @bit_number @bit_integer ||| @bit_float + defmacrop domain_key(key), do: {:domain_key, key} + + @domain_key_types [ + {:domain_key, :binary}, + {:domain_key, :empty_list}, + {:domain_key, :integer}, + {:domain_key, :float}, + {:domain_key, :pid}, + {:domain_key, :port}, + {:domain_key, :reference}, + {:domain_key, :fun}, + {:domain_key, :atom}, + {:domain_key, :tuple}, + {:domain_key, :map}, + {:domain_key, :list} + ] + @fun_top :fun_top @atom_top {:negation, :sets.new(version: 2)} @map_top [{:open, %{}, []}] @@ -46,6 +63,11 @@ defmodule Module.Types.Descr do @not_non_empty_list Map.delete(@term, :list) @not_list Map.replace!(@not_non_empty_list, :bitmap, @bit_top - @bit_empty_list) + @not_set %{optional: 1} + @term_or_optional Map.put(@term, :optional, 1) + @term_or_dynamic_optional Map.put(@term, :dynamic, %{optional: 1}) + @not_atom_or_optional Map.delete(@term_or_optional, :atom) + @empty_intersection [0, []] @empty_difference [0, []] @@ -66,7 +88,7 @@ defmodule Module.Types.Descr do def atom(as), do: %{atom: atom_new(as)} def atom(), do: %{atom: @atom_top} def binary(), do: %{bitmap: @bit_binary} - def closed_map(pairs), do: map_descr(:closed, pairs) + def closed_map(pairs), do: map_descr(:closed, pairs, @term_or_optional, false) def empty_list(), do: %{bitmap: @bit_empty_list} def empty_map(), do: %{map: @map_empty} def integer(), do: %{bitmap: @bit_integer} @@ -74,7 +96,8 @@ defmodule Module.Types.Descr do def list(type), do: list_descr(type, @empty_list, true) def non_empty_list(type, tail \\ @empty_list), do: list_descr(type, tail, false) def open_map(), do: %{map: @map_top} - def open_map(pairs), do: map_descr(:open, pairs) + def open_map(pairs), do: map_descr(:open, pairs, @term_or_optional, false) + def open_map(pairs, default), do: map_descr(:open, pairs, if_set(default), true) def open_tuple(elements, _fallback \\ term()), do: tuple_descr(:open, elements) def pid(), do: %{bitmap: @bit_pid} def port(), do: %{bitmap: @bit_port} @@ -231,15 +254,18 @@ defmodule Module.Types.Descr do # is equivalent to `%{:foo => integer() or not_set()}`. # # `not_set()` has no meaning outside of map types. - - @not_set %{optional: 1} - @term_or_optional Map.put(@term, :optional, 1) - @term_or_dynamic_optional Map.put(@term, :dynamic, %{optional: 1}) - @not_atom_or_optional Map.delete(@term_or_optional, :atom) - def not_set(), do: @not_set + def if_set(:term), do: term_or_optional() - def if_set(type), do: Map.put(type, :optional, 1) + + # If type contains a :dynamic part, :optional gets added there. + def if_set(type) do + case type do + %{dynamic: dyn} -> Map.put(%{type | dynamic: Map.put(dyn, :optional, 1)}, :optional, 1) + _ -> Map.put(type, :optional, 1) + end + end + defp term_or_optional(), do: @term_or_optional @compile {:inline, @@ -539,6 +565,8 @@ defmodule Module.Types.Descr do end end + defp empty_or_optional?(type), do: empty?(remove_optional(type)) + # For atom, bitmap, tuple, and optional, if the key is present, # then they are not empty, defp empty_key?(:fun, value), do: fun_empty?(value) @@ -944,6 +972,7 @@ defmodule Module.Types.Descr do end end + defp atom_only?(:term), do: false defp atom_only?(descr), do: empty?(Map.delete(descr, :atom)) defp atom_new(as) when is_list(as), do: {:union, :sets.from_list(as, version: 2)} @@ -2216,34 +2245,79 @@ defmodule Module.Types.Descr do # is the union of `%{..., a: atom(), b: if_set(not integer())}` and # `%{..., a: if_set(not atom()), b: integer()}`. For maps with more keys, # each key in a negated literal may create a new union when eliminated. + # + # Instead of a tag :open or :closed, we can also use a map of domains which + # specifies for each defined key domain (@domain_key_types) the type associated with + # those keys. + # + # For instance, the type `%{atom() => if_set(integer())}` is the type of maps where atom keys + # map to integers, without any non-atom keys. It is represented using the map literal + # `{%{atom: if_set(integer())}, [], []}`, with no defined keys or negations. + # + # The type `%{..., atom() => integer()}` represents maps with atom keys bound to integers, + # and other keys bound to any type. It will be represented using a map domain that maps + # atom to `if_set(integer())`, and every other domain key to `term_or_optional()`. + + defp map_descr(tag, pairs, default, force?) do + {fields, domains, dynamic?} = map_descr_pairs(pairs, [], %{}, false) + + map_new = + if domains != %{} or force? do + domains = + if tag == :open do + Enum.reduce(@domain_key_types, domains, &Map.put_new(&2, &1, default)) + else + domains + end - defp map_descr(tag, fields) do - case map_descr_pairs(fields, [], false) do - {fields, true} -> - %{dynamic: %{map: map_new(tag, fields |> Enum.reverse() |> :maps.from_list())}} + map_new(domains, fields) + else + map_new(tag, fields) + end - {_, false} -> - %{map: map_new(tag, :maps.from_list(fields))} + case dynamic? do + true -> %{dynamic: %{map: map_new}} + false -> %{map: map_new} end end - defp map_descr_pairs([{key, :term} | rest], acc, dynamic?) do - map_descr_pairs(rest, [{key, :term} | acc], dynamic?) + # TODO: Double check if we indeed want the union here + # when we start using domain types from Elixir itself + defp map_put_domain(domain, key, value) do + Map.update(domain, key, if_set(value), &union(&1, value)) end - defp map_descr_pairs([{key, value} | rest], acc, dynamic?) do - case :maps.take(:dynamic, value) do - :error -> map_descr_pairs(rest, [{key, value} | acc], dynamic?) - {dynamic, _static} -> map_descr_pairs(rest, [{key, dynamic} | acc], true) + defp map_descr_pairs([{key, :term} | rest], fields, domain, dynamic?) do + case is_atom(key) do + true -> map_descr_pairs(rest, [{key, :term} | fields], domain, dynamic?) + false -> map_descr_pairs(rest, fields, map_put_domain(domain, key, :term), dynamic?) end end - defp map_descr_pairs([], acc, dynamic?) do - {acc, dynamic?} + defp map_descr_pairs([{key, value} | rest], fields, domain, dynamic?) do + {value, dynamic?} = + case :maps.take(:dynamic, value) do + :error -> {value, dynamic?} + {dynamic, _static} -> {dynamic, true} + end + + case is_atom(key) do + true -> map_descr_pairs(rest, [{key, value} | fields], domain, dynamic?) + false -> map_descr_pairs(rest, fields, map_put_domain(domain, key, value), dynamic?) + end + end + + defp map_descr_pairs([], fields, domain, dynamic?) do + {fields |> Enum.reverse() |> :maps.from_list(), domain, dynamic?} end - defp tag_to_type(:open), do: term_or_optional() - defp tag_to_type(:closed), do: not_set() + defp tuple_tag_to_type(:open), do: term_or_optional() + defp tuple_tag_to_type(:closed), do: not_set() + + # Gets the default type associated to atom keys in a map. + defp map_key_tag_to_type(:open), do: term_or_optional() + defp map_key_tag_to_type(:closed), do: not_set() + defp map_key_tag_to_type(domains = %{}), do: Map.get(domains, domain_key(:atom), not_set()) defguardp is_optional_static(map) when is_map(map) and is_map_key(map, :optional) @@ -2420,6 +2494,58 @@ defmodule Module.Types.Descr do :maps.iterator(open) |> :maps.next() |> map_literal_intersection_loop(closed) end + # At least one tag is a tag-domain pair. + defp map_literal_intersection(tag_or_domains1, map1, tag_or_domains2, map2) do + # For a closed map with domains intersected with an open map with domains: + # 1. The result is closed (more restrictive) + # 2. We need to check each domain in the open map against the closed map + default1 = map_key_tag_to_type(tag_or_domains1) + default2 = map_key_tag_to_type(tag_or_domains2) + + # Compute the new domain + tag_or_domains = map_domain_intersection(tag_or_domains1, tag_or_domains2) + + # Go over all fields in map1 and map2 with default atom types atom1 and atom2 + # 1. If key is in both maps, compute non empty intersection (:error if it is none) + # 2. If key is only in map1, compute non empty intersection with atom2 + # 3. If key is only in map2, compute non empty intersection with atom1 + # We do that by computing intersection on all key labels in both map1 and map2, + # using default values when a key is not present. + {tag_or_domains, + symmetrical_merge(map1, default1, map2, default2, fn _key, v1, v2 -> + non_empty_intersection!(v1, v2) + end)} + end + + # Compute the intersection of two tags or tag-domain pairs. + defp map_domain_intersection(:closed, _), do: :closed + defp map_domain_intersection(_, :closed), do: :closed + defp map_domain_intersection(:open, tag_or_domains), do: tag_or_domains + defp map_domain_intersection(tag_or_domains, :open), do: tag_or_domains + + defp map_domain_intersection(domains1 = %{}, domains2 = %{}) do + new_domains = + for {domain_key(_) = domain_key, type1} <- domains1, reduce: %{} do + acc_domains -> + case domains2 do + %{^domain_key => type2} -> + inter = intersection(type1, type2) + + if empty_or_optional?(inter) do + acc_domains + else + Map.put(acc_domains, domain_key, inter) + end + + _ -> + acc_domains + end + end + + # If the explicit domains are empty, use simple atom tags + if map_size(new_domains) == 0, do: :closed, else: new_domains + end + defp map_literal_intersection_loop(:none, acc), do: {:closed, acc} defp map_literal_intersection_loop({key, type1, iterator}, acc) do @@ -2452,7 +2578,7 @@ defmodule Module.Types.Descr do {:open, fields2, []}, dnf1 when map_size(fields2) == 1 -> Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc -> {key, value, _rest} = :maps.next(:maps.iterator(fields2)) - t_diff = difference(Map.get(fields1, key, tag_to_type(tag1)), value) + t_diff = difference(Map.get(fields1, key, map_key_tag_to_type(tag1)), value) if empty?(t_diff) do acc @@ -2524,11 +2650,9 @@ defmodule Module.Types.Descr do # Optimization: if the key does not exist in the map, avoid building # if_set/not_set pairs and return the popped value directly. - defp map_fetch_static(%{map: [{tag, fields, []}]}, key) when not is_map_key(fields, key) do - case tag do - :open -> {true, term()} - :closed -> {true, none()} - end + defp map_fetch_static(%{map: [{tag_or_domains, fields, []}]}, key) + when not is_map_key(fields, key) do + map_key_tag_to_type(tag_or_domains) |> pop_optional_static() end # Takes a map dnf and returns the union of types it can take for a given key. @@ -2536,15 +2660,13 @@ defmodule Module.Types.Descr do defp map_fetch_static(%{map: dnf}, key) do dnf |> Enum.reduce(none(), fn - # Optimization: if there are no negatives, - # we can return the value directly. + # Optimization: if there are no negatives and key exists, return its value {_tag, %{^key => value}, []}, acc -> value |> union(acc) - # Optimization: if there are no negatives - # and the key does not exist, return the default one. + # Optimization: if there are no negatives and the key does not exist, return the default one. {tag, %{}, []}, acc -> - tag_to_type(tag) |> union(acc) + map_key_tag_to_type(tag) |> union(acc) {tag, fields, negs}, acc -> {fst, snd} = map_pop_key(tag, fields, key) @@ -2600,6 +2722,154 @@ defmodule Module.Types.Descr do end end + @doc """ + Refreshes the type of map after assuming some type was given to a key of a given type. + Assuming that the descr is exclusively a map (or dynamic). + """ + # TODO: Figure out how this operation will be used from Elixir + def map_refresh(:term, _key, _type), do: :badmap + + def map_refresh(descr, key_descr, type) do + {dynamic_descr, static_descr} = Map.pop(descr, :dynamic) + key_descr = unfold(key_descr) + type = unfold(type) + + cond do + # Either 1) static part is a map, or 2) static part is empty and dynamic part contains maps + not map_only?(static_descr) -> + :badmap + + empty?(static_descr) and not (not is_nil(dynamic_descr) and descr_key?(dynamic_descr, :map)) -> + :badmap + + # Either of those three types could be dynamic. + not (not is_nil(dynamic_descr) or Map.has_key?(key_descr, :dynamic) or + Map.has_key?(type, :dynamic)) -> + map_refresh_static(descr, key_descr, type) + + true -> + # If one of those is dynamic, we just compute the union + {descr_dynamic, descr_static} = Map.pop(descr, :dynamic, descr) + {key_dynamic, key_static} = Map.pop(key_descr, :dynamic, key_descr) + {type_dynamic, type_static} = Map.pop(type, :dynamic, type) + + with {:ok, new_static} <- map_refresh_static(descr_static, key_static, type_static), + {:ok, new_dynamic} <- map_refresh_static(descr_dynamic, key_dynamic, type_dynamic) do + {:ok, union(new_static, dynamic(new_dynamic))} + end + end + end + + def map_refresh_static(%{map: _} = descr, key_descr = %{}, type) do + # Check if descr is a valid map, + case atom_fetch(key_descr) do + # If the key_descr is a singleton, we directly put the type into the map. + {:finite, [single_key]} -> + map_put(descr, single_key, type) + + # In this case, we iterate on key_descr to add type to each key type it covers. + # Since we do not know which key will be used, we do the union with previous types. + _ -> + new_descr = + key_descr + |> covered_key_types() + |> Enum.reduce(descr, fn + {:atom, atom_key}, acc -> + map_refresh_atom(acc, atom_key, type) + + domain_key, acc -> + map_refresh_domain(acc, domain_key, type) + end) + + {:ok, new_descr} + end + end + + def map_refresh_static(:term, _key_descr, _type), do: {:ok, open_map()} + def map_refresh_static(_, _, _), do: {:ok, none()} + + @doc """ + Updates a key in a map type by fetching its current type, unioning it with a + `new_additional_type`, and then putting the resulting union type back. + + Returns: + - `{:ok, new_map_descr}`: If successful. + - `:badmap`: If the input `descr` is not a valid map type. + - `:badkey`: If the key is considered invalid during the take operation (e.g., + an optional key that resolves to an empty type). + """ + # TODO: Figure out how this operation will be used from Elixir + def map_refresh_key(descr, key, new_additional_type) when is_atom(key) do + case map_fetch(descr, key) do + :badmap -> + :badmap + + # Key is not present: we just add the new one and make it optional. + :badkey -> + with {:ok, descr} <- map_put(descr, key, if_set(new_additional_type)) do + descr + end + + {_optional?, current_key_type} -> + type_to_put = union(current_key_type, new_additional_type) + + case map_fetch_and_put(descr, key, type_to_put) do + {_taken_type, new_map_descr} -> new_map_descr + # Propagates :badmap or :badkey from map_fetch_and_put + error -> error + end + end + end + + def map_refresh_domain(%{map: [{tag, fields, []}]}, domain, type) do + %{map: [{map_refresh_tag(tag, domain, type), fields, []}]} + end + + def map_refresh_domain(%{map: dnf}, domain, type) do + Enum.map(dnf, fn + {tag, fields, []} -> + {map_refresh_tag(tag, domain, type), fields, []} + + {tag, fields, negs} -> + # For negations, we count on the idea that a negation will not remove any + # type from a domain unless it completely cancels out the type. + # So for any non-empty map dnf, we just update the domain with the new type, + # as well as its negations to keep them accurate. + {map_refresh_tag(tag, domain, type), fields, + Enum.map(negs, fn {neg_tag, neg_fields} -> + {map_refresh_tag(neg_tag, domain, type), neg_fields} + end)} + end) + end + + def map_refresh_atom(descr = %{map: dnf}, atom_key, type) do + case atom_key do + {:union, keys} -> + keys + |> :sets.to_list() + |> Enum.reduce(descr, fn key, acc -> map_refresh_key(acc, key, type) end) + + {:negation, keys} -> + # 1) Fetch all the possible keys in the dnf + # 2) Get them all, except the ones in neg_atoms + possible_keys = map_fetch_all_key_names(dnf) + considered_keys = :sets.subtract(possible_keys, keys) + + considered_keys + |> :sets.to_list() + |> Enum.reduce(descr, fn key, acc -> map_refresh_key(acc, key, type) end) + |> map_refresh_domain(domain_key(:atom), type) + end + end + + def map_refresh_tag(tag_or_domains, domain_key, type) do + case tag_or_domains do + :open -> :open + :closed -> %{domain_key => if_set(type)} + domains = %{} -> Map.update(domains, domain_key, if_set(type), &union(&1, type)) + end + end + defp map_put_shared(descr, key, type) do with {nil, descr} <- map_take(descr, key, nil, &map_put_static(&1, key, type)) do {:ok, descr} @@ -2632,6 +2902,249 @@ defmodule Module.Types.Descr do end end + @doc """ + Computes the union of types for keys matching `key_type` within the `map_type`. + + This generalizes `map_fetch/2` (which operates on a single literal key) to + work with a key type (e.g., `atom()`, `integer()`, `:a or :b`). It's based + on the map-selection operator t.[t'] described in Section 4.2 of "Typing Records, + Maps, and Structs" (Castagna et al., ICFP 2023). + + ## Return Values + + The function returns a tuple indicating the outcome and the resulting type union: + + * `{:ok, type}`: Standard success. `type` is the resulting union of types + found for the matching keys. This covers two sub-cases: + * **Keys definitely exist:** If `disjoint?(type, not_set())` is true, + all keys matching `key_type` are guaranteed to exist. + * **Keys may exist:** If `type` includes `not_set()`, some keys + matching `key_type` might exist (contributing their types) while + others might be absent (contributing `not_set()`). + + * `{:ok_absent, type}`: Success, but the resulting `type` is `none()` or a + subtype of `not_set()`. This indicates that no key matching `key_type` + can exist with a value other than `not_set()`. The caller may wish to + issue a warning, as this often implies selecting a field that is + effectively undefined. + + # TODO: implement/decide if worth it (it's from the paper) + * `{:ok_spillover, type}`: Success, and `type` is the resulting union. + However, this indicates that the `key_type` included keys not explicitly + covered by the `map_type`'s fields or domain specifications. The + projection relied on the map's default behavior (e.g., the `term()` + value type for unspecified keys in an open map). The caller may wish to + issue a warning, as this could conceal issues like selecting keys + not intended by the map's definition. + + * `:badmap`: The input `map_type` was invalid (e.g., not a map type or + a dynamic type wrapping a map type). + + * `:badkeytype`: The input `key_type` was invalid (e.g., not a subtype + of the allowed key types like `atom()`, `integer()`, etc.). + """ + # TODO: Figure out how to use this operation from Elixir + def map_get(:term, _key_descr), do: :badmap + + def map_get(%{} = descr, key_descr) do + case :maps.take(:dynamic, descr) do + :error -> + if descr_key?(descr, :map) and map_only?(descr) do + {optional?, type_selected} = map_get_static(descr, key_descr) |> pop_optional_static() + + cond do + empty?(type_selected) -> {:ok_absent, atom([nil])} + optional? -> {:ok, nil_or_type(type_selected)} + true -> {:ok_present, type_selected} + end + else + :badmap + end + + {dynamic, static} -> + if descr_key?(dynamic, :map) and map_only?(static) do + {optional_dynamic?, dynamic_type} = + map_get_static(dynamic, key_descr) |> pop_optional_static() + + {optional_static?, static_type} = + map_get_static(static, key_descr) |> pop_optional_static() + + type_selected = union(dynamic(dynamic_type), static_type) + + cond do + empty?(type_selected) -> {:ok_absent, atom([nil])} + optional_dynamic? or optional_static? -> {:ok, nil_or_type(type_selected)} + true -> {:ok_present, type_selected} + end + else + :badmap + end + end + end + + # Returns the list of key types that are covered by the key_descr. + # E.g., for `{atom([:ok]), term} or integer()` it returns `[:tuple, :integer]`. + # We treat bitmap types as a separate key type. + defp covered_key_types(:term), do: @domain_key_types + + defp covered_key_types(key_descr) do + for {type_kind, type} <- key_descr, reduce: [] do + acc -> + cond do + type_kind == :atom -> [{:atom, type} | acc] + type_kind == :bitmap -> bitmap_to_domain_keys(type) ++ acc + not empty?(%{type_kind => type}) -> [domain_key(type_kind) | acc] + true -> acc + end + end + end + + defp bitmap_to_domain_keys(bitmap) do + [ + if((bitmap &&& @bit_binary) != 0, do: domain_key(:binary)), + if((bitmap &&& @bit_empty_list) != 0, do: domain_key(:empty_list)), + if((bitmap &&& @bit_integer) != 0, do: domain_key(:integer)), + if((bitmap &&& @bit_float) != 0, do: domain_key(:float)), + if((bitmap &&& @bit_pid) != 0, do: domain_key(:pid)), + if((bitmap &&& @bit_port) != 0, do: domain_key(:port)), + if((bitmap &&& @bit_reference) != 0, do: domain_key(:reference)) + ] + |> Enum.reject(&is_nil/1) + end + + defp nil_or_type(type), do: union(type, atom([nil])) + + defp unfold_domains(:closed), do: %{} + + defp unfold_domains(:open), + do: Map.new(@domain_key_types, fn domain_key -> {domain_key, @term_or_optional} end) + + defp unfold_domains(domains = %{}), do: domains + + defp map_get_static(%{map: [{tag_or_domains, fields, []}]}, key_descr) do + # For each non-empty kind of type in the key_descr, we add the corresponding key domain in a union. + domains = unfold_domains(tag_or_domains) + + key_descr + |> covered_key_types() + |> Enum.reduce(none(), fn + # Note: we could stop if we reach term_or_optional() + {:atom, atom_type}, acc -> map_get_atom([{domains, fields, []}], atom_type) |> union(acc) + key_type, acc -> Map.get(domains, key_type, not_set()) |> union(acc) + end) + end + + defp map_get_static(%{map: dnf}, key_descr) do + key_descr + |> covered_key_types() + |> Enum.reduce(none(), fn + {:atom, atom_type}, acc -> + map_get_atom(dnf, atom_type) |> union(acc) + + domain_key, acc -> + map_get_domain(dnf, domain_key) |> union(acc) + end) + end + + defp map_get_static(%{}, _key), do: not_set() + defp map_get_static(:term, _key), do: term_or_optional() + + # Given a map dnf return the union of types for a given atom type. Handles two cases: + # 1. A union of atoms (e.g., `{:union, atoms}`): + # - Iterates through each atom in the union. + # - Fetches the type for each atom and combines them into a union. + # + # 2. A negation of atoms (e.g., `{:negation, atoms}`): + # - Fetches all possible keys in the map's DNF. + # - Excludes the negated atoms from the considered keys. + # - Includes the domain of all atoms in the map's DNF. + # + # Example: + # Fetching a key of type `atom() and not (:a)` from a map of type + # `%{a: atom(), b: float(), atom() => pid()}` + # would return either `nil` or `float()` (key `:b`) or `pid()` (key `atom()`), but not `atom()` (key `:a`). + defp map_get_atom(dnf, atom_type) do + case atom_type do + {:union, atoms} -> + atoms + |> :sets.to_list() + |> Enum.reduce(none(), fn atom, acc -> + {static_optional?, type} = map_fetch_static(%{map: dnf}, atom) + + if static_optional? do + union(type, acc) |> nil_or_type() |> if_set() + else + union(type, acc) + end + end) + + {:negation, atoms} -> + # 1) Fetch all the possible keys in the dnf + # 2) Get them all, except the ones in neg_atoms + possible_keys = map_fetch_all_key_names(dnf) + considered_keys = :sets.subtract(possible_keys, atoms) + + considered_keys + |> :sets.to_list() + |> Enum.reduce(none(), fn atom, acc -> + {static_optional?, type} = map_fetch_static(%{map: dnf}, atom) + + if static_optional? do + union(type, acc) |> nil_or_type() |> if_set() + else + union(type, acc) + end + end) + |> union(map_get_domain(dnf, domain_key(:atom))) + end + end + + # Fetch all present keys in a map dnf (including negated ones). + defp map_fetch_all_key_names(dnf) do + dnf + |> Enum.reduce(:sets.new(version: 2), fn {_tag, fields, negs}, acc -> + keys = :sets.from_list(Map.keys(fields)) + + # Add all the negative keys + # Example: %{...} and not %{a: not_set()} makes key :a present in the map + Enum.reduce(negs, keys, fn {_tag, neg_fields}, acc -> + :sets.from_list(Map.keys(neg_fields)) |> :sets.union(acc) + end) + |> :sets.union(acc) + end) + end + + # Take a map dnf and return the union of types for the given key domain. + defp map_get_domain(dnf, domain_key(_) = domain_key) do + dnf + |> Enum.reduce(none(), fn + {tag, _fields, []}, acc when is_atom(tag) -> + map_key_tag_to_type(tag) |> union(acc) + + # Optimization: if there are no negatives and domains exists, return its value + {%{^domain_key => value}, _fields, []}, acc -> + value |> union(acc) + + # Optimization: if there are no negatives and the key does not exist, return the default type. + {domains = %{}, _fields, []}, acc -> + map_key_tag_to_type(domains) |> union(acc) + + {tag_or_domains, fields, negs}, acc -> + {fst, snd} = map_pop_domain(tag_or_domains, fields, domain_key) + + case map_split_negative_domain(negs, domain_key) do + :empty -> + acc + + negative -> + negative + |> pair_make_disjoint() + |> pair_eliminate_negations_fst(fst, snd) + |> union(acc) + end + end) + end + @doc """ Removes a key from a map type and return its type. @@ -2742,53 +3255,102 @@ defmodule Module.Types.Descr do defp map_empty?(:open, fs, [{:closed, _} | negs]), do: map_empty?(:open, fs, negs) defp map_empty?(tag, fields, [{neg_tag, neg_fields} | negs]) do - (Enum.all?(neg_fields, fn {neg_key, neg_type} -> - cond do - # Keys that are present in the negative map, but not in the positive one - is_map_key(fields, neg_key) -> - true - - # The key is not shared between positive and negative maps, - # if the negative type is optional, then there may be a value in common - tag == :closed -> - is_optional_static(neg_type) - - # There may be value in common - tag == :open -> - diff = difference(term_or_optional(), neg_type) - empty?(diff) or map_empty?(tag, Map.put(fields, neg_key, diff), negs) - end - end) and - Enum.all?(fields, fn {key, type} -> - case neg_fields do - %{^key => neg_type} -> - diff = difference(type, neg_type) - empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs) - - %{} -> - cond do - neg_tag == :open -> - true - - neg_tag == :closed and not is_optional_static(type) -> - false - - true -> - # an absent key in a open negative map can be ignored - diff = difference(type, tag_to_type(neg_tag)) - empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs) - end + if map_check_domain_keys(tag, neg_tag) do + atom_default = map_key_tag_to_type(tag) + neg_atom_default = map_key_tag_to_type(neg_tag) + + (Enum.all?(neg_fields, fn {neg_key, neg_type} -> + cond do + # Ignore keys present in both maps; will be handled below + is_map_key(fields, neg_key) -> + true + + # The key is not shared between positive and negative maps, + # if the negative type is optional, then there may be a value in common + tag == :closed -> + is_optional_static(neg_type) + + # There may be value in common + tag == :open -> + diff = difference(term_or_optional(), neg_type) + empty?(diff) or map_empty?(tag, Map.put(fields, neg_key, diff), negs) + + true -> + diff = difference(atom_default, neg_type) + empty?(diff) or map_empty?(tag, Map.put(fields, neg_key, diff), negs) end - end)) or map_empty?(tag, fields, negs) + end) and + Enum.all?(fields, fn {key, type} -> + case neg_fields do + %{^key => neg_type} -> + diff = difference(type, neg_type) + empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs) + + %{} -> + cond do + neg_tag == :open -> + true + + neg_tag == :closed and not is_optional_static(type) -> + false + + true -> + # an absent key in a open negative map can be ignored + diff = difference(type, neg_atom_default) + empty?(diff) or map_empty?(tag, Map.put(fields, key, diff), negs) + end + end + end)) or map_empty?(tag, fields, negs) + else + map_empty?(tag, fields, negs) + end + end + + # Verify the domain condition from equation (22) in paper ICFP'23 https://www.irif.fr/~gc/papers/icfp23.pdf + # which is that every domain key type in the positive map is a subtype + # of the corresponding domain key type in the negative map. + defp map_check_domain_keys(:closed, _), do: true + defp map_check_domain_keys(_, :open), do: true + + # An open map is a subtype iff the negative domains are all present as term_or_optional() + defp map_check_domain_keys(:open, neg_domains) do + map_size(neg_domains) == length(@domain_key_types) and + Enum.all?(neg_domains, fn {domain_key(_), type} -> subtype?(term_or_optional(), type) end) + end + + # A positive domains is smaller than a closed map iff all its keys are empty or optional + defp map_check_domain_keys(pos_domains, :closed) do + Enum.all?(pos_domains, fn {domain_key(_), type} -> empty_or_optional?(type) end) + end + + # Component-wise comparison of domains + defp map_check_domain_keys(pos_domains, neg_domains) do + Enum.all?(pos_domains, fn {domain_key(_) = domain_key, type} -> + subtype?(type, Map.get(neg_domains, domain_key, not_set())) + end) end defp map_pop_key(tag, fields, key) do case :maps.take(key, fields) do {value, fields} -> {value, %{map: map_new(tag, fields)}} - :error -> {tag_to_type(tag), %{map: map_new(tag, fields)}} + :error -> {map_key_tag_to_type(tag), %{map: map_new(tag, fields)}} end end + # Pop a domain type, e.g. popping integers from %{integer() => binary()} + # returns {if_set(binary()), %{integer() => if_set(binary()}} + # If the domain is not present, use the tag to type as default. + defp map_pop_domain(domains = %{}, fields, domain_key) do + case :maps.take(domain_key, domains) do + {value, domains} -> {if_set(value), %{map: map_new(domains, fields)}} + :error -> {map_key_tag_to_type(domains), %{map: map_new(domains, fields)}} + end + end + + # Atom case + defp map_pop_domain(tag, fields, _domain_key), + do: {map_key_tag_to_type(tag), %{map: map_new(tag, fields)}} + defp map_split_negative(negs, key) do Enum.reduce_while(negs, [], fn # A negation with an open map means the whole thing is empty. @@ -2797,6 +3359,13 @@ defmodule Module.Types.Descr do end) end + defp map_split_negative_domain(negs, domain_key) do + Enum.reduce_while(negs, [], fn + {:open, fields}, _acc when map_size(fields) == 0 -> {:halt, :empty} + {tag, fields}, neg_acc -> {:cont, [map_pop_domain(tag, fields, domain_key) | neg_acc]} + end) + end + # Use heuristics to normalize a map dnf for pretty printing. defp map_normalize(dnfs) do for dnf <- dnfs, not map_empty?([dnf]) do @@ -2915,11 +3484,26 @@ defmodule Module.Types.Descr do {:map, [], []} end + def map_literal_to_quoted({domains = %{}, fields}, _opts) + when map_size(domains) == 0 and map_size(fields) == 0 do + {:empty_map, [], []} + end + def map_literal_to_quoted({:open, %{__struct__: @not_atom_or_optional} = fields}, _opts) when map_size(fields) == 1 do {:non_struct_map, [], []} end + def map_literal_to_quoted({domains = %{}, fields}, opts) do + domain_fields = + for {domain_key(domain_type), value_type} <- domains do + {{domain_type, [], []}, map_value_to_quoted(value_type, opts)} + end + + regular_fields_quoted = map_fields_to_quoted(:closed, Enum.sort(fields), opts) + {:%{}, [], domain_fields ++ regular_fields_quoted} + end + def map_literal_to_quoted({tag, fields}, opts) do case tag do :closed -> @@ -2973,13 +3557,17 @@ defmodule Module.Types.Descr do literal_to_quoted(key) end - {optional?, type} = pop_optional_static(type) + {key, map_value_to_quoted(type, opts)} + end + end - cond do - not optional? -> {key, to_quoted(type, opts)} - empty?(type) -> {key, {:not_set, [], []}} - true -> {key, {:if_set, [], [to_quoted(type, opts)]}} - end + defp map_value_to_quoted(type, opts) do + {optional?, type} = pop_optional_static(type) + + cond do + not optional? -> to_quoted(type, opts) + empty?(type) -> {:not_set, [], []} + true -> {:if_set, [], [to_quoted(type, opts)]} end end @@ -3410,7 +3998,7 @@ defmodule Module.Types.Descr do defp tuple_get(dnf, index) do Enum.reduce(dnf, none(), fn - {tag, elements}, acc -> Enum.at(elements, index, tag_to_type(tag)) |> union(acc) + {tag, elements}, acc -> Enum.at(elements, index, tuple_tag_to_type(tag)) |> union(acc) end) end @@ -3708,9 +4296,9 @@ defmodule Module.Types.Descr do ## Map helpers + # Erlang maps:merge_with/3 has to preserve the order in combiner. + # We don't care about the order, so we have a faster implementation. defp symmetrical_merge(left, right, fun) do - # Erlang maps:merge_with/3 has to preserve the order in combiner. - # We don't care about the order, so we have a faster implementation. if map_size(left) > map_size(right) do iterator_merge(:maps.next(:maps.iterator(right)), left, fun) else @@ -3730,9 +4318,44 @@ defmodule Module.Types.Descr do defp iterator_merge(:none, map, _fun), do: map + # Perform a symmetrical merge with default values + defp symmetrical_merge(left, left_default, right, right_default, fun) do + iterator = :maps.next(:maps.iterator(left)) + iterator_merge_left(iterator, left_default, right, right_default, %{}, fun) + end + + defp iterator_merge_left({key, v1, iterator}, v1_default, map, v2_default, acc, fun) do + value = + case map do + %{^key => v2} -> fun.(key, v1, v2) + %{} -> fun.(key, v1, v2_default) + end + + acc = Map.put(acc, key, value) + iterator_merge_left(:maps.next(iterator), v1_default, map, v2_default, acc, fun) + end + + defp iterator_merge_left(:none, v1_default, map, _v2_default, acc, fun) do + iterator_merge_right(:maps.next(:maps.iterator(map)), v1_default, acc, fun) + end + + defp iterator_merge_right({key, v2, iterator}, v1_default, acc, fun) do + acc = + case acc do + %{^key => _} -> acc + %{} -> Map.put(acc, key, fun.(key, v1_default, v2)) + end + + iterator_merge_right(:maps.next(iterator), v1_default, acc, fun) + end + + defp iterator_merge_right(:none, _v1_default, acc, _fun) do + acc + end + + # Erlang maps:intersect_with/3 has to preserve the order in combiner. + # We don't care about the order, so we have a faster implementation. defp symmetrical_intersection(left, right, fun) do - # Erlang maps:intersect_with/3 has to preserve the order in combiner. - # We don't care about the order, so we have a faster implementation. if map_size(left) > map_size(right) do iterator_intersection(:maps.next(:maps.iterator(right)), left, [], fun) else diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index c1346661be9..8120c8f5ab8 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -16,6 +16,16 @@ defmodule Module.Types.DescrTest do import Module.Types.Descr, except: [fun: 1] + defmacrop domain_key(key), do: {:domain_key, key} + + defp number(), do: union(integer(), float()) + defp empty_tuple(), do: tuple([]) + defp tuple_of_size_at_least(n) when is_integer(n), do: open_tuple(List.duplicate(term(), n)) + defp tuple_of_size(n) when is_integer(n) and n >= 0, do: tuple(List.duplicate(term(), n)) + defp list(elem_type, tail_type), do: union(empty_list(), non_empty_list(elem_type, tail_type)) + defp map_with_default(descr), do: open_map([], if_set(descr)) + defp nil_or_type(type), do: union(type, atom([nil])) + describe "union" do test "bitmap" do assert union(integer(), float()) == union(float(), integer()) @@ -101,8 +111,28 @@ defmodule Module.Types.DescrTest do a_integer_open = open_map(a: integer()) assert equal?(union(closed_map(a: integer()), a_integer_open), a_integer_open) - assert difference(open_map(a: integer()), closed_map(b: boolean())) - |> equal?(open_map(a: integer())) + # Domain key types + atom_to_atom = open_map([{domain_key(:atom), atom()}]) + atom_to_integer = open_map([{domain_key(:atom), integer()}]) + + # Test union identity and different type maps + assert union(atom_to_atom, atom_to_atom) == atom_to_atom + + # Test subtype relationships with domain key maps + refute open_map([{domain_key(:atom), union(atom(), integer())}]) + |> subtype?(union(atom_to_atom, atom_to_integer)) + + assert union(atom_to_atom, atom_to_integer) + |> subtype?(open_map([{domain_key(:atom), union(atom(), integer())}])) + + # Test unions with empty and open maps + assert union(empty_map(), open_map([{domain_key(:integer), atom()}])) + |> equal?(open_map([{domain_key(:integer), atom()}])) + + assert union(open_map(), open_map([{domain_key(:integer), atom()}])) == open_map() + + # Test union of open map and map with domain key + assert union(open_map(), open_map([{domain_key(:integer), atom()}])) == open_map() end test "list" do @@ -263,7 +293,64 @@ defmodule Module.Types.DescrTest do assert empty?(intersection(closed_map(a: integer()), closed_map(a: atom()))) end - defp number(), do: union(integer(), float()) + test "map with domain keys" do + # %{..., int => t1, atom => t2} and %{int => t3} + # intersection is %{int => t1 and t3, atom => none} + map1 = open_map([{domain_key(:integer), integer()}, {domain_key(:atom), atom()}]) + map2 = closed_map([{domain_key(:integer), number()}]) + + intersection = intersection(map1, map2) + + expected = + closed_map([{domain_key(:integer), integer()}, {domain_key(:atom), none()}]) + + assert equal?(intersection, expected) + + # %{..., int => t1, atom => t2} and %{int => t3, pid => t4} + # intersection is %{int =>t1 and t3, atom => none, pid => t4} + map1 = open_map([{domain_key(:integer), integer()}, {domain_key(:atom), atom()}]) + map2 = closed_map([{domain_key(:integer), float()}, {domain_key(:pid), binary()}]) + + intersection = intersection(map1, map2) + + expected = + closed_map([ + {domain_key(:integer), intersection(integer(), float())}, + {domain_key(:atom), none()}, + {domain_key(:pid), binary()} + ]) + + assert equal?(intersection, expected) + + # %{..., int => t1, string => t3} and %{int => t4} + # intersection is %{int => t1 and t4, string => none} + map1 = open_map([{domain_key(:integer), integer()}, {domain_key(:binary), binary()}]) + map2 = closed_map([{domain_key(:integer), float()}]) + + intersection = intersection(map1, map2) + + assert equal?( + intersection, + closed_map([ + {domain_key(:integer), intersection(integer(), float())}, + {domain_key(:binary), none()} + ]) + ) + + assert subtype?(empty_map(), closed_map([{domain_key(:integer), atom()}])) + + t1 = closed_map([{domain_key(:integer), atom()}]) + t2 = closed_map([{domain_key(:integer), binary()}]) + + assert equal?(intersection(t1, t2), empty_map()) + + t1 = closed_map([{domain_key(:integer), atom()}]) + t2 = closed_map([{domain_key(:atom), term()}]) + + # their intersection is the empty map + refute empty?(intersection(t1, t2)) + assert equal?(intersection(t1, t2), empty_map()) + end test "list" do assert intersection(list(term()), list(term())) == list(term()) @@ -376,10 +463,6 @@ defmodule Module.Types.DescrTest do assert empty?(difference(dynamic(integer()), integer())) end - defp empty_tuple(), do: tuple([]) - defp tuple_of_size_at_least(n) when is_integer(n), do: open_tuple(List.duplicate(term(), n)) - defp tuple_of_size(n) when is_integer(n) and n >= 0, do: tuple(List.duplicate(term(), n)) - test "tuple" do assert empty?(difference(open_tuple([atom()]), open_tuple([term()]))) refute empty?(difference(tuple(), empty_tuple())) @@ -449,9 +532,57 @@ defmodule Module.Types.DescrTest do |> equal?(open_map(a: atom())) refute empty?(difference(open_map(), empty_map())) + + assert difference(open_map(a: integer()), closed_map(b: boolean())) + |> equal?(open_map(a: integer())) end - defp list(elem_type, tail_type), do: union(empty_list(), non_empty_list(elem_type, tail_type)) + test "map with domain keys" do + # Non-overlapping domain keys + t1 = closed_map([{domain_key(:integer), atom()}]) + t2 = closed_map([{domain_key(:atom), binary()}]) + assert equal?(difference(t1, t2) |> union(empty_map()), t1) + assert empty?(difference(t1, t1)) + + # %{atom() => t1} and not %{atom() => t2} is not %{atom() => t1 and not t2} + t3 = closed_map([{domain_key(:integer), atom()}]) + t4 = closed_map([{domain_key(:integer), atom([:ok])}]) + assert subtype?(difference(t3, t4), t3) + + refute difference(t3, t4) + |> equal?(closed_map([{domain_key(:integer), difference(atom(), atom([:ok]))}])) + + # Difference with a non-domain key map + t5 = closed_map([{domain_key(:integer), union(atom(), integer())}]) + t6 = closed_map(a: atom()) + assert equal?(difference(t5, t6), t5) + + # Removing atom keys from a map with defined atom keys + a_number = closed_map(a: number()) + a_number_and_pids = closed_map([{:a, number()}, {domain_key(:atom), pid()}]) + atom_to_float = closed_map([{domain_key(:atom), float()}]) + atom_to_term = closed_map([{domain_key(:atom), term()}]) + atom_to_pid = closed_map([{domain_key(:atom), pid()}]) + t_diff = difference(a_number, atom_to_float) + + # Removing atom keys that map to float, make the :a key point to integer only. + assert map_fetch(t_diff, :a) == {false, integer()} + # %{a => number, atom => pid} and not %{atom => float} gives numbers on :a + assert map_fetch(difference(a_number_and_pids, atom_to_float), :a) == {false, number()} + + assert map_fetch(t_diff, :foo) == :badkey + + assert subtype?(a_number, atom_to_term) + refute subtype?(a_number, atom_to_float) + + # Removing all atom keys from map %{:a => type} means there is nothing left. + assert empty?(difference(a_number, atom_to_term)) + refute empty?(intersection(atom_to_term, a_number)) + assert empty?(intersection(atom_to_pid, a_number)) + + # (%{:a => number} and not %{:a => float}) is %{:a => integer} + assert equal?(difference(a_number, atom_to_float), closed_map(a: integer())) + end test "list" do # Basic list type differences @@ -556,6 +687,16 @@ defmodule Module.Types.DescrTest do assert dynamic(open_map(a: union(integer(), binary()))) == open_map(a: dynamic(integer()) |> union(binary())) + + # For domains too + t1 = dynamic(open_map([{domain_key(:integer), integer()}])) + t2 = open_map([{domain_key(:integer), dynamic(integer())}]) + assert t1 == t2 + + # if_set on dynamic fields also must work + t1 = dynamic(open_map(a: if_set(integer()))) + t2 = open_map(a: if_set(dynamic(integer()))) + assert t1 == t2 end end @@ -612,6 +753,27 @@ defmodule Module.Types.DescrTest do assert subtype?(closed_map(a: integer()), closed_map(a: if_set(integer()))) refute subtype?(closed_map(a: if_set(term())), closed_map(a: term())) assert subtype?(closed_map(a: term()), closed_map(a: if_set(term()))) + + # With domains + t1 = closed_map([{domain_key(:integer), number()}]) + t2 = closed_map([{domain_key(:integer), integer()}]) + + assert subtype?(t2, t1) + + t1_minus_t2 = difference(t1, t2) + refute empty?(t1_minus_t2) + + assert subtype?(map_with_default(number()), open_map()) + t = difference(open_map(), map_with_default(number())) + refute empty?(t) + refute subtype?(open_map(), map_with_default(number())) + assert subtype?(map_with_default(integer()), map_with_default(number())) + refute subtype?(map_with_default(float()), map_with_default(atom())) + + assert equal?( + intersection(map_with_default(number()), map_with_default(float())), + map_with_default(float()) + ) end test "list" do @@ -1438,7 +1600,6 @@ defmodule Module.Types.DescrTest do test "map_fetch with dynamic" do assert map_fetch(dynamic(), :a) == {true, dynamic()} - assert map_fetch(union(dynamic(), integer()), :a) == :badmap assert map_fetch(union(dynamic(open_map(a: integer())), integer()), :a) == :badmap assert map_fetch(union(dynamic(integer()), integer()), :a) == :badmap @@ -1455,6 +1616,104 @@ defmodule Module.Types.DescrTest do |> map_fetch(:a) == {false, union(dynamic(atom()), integer())} end + test "map_fetch with domain keys" do + integer_to_atom = open_map([{domain_key(:integer), atom()}]) + assert map_fetch(integer_to_atom, :foo) == :badkey + + # the key :a is for sure of type pid and exists in type + # %{atom() => pid()} and not %{:a => not_set()} + t1 = closed_map([{domain_key(:atom), pid()}]) + t2 = closed_map(a: not_set()) + t3 = open_map(a: not_set()) + + # Indeed, t2 is equivalent to the empty map + assert map_fetch(difference(t1, t2), :a) == :badkey + assert map_fetch(difference(t1, t3), :a) == {false, pid()} + + t4 = closed_map([{domain_key(:pid), atom()}]) + assert map_fetch(difference(t1, t4) |> difference(t3), :a) == {false, pid()} + + assert map_fetch(closed_map([{domain_key(:atom), pid()}]), :a) == :badkey + + assert map_fetch(dynamic(closed_map([{domain_key(:atom), pid()}])), :a) == + {true, dynamic(pid())} + + assert closed_map([{domain_key(:atom), number()}]) + |> difference(open_map(a: if_set(integer()))) + |> map_fetch(:a) == {false, float()} + + assert closed_map([{domain_key(:atom), number()}]) + |> difference(closed_map(b: if_set(integer()))) + |> map_fetch(:a) == :badkey + end + + test "map_get with domain keys" do + assert map_get(term(), term()) == :badmap + + map_type = closed_map([{domain_key(:tuple), binary()}]) + assert map_get(map_type, tuple()) == {:ok, nil_or_type(binary())} + + # Type with all domain types + # %{:bar => :ok, integer() => :int, float() => :float, atom() => binary(), binary() => integer(), tuple() => float(), map() => pid(), reference() => port(), pid() => boolean()} + all_domains = + closed_map([ + {:bar, atom([:ok])}, + {domain_key(:integer), atom([:int])}, + {domain_key(:float), atom([:float])}, + {domain_key(:atom), binary()}, + {domain_key(:binary), integer()}, + {domain_key(:tuple), float()}, + {domain_key(:map), pid()}, + {domain_key(:reference), port()}, + {domain_key(:pid), reference()}, + {domain_key(:port), boolean()} + ]) + + assert map_get(all_domains, atom([:bar])) == {:ok_present, atom([:ok])} + + assert map_get(all_domains, integer()) == {:ok, atom([:int]) |> nil_or_type()} + assert map_get(all_domains, number()) == {:ok, atom([:int, :float]) |> nil_or_type()} + + assert map_get(all_domains, empty_list()) == {:ok_absent, atom([nil])} + assert map_get(all_domains, atom([:foo])) == {:ok, binary() |> nil_or_type()} + assert map_get(all_domains, binary()) == {:ok, integer() |> nil_or_type()} + assert map_get(all_domains, tuple([integer(), atom()])) == {:ok, nil_or_type(float())} + assert map_get(all_domains, empty_map()) == {:ok, pid() |> nil_or_type()} + + # Union + assert map_get(all_domains, union(tuple(), empty_map())) == + {:ok, union(float(), pid() |> nil_or_type())} + + # Removing all maps with tuple keys + t_no_tuple = difference(all_domains, closed_map([{domain_key(:tuple), float()}])) + t_really_no_tuple = difference(all_domains, open_map([{domain_key(:tuple), float()}])) + assert subtype?(all_domains, open_map()) + # It's only closed maps, so it should not change + assert map_get(t_no_tuple, tuple()) == {:ok, float() |> nil_or_type()} + # This time we actually removed all tuple to float keys + assert map_get(t_really_no_tuple, tuple()) == {:ok_absent, atom([nil])} + + t1 = closed_map([{domain_key(:tuple), integer()}]) + t2 = closed_map([{domain_key(:tuple), float()}]) + t3 = union(t1, t2) + assert map_get(t3, tuple()) == {:ok, number() |> nil_or_type()} + end + + test "map_get with dynamic" do + {_answer, type_selected} = map_get(dynamic(), term()) + assert equal?(type_selected, dynamic() |> nil_or_type()) + end + + test "map_get with atom fall back" do + map = closed_map([{:a, atom([:a])}, {:b, atom([:b])}, {domain_key(:atom), pid()}]) + assert map_get(map, atom([:a, :b])) == {:ok_present, atom([:a, :b])} + assert map_get(map, atom([:a, :c])) == {:ok, union(atom([:a]), pid() |> nil_or_type())} + assert map_get(map, atom() |> difference(atom([:a, :b]))) == {:ok, pid() |> nil_or_type()} + + assert map_get(map, atom() |> difference(atom([:a]))) == + {:ok, union(atom([:b]), pid() |> nil_or_type())} + end + test "map_delete" do assert map_delete(term(), :a) == :badmap assert map_delete(integer(), :a) == :badmap @@ -1493,7 +1752,10 @@ defmodule Module.Types.DescrTest do # Deleting from a difference of maps {:ok, type} = - map_delete(difference(closed_map(a: integer(), b: atom()), closed_map(a: integer())), :b) + map_delete( + difference(closed_map(a: integer(), b: atom()), closed_map(a: integer())), + :b + ) assert equal?(type, closed_map(a: integer())) @@ -1501,6 +1763,12 @@ defmodule Module.Types.DescrTest do assert equal?(type, open_map(a: not_set())) end + test "map_delete with atom fallback" do + assert closed_map([{:a, integer()}, {:b, atom()}, {domain_key(:atom), pid()}]) + |> map_delete(:a) == + {:ok, closed_map([{:a, not_set()}, {:b, atom()}, {domain_key(:atom), pid()}])} + end + test "map_take" do assert map_take(term(), :a) == :badmap assert map_take(integer(), :a) == :badmap @@ -1588,11 +1856,15 @@ defmodule Module.Types.DescrTest do assert equal?( type, - union(closed_map(a: integer(), c: boolean()), closed_map(b: atom(), c: boolean())) + union( + closed_map(a: integer(), c: boolean()), + closed_map(b: atom(), c: boolean()) + ) ) # Put a key-value pair in a dynamic map - assert map_put(dynamic(open_map()), :a, integer()) == {:ok, dynamic(open_map(a: integer()))} + assert map_put(dynamic(open_map()), :a, integer()) == + {:ok, dynamic(open_map(a: integer()))} # Put a key-value pair in an intersection of maps {:ok, type} = @@ -1617,6 +1889,64 @@ defmodule Module.Types.DescrTest do {false, type} = map_fetch(map, :a) assert equal?(type, atom()) end + + test "map_put with domain keys" do + # Using a literal key or an expression of that singleton key is the same + assert map_refresh(empty_map(), atom([:a]), integer()) == {:ok, closed_map(a: integer())} + + # Several keys + assert map_refresh(empty_map(), atom([:a, :b]), integer()) == + {:ok, closed_map(a: if_set(integer()), b: if_set(integer()))} + + assert map_refresh(empty_map(), integer(), integer()) == + {:ok, closed_map([{domain_key(:integer), integer()}])} + + assert map_refresh(closed_map([{domain_key(:integer), integer()}]), integer(), float()) == + {:ok, closed_map([{domain_key(:integer), number()}])} + + assert map_refresh(open_map(), integer(), integer()) == {:ok, open_map()} + + # TODO: Revisit this + # {:ok, type} = map_refresh(empty_map(), integer(), dynamic()) + # assert equal?(type, dynamic(closed_map([{domain_key(:integer), term()}]))) + + # Adding a key of type float to a dynamic only guarantees that we have a map + # as we cannot express "has at least one key of type float => float" + {:ok, type} = map_refresh(dynamic(), float(), float()) + assert equal?(type, dynamic(open_map())) + + assert closed_map([{domain_key(:integer), integer()}]) + |> difference(open_map()) + |> empty?() + + assert closed_map([{domain_key(:integer), integer()}]) + |> difference(open_map()) + |> map_refresh(integer(), float()) == :badmap + + assert map_refresh(empty_map(), number(), float()) == + {:ok, + closed_map([ + {domain_key(:integer), float()}, + {domain_key(:float), float()} + ])} + + # Tricky cases with atoms: + # We add one atom fields that maps to an integer, which is not :a. So we do not touch + # :a, add integer to :b, and add a domain field. + assert map_refresh( + closed_map(a: pid(), b: pid()), + atom() |> difference(atom([:a])), + integer() + ) == + {:ok, + closed_map([ + {:a, pid()}, + {:b, union(pid(), integer())}, + {domain_key(:atom), integer()} + ])} + + assert map_refresh(empty_map(), term(), integer()) == {:ok, map_with_default(integer())} + end end describe "disjoint" do @@ -1722,7 +2052,8 @@ defmodule Module.Types.DescrTest do "empty_list() or non_empty_list(float() or integer(), pid())" # Merge last element types - assert union(list(atom([:ok]), integer()), list(atom([:ok]), float())) |> to_quoted_string() == + assert union(list(atom([:ok]), integer()), list(atom([:ok]), float())) + |> to_quoted_string() == "empty_list() or non_empty_list(:ok, float() or integer())" assert union(dynamic(list(integer(), float())), dynamic(list(integer(), pid()))) @@ -1747,11 +2078,6 @@ defmodule Module.Types.DescrTest do assert tuple([closed_map(a: integer()), open_map()]) |> to_quoted_string() == "{%{a: integer()}, map()}" - # TODO: eliminate tuple differences - # assert difference(tuple([number(), term()]), tuple([integer(), atom()])) - # |> to_quoted_string() == - # "{float(), term()} or {number(), term() and not atom()}" - assert union(tuple([integer(), atom()]), tuple([integer(), atom()])) |> to_quoted_string() == "{integer(), atom()}" @@ -1820,7 +2146,12 @@ defmodule Module.Types.DescrTest do ) decimal_int = - closed_map(__struct__: atom([Decimal]), coef: integer(), exp: integer(), sign: integer()) + closed_map( + __struct__: atom([Decimal]), + coef: integer(), + exp: integer(), + sign: integer() + ) assert atom([:error]) |> union( @@ -1908,7 +2239,7 @@ defmodule Module.Types.DescrTest do """ end - test "map" do + test "map as records" do assert empty_map() |> to_quoted_string() == "empty_map()" assert open_map() |> to_quoted_string() == "map()" @@ -1968,9 +2299,15 @@ defmodule Module.Types.DescrTest do "%{..., a: float() or integer()}" # Fusing complex nested maps with unions - assert closed_map(status: atom([:ok]), data: closed_map(value: term(), count: empty_list())) + assert closed_map( + status: atom([:ok]), + data: closed_map(value: term(), count: empty_list()) + ) |> union( - closed_map(status: atom([:ok]), data: closed_map(value: term(), count: open_map())) + closed_map( + status: atom([:ok]), + data: closed_map(value: term(), count: open_map()) + ) ) |> union(closed_map(status: atom([:error]), reason: atom([:timeout]))) |> union(closed_map(status: atom([:error]), reason: atom([:crash]))) @@ -1994,7 +2331,10 @@ defmodule Module.Types.DescrTest do "%{data: %{x: float() or integer(), y: atom()}, meta: map()}" # Test complex combinations - assert intersection(open_map(a: number(), b: atom()), open_map(a: integer(), c: boolean())) + assert intersection( + open_map(a: number(), b: atom()), + open_map(a: integer(), c: boolean()) + ) |> union(difference(open_map(x: atom()), open_map(x: boolean()))) |> to_quoted_string() == "%{..., a: integer(), b: atom(), c: boolean()} or %{..., x: atom() and not boolean()}" @@ -2017,6 +2357,14 @@ defmodule Module.Types.DescrTest do |> to_quoted_string() == "%{..., a: float(), b: atom(), c: port()}" end + test "maps as dictionaries" do + assert closed_map([{domain_key(:integer), integer()}]) + |> to_quoted_string() == "%{integer() => if_set(integer())}" + + assert closed_map([{domain_key(:integer), integer()}, {:float, float()}]) + |> to_quoted_string() == "%{integer() => if_set(integer()), float: float()}" + end + test "structs" do assert open_map(__struct__: atom([URI])) |> to_quoted_string() == "%{..., __struct__: URI}" From f54b192823392d3f3429196d123f016057adc1c9 Mon Sep 17 00:00:00 2001 From: Gary Rennie Date: Fri, 11 Jul 2025 14:05:33 +0100 Subject: [PATCH 061/111] Add ETS to the Erlang Term Storage section of erlang libs (#14644) This will ensure that it appears in the short search results on ExDoc instead of having to navigate through to the search results. --- lib/elixir/pages/getting-started/erlang-libraries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/pages/getting-started/erlang-libraries.md b/lib/elixir/pages/getting-started/erlang-libraries.md index 667b390e11f..e0bdbf4ad1e 100644 --- a/lib/elixir/pages/getting-started/erlang-libraries.md +++ b/lib/elixir/pages/getting-started/erlang-libraries.md @@ -85,7 +85,7 @@ iex> :digraph.get_short_path(digraph, v0, v2) Note that the functions in `:digraph` alter the graph structure in-place, this is possible because they are implemented as ETS tables, explained next. -## Erlang Term Storage +## Erlang Term Storage (ETS) The modules [`:ets`](`:ets`) and [`:dets`](`:dets`) handle storage of large data structures in memory or on disk respectively. From fec9899eadff7b86879ad2b2ae6b3963a33e292d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Jul 2025 15:51:18 +0200 Subject: [PATCH 062/111] Revamp Mix & OTP guides (#14637) --- lib/elixir/pages/getting-started/debugging.md | 4 +- lib/elixir/pages/mix-and-otp/agents.md | 69 ++- .../mix-and-otp/config-and-distribution.md | 305 +++++++++++++ .../pages/mix-and-otp/config-and-releases.md | 429 ------------------ .../dependencies-and-umbrella-projects.md | 305 ------------- .../pages/mix-and-otp/distributed-tasks.md | 361 --------------- .../pages/mix-and-otp/docs-tests-and-with.md | 408 ++++++++--------- .../pages/mix-and-otp/dynamic-supervisor.md | 238 +++++----- lib/elixir/pages/mix-and-otp/genservers.md | 424 +++++++++-------- .../pages/mix-and-otp/introduction-to-mix.md | 8 +- lib/elixir/pages/mix-and-otp/releases.md | 170 +++++++ .../mix-and-otp/supervisor-and-application.md | 259 +++++------ .../pages/mix-and-otp/task-and-gen-tcp.md | 171 ++++--- lib/elixir/scripts/elixir_docs.exs | 10 +- lib/mix/lib/mix/project.ex | 61 +++ 15 files changed, 1341 insertions(+), 1881 deletions(-) create mode 100644 lib/elixir/pages/mix-and-otp/config-and-distribution.md delete mode 100644 lib/elixir/pages/mix-and-otp/config-and-releases.md delete mode 100644 lib/elixir/pages/mix-and-otp/dependencies-and-umbrella-projects.md delete mode 100644 lib/elixir/pages/mix-and-otp/distributed-tasks.md create mode 100644 lib/elixir/pages/mix-and-otp/releases.md diff --git a/lib/elixir/pages/getting-started/debugging.md b/lib/elixir/pages/getting-started/debugging.md index b9fe6ea2475..a370f70f1d3 100644 --- a/lib/elixir/pages/getting-started/debugging.md +++ b/lib/elixir/pages/getting-started/debugging.md @@ -77,7 +77,7 @@ dbg(Map.put(feature, :in_version, "1.14.0")) The code above prints this: -```shell +```text [my_file.exs:2: (file)] feature #=> %{inspiration: "Rust", name: :dbg} [my_file.exs:3: (file)] @@ -97,7 +97,7 @@ __ENV__.file This code prints: -```shell +```text [dbg_pipes.exs:5: (file)] __ENV__.file #=> "/home/myuser/dbg_pipes.exs" |> String.split("/", trim: true) #=> ["home", "myuser", "dbg_pipes.exs"] diff --git a/lib/elixir/pages/mix-and-otp/agents.md b/lib/elixir/pages/mix-and-otp/agents.md index 4df693b71ae..86e4252c8c8 100644 --- a/lib/elixir/pages/mix-and-otp/agents.md +++ b/lib/elixir/pages/mix-and-otp/agents.md @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2021 The Elixir Team --> -# Simple state management with agents +# Simple state with agents In this chapter, we will learn how to keep and share state between multiple entities. If you have previous programming experience, you may think of globally shared variables, but the model we will learn here is quite different. The next chapters will generalize the concepts introduced here. @@ -11,19 +11,14 @@ If you have skipped the *Getting Started* guide or read it long ago, be sure to ## The trouble with (mutable) state -Elixir is an immutable language where nothing is shared by default. If we want to share information, which can be read and modified from multiple places, we have two main options in Elixir: +Elixir is an immutable language where nothing is shared by default. If we want to share information, this is typically done by sending messages between processes. - * Using processes and message passing - * [ETS (Erlang Term Storage)](`:ets`) - -We covered processes in the *Getting Started* guide. ETS (Erlang Term Storage) is a new topic that we will explore in later chapters. When it comes to processes though, we rarely hand-roll our own, instead we use the abstractions available in Elixir and OTP: +When it comes to processes though, we rarely hand-roll our own, instead we use the abstractions available in Elixir and OTP: * `Agent` — Simple wrappers around state. * `GenServer` — "Generic servers" (processes) that encapsulate state, provide sync and async calls, support code reloading, and more. * `Task` — Asynchronous units of computation that allow spawning a process and potentially retrieving its result at a later time. -We will explore these abstractions as we move forward. Keep in mind that they are all implemented on top of processes using the basic features provided by the VM, like `send/2`, `receive/1`, `spawn/1` and `Process.link/1`. - Here, we will use agents, and create a module named `KV.Bucket`, responsible for storing our key-value entries in a way that allows them to be read and modified by other processes. ## Agents 101 @@ -47,7 +42,7 @@ iex> Agent.stop(agent) :ok ``` -We started an agent with an initial state of an empty list. We updated the agent's state, adding our new item to the head of the list. The second argument of `Agent.update/3` is a function that takes the agent's current state as input and returns its desired new state. Finally, we retrieved the whole list. The second argument of `Agent.get/3` is a function that takes the state as input and returns the value that `Agent.get/3` itself will return. Once we are done with the agent, we can call `Agent.stop/3` to terminate the agent process. +We started an agent with an initial state of an empty list. The `start_link/1` function returned the `:ok` tuple with a process identifier (PID) of the agent. We will use this PID for all further interactions. We then updated the agent's state, adding our new item to the head of the list. The second argument of `Agent.update/3` is a function that takes the agent's current state as input and returns its desired new state. Finally, we retrieved the whole list. The second argument of `Agent.get/3` is a function that takes the state as input and returns the value that `Agent.get/3` itself will return. Once we are done with the agent, we can call `Agent.stop/3` to terminate the agent process. The `Agent.update/3` function accepts as a second argument any function that receives one argument and returns a value: @@ -93,7 +88,9 @@ Also note the `async: true` option passed to `ExUnit.Case`. This option makes th Async or not, our new test should obviously fail, as none of the functionality is implemented in the module being tested: ```text -** (UndefinedFunctionError) function KV.Bucket.start_link/1 is undefined (module KV.Bucket is not available) +1) test stores values by key (KV.BucketTest) + test/kv/bucket_test.exs:4 + ** (UndefinedFunctionError) function KV.Bucket.start_link/1 is undefined (module KV.Bucket is not available) ``` In order to fix the failing test, let's create a file at `lib/kv/bucket.ex` with the contents below. Feel free to give a try at implementing the `KV.Bucket` module yourself using agents before peeking at the implementation below. @@ -104,9 +101,11 @@ defmodule KV.Bucket do @doc """ Starts a new bucket. + + All options are forwarded to `Agent.start_link/2`. """ - def start_link(_opts) do - Agent.start_link(fn -> %{} end) + def start_link(opts) do + Agent.start_link(fn -> %{} end, opts) end @doc """ @@ -125,49 +124,43 @@ defmodule KV.Bucket do end ``` -The first step in our implementation is to call `use Agent`. Most of the functionality we will learn, such as `GenServer` and `Supervisor`, follow this pattern. For all of them, calling `use` generates a `child_spec/1` function with default configuration, which will be handy when we start supervising processes in chapter 4. +The first step in our implementation is to call `use Agent`. This is a pattern we will see throughout the guides and understand in depth in the next chapter. -Then we define a `start_link/1` function, which will effectively start the agent. It is a convention to define a `start_link/1` function that always accepts a list of options. We don't plan on using any options right now, but we might later on. We then proceed to call `Agent.start_link/1`, which receives an anonymous function that returns the Agent's initial state. +Then we define a `start_link/1` function, which will effectively start the agent. It is a convention to define a `start_link/1` function that always accepts a list of options. We then call `Agent.start_link/2` passing an anonymous function that returns the Agent's initial state and the same list of options we received. We are keeping a map inside the agent to store our keys and values. Getting and putting values on the map is done with the Agent API and the capture operator `&`, introduced in [the Getting Started guide](../getting-started/anonymous-functions.md#the-capture-operator). The agent passes its state to the anonymous function via the `&1` argument when `Agent.get/2` and `Agent.update/2` are called. Now that the `KV.Bucket` module has been defined, our test should pass! You can try it yourself by running: `mix test`. -## Test setup with ExUnit callbacks +## Naming processes -Before moving on and adding more features to `KV.Bucket`, let's talk about ExUnit callbacks. As you may expect, all `KV.Bucket` tests will require a bucket agent to be up and running. Luckily, ExUnit supports callbacks that allow us to skip such repetitive tasks. +When starting `KV.Bucket`, we pass a list of options which we forward to `Agent.start_link/2`. One of the options accepted by `Agent.start_link/2` is a name option which allows us to name a process, so we can interact with it using its name instead of its PID. -Let's rewrite the test case to use callbacks: +Let's write a test as an example. Back on `KV.BucketTest`, add this: ```elixir -defmodule KV.BucketTest do - use ExUnit.Case, async: true + test "stores values by key on a named process" do + {:ok, _} = KV.Bucket.start_link(name: :shopping_list) + assert KV.Bucket.get(:shopping_list, "milk") == nil - setup do - {:ok, bucket} = KV.Bucket.start_link([]) - %{bucket: bucket} + KV.Bucket.put(:shopping_list, "milk", 3) + assert KV.Bucket.get(:shopping_list, "milk") == 3 end - - test "stores values by key", %{bucket: bucket} do - assert KV.Bucket.get(bucket, "milk") == nil - - KV.Bucket.put(bucket, "milk", 3) - assert KV.Bucket.get(bucket, "milk") == 3 - end -end ``` -We have first defined a setup callback with the help of the `setup/1` macro. The `setup/1` macro defines a callback that is run before every test, in the same process as the test itself. - -Note that we need a mechanism to pass the `bucket` PID from the callback to the test. We do so by using the *test context*. When we return `%{bucket: bucket}` from the callback, ExUnit will merge this map into the test context. Since the test context is a map itself, we can pattern match the bucket out of it, providing access to the bucket inside the test: +However, keep in mind that names are shared in the current node. If two tests attempt to create two processes named `:shopping_list` at the same time, one would succeed and the other would fail. For this reason, it is a common practice in Elixir to name processes started during tests after the test itself, like this: ```elixir -test "stores values by key", %{bucket: bucket} do - # `bucket` is now the bucket from the setup block -end + test "stores values by key on a named process", config do + {:ok, _} = KV.Bucket.start_link(name: config.test) + assert KV.Bucket.get(config.test, "milk") == nil + + KV.Bucket.put(config.test, "milk", 3) + assert KV.Bucket.get(config.test, "milk") == 3 + end ``` -You can read more about ExUnit cases in the [`ExUnit.Case` module documentation](`ExUnit.Case`) and more about callbacks in `ExUnit.Callbacks`. +The `config` argument, passed after the test name, is the *test context* and it includes configuration and metadata about the current test, which is useful in scenarios like these. ## Other agent actions @@ -214,4 +207,4 @@ end When a long action is performed on the server, all other requests to that particular server will wait until the action is done, which may cause some clients to timeout. -In the next chapter, we will explore GenServers, where the segregation between clients and servers is made more apparent. +Some APIs, such as GenServers, make a clearer distiction between client and server, and we will explore them in future chapters. Next let's talk about naming things, applications, and supervisors. diff --git a/lib/elixir/pages/mix-and-otp/config-and-distribution.md b/lib/elixir/pages/mix-and-otp/config-and-distribution.md new file mode 100644 index 00000000000..6ed27bfb771 --- /dev/null +++ b/lib/elixir/pages/mix-and-otp/config-and-distribution.md @@ -0,0 +1,305 @@ + + +# Configuration and distribution + +So far we have hardcoded our applications to run a web server on port 4040. This has been somewhat problematic since we can't, for example, run our development server and tests at the same time. In this chapter, we will learn how to use the application environment for configuration, paving the way for us to enable distribution by running multiple development servers on the same machine (on different ports). + +In this last guide, we will make the routing table for our distributed key-value store configurable, and then finally package the software for production. + +Let's do this. + +## Application environment + +In the chapter [Registries, applications, and supervisors](supervisor-and-application.md), we have learned that our project is backed by an application, which bundles our modules and specifies how your supervision tree starts and shuts down. Each application can also have its own configuration, which in Erlang/OTP (and therefore Elixir) is called "application environment". + +We can use the application environment to configure our own application, as well as others. Let's see the application environment in practice. Create a file `config/runtime.exs` with the following: + +```elixir +import Config + +port = + cond do + port_env = System.get_env("PORT") -> + String.to_integer(port_env) + + config_env() == :test -> + 4040 + + true -> + 4050 + end + +config :kv, :port, port +``` + +The above is attempting to read the "PORT" environment variable and use it as the port if defined. Otherwise, we default to port `4040` for tests and port `4050` for other environments, eliminating the conflict between environments we have seen in the past. Then we store its value under the `:port` key of our `:kv` application. + +Now we just need to read this configuration. Open up `lib/kv.ex` and the `start/2` function to the following: + +```elixir + def start(_type, _args) do + port = Application.fetch_env!(:kv, :port) + + children = [ + {Registry, name: KV, keys: :unique}, + {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}, + {Task.Supervisor, name: KV.ServerSupervisor}, + Supervisor.child_spec({Task, fn -> KV.Server.accept(port) end}, restart: :permanent) + ] + + Supervisor.start_link(children, strategy: :one_for_one) + end +``` + +Run `iex -S mix` and you will see the following message printed: + +```text +[info] Accepting connections on port 4050 +``` + +Run tests, without killing the development server, and you will see it running on port 4040. + +Our change was straight-forward. We used `Application.fetch_env!/2` to read the entry for `port` in `:kv`'s environment. We explicitly used `fetch_env!/2` (instead of `get_env/2` or `fetch_env`) because it will raise if the port was not configured (preventing the app from booting). + +## Compile vs runtime configuration + +Configuration files provide a mechanism for us to configure the environment of any application. Elixir provides two configuration entry points: + + * `config/config.exs` — this file is read at build time, before we compile our application and before we even load our dependencies. This means we can't access the code in our application nor in our dependencies. However, it means we can control how they are compiled + + * `config/runtime.exs` — this file is read after our application and dependencies are compiled and therefore it can configure how our application works at runtime. If you want to read system environment variables (via `System.get_env/1`) or access external configuration, this is the appropriate place to do so + +You can learn more about configuration in the `Config` and `Config.Provider` modules. + +Generally speaking, we use `Application.fetch_env!/2` (and friends) to read runtime configuration. `Application.compile_env/2` is available for reading compile-time configuration. This allows Elixir to track which modules to recompile when the compilation environment changes. + +Now that we can start multiple servers, let's explore distribution. + +## Our first distributed code + +Elixir ships with facilities to connect nodes and exchange information between them. In fact, we use the same concepts of processes, message passing and receiving messages when working in a distributed environment because Elixir processes are *location transparent*. This means that when sending a message, it doesn't matter if the recipient process is on the same node or on another node, the VM will be able to deliver the message in both cases. + +In order to run distributed code, we need to start the VM with a name. The name can be short (when in the same network) or long (requires the full computer address). Let's start a new IEx session: + +```console +$ iex --sname foo +``` + +You can see now the prompt is slightly different and shows the node name followed by the computer name: + + Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help) + iex(foo@jv)1> + +My computer is named `jv`, so I see `foo@jv` in the example above, but you will get a different result. We will use `foo@computer-name` in the following examples and you should update them accordingly when trying out the code. + +Let's define a module named `Hello` in this shell: + +```elixir +iex> defmodule Hello do +...> def world, do: IO.puts("hello world") +...> end +``` + +If you have another computer on the same network with both Erlang and Elixir installed, you can start another shell on it. If you don't, you can start another IEx session in another terminal. In either case, give it the short name of `bar`: + +```console +$ iex --sname bar +``` + +Note that inside this new IEx session, we cannot access `Hello.world/0`: + +```elixir +iex> Hello.world +** (UndefinedFunctionError) function Hello.world/0 is undefined (module Hello is not available) + Hello.world() +``` + +However, we can spawn a new process on `foo@computer-name` from `bar@computer-name`! Let's give it a try (where `@computer-name` is the one you see locally): + +```elixir +iex> Node.spawn_link(:"foo@computer-name", fn -> Hello.world() end) +#PID<9014.59.0> +hello world +``` + +Elixir spawned a process on another node and returned its PID. You can see the PID number no longer starts with zero, showing it belongs to another node. The code then executed on the other node where the `Hello.world/0` function exists and invoked that function. Note that the result of "hello world" was printed on the current node `bar` and not on `foo`. In other words, the message to be printed was sent back from `foo` to `bar`. This happens because the process spawned on the other node (`foo`) knows all the output should be sent back to the original node! + +We can send and receive messages from the PID returned by `Node.spawn_link/2` as usual. Let's try a quick ping-pong example: + +```elixir +iex> pid = Node.spawn_link(:"foo@computer-name", fn -> +...> receive do +...> {:ping, client} -> send(client, :pong) +...> end +...> end) +#PID<9014.59.0> +iex> send(pid, {:ping, self()}) +{:ping, #PID<0.73.0>} +iex> flush() +:pong +:ok +``` + +In other words, we can spawn processes in other nodes, hold onto their PIDs, and then send messages to them as if they were running on the same machine. That's the *location transparency* principle. And because everything we have built so far was built on top of messaging passing, we should be able to adjust our key-value store to become a distributed one with little work. + +## Distributed naming registry with `:global` + +First, let's check that our code is not currently distributed. Start a new node like this: + +```console +$ PORT=4100 iex --sname foo -S mix +``` + +And the other like this: + +```console +$ PORT=4101 iex --sname bar -S mix +``` + +Now, within `foo@computer-name`, do this: + +```elixir +iex> :erpc.call(:"bar@computer-name", KV, :create_bucket, ["shopping"]) +{:ok, #PID<22121.164.0>} +``` + +Instead of using `Node.spawn_link/2`, we used [Erlang's builtin RPC module](`:erpc`) to call the function `create_bucket` in the `KV` module passing a one element list with the string "shopping" as the argument list. We could have used `Node.spawn_link/2`, but `:erpc.call/4` conveniently returns the result of the invocation. + +Still in `foo@computer-name`, let's try to access the bucket: + +```elixir +iex> KV.lookup_bucket("shopping") +nil +``` + +It returns `nil`. However, if you run `KV.lookup_bucket("shopping")` in `bar@computer-name`, it will return the proper bucket. In other words, the nodes can communicate with each other, but buckets spawned in one node are not visible to the other. + +This is because we are using [Elixir's Registry](`Registry`) to name our buckets, which is a **local** process registry. In other words, it is designed for processes running on a single node and not for distribution. + +Luckily, Erlang ships with a distributed registry called [`:global`](`:global`), which is directly supported by the `:name` option by passing a `{:global, name}` tuple. All we need to do is update the `via/1` function in `lib/kv.ex` from this: + +```elixir + defp via(name), do: {:via, Registry, {KV, name}} +``` + +to this: + +```elixir + defp via(name), do: {:global, name} +``` + +Do the change above and restart both `foo@computer-name` and `bar@computer-name`. Now, back on `foo@computer-name`, let's give it another try: + +```elixir +iex> :erpc.call(:"bar@computer-name", KV, :create_bucket, ["shopping"]) +{:ok, #PID<21821.179.0>} +iex> KV.lookup_bucket("shopping") +#PID<21821.179.0> +``` + +And there you go! By simply changing which naming registry we used, we now have a distributed key value store. You can even try using `telnet` to connect to the servers on different ports and validate that changes in one session are visible in the other one. Exciting! + +## Node discovery and dependencies + +There is one essential ingredient to wrap up our distributed key-value store. In order for the `:global` registry to work, we need to make sure the nodes are connected to each other. When we run `:erpc` call passing the node name: + +```elixir +:erpc.call(:"bar@computer-name", KV, :create_bucket, ["shopping"]) +``` + +Elixir automatically connected the nodes together. This is easy to do in an IEx session when both instances are running on the same machine but it requires more work in a production environment, where instances are on different machines which may be started at any time and running on different IP addresses. + +Luckily for us, this is also a well-solved problem. For example, if you are using [the Phoenix web framework](https://phoenixframework.org) in production, it ships with [the `dns_cluster` package](https://github.com/phoenixframework/dns_cluster), which automatically runs DNS queries to find new nodes and connect them. If you are using Kubernetes or cloud providers, [packages like `libcluster`](https://github.com/bitwalker/libcluster) ship with different strategies to discover and connect nodes. + +Installing dependencies in Elixir is simple. Most commonly, we use the [Hex Package Manager](https://hex.pm), by listing the dependency inside the deps function in our `mix.exs` file: + +```elixir +def deps do + [{:dns_cluster, "~> 0.2"}] +end +``` + +This dependency refers to the latest version of `dns_cluster` in the 0.x version series that has been pushed to Hex. This is indicated by the `~>` preceding the version number. For more information on specifying version requirements, see the documentation for the `Version` module. + +Typically, stable releases are pushed to Hex. If you want to depend on an external dependency still in development, Mix is able to manage Git dependencies too: + +```elixir +def deps do + [{:dns_cluster, git: "https://github.com/phoenixframework/dns_cluster.git"}] +end +``` + +You will notice that when you add a dependency to your project, Mix generates a `mix.lock` file that guarantees *repeatable builds*. The lock file must be checked in to your version control system, to guarantee that everyone who uses the project will use the same dependency versions as you. + +Mix provides many tasks for working with dependencies, which can be seen in `mix help`: + +```console +$ mix help +mix deps # Lists dependencies and their status +mix deps.clean # Deletes the given dependencies' files +mix deps.compile # Compiles dependencies +mix deps.get # Gets all out of date dependencies +mix deps.tree # Prints the dependency tree +mix deps.unlock # Unlocks the given dependencies +mix deps.update # Updates the given dependencies +``` + +The most common tasks are `mix deps.get` and `mix deps.update`. Once fetched, dependencies are automatically compiled for you. You can read more about deps by running `mix help deps`. + +To wrap up this chapter, we will build a very simple node discovery mechanism, where the name of the nodes we should connect to are given on boot, using the lessons we learned in this chapter. + +## `Node.connect/1` + +We will change our application to support a "NODES" environment variable with the name of all nodes each instance should connect to. + +Open up `config/runtime.exs` and add this to the bottom: + +```elixir +nodes = + System.get_env("NODES", "") + |> String.split(",", trim: true) + |> Enum.map(&String.to_atom/1) + +config :kv, :nodes, nodes +``` + +We fetch the environment variable, split it on "," while discarding all empty strings, and then convert each entry to an atom, as node names are atoms. + +Now, in your `start/2` callback, we will add this to of the `start/2` function: + +```elixir + def start(_type, _args) do + for node <- Application.fetch_env!(:kv, :nodes) do + Node.connect(node) + end +``` + +Now we can start our nodes as: + +```console +$ NODES="foo@computer-name,bar@computer-name" PORT=4040 iex --sname foo -S mix +$ NODES="foo@computer-name,bar@computer-name" PORT=4041 iex --sname bar -S mix +``` + +And they should connect to each other. Give it a try! + +In an actual production system, there is some additional care we must take. For example, we often use `--name` instead of `--sname` and give fully qualified node names. + +Furthermore, when connecting two instances, we must guarantee they have the same cookie, which is a secret Erlang uses to authorize the connection. When they run on the same machine, they share the same cookie by default, but it must be either explicitly set or shared in other ways when deploying in a cluster. + +We will revisit these topics in the last chapter when we talk about releases. + +## Distributed system trade-offs + +In this chapter, we made our key-value store distributed by using the `:global` naming registry. However, it is important to keep in mind that every distributed system, be it a library or a full-blown database, is designed with a series of trade-offs in mind. + +In particular, `:global` requires consistency across all known nodes whenever a new bucket is created. For example, if your cluster has three nodes, creating a new bucket will require all three nodes to agree on its name. This means if one node is unresponsive, perhaps due to a [network partition](https://en.wikipedia.org/wiki/Network_partition), the node will have to either reconnect or be kicked out before registration succeeds. This also means that, as your cluster grows in size, registration becomes more expensive, although lookups are always cheap and immediate. Within the ecosystem, there are other named registries, which explore different trade-offs, such as [Syn](https://github.com/ostinelli/syn). + +Further complications arise when we consider storage. Today, when our nodes terminate, we lose all data stored in the buckets. In our current design, since we allow each node to store their own buckets, it means we would need to backup each node. And, if we don't want data losses, we would also need to replicate the data. + +For those reasons, it is still very common to use a database (or any storage system) when writing production applications in Elixir, and use Elixir to implement the realtime and collaborative aspects of your applications that extend beyond storage. For example, we can use Elixir to track which clients are connected to the cluster at any given moment or implement a feed where users are notified in realtime whenever items are added or removed from a bucket. + +In fact, that's exactly what we will build in the next chapter. Allowing us to wrap up everything we have learned so far and also talk about one of the essential building blocks in Elixir software: GenServers. diff --git a/lib/elixir/pages/mix-and-otp/config-and-releases.md b/lib/elixir/pages/mix-and-otp/config-and-releases.md deleted file mode 100644 index 0eb7b83ac9c..00000000000 --- a/lib/elixir/pages/mix-and-otp/config-and-releases.md +++ /dev/null @@ -1,429 +0,0 @@ - - -# Configuration and releases - -In this last guide, we will make the routing table for our distributed key-value store configurable, and then finally package the software for production. - -Let's do this. - -## Application environment - -So far we have hard-coded the routing table into the `KV.Router` module. However, we would like to make the table dynamic. This allows us not only to configure development/test/production, but also to allow different nodes to run with different entries in the routing table. There is a feature of OTP that does exactly that: the application environment. - -Each application has an environment that stores the application's specific configuration by key. For example, we could store the routing table in the `:kv` application environment, giving it a default value and allowing other applications to change the table as needed. - -Open up `apps/kv/mix.exs` and change the `application/0` function to return the following: - -```elixir -def application do - [ - extra_applications: [:logger], - env: [routing_table: []], - mod: {KV, []} - ] -end -``` - -We have added a new `:env` key to the application. It returns the application default environment, which has an entry of key `:routing_table` and value of an empty list. It makes sense for the application environment to ship with an empty table, as the specific routing table depends on the testing/deployment structure. - -In order to use the application environment in our code, we need to replace `KV.Router.table/0` with the definition below: - -```elixir -@doc """ -The routing table. -""" -def table do - Application.fetch_env!(:kv, :routing_table) -end -``` - -We use `Application.fetch_env!/2` to read the entry for `:routing_table` in `:kv`'s environment. You can find more information and other functions to manipulate the app environment in the `Application` module. - -Since our routing table is now empty, our distributed tests should fail. Restart the apps and re-run tests to see the failure: - -```console -$ iex --sname bar -S mix -$ elixir --sname foo -S mix test --only distributed -``` - -We need a way to configure the application environment. That's when we use configuration files. - -## Configuration - -Configuration files provide a mechanism for us to configure the environment of any application. Elixir provides two configuration entry points: - - * `config/config.exs` — this file is read at build time, before we compile our application and before we even load our dependencies. This means we can't access the code in our application nor in our dependencies. However, it means we can control how they are compiled - - * `config/runtime.exs` — this file is read after our application and dependencies are compiled and therefore it can configure how our application works at runtime. If you want to read system environment variables (via `System.get_env/1`) or access external configuration, this is the appropriate place to do so - -You can learn more about configuration in the `Config` and `Config.Provider` modules. For now, let's see an example. - -We can configure IEx default prompt to another value by creating a `config/runtime.exs` file with the following content: - -```elixir -import Config -config :iex, default_prompt: ">>>" -``` - -Start IEx with `iex -S mix` and you can see that the IEx prompt has changed. - -This means we can also configure our `:routing_table` directly in the `config/runtime.exs` file. However, which configuration value should we use? - -Currently we have two tests tagged with `@tag :distributed`. The "server interaction" test in `KVServerTest`, and the "route requests across nodes" in `KV.RouterTest`. Both tests are failing since they require a routing table, which is currently empty. - -For simplicity, we will define a routing table that always points to the current node. That's the table we will use for development and most of our tests. Back in `config/runtime.exs`, add this line: - -```elixir -config :kv, :routing_table, [{?a..?z, node()}] -``` - -With such a simple table available, we can now remove `@tag :distributed` from the test in `test/kv_server_test.exs`. If you run the complete suite, the test should now pass. - -However, for the tests in `KV.RouterTest`, we effectively need two nodes in our routing table. To do so, we will write a setup block that runs before all tests in that file. The setup block will change the application environment and revert it back once we are done, like this: - -```elixir -defmodule KV.RouterTest do - use ExUnit.Case - - setup_all do - current = Application.get_env(:kv, :routing_table) - - Application.put_env(:kv, :routing_table, [ - {?a..?m, :"foo@computer-name"}, - {?n..?z, :"bar@computer-name"} - ]) - - on_exit fn -> Application.put_env(:kv, :routing_table, current) end - end - - @tag :distributed - test "route requests across nodes" do -``` - -Note we removed `async: true` from `use ExUnit.Case`. Since the application environment is a global storage, tests that modify it cannot run concurrently. With all changes in place, all tests should pass, including the distributed one. - -## Releases - -Now that our application runs distributed, you may be wondering how we can package our application to run in production. After all, all of our code so far depends on Erlang and Elixir versions that are installed in your current system. To achieve this goal, Elixir provides releases. - -A release is a self-contained directory that consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine (VM) and runtime. Once a release is assembled, it can be packaged and deployed to a target as long as the target runs on the same operating system (OS) distribution and version as the machine that assembled the release. - -In a regular project, we can assemble a release by simply running `mix release`. However, we have an umbrella project, and in such cases Elixir requires some extra input from us. Let's see what is necessary: - -```shell -$ MIX_ENV=prod mix release -** (Mix) Umbrella projects require releases to be explicitly defined with a non-empty applications key that chooses which umbrella children should be part of the releases: - -releases: [ - foo: [ - applications: [child_app_foo: :permanent] - ], - bar: [ - applications: [child_app_bar: :permanent] - ] -] - -Alternatively you can perform the release from the children applications -``` - -That's because an umbrella project gives us plenty of options when deploying the software. We can: - - * deploy all applications in the umbrella to a node that will work as both TCP server and key-value storage - - * deploy the `:kv_server` application to work only as a TCP server as long as the routing table points only to other nodes - - * deploy only the `:kv` application when we want a node to work only as storage (no TCP access) - -As a starting point, let's define a release that includes both `:kv_server` and `:kv` applications. We will also add a version to it. Open up the `mix.exs` in the umbrella root and add inside `def project`: - -```elixir -releases: [ - foo: [ - version: "0.0.1", - applications: [kv_server: :permanent, kv: :permanent] - ] -] -``` - -That defines a release named `foo` with both `kv_server` and `kv` applications. Their mode is set to `:permanent`, which means that, if those applications crash, the whole node terminates. That's reasonable since those applications are essential to our system. - -Before we assemble the release, let's also define our routing table for production. Given we expect to have two nodes, we need to update `config/runtime.exs` to look like this: - -```elixir -import Config - -config :kv, :routing_table, [{?a..?z, node()}] - -if config_env() == :prod do - config :kv, :routing_table, [ - {?a..?m, :"foo@computer-name"}, - {?n..?z, :"bar@computer-name"} - ] -end -``` - -We have hard-coded the table and node names, which is good enough for our example, but you would likely move it to an external configuration system in an actual production setup. We have also wrapped it in a `config_env() == :prod` check, so this configuration does not apply to other environments. - -With the configuration in place, let's give assembling the release another try: - - $ MIX_ENV=prod mix release foo - * assembling foo-0.0.1 on MIX_ENV=prod - * skipping runtime configuration (config/runtime.exs not found) - - Release created at _build/prod/rel/foo! - - # To start your system - _build/prod/rel/foo/bin/foo start - - Once the release is running: - - # To connect to it remotely - _build/prod/rel/foo/bin/foo remote - - # To stop it gracefully (you may also send SIGINT/SIGTERM) - _build/prod/rel/foo/bin/foo stop - - To list all commands: - - _build/prod/rel/foo/bin/foo - -Excellent! A release was assembled in `_build/prod/rel/foo`. Inside the release, there will be a `bin/foo` file which is the entry point to your system. It supports multiple commands, such as: - - * `bin/foo start`, `bin/foo start_iex`, `bin/foo restart`, and `bin/foo stop` — for general management of the release - - * `bin/foo rpc COMMAND` and `bin/foo remote` — for running commands on the running system or to connect to the running system - - * `bin/foo eval COMMAND` — to start a fresh system that runs a single command and then shuts down - - * `bin/foo daemon` and `bin/foo daemon_iex` — to start the system as a daemon on Unix-like systems - - * `bin/foo install` — to install the system as a service on Windows machines - -If you run `bin/foo start`, it will start the system using a short name (`--sname`) equal to the release name, which in this case is `foo`. The next step is to start a system named `bar`, so we can connect `foo` and `bar` together, like we did in the previous chapter. But before we achieve this, let's talk a bit about the benefits of releases. - -## Why releases? - -Releases allow developers to precompile and package all of their code and the runtime into a single unit. The benefits of releases are: - - * Code preloading. The VM has two mechanisms for loading code: interactive and embedded. By default, it runs in the interactive mode which dynamically loads modules when they are used for the first time. The first time your application calls `Enum.map/2`, the VM will find the `Enum` module and load it. There's a downside. When you start a new server in production, it may need to load many other modules, causing the first requests to have an unusual spike in response time. Releases run in embedded mode, which loads all available modules upfront, guaranteeing your system is ready to handle requests after booting. - - * Configuration and customization. Releases give developers fine grained control over system configuration and the VM flags used to start the system. - - * Self-contained. A release does not require the source code to be included in your production artifacts. All of the code is precompiled and packaged. Releases do not even require Erlang or Elixir on your servers, as they include the Erlang VM and its runtime by default. Furthermore, both Erlang and Elixir standard libraries are stripped to bring only the parts you are actually using. - - * Multiple releases. You can assemble different releases with different configuration per application or even with different applications altogether. - -We have written extensive documentation on releases, so [please check the official documentation for more information](`mix release`). For now, we will continue exploring some of the features outlined above. - -## Assembling multiple releases - -So far, we have assembled a release named `foo`, but our routing table contains information for both `foo` and `bar`. Let's start `foo`: - - $ _build/prod/rel/foo/bin/foo start - 16:58:58.508 [info] Accepting connections on port 4040 - -And let's connect to it and issue a request in another terminal: - - $ telnet 127.0.0.1 4040 - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - CREATE bitsandpieces - OK - PUT bitsandpieces sword 1 - OK - GET bitsandpieces sword - 1 - OK - GET shopping foo - Connection closed by foreign host. - -Our application works already when we operate on the bucket named "bitsandpieces". But since the "shopping" bucket would be stored on `bar`, the request fails as `bar` is not available. If you go back to the terminal running `foo`, you will see: - - 17:16:19.555 [error] Task #PID<0.622.0> started from #PID<0.620.0> terminating - ** (stop) exited in: GenServer.call({KV.RouterTasks, :"bar@computer-name"}, {:start_task, [{:"foo@josemac-2", #PID<0.622.0>, #PID<0.622.0>}, [#PID<0.622.0>, #PID<0.620.0>, #PID<0.618.0>], :monitor, {KV.Router, :route, ["shopping", KV.Registry, :lookup, [KV.Registry, "shopping"]]}], :temporary, nil}, :infinity) - ** (EXIT) no connection to bar@computer-name - (elixir) lib/gen_server.ex:1010: GenServer.call/3 - (elixir) lib/task/supervisor.ex:454: Task.Supervisor.async/6 - (kv) lib/kv/router.ex:21: KV.Router.route/4 - (kv_server) lib/kv_server/command.ex:74: KVServer.Command.lookup/2 - (kv_server) lib/kv_server.ex:29: KVServer.serve/1 - (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2 - (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3 - Function: #Function<0.128611034/0 in KVServer.loop_acceptor/1> - Args: [] - -Let's now define a release for `:bar`. One first step could be to define a release exactly like `foo` inside `mix.exs`. Additionally we will set the `cookie` option on both releases to `weknoweachother` in order for them to allow connections from each other. See the [Distributed Erlang Documentation](http://www.erlang.org/doc/reference_manual/distributed.html) for further information on this topic: - -```elixir -releases: [ - foo: [ - version: "0.0.1", - applications: [kv_server: :permanent, kv: :permanent], - cookie: "weknoweachother" - ], - bar: [ - version: "0.0.1", - applications: [kv_server: :permanent, kv: :permanent], - cookie: "weknoweachother" - ] -] -``` - -And now let's assemble both releases: - -```shell -$ MIX_ENV=prod mix release foo -$ MIX_ENV=prod mix release bar -``` - -Stop `foo` if it's still running and re-start it to load the `cookie`: - -```shell -$ _build/prod/rel/foo/bin/foo start -``` - -And start `bar` in another terminal: - -```shell -$ _build/prod/rel/bar/bin/bar start -``` - -You should see an error like the error below happen 5 times, before the application finally shuts down: - -```text - 17:21:57.567 [error] Task #PID<0.620.0> started from KVServer.Supervisor terminating - ** (MatchError) no match of right hand side value: {:error, :eaddrinuse} - (kv_server) lib/kv_server.ex:12: KVServer.accept/1 - (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2 - (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3 - Function: #Function<0.98032413/0 in KVServer.Application.start/2> - Args: [] -``` - -That's happening because the release `foo` is already listening on port `4040` and `bar` is trying to do the same! One option could be to move the `:port` configuration to the application environment, like we did for the routing table, and setup different ports per node. - -But let's try something else. Let's make it so the `bar` release contains only the `:kv` application. So it works as a storage but it won't have a front-end. Change the `:bar` information to this: - -```elixir -releases: [ - foo: [ - version: "0.0.1", - applications: [kv_server: :permanent, kv: :permanent], - cookie: "weknoweachother" - ], - bar: [ - version: "0.0.1", - applications: [kv: :permanent], - cookie: "weknoweachother" - ] -] -``` - -And now let's assemble `bar` once more: - - $ MIX_ENV=prod mix release bar - -And finally successfully boot it: - - $ _build/prod/rel/bar/bin/bar start - -If you connect to localhost once again and perform another request, now everything should work, as long as the routing table contains the correct node names. Outstanding! - -With releases, we were able to "cut different slices" of our project and prepared them to run in production, all packaged into a single directory. - -## Configuring releases - -Releases also provide built-in hooks for configuring almost every need of the production system: - - * `config/config.exs` — provides build-time application configuration, which is executed before our application compiles. This file often imports configuration files based on the environment, such as `config/dev.exs` and `config/prod.exs`. - - * `config/runtime.exs` — provides runtime application configuration. It is executed every time the release boots and is further extensible via config providers. - - * `rel/env.sh.eex` and `rel/env.bat.eex` — template files that are copied into every release and executed on every command to set up environment variables, including ones specific to the VM, and the general environment. - - * `rel/vm.args.eex` — a template file that is copied into every release and provides static configuration of the Erlang Virtual Machine and other runtime flags. - -As we have seen, `config/config.exs` and `config/runtime.exs` are loaded during releases and regular Mix commands. On the other hand, `rel/env.sh.eex` and `rel/vm.args.eex` are specific to releases. Let's take a look. - -### Operating System environment configuration - -Every release contains an environment file, named `env.sh` on Unix-like systems and `env.bat` on Windows machines, that executes before the Elixir system starts. In this file, you can execute any OS-level code, such as invoke other applications, set environment variables and so on. Some of those environment variables can even configure how the release itself runs. - -For instance, releases run using short-names (`--sname`). However, if you want to actually run a distributed key-value store in production, you will need multiple nodes and start the release with the `--name` option. We can achieve this by setting the `RELEASE_DISTRIBUTION` environment variable inside the `env.sh` and `env.bat` files. Mix already has a template for said files which we can customize, so let's ask Mix to copy them to our application: - - $ mix release.init - * creating rel/vm.args.eex - * creating rel/remote.vm.args.eex - * creating rel/env.sh.eex - * creating rel/env.bat.eex - -If you open up `rel/env.sh.eex`, you will see: - -```shell -#!/bin/sh - -# # Sets and enables heart (recommended only in daemon mode) -# case $RELEASE_COMMAND in -# daemon*) -# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND" -# export HEART_COMMAND -# export ELIXIR_ERL_OPTIONS="-heart" -# ;; -# *) -# ;; -# esac - -# # Set the release to load code on demand (interactive) instead of preloading (embedded). -# export RELEASE_MODE=interactive - -# # Set the release to work across nodes. -# # RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". -# export RELEASE_DISTRIBUTION=name -# export RELEASE_NODE=<%= @release.name %> -``` - -The steps necessary to work across nodes is already commented out as an example. You can enable full distribution by uncommenting the last two lines by removing the leading `# `. - -If you are on Windows, you will have to open up `rel/env.bat.eex`, where you will find this: - -```bat -@echo off -rem Set the release to load code on demand (interactive) instead of preloading (embedded). -rem set RELEASE_MODE=interactive - -rem Set the release to work across nodes. -rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". -rem set RELEASE_DISTRIBUTION=name -rem set RELEASE_NODE=<%= @release.name %> -``` - -Once again, uncomment the last two lines by removing the leading `rem ` to enable full distribution. And that's all! - -### VM arguments - -The `rel/vm.args.eex` allows you to specify low-level flags that control how the Erlang VM and its runtime operate. You specify entries as if you were specifying arguments in the command line with code comments also supported. Here is the default generated file: - - ## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html - ## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here - - ## Increase number of concurrent ports/sockets - ##+Q 65536 - - ## Tweak GC to run more often - ##-env ERL_FULLSWEEP_AFTER 10 - -You can see [a complete list of VM arguments and flags in the Erlang documentation](http://www.erlang.org/doc/man/erl.html). - -## Summing up - -Throughout the guide, we have built a very simple distributed key-value store as an opportunity to explore many constructs like generic servers, supervisors, tasks, agents, applications and more. Not only that, we have written tests for the whole application, got familiar with ExUnit, and learned how to use the Mix build tool to accomplish a wide range of tasks. - -If you are looking for a distributed key-value store to use in production, you should definitely look into [Riak](http://riak.com/products/riak-kv/), which also runs in the Erlang VM. In Riak, the buckets are replicated, to avoid data loss, and instead of a router, they use [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) to map a bucket to a node. A consistent hashing algorithm helps reduce the amount of data that needs to be migrated when new storage nodes are added to your live system. - -Of course, Elixir can be used for much more than distributed key-value stores. Embedded systems, data-processing and data-ingestion, web applications, audio/video streaming systems, and others are many of the different domains Elixir excels at. We hope this guide has prepared you to explore any of those domains or any future domain you may desire to bring Elixir into. - -Happy coding! diff --git a/lib/elixir/pages/mix-and-otp/dependencies-and-umbrella-projects.md b/lib/elixir/pages/mix-and-otp/dependencies-and-umbrella-projects.md deleted file mode 100644 index cecc976323f..00000000000 --- a/lib/elixir/pages/mix-and-otp/dependencies-and-umbrella-projects.md +++ /dev/null @@ -1,305 +0,0 @@ - - -# Dependencies and umbrella projects - -In this chapter, we will discuss how to manage dependencies in Mix. - -Our `kv` application is complete, so it's time to implement the server that will handle the requests we defined in the first chapter: - -```text -CREATE shopping -OK - -PUT shopping milk 1 -OK - -PUT shopping eggs 3 -OK - -GET shopping milk -1 -OK - -DELETE shopping eggs -OK -``` - -However, instead of adding more code to the `kv` application, we are going to build the TCP server as another application that is a client of the `kv` application. Since the whole runtime and Elixir ecosystem are geared towards applications, it makes sense to break our projects into smaller applications that work together rather than building a big, monolithic app. - -Before creating our new application, we must discuss how Mix handles dependencies. In practice, there are two kinds of dependencies we usually work with: internal and external dependencies. Mix supports mechanisms to work with both. - -## External dependencies - -External dependencies are the ones not tied to your business domain. For example, if you need an HTTP API for your distributed KV application, you can use the [Plug](https://github.com/elixir-lang/plug) project as an external dependency. - -Installing external dependencies is simple. Most commonly, we use the [Hex Package Manager](https://hex.pm), by listing the dependency inside the deps function in our `mix.exs` file: - -```elixir -def deps do - [{:plug, "~> 1.0"}] -end -``` - -This dependency refers to the latest version of Plug in the 1.x.x version series that has been pushed to Hex. This is indicated by the `~>` preceding the version number. For more information on specifying version requirements, see the documentation for the `Version` module. - -Typically, stable releases are pushed to Hex. If you want to depend on an external dependency still in development, Mix is able to manage Git dependencies too: - -```elixir -def deps do - [{:plug, git: "https://github.com/elixir-lang/plug.git"}] -end -``` - -You will notice that when you add a dependency to your project, Mix generates a `mix.lock` file that guarantees *repeatable builds*. The lock file must be checked in to your version control system, to guarantee that everyone who uses the project will use the same dependency versions as you. - -Mix provides many tasks for working with dependencies, which can be seen in `mix help`: - -```console -$ mix help -mix deps # Lists dependencies and their status -mix deps.clean # Deletes the given dependencies' files -mix deps.compile # Compiles dependencies -mix deps.get # Gets all out of date dependencies -mix deps.tree # Prints the dependency tree -mix deps.unlock # Unlocks the given dependencies -mix deps.update # Updates the given dependencies -``` - -The most common tasks are `mix deps.get` and `mix deps.update`. Once fetched, dependencies are automatically compiled for you. You can read more about deps by typing `mix help deps`, and in the documentation for the `Mix.Tasks.Deps` module. - -## Internal dependencies - -Internal dependencies are the ones that are specific to your project. They usually don't make sense outside the scope of your project/company/organization. Most of the time, you want to keep them private, whether due to technical, economic or business reasons. - -If you have an internal dependency, Mix supports two methods to work with them: Git repositories or umbrella projects. - -For example, if you push the `kv` project to a Git repository, you'll need to list it in your deps code in order to use it: - -```elixir -def deps do - [{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}] -end -``` - -If the repository is private though, you may need to specify the private URL `git@github.com:YOUR_ACCOUNT/kv.git`. In any case, Mix will be able to fetch it for you as long as you have the proper credentials. - -Using Git repositories for internal dependencies is somewhat discouraged in Elixir. Remember that the runtime and the Elixir ecosystem already provide the concept of applications. As such, we expect you to frequently break your code into applications that can be organized logically, even within a single project. - -However, if you push every application as a separate project to a Git repository, your projects may become very hard to maintain as you will spend a lot of time managing those Git repositories rather than writing your code. - -For this reason, Mix supports "umbrella projects". Umbrella projects are used to build applications that run together in a single repository. That is exactly the style we are going to explore in the next sections. - -Let's create a new Mix project. We are going to creatively name it `kv_umbrella`, and this new project will have both the existing `kv` application and the new `kv_server` application inside. The directory structure will look like this: - - + kv_umbrella - + apps - + kv - + kv_server - -The interesting thing about this approach is that Mix has many conveniences for working with such projects, such as the ability to compile and test all applications inside `apps` with a single command. However, even though they are all listed together inside `apps`, they are still decoupled from each other, so you can build, test and deploy each application in isolation if you want to. - -So let's get started! - -## Umbrella projects - -Let's start a new project using `mix new`. This new project will be named `kv_umbrella` and we need to pass the `--umbrella` option when creating it. Do not create this new project inside the existing `kv` project! - -```console -$ mix new kv_umbrella --umbrella -* creating README.md -* creating .formatter.exs -* creating .gitignore -* creating mix.exs -* creating apps -* creating config -* creating config/config.exs -``` - -From the printed information, we can see far fewer files are generated. The generated `mix.exs` file is different too. Let's take a look (comments have been removed): - -```elixir -defmodule KvUmbrella.MixProject do - use Mix.Project - - def project do - [ - apps_path: "apps", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - defp deps do - [] - end -end -``` - -What makes this project different from the previous one is the `apps_path: "apps"` entry in the project definition. This means this project will act as an umbrella. Such projects do not have source files nor tests, although they can have their own dependencies. Each child application must be defined inside the `apps` directory. - -Let's move inside the apps directory and start building `kv_server`. This time, we are going to pass the `--sup` flag, which will tell Mix to generate a supervision tree automatically for us, instead of building one manually as we did in previous chapters: - -```console -$ cd kv_umbrella/apps -$ mix new kv_server --module KVServer --sup -``` - -The generated files are similar to the ones we first generated for `kv`, with a few differences. Let's open up `mix.exs`: - -```elixir -defmodule KVServer.MixProject do - use Mix.Project - - def project do - [ - app: :kv_server, - version: "0.1.0", - build_path: "../../_build", - config_path: "../../config/config.exs", - deps_path: "../../deps", - lockfile: "../../mix.lock", - elixir: "~> 1.14", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - # Run "mix help compile.app" to learn about applications - def application do - [ - extra_applications: [:logger], - mod: {KVServer.Application, []} - ] - end - - # Run "mix help deps" to learn about dependencies - defp deps do - [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, - # {:sibling_app_in_umbrella, in_umbrella: true}, - ] - end -end -``` - -First of all, since we generated this project inside `kv_umbrella/apps`, Mix automatically detected the umbrella structure and added four lines to the project definition: - -```elixir -build_path: "../../_build", -config_path: "../../config/config.exs", -deps_path: "../../deps", -lockfile: "../../mix.lock", -``` - -Those options mean all dependencies will be checked out to `kv_umbrella/deps`, and they will share the same build, config, and lock files. We haven't talked about configuration yet, but from here we can build the intuition that all configuration and dependencies are shared across all projects in an umbrella, and it is not per application. - -The second change is in the `application` function inside `mix.exs`: - -```elixir -def application do - [ - extra_applications: [:logger], - mod: {KVServer.Application, []} - ] -end -``` - -Because we passed the `--sup` flag, Mix automatically added `mod: {KVServer.Application, []}`, specifying that `KVServer.Application` is our application callback module. `KVServer.Application` will start our application supervision tree. - -In fact, let's open up `lib/kv_server/application.ex`: - -```elixir -defmodule KVServer.Application do - # See https://hexdocs.pm/elixir/Application.html - # for more information on OTP Applications - @moduledoc false - - use Application - - @impl true - def start(_type, _args) do - # List all child processes to be supervised - children = [ - # Starts a worker by calling: KVServer.Worker.start_link(arg) - # {KVServer.Worker, arg}, - ] - - # See https://hexdocs.pm/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: KVServer.Supervisor] - Supervisor.start_link(children, opts) - end -end -``` - -Notice that it defines the application callback function, `start/2`, and instead of defining a supervisor named `KVServer.Supervisor` that uses the `Supervisor` module, it conveniently defined the supervisor inline! You can read more about such supervisors by reading the `Supervisor` module documentation. - -We can already try out our first umbrella child. We could run tests inside the `apps/kv_server` directory, but that wouldn't be much fun. Instead, go to the root of the umbrella project and run `mix test`: - -```console -$ mix test -``` - -And it works! - -Since we want `kv_server` to eventually use the functionality we defined in `kv`, we need to add `kv` as a dependency to our application. - -## Dependencies within an umbrella project - -Dependencies between applications in an umbrella project must still be explicitly defined and Mix makes it easy to do so. Open up `apps/kv_server/mix.exs` and change the `deps/0` function to the following: - -```elixir -defp deps do - [{:kv, in_umbrella: true}] -end -``` - -The line above makes `:kv` available as a dependency inside `:kv_server` and automatically starts the `:kv` application before the server starts. - -Finally, copy the `kv` application we have built so far to the `apps` directory in our new umbrella project. The final directory structure should match the structure we mentioned earlier: - - + kv_umbrella - + apps - + kv - + kv_server - -We now need to modify `apps/kv/mix.exs` to contain the umbrella entries we have seen in `apps/kv_server/mix.exs`. Open up `apps/kv/mix.exs` and add to the `project/0` function: - -```elixir -build_path: "../../_build", -config_path: "../../config/config.exs", -deps_path: "../../deps", -lockfile: "../../mix.lock", -``` - -Now you can run tests for both projects from the umbrella root with `mix test`. Sweet! - -## Don't drink the kool aid - -Umbrella projects are a convenience to help you organize and manage multiple applications. While it provides a degree of separation between applications, those applications are not fully decoupled, as they share the same configuration and the same dependencies. - -The pattern of keeping multiple applications in the same repository is known as "mono-repo". Umbrella projects maximize this pattern by providing conveniences to compile, test and run multiple applications at once. - -If you find yourself in a position where you want to use different configurations in each application for the same dependency or use different dependency versions, then it is likely your codebase has grown beyond what umbrellas can provide. - -The good news is that breaking an umbrella apart is quite straightforward, as you simply need to move applications outside of the umbrella project's `apps/` directory and update the project's mix.exs file to no longer set the `build_path`, `config_path`, `deps_path`, and `lockfile` configuration. You can depend on private projects outside of the umbrella in multiple ways: - - 1. Move it to a separate folder within the same repository and point to it using a path dependency (the mono-repo pattern) - 2. Move the repository to a separate Git repository and depend on it - 3. Publish the project to a private [Hex.pm](https://hex.pm/) organization - -## Summing up - -In this chapter, we have learned more about Mix dependencies and umbrella projects. While we may run `kv` without a server, our `kv_server` depends directly on `kv`. By breaking them into separate applications, we gain more control in how they are developed and tested. - -When using umbrella applications, it is important to have a clear boundary between them. Our upcoming `kv_server` must only access public APIs defined in `kv`. Think of your umbrella apps as any other dependency or even Elixir itself: you can only access what is public and documented. Reaching into private functionality in your dependencies is a poor practice that will eventually cause your code to break when a new version is up. - -Umbrella applications can also be used as a stepping stone for eventually extracting an application from your codebase. For example, imagine a web application that has to send "push notifications" to its users. The whole "push notifications system" can be developed as a separate application in the umbrella, with its own supervision tree and APIs. If you ever run into a situation where another project needs the push notifications system, the system can be moved to a private repository or [a Hex package](https://hex.pm/). - -Finally, keep in mind that applications in an umbrella project all share the same configurations and dependencies. If two applications in your umbrella need to configure the same dependency in drastically different ways or even use different versions, you have probably outgrown the benefits brought by umbrellas. Remember you can break the umbrella and still leverage the benefits behind "mono-repos". - -With our umbrella project up and running, it is time to start writing our server. diff --git a/lib/elixir/pages/mix-and-otp/distributed-tasks.md b/lib/elixir/pages/mix-and-otp/distributed-tasks.md deleted file mode 100644 index 7461017527a..00000000000 --- a/lib/elixir/pages/mix-and-otp/distributed-tasks.md +++ /dev/null @@ -1,361 +0,0 @@ - - -# Distributed tasks and tags - -In this chapter, we will go back to the `:kv` application and add a routing layer that will allow us to distribute requests between nodes based on the bucket name. - -The routing layer will receive a routing table of the following format: - -```elixir -[ - {?a..?m, :"foo@computer-name"}, - {?n..?z, :"bar@computer-name"} -] -``` - -The router will check the first byte of the bucket name against the table and dispatch to the appropriate node based on that. For example, a bucket starting with the letter "a" (`?a` represents the Unicode codepoint of the letter "a") will be dispatched to node `foo@computer-name`. - -If the matching entry points to the node evaluating the request, then we've finished routing, and this node will perform the requested operation. If the matching entry points to a different node, we'll pass the request to said node, which will look at its own routing table (which may be different from the one in the first node) and act accordingly. If no entry matches, an error will be raised. - -> Note: we will be using two nodes in the same machine throughout this chapter. You are free to use two (or more) different machines on the same network but you need to do some prep work. First of all, you need to ensure all machines have a `~/.erlang.cookie` file with exactly the same value. Then you need to guarantee [epmd](https://www.erlang.org/doc/apps/erts/epmd_cmd) is running on a port that is not blocked (you can run `epmd -d` for debug info). - -## Our first distributed code - -Elixir ships with facilities to connect nodes and exchange information between them. In fact, we use the same concepts of processes, message passing and receiving messages when working in a distributed environment because Elixir processes are *location transparent*. This means that when sending a message, it doesn't matter if the recipient process is on the same node or on another node, the VM will be able to deliver the message in both cases. - -In order to run distributed code, we need to start the VM with a name. The name can be short (when in the same network) or long (requires the full computer address). Let's start a new IEx session: - -```console -$ iex --sname foo -``` - -You can see now the prompt is slightly different and shows the node name followed by the computer name: - - Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help) - iex(foo@jv)1> - -My computer is named `jv`, so I see `foo@jv` in the example above, but you will get a different result. We will use `foo@computer-name` in the following examples and you should update them accordingly when trying out the code. - -Let's define a module named `Hello` in this shell: - -```elixir -iex> defmodule Hello do -...> def world, do: IO.puts("hello world") -...> end -``` - -If you have another computer on the same network with both Erlang and Elixir installed, you can start another shell on it. If you don't, you can start another IEx session in another terminal. In either case, give it the short name of `bar`: - -```console -$ iex --sname bar -``` - -Note that inside this new IEx session, we cannot access `Hello.world/0`: - -```elixir -iex> Hello.world -** (UndefinedFunctionError) function Hello.world/0 is undefined (module Hello is not available) - Hello.world() -``` - -However, we can spawn a new process on `foo@computer-name` from `bar@computer-name`! Let's give it a try (where `@computer-name` is the one you see locally): - -```elixir -iex> Node.spawn_link(:"foo@computer-name", fn -> Hello.world() end) -#PID<9014.59.0> -hello world -``` - -Elixir spawned a process on another node and returned its PID. The code then executed on the other node where the `Hello.world/0` function exists and invoked that function. Note that the result of "hello world" was printed on the current node `bar` and not on `foo`. In other words, the message to be printed was sent back from `foo` to `bar`. This happens because the process spawned on the other node (`foo`) knows all the output should be sent back to the original node! - -We can send and receive messages from the PID returned by `Node.spawn_link/2` as usual. Let's try a quick ping-pong example: - -```elixir -iex> pid = Node.spawn_link(:"foo@computer-name", fn -> -...> receive do -...> {:ping, client} -> send(client, :pong) -...> end -...> end) -#PID<9014.59.0> -iex> send(pid, {:ping, self()}) -{:ping, #PID<0.73.0>} -iex> flush() -:pong -:ok -``` - -From our quick exploration, we could conclude that we should use `Node.spawn_link/2` to spawn processes on a remote node every time we need to do a distributed computation. However, we have learned throughout this guide that spawning processes outside of supervision trees should be avoided if possible, so we need to look for other options. - -There are three better alternatives to `Node.spawn_link/2` that we could use in our implementation: - -1. We could use Erlang's [`:erpc`](`:erpc`) module to execute functions on a remote node. Inside the `bar@computer-name` shell above, you can call `:erpc.call(:"foo@computer-name", Hello, :world, [])` and it will print "hello world" - -2. We could have a server running on the other node and send requests to that node via the `GenServer` API. For example, you can call a server on a remote node by using `GenServer.call({name, node}, arg)` or passing the remote process PID as the first argument - -3. We could use [tasks](`Task`), which we have learned about in [a previous chapter](task-and-gen-tcp.md), as they can be spawned on both local and remote nodes - -The options above have different properties. The GenServer would serialize your requests on a single server, while tasks are effectively running asynchronously on the remote node, with the only serialization point being the spawning done by the supervisor. - -For our routing layer, we are going to use tasks, but feel free to explore the other alternatives too. - -## async/await - -So far we have explored tasks that are started and run in isolation, without regard to their return value. However, sometimes it is useful to run a task to compute a value and read its result later on. For this, tasks also provide the `async/await` pattern: - -```elixir -task = Task.async(fn -> compute_something_expensive() end) -res = compute_something_else() -res + Task.await(task) -``` - -`async/await` provides a very simple mechanism to compute values concurrently. Not only that, `async/await` can also be used with the same `Task.Supervisor` we have used in previous chapters. We just need to call `Task.Supervisor.async/2` instead of `Task.Supervisor.start_child/2` and use `Task.await/2` to read the result later on. - -## Distributed tasks - -Distributed tasks are exactly the same as supervised tasks. The only difference is that we pass the node name when spawning the task on the supervisor. Open up `lib/kv/supervisor.ex` from the `:kv` application. Let's add a task supervisor as the last child of the tree: - -```elixir -{Task.Supervisor, name: KV.RouterTasks}, -``` - -Now, let's start two named nodes again, but inside the `:kv` application: - -```console -$ cd apps/kv -$ iex --sname foo -S mix -$ iex --sname bar -S mix -``` - -From inside `bar@computer-name`, we can now spawn a task directly on the other node via the supervisor: - -```elixir -iex> task = Task.Supervisor.async({KV.RouterTasks, :"foo@computer-name"}, fn -> -...> {:ok, node()} -...> end) -%Task{ - mfa: {:erlang, :apply, 2}, - owner: #PID<0.122.0>, - pid: #PID<12467.88.0>, - ref: #Reference<0.0.0.400> -} -iex> Task.await(task) -{:ok, :"foo@computer-name"} -``` - -Our first distributed task retrieves the name of the node the task is running on. Notice we have given an anonymous function to `Task.Supervisor.async/2` but, in distributed cases, it is preferable to give the module, function, and arguments explicitly: - -```elixir -iex> task = Task.Supervisor.async({KV.RouterTasks, :"foo@computer-name"}, Kernel, :node, []) -%Task{ - mfa: {Kernel, :node, 0}, - owner: #PID<0.122.0>, - pid: #PID<12467.89.0>, - ref: #Reference<0.0.0.404> -} -iex> Task.await(task) -:"foo@computer-name" -``` - -The difference is that anonymous functions require the target node to have exactly the same code version as the caller. Using module, function, and arguments is more robust because you only need to find a function with matching arity in the given module. - -With this knowledge in hand, let's finally write the routing code. - -## Routing layer - -Create a file at `lib/kv/router.ex` with the following contents: - -```elixir -defmodule KV.Router do - @doc """ - Dispatch the given `mod`, `fun`, `args` request - to the appropriate node based on the `bucket`. - """ - def route(bucket, mod, fun, args) do - # Get the first byte of the binary - first = :binary.first(bucket) - - # Try to find an entry in the table() or raise - entry = - Enum.find(table(), fn {enum, _node} -> - first in enum - end) || no_entry_error(bucket) - - # If the entry node is the current node - if elem(entry, 1) == node() do - apply(mod, fun, args) - else - {KV.RouterTasks, elem(entry, 1)} - |> Task.Supervisor.async(KV.Router, :route, [bucket, mod, fun, args]) - |> Task.await() - end - end - - defp no_entry_error(bucket) do - raise "could not find entry for #{inspect bucket} in table #{inspect table()}" - end - - @doc """ - The routing table. - """ - def table do - # Replace computer-name with your local machine name - [{?a..?m, :"foo@computer-name"}, {?n..?z, :"bar@computer-name"}] - end -end -``` - -Let's write a test to verify our router works. Create a file named `test/kv/router_test.exs` containing: - -```elixir -defmodule KV.RouterTest do - use ExUnit.Case, async: true - - test "route requests across nodes" do - assert KV.Router.route("hello", Kernel, :node, []) == - :"foo@computer-name" - assert KV.Router.route("world", Kernel, :node, []) == - :"bar@computer-name" - end - - test "raises on unknown entries" do - assert_raise RuntimeError, ~r/could not find entry/, fn -> - KV.Router.route(<<0>>, Kernel, :node, []) - end - end -end -``` - -The first test invokes `Kernel.node/0`, which returns the name of the current node, based on the bucket names "hello" and "world". According to our routing table so far, we should get `foo@computer-name` and `bar@computer-name` as responses, respectively. - -The second test checks that the code raises for unknown entries. - -In order to run the first test, we need to have two nodes running. Let's move into `apps/kv` and restart the node named `bar` which is going to be used by tests. This way, `bar` will not load the `:kv_server` app and leave the port available for `foo` and tests. - -```console -$ cd apps/kv -$ iex --sname bar -S mix -``` - -And now run tests with: - -```console -$ elixir --sname foo -S mix test -``` - -The test should pass. - -## Test filters and tags - -Although our tests pass, our testing structure is getting more complex. In particular, running tests with only `mix test` causes failures in our suite, since our test requires a connection to another node. - -Luckily, ExUnit ships with a facility to tag tests, allowing us to run specific callbacks or even filter tests altogether based on those tags. We have already used the `:capture_log` tag in the previous chapter, which has its semantics specified by ExUnit itself. - -This time let's add a `:distributed` tag to `test/kv/router_test.exs`: - -```elixir -@tag :distributed -test "route requests across nodes" do -``` - -Writing `@tag :distributed` is equivalent to writing `@tag distributed: true`. - -With the test properly tagged, we can now check if the node is alive on the network and, if not, we can exclude all distributed tests. Open up `test/test_helper.exs` inside the `:kv` application and add the following: - -```elixir -exclude = - if Node.alive?(), do: [], else: [distributed: true] - -ExUnit.start(exclude: exclude) -``` - -Now run tests with `mix test`: - -```console -$ mix test -Excluding tags: [distributed: true] - -....... - -Finished in 0.05 seconds -9 tests, 0 failures, 1 excluded -``` - -This time all tests passed and ExUnit warned us that distributed tests were being excluded. If you run tests with `$ elixir --sname foo -S mix test`, one extra test should run and successfully pass as long as the `bar@computer-name` node is available. - -The `mix test` command also allows us to dynamically include and exclude tags. For example, we can run `$ mix test --include distributed` to run distributed tests regardless of the value set in `test/test_helper.exs`. We could also pass `--exclude` to exclude a particular tag from the command line. Finally, `--only` can be used to run only tests with a particular tag: - -```console -$ elixir --sname foo -S mix test --only distributed -``` - -You can read more about filters, tags, and the default tags in the `ExUnit.Case` module documentation. - -## Wiring it all up - -Now with our routing system in place, let's change `KVServer` to use the router. Replace the `lookup/2` function in `KVServer.Command` from this: - -```elixir -defp lookup(bucket, callback) do - case KV.Registry.lookup(KV.Registry, bucket) do - {:ok, pid} -> callback.(pid) - :error -> {:error, :not_found} - end -end -``` - -by this: - -```elixir -defp lookup(bucket, callback) do - case KV.Router.route(bucket, KV.Registry, :lookup, [KV.Registry, bucket]) do - {:ok, pid} -> callback.(pid) - :error -> {:error, :not_found} - end -end -``` - -Instead of directly looking up the registry, we are using the router instead to match a specific node. Then we get a `pid` that can be from any process in our cluster. From now on, `GET`, `PUT` and `DELETE` requests are all routed to the appropriate node. - -Let's also make sure that when a new bucket is created it ends up on the correct node. Replace the `run/1` function in `KVServer.Command`, the one that matches the `:create` command, with the following: - -```elixir -def run({:create, bucket}) do - case KV.Router.route(bucket, KV.Registry, :create, [KV.Registry, bucket]) do - pid when is_pid(pid) -> {:ok, "OK\r\n"} - _ -> {:error, "FAILED TO CREATE BUCKET"} - end -end -``` - -Now if you run the tests, you will see that an existing test that checks the server interaction will fail, as it will attempt to use the routing table. To address this failure, change the `test_helper.exs` for `:kv_server` application as we did for `:kv` and add `@tag :distributed` to this test too: - -```elixir -@tag :distributed -test "server interaction", %{socket: socket} do -``` - -However, keep in mind that by making the test distributed, we will likely run it less frequently, since we may not do the distributed setup on every test run. We will learn how to address this in the next chapter, by effectively learning how to make the routing table configurable. - -## Summing up - -We have only scratched the surface of what is possible when it comes to distribution. - -In all of our examples, we relied on Erlang's ability to automatically connect nodes whenever there is a request. For example, when we invoked `Node.spawn_link(:"foo@computer-name", fn -> Hello.world() end)`, Erlang automatically connected to said node and started a new process. However, you may also want to take a more explicit approach to connections, by using `Node.connect/1` and `Node.disconnect/1`. - -By default, Erlang establishes a fully meshed network, which means all nodes are connected to each other. Under this topology, the Erlang distribution is known to scale to several dozens of nodes in the same cluster. Erlang also has the concept of hidden nodes, which can allow developers to assemble custom topologies as seen in projects such as [Partisan](https://github.com/lasp-lang/partisan). - -In production, you may have nodes connecting and disconnecting at any time. In such scenarios, you need to provide *node discoverability*. Libraries such as [libcluster](https://github.com/bitwalker/libcluster/) and [dns_cluster](https://github.com/phoenixframework/dns_cluster) provide several strategies for node discoverability using DNS, Kubernetes, etc. - -Distributed key-value stores, used in real-life, need to consider the fact nodes may go up and down at any time and also migrate the bucket across nodes. Even further, buckets often need to be duplicated between nodes, so a failure in a node does not lead to the whole bucket being lost. This process is called *replication*. Our implementation won't attempt to tackle such problems. Instead, we assume there is a fixed number of nodes and therefore use a fixed routing table. - -These topics can be daunting at first but remember that most Elixir frameworks abstract those concerns for you. For example, when using [the Phoenix web framework](https://phoenixframework.org), its plug-and-play abstractions take care of sending messages and tracking how users join and leave a cluster. However, if you are interested in distributed systems after all, there is much to explore. Here are some additional references: - - * [The excellent Distribunomicon chapter from Learn You Some Erlang](http://learnyousomeerlang.com/distribunomicon) - * Erlang's [`:global` module](`:global`), which can provide global names and global locks, allowing unique names and unique locks in a whole cluster of machines - * Erlang's [`:pg` module](`:pg`), which allows process to join different groups shared across the whole cluster - * [Phoenix PubSub project](https://github.com/phoenixframework/phoenix_pubsub), which provides a distributed messaging system and a distributed presence system for tracking users and processes in a cluster - -You will also find many libraries for building distributed systems within the overall Erlang ecosystem. For now, it is time to go back to our simple distributed key-value store and learn how to configure and package it for production. diff --git a/lib/elixir/pages/mix-and-otp/docs-tests-and-with.md b/lib/elixir/pages/mix-and-otp/docs-tests-and-with.md index e29949dded9..eb9d0c62412 100644 --- a/lib/elixir/pages/mix-and-otp/docs-tests-and-with.md +++ b/lib/elixir/pages/mix-and-otp/docs-tests-and-with.md @@ -25,7 +25,7 @@ DELETE shopping eggs OK ``` -After the parsing is done, we will update our server to dispatch the parsed commands to the `:kv` application we built previously. +After the parsing is done, we will update our server to dispatch the parsed commands to the relevant buckets. ## Doctests @@ -33,16 +33,16 @@ On the language homepage, we mention that Elixir makes documentation a first-cla In this section, we will implement the parsing functionality, document it and make sure our documentation is up to date with doctests. This helps us provide documentation with accurate code samples. -Let's create our command parser at `lib/kv_server/command.ex` and start with the doctest: +Let's create our command parser at `lib/kv/command.ex` and start with the doctest: ```elixir -defmodule KVServer.Command do +defmodule KV.Command do @doc ~S""" Parses the given `line` into a command. ## Examples - iex> KVServer.Command.parse("CREATE shopping\r\n") + iex> KV.Command.parse("CREATE shopping\r\n") {:ok, {:create, "shopping"}} """ @@ -56,29 +56,29 @@ Doctests are specified by an indentation of four spaces followed by the `iex>` p Also, note that we started the documentation string using `@doc ~S"""`. The `~S` prevents the `\r\n` characters from being converted to a carriage return and line feed until they are evaluated in the test. -To run our doctests, we'll create a file at `test/kv_server/command_test.exs` and call `doctest KVServer.Command` in the test case: +To run our doctests, we'll create a file at `test/kv/command_test.exs` and call `doctest KV.Command` in the test case: ```elixir -defmodule KVServer.CommandTest do +defmodule KV.CommandTest do use ExUnit.Case, async: true - doctest KVServer.Command + doctest KV.Command end ``` Run the test suite and the doctest should fail: ```text - 1) doctest KVServer.Command.parse/1 (1) (KVServer.CommandTest) - test/kv_server/command_test.exs:3 + 1) doctest KV.Command.parse/1 (1) (KV.CommandTest) + test/kv/command_test.exs:3 Doctest failed doctest: - iex> KVServer.Command.parse("CREATE shopping\r\n") + iex> KV.Command.parse("CREATE shopping\r\n") {:ok, {:create, "shopping"}} - code: KVServer.Command.parse "CREATE shopping\r\n" === {:ok, {:create, "shopping"}} + code: KV.Command.parse "CREATE shopping\r\n" === {:ok, {:create, "shopping"}} left: :not_implemented right: {:ok, {:create, "shopping"}} stacktrace: - lib/kv_server/command.ex:7: KVServer.Command (module) + lib/kv/command.ex:7: KV.Command (module) ``` Excellent! @@ -96,50 +96,50 @@ end Our implementation splits the line on whitespace and then matches the command against a list. Using `String.split/1` means our commands will be whitespace-insensitive. Leading and trailing whitespace won't matter, nor will consecutive spaces between words. Let's add some new doctests to test this behavior along with the other commands: ```elixir -@doc ~S""" -Parses the given `line` into a command. + @doc ~S""" + Parses the given `line` into a command. -## Examples + ## Examples - iex> KVServer.Command.parse "CREATE shopping\r\n" - {:ok, {:create, "shopping"}} + iex> KV.Command.parse "CREATE shopping\r\n" + {:ok, {:create, "shopping"}} - iex> KVServer.Command.parse "CREATE shopping \r\n" - {:ok, {:create, "shopping"}} + iex> KV.Command.parse "CREATE shopping \r\n" + {:ok, {:create, "shopping"}} - iex> KVServer.Command.parse "PUT shopping milk 1\r\n" - {:ok, {:put, "shopping", "milk", "1"}} + iex> KV.Command.parse "PUT shopping milk 1\r\n" + {:ok, {:put, "shopping", "milk", "1"}} - iex> KVServer.Command.parse "GET shopping milk\r\n" - {:ok, {:get, "shopping", "milk"}} + iex> KV.Command.parse "GET shopping milk\r\n" + {:ok, {:get, "shopping", "milk"}} - iex> KVServer.Command.parse "DELETE shopping eggs\r\n" - {:ok, {:delete, "shopping", "eggs"}} + iex> KV.Command.parse "DELETE shopping eggs\r\n" + {:ok, {:delete, "shopping", "eggs"}} -Unknown commands or commands with the wrong number of -arguments return an error: + Unknown commands or commands with the wrong number of + arguments return an error: - iex> KVServer.Command.parse "UNKNOWN shopping eggs\r\n" - {:error, :unknown_command} + iex> KV.Command.parse "UNKNOWN shopping eggs\r\n" + {:error, :unknown_command} - iex> KVServer.Command.parse "GET shopping\r\n" - {:error, :unknown_command} + iex> KV.Command.parse "GET shopping\r\n" + {:error, :unknown_command} -""" + """ ``` With doctests at hand, it is your turn to make tests pass! Once you're ready, you can compare your work with our solution below: ```elixir -def parse(line) do - case String.split(line) do - ["CREATE", bucket] -> {:ok, {:create, bucket}} - ["GET", bucket, key] -> {:ok, {:get, bucket, key}} - ["PUT", bucket, key, value] -> {:ok, {:put, bucket, key, value}} - ["DELETE", bucket, key] -> {:ok, {:delete, bucket, key}} - _ -> {:error, :unknown_command} + def parse(line) do + case String.split(line) do + ["CREATE", bucket] -> {:ok, {:create, bucket}} + ["GET", bucket, key] -> {:ok, {:get, bucket, key}} + ["PUT", bucket, key, value] -> {:ok, {:put, bucket, key, value}} + ["DELETE", bucket, key] -> {:ok, {:delete, bucket, key}} + _ -> {:error, :unknown_command} + end end -end ``` Notice how we were able to elegantly parse the commands without adding a bunch of `if/else` clauses that check the command name and number of arguments! @@ -147,104 +147,107 @@ Notice how we were able to elegantly parse the commands without adding a bunch o Finally, you may have observed that each doctest corresponds to a different test in our suite, which now reports a total of 7 doctests. That is because ExUnit considers the following to define two different doctests: ```elixir -iex> KVServer.Command.parse("UNKNOWN shopping eggs\r\n") +iex> KV.Command.parse("UNKNOWN shopping eggs\r\n") {:error, :unknown_command} -iex> KVServer.Command.parse("GET shopping\r\n") +iex> KV.Command.parse("GET shopping\r\n") {:error, :unknown_command} ``` Without new lines, as seen below, ExUnit compiles it into a single doctest: ```elixir -iex> KVServer.Command.parse("UNKNOWN shopping eggs\r\n") +iex> KV.Command.parse("UNKNOWN shopping eggs\r\n") {:error, :unknown_command} -iex> KVServer.Command.parse("GET shopping\r\n") +iex> KV.Command.parse("GET shopping\r\n") {:error, :unknown_command} ``` As the name says, doctest is documentation first and a test later. Their goal is not to replace tests but to provide up-to-date documentation. You can read more about doctests in the `ExUnit.DocTest` documentation. -## `with` +## Using `with` As we are now able to parse commands, we can finally start implementing the logic that runs the commands. Let's add a stub definition for this function for now: ```elixir -defmodule KVServer.Command do +defmodule KV.Command do @doc """ Runs the given command. """ - def run(command) do - {:ok, "OK\r\n"} + def run(command, socket) do + :gen_tcp.send(socket, "OK\r\n") + :ok end end ``` -Before we implement this function, let's change our server to start using our new `parse/1` and `run/1` functions. Remember, our `read_line/1` function was also crashing when the client closed the socket, so let's take the opportunity to fix it, too. Open up `lib/kv_server.ex` and replace the existing server definition: +Before we implement this function, let's change our server to start using our new `parse/1` and `run/1` functions. Remember, our `read_line/1` function was also crashing when the client closed the socket, so let's take the opportunity to fix it, too. Open up `lib/kv/server.ex` and replace the existing server definition: ```elixir -defp serve(socket) do - socket - |> read_line() - |> write_line(socket) + defp serve(socket) do + socket + |> read_line() + |> write_line(socket) - serve(socket) -end + serve(socket) + end -defp read_line(socket) do - {:ok, data} = :gen_tcp.recv(socket, 0) - data -end + defp read_line(socket) do + {:ok, data} = :gen_tcp.recv(socket, 0) + data + end -defp write_line(line, socket) do - :gen_tcp.send(socket, line) -end + defp write_line(line, socket) do + :gen_tcp.send(socket, line) + end ``` by the following: ```elixir -defp serve(socket) do - msg = - case read_line(socket) do - {:ok, data} -> - case KVServer.Command.parse(data) do - {:ok, command} -> - KVServer.Command.run(command) - {:error, _} = err -> - err - end - {:error, _} = err -> - err - end - - write_line(socket, msg) - serve(socket) -end + defp serve(socket) do + msg = + case read_line(socket) do + {:ok, data} -> + case KV.Command.parse(data) do + {:ok, command} -> + KV.Command.run(command, socket) + + {:error, _} = err -> + err + end + + {:error, _} = err -> + err + end + + write_line(socket, msg) + serve(socket) + end -defp read_line(socket) do - :gen_tcp.recv(socket, 0) -end + defp read_line(socket) do + :gen_tcp.recv(socket, 0) + end -defp write_line(socket, {:ok, text}) do - :gen_tcp.send(socket, text) -end + defp write_line(_socket, :ok) do + :ok + end -defp write_line(socket, {:error, :unknown_command}) do - # Known error; write to the client - :gen_tcp.send(socket, "UNKNOWN COMMAND\r\n") -end + defp write_line(socket, {:error, :unknown_command}) do + # Known error; write to the client + :gen_tcp.send(socket, "UNKNOWN COMMAND\r\n") + end -defp write_line(_socket, {:error, :closed}) do - # The connection was closed, exit politely - exit(:shutdown) -end + defp write_line(_socket, {:error, :closed}) do + # The connection was closed, exit politely + exit(:shutdown) + end -defp write_line(socket, {:error, error}) do - # Unknown error; write to the client and exit - :gen_tcp.send(socket, "ERROR\r\n") - exit(error) -end + defp write_line(socket, {:error, error}) do + # Unknown error; write to the client and exit + :gen_tcp.send(socket, "ERROR\r\n") + exit(error) + end ``` If we start our server, we can now send commands to it. For now, we will get two different responses: "OK" when the command is known and "UNKNOWN COMMAND" otherwise: @@ -264,18 +267,18 @@ This means our implementation is going in the correct direction, but it doesn't The previous implementation used pipelines which made the logic straightforward to follow. However, now that we need to handle different error codes along the way, our server logic is nested inside many `case` calls. -Thankfully, Elixir v1.2 introduced the `with` construct, which allows you to simplify code like the above, replacing nested `case` calls with a chain of matching clauses. Let's rewrite the `serve/1` function to use `with`: +Thankfully, Elixir has the `with` construct, which allows you to simplify code like the above, replacing nested `case` calls with a chain of matching clauses. Let's rewrite the `serve/1` function to use `with`: ```elixir -defp serve(socket) do - msg = - with {:ok, data} <- read_line(socket), - {:ok, command} <- KVServer.Command.parse(data), - do: KVServer.Command.run(command) - - write_line(socket, msg) - serve(socket) -end + defp serve(socket) do + msg = + with {:ok, data} <- read_line(socket), + {:ok, command} <- KV.Command.parse(data), + do: KV.Command.run(command, socket) + + write_line(socket, msg) + serve(socket) + end ``` Much better! `with` will retrieve the value returned by the right-side of `<-` and match it against the pattern on the left side. If the value matches the pattern, `with` moves on to the next expression. In case there is no match, the non-matching value is returned. @@ -286,55 +289,60 @@ You can read more about `with/1` in our documentation. ## Running commands -The last step is to implement `KVServer.Command.run/1`, to run the parsed commands against the `:kv` application. Its implementation is shown below: +The last step is to implement `KV.Command.run/1` to run the parsed commands on top of buckets. Its implementation is shown below: ```elixir -@doc """ -Runs the given command. -""" -def run(command) - -def run({:create, bucket}) do - KV.Registry.create(KV.Registry, bucket) - {:ok, "OK\r\n"} -end + @doc """ + Runs the given command. + """ + def run(command, socket) -def run({:get, bucket, key}) do - lookup(bucket, fn pid -> - value = KV.Bucket.get(pid, key) - {:ok, "#{value}\r\nOK\r\n"} - end) -end + def run({:create, bucket}, socket) do + KV.create_bucket(bucket) + :gen_tcp.send(socket, "OK\r\n") + :ok + end -def run({:put, bucket, key, value}) do - lookup(bucket, fn pid -> - KV.Bucket.put(pid, key, value) - {:ok, "OK\r\n"} - end) -end + def run({:get, bucket, key}, socket) do + lookup(bucket, fn pid -> + value = KV.Bucket.get(pid, key) + :gen_tcp.send(socket, "#{value}\r\nOK\r\n") + :ok + end) + end -def run({:delete, bucket, key}) do - lookup(bucket, fn pid -> - KV.Bucket.delete(pid, key) - {:ok, "OK\r\n"} - end) -end + def run({:put, bucket, key, value}, socket) do + lookup(bucket, fn pid -> + KV.Bucket.put(pid, key, value) + :gen_tcp.send(socket, "OK\r\n") + :ok + end) + end -defp lookup(bucket, callback) do - case KV.Registry.lookup(KV.Registry, bucket) do - {:ok, pid} -> callback.(pid) - :error -> {:error, :not_found} + def run({:delete, bucket, key}, socket) do + lookup(bucket, fn pid -> + KV.Bucket.delete(pid, key) + :gen_tcp.send(socket, "OK\r\n") + :ok + end) + end + + defp lookup(bucket, callback) do + if bucket = KV.lookup_bucket(bucket) do + callback.(bucket) + else + {:error, :not_found} + end end -end ``` -Every function clause dispatches the appropriate command to the `KV.Registry` server that we registered during the `:kv` application startup. Since our `:kv_server` depends on the `:kv` application, it is completely fine to depend on the services it provides. +Each function clause dispatches the appropriate command to the appropriate bucket. -You might have noticed we have a function head, `def run(command)`, without a body. In the [Modules and Functions](../getting-started/modules-and-functions.md#default-arguments) chapter, we learned that a bodiless function can be used to declare default arguments for a multi-clause function. Here is another use case where we use a function without a body to document what the arguments are. +You might have noticed we have a function head, `def run(command, socket)`, without a body. In the [Modules and Functions](../getting-started/modules-and-functions.md#default-arguments) chapter, we learned that a bodiless function can be used to declare default arguments for a multi-clause function. Here is another use case where we use a function without a body to document what the arguments are. -Note that we have also defined a private function named `lookup/2` to help with the common functionality of looking up a bucket and returning its `pid` if it exists, `{:error, :not_found}` otherwise. +We have also defined a private function named `lookup/2` to help with the common functionality of looking up a bucket and returning its `pid` if it exists, `{:error, :not_found}` otherwise. -By the way, since we are now returning `{:error, :not_found}`, we should amend the `write_line/2` function in `KVServer` to print such error as well: +By the way, since we are now returning `{:error, :not_found}`, we should amend the `write_line/2` function in `KV.Server` to print such error as well: ```elixir defp write_line(socket, {:error, :not_found}) do @@ -342,67 +350,57 @@ defp write_line(socket, {:error, :not_found}) do end ``` -Our server functionality is almost complete. Only tests are missing. This time, we have left tests for last because there are some important considerations to be made. - -`KVServer.Command.run/1`'s implementation is sending commands directly to the server named `KV.Registry`, which is registered by the `:kv` application. This means this server is global and if we have two tests sending messages to it at the same time, our tests will conflict with each other (and likely fail). We need to decide between having unit tests that are isolated and can run asynchronously, or writing integration tests that work on top of the global state, but exercise our application's full stack as it is meant to be exercised in production. - -So far we have only written unit tests, typically testing a single module directly. However, in order to make `KVServer.Command.run/1` testable as a unit we would need to change its implementation to not send commands directly to the `KV.Registry` process but instead pass a server as an argument. For example, we would need to change `run`'s signature to `def run(command, pid)` and then change all clauses accordingly: +Our server functionality is almost complete. Only tests are missing. -```elixir -def run({:create, bucket}, pid) do - KV.Registry.create(pid, bucket) - {:ok, "OK\r\n"} -end - -# ... other run clauses ... -``` +## Integration tests -Feel free to go ahead and do the changes above and write some unit tests. The idea is that your tests will start an instance of the `KV.Registry` and pass it as an argument to `run/2` instead of relying on the global `KV.Registry`. This has the advantage of keeping our tests asynchronous as there is no shared state. +`KV.Command.run/1`'s implementation is sending commands directly to the `KV` module, which is using a local registry to name processes. This means if we have two tests sending messages to the same bucket, our tests will conflict with each other (and likely fail). One might think this would be a reason to use mocks and other strategies to keep our tests isolated, but such techniques often make our testing environment too distant from how our code actually runs in production, and you may end-up with bugs lurking. -But let's also try something different. Let's write integration tests that rely on the global server names to exercise the whole stack from the TCP server to the bucket. Our integration tests will rely on global state and must be synchronous. With integration tests, we get coverage on how the components in our application work together at the cost of test performance. They are typically used to test the main flows in your application. For example, we should avoid using integration tests to test an edge case in our command parsing implementation. +Luckily, there is a technique that we have been using throughout this guide that would be equally applicable here: it is ok to rely on the local registry as long as each test uses unique names. Using a combination of the test module and test name is more than enough to guarantee that. -Our integration test will use a TCP client that sends commands to our server and assert we are getting the desired responses. +So let's write integration tests that rely on unique names to exercise the whole stack from the TCP server to the bucket. -Let's implement the integration test in `test/kv_server_test.exs` as shown below: +Create a new file at `test/kv/server_test.exs` as shown below: ```elixir -defmodule KVServerTest do - use ExUnit.Case +defmodule KV.ServerTest do + use ExUnit.Case, async: true - setup do - Application.stop(:kv) - :ok = Application.start(:kv) - end + @socket_options [:binary, packet: :line, active: false] - setup do - opts = [:binary, packet: :line, active: false] - {:ok, socket} = :gen_tcp.connect(~c"localhost", 4040, opts) - %{socket: socket} + setup config do + {:ok, socket} = :gen_tcp.connect(~c"localhost", 4040, @socket_options) + test_name = config.test |> Atom.to_string() |> String.replace(" ", "-") + %{socket: socket, name: "#{config.module}-#{test_name}"} end - test "server interaction", %{socket: socket} do - assert send_and_recv(socket, "UNKNOWN shopping\r\n") == - "UNKNOWN COMMAND\r\n" + test "server interaction", %{socket: socket, name: name} do + # CREATE + assert send_and_recv(socket, "CREATE #{name}\r\n") == "OK\r\n" - assert send_and_recv(socket, "GET shopping eggs\r\n") == - "NOT FOUND\r\n" + # PUT + assert send_and_recv(socket, "PUT #{name} eggs 3\r\n") == "OK\r\n" - assert send_and_recv(socket, "CREATE shopping\r\n") == - "OK\r\n" + # GET + assert send_and_recv(socket, "GET #{name} eggs\r\n") == "3\r\n" + assert send_and_recv(socket, "") == "OK\r\n" - assert send_and_recv(socket, "PUT shopping eggs 3\r\n") == - "OK\r\n" + # DELETE + assert send_and_recv(socket, "DELETE #{name} eggs\r\n") == "OK\r\n" - # GET returns two lines - assert send_and_recv(socket, "GET shopping eggs\r\n") == "3\r\n" + # GET + assert send_and_recv(socket, "GET #{name} eggs\r\n") == "\r\n" assert send_and_recv(socket, "") == "OK\r\n" + end - assert send_and_recv(socket, "DELETE shopping eggs\r\n") == - "OK\r\n" + test "unknown command", %{socket: socket} do + assert send_and_recv(socket, "WHATEVER\r\n") == + "UNKNOWN COMMAND\r\n" + end - # GET returns two lines - assert send_and_recv(socket, "GET shopping eggs\r\n") == "\r\n" - assert send_and_recv(socket, "") == "OK\r\n" + test "unknown bucket", %{socket: socket} do + assert send_and_recv(socket, "GET whatever eggs\r\n") == + "NOT FOUND\r\n" end defp send_and_recv(socket, command) do @@ -413,38 +411,16 @@ defmodule KVServerTest do end ``` -Our integration test checks all server interaction, including unknown commands and not found errors. It is worth noting that, as with ETS tables and linked processes, there is no need to close the socket. Once the test process exits, the socket is automatically closed. - -This time, since our test relies on global data, we have not given `async: true` to `use ExUnit.Case`. Furthermore, in order to guarantee our test is always in a clean state, we stop and start the `:kv` application before each test. In fact, stopping the `:kv` application even prints a warning on the terminal: - -```text -18:12:10.698 [info] Application kv exited: :stopped -``` +Run `mix test` and the tests should all pass. However, make sure to terminate any `iex -S mix` session you may have running, as currently tests and development environment are running on the same port (4040). We will address it in the next chapter. -To avoid printing log messages during tests, ExUnit provides a neat feature called `:capture_log`. By setting `@tag :capture_log` before each test or `@moduletag :capture_log` for the whole test module, ExUnit will automatically capture anything that is logged while the test runs. In case our test fails, the captured logs will be printed alongside the ExUnit report. +We added three tests, the first one tests most bucket actions, while the other two deal with error cases. Given there is a lot of shared setup across these tests, we used the `setup/2` macro to deal with common boilerplate. The macro receives the same *test context* as tests and starts a client TCP connection per test. It also defines a unique bucket name using the module name and the test name, making sure any space in the test name is replaced by `-` as to not interfere with our command parsing logic. -Between `use ExUnit.Case` and `setup`, add the following call: +Then, in each test, we pattern matched on the *test context*, extracting the socket or name as necessary. This is similar to the code we wrote in `test/kv/bucket_test.exs`: ```elixir -@moduletag :capture_log + test "stores values by key on a named process", config do ``` -In case the test crashes, you will see a report as follows: - -```text - 1) test server interaction (KVServerTest) - test/kv_server_test.exs:17 - ** (RuntimeError) oops - stacktrace: - test/kv_server_test.exs:29 - - The following output was logged: - - 13:44:10.035 [notice] Application kv exited: :stopped -``` - -With this simple integration test, we start to see why integration tests may be slow. Not only can this test not run asynchronously, but it also requires the expensive setup of stopping and starting the `:kv` application. - -At the end of the day, it is up to you and your team to figure out the best testing strategy for your applications. You need to balance code quality, confidence, and test suite runtime. For example, we may start with testing the server only with integration tests, but if the server continues to grow in future releases, or it becomes a part of the application with frequent bugs, it is important to consider breaking it apart and writing more intensive unit tests that don't have the weight of an integration test. +Except back then we matched on all config and, this time around, we matched only on the data we needed. -Let's move to the next chapter. We will finally make our system distributed by adding a bucket routing mechanism. We will use this opportunity to also improve our testing chops. +Let's move to the next chapter. We will finally make our system distributed by adding a tiny bit of configuration and, *spoiler alert*, changing one line of code. diff --git a/lib/elixir/pages/mix-and-otp/dynamic-supervisor.md b/lib/elixir/pages/mix-and-otp/dynamic-supervisor.md index 8c83e4bb783..78ea475582d 100644 --- a/lib/elixir/pages/mix-and-otp/dynamic-supervisor.md +++ b/lib/elixir/pages/mix-and-otp/dynamic-supervisor.md @@ -5,161 +5,192 @@ # Supervising dynamic children -We have now successfully defined our supervisor which is automatically started (and stopped) as part of our application life cycle. +We have successfully learned how our supervision tree is automatically started (and stopped) as part of our application's life cycle. We can also name our buckets via the `:name` option. We also learned that, in practice, we should always start new processes inside supervisors. Let's apply these insights by ensuring our buckets are named and supervised. -Remember, however, that our `KV.Registry` is both linking (via `start_link`) and monitoring (via `monitor`) bucket processes in the `handle_cast/2` callback: +## Child specs + +Supervisors know how to start processes because they are given "child specifications". In our `lib/kv.ex` file, we defined a list of children with a single child spec: ```elixir -{:ok, bucket} = KV.Bucket.start_link([]) -ref = Process.monitor(bucket) + children = [ + {Registry, name: KV, keys: :unique} + ] ``` -Links are bidirectional, which implies that a crash in a bucket will crash the registry. Although we now have the supervisor, which guarantees the registry will be back up and running, crashing the registry still means we lose all data associating bucket names to their respective processes. - -In other words, we want the registry to keep on running even if a bucket crashes. Let's write a new registry test: +When the child specification is a tuple (as above) or module, then it is equivalent to calling the `child_spec/1` function on said module, which then returns the full specification. The pair above is equivalent to: ```elixir -test "removes bucket on crash", %{registry: registry} do - KV.Registry.create(registry, "shopping") - {:ok, bucket} = KV.Registry.lookup(registry, "shopping") - - # Stop the bucket with non-normal reason - Agent.stop(bucket, :shutdown) - assert KV.Registry.lookup(registry, "shopping") == :error -end +iex> Registry.child_spec(name: KV, keys: :unique) +%{ + id: KV, + start: {Registry, :start_link, [[name: KV, keys: :unique]]}, + type: :supervisor +} ``` -The test is similar to "removes bucket on exit" except that we are being a bit more harsh by sending `:shutdown` as the exit reason instead of `:normal`. If a process terminates with a reason other than `:normal`, all linked processes receive an EXIT signal, causing the linked process to also terminate unless it is trapping exits. +The underlying map returns the `:id` (required), the module-function-args triplet to invoke to start the process (required), the type of the process (optional), among other optional keys. In other words, the `child_spec/1` function allows us to compose and encapsulate specifications in modules. -Since the bucket terminated, the registry also stopped, and our test fails when trying to `GenServer.call/3` it: +Therefore, if we want to supervise `KV.Bucket`, we only need to define a `child_spec/1` function. Luckily for us, whenever we invoke `use Agent` (or `use GenServer` or `use Supervisor` and so forth), an implementation with reasonable defaults is provided. So let's take it for a spin. Back on `iex -S mix`, try this: -```text - 1) test removes bucket on crash (KV.RegistryTest) - test/kv/registry_test.exs:26 - ** (exit) exited in: GenServer.call(#PID<0.148.0>, {:lookup, "shopping"}, 5000) - ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started - code: assert KV.Registry.lookup(registry, "shopping") == :error - stacktrace: - (elixir) lib/gen_server.ex:770: GenServer.call/3 - test/kv/registry_test.exs:33: (test) +```elixir +iex> KV.Bucket.child_spec([]) +%{id: KV.Bucket, start: {KV.Bucket, :start_link, [[]]}} +iex> KV.Bucket.child_spec([name: :shopping]) +%{id: KV.Bucket, start: {KV.Bucket, :start_link, [[name: :shopping]]}} ``` -We are going to solve this issue by defining a new supervisor that will spawn and supervise all buckets. Opposite to the previous Supervisor we defined, the children are not known upfront, but they are rather started dynamically. For those situations, we use a supervisor optimized to such use cases called `DynamicSupervisor`. The `DynamicSupervisor` does not expect a list of children during initialization; instead each child is started manually via `DynamicSupervisor.start_child/2`. +Let's try to start it as part of a supervisor then, using the `{module, options}` format to pass the bucket name (let's also use an atom as the name for convenience): -## The bucket supervisor +```elixir +iex> children = [{KV.Bucket, name: :shopping}] +iex> Supervisor.start_link(children, strategy: :one_for_one) +iex> KV.Bucket.put(:shopping, "milk", 1) +:ok +iex> KV.Bucket.get(:shopping, "milk") +1 +``` -Since a `DynamicSupervisor` does not define any children during initialization, the `DynamicSupervisor` also allows us to skip the work of defining a whole separate module with the usual `start_link` function and the `init` callback. Instead, we can define a `DynamicSupervisor` directly in the supervision tree, by giving it a name and a strategy. +What happens now if we explicitly kill the bucket process? + +```elixir +# Find the pid for the given name +iex> pid = Process.whereis(:shopping) +#PID<0.48.0> +# Send it a kill exit signal +iex> Process.exit(pid, :kill) +true +# But a new process is alive in its place +iex> Process.whereis(:shopping) +#PID<0.50.0> +``` -Open up `lib/kv/supervisor.ex` and add the dynamic supervisor as a child as follows: +Given our buckets can already be supervised, it is time to hook them into our supervision tree. + +## Dynamic supervisors + +Given our buckets can already be supervised, you may be thinking to start them as part of our application `start/2` callback, such as: ```elixir - def init(:ok) do children = [ - {KV.Registry, name: KV.Registry}, - {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one} + {Registry, name: KV, keys: :unique} + {KV.Bucket, name: {:via, Registry, {KV, "shopping"}}} ] - - Supervisor.init(children, strategy: :one_for_one) - end ``` -Remember that the name of a process can be any atom. So far, we have named processes with the same name as the modules that define their implementation. For example, the process defined by `KV.Registry` was given a process name of `KV.Registry`. This is simply a convention: If later there is an error in your system that says, "process named KV.Registry crashed with reason", we know exactly where to investigate. - -In this case, there is no module, so we picked the name `KV.BucketSupervisor`. It could have been any other name. We also chose the `:one_for_one` strategy, which is currently the only available strategy for dynamic supervisors. +And while the above would definitely work, it comes with a huge caveat: it only starts a single bucket. In practice, we want the user to be able to create new buckets at any time. In other words, we need to start and supervise processes dynamically. -Run `iex -S mix` so we can give our dynamic supervisor a try: +While the `Supervisor` module has APIs for starting children after its initialization, it was not designed or optimized for the use case of having potentially millions of children. For this purpose, Elixir instead provides the `DynamicSupervisor` module. Using it is quite similar to `Supervisor` except that, instead of specifying the children during start, you do it afterwards. Let's take it for a spin: ```elixir -iex> {:ok, bucket} = DynamicSupervisor.start_child(KV.BucketSupervisor, KV.Bucket) -{:ok, #PID<0.72.0>} -iex> KV.Bucket.put(bucket, "eggs", 3) +iex> {:ok, sup_pid} = DynamicSupervisor.start_link(strategy: :one_for_one) +iex> DynamicSupervisor.start_child(sup_pid, {KV.Bucket, name: :another_list}) +iex> KV.Bucket.put(:another_list, "milk", 1) :ok -iex> KV.Bucket.get(bucket, "eggs") -3 +iex> KV.Bucket.get(:another_list, "milk") +1 ``` -`DynamicSupervisor.start_child/2` expects the name of the supervisor and the child specification of the child to be started. - -The last step is to change the registry to use the dynamic supervisor: +And it all works as expected. In fact, we can even give names to `DynamicSupervisor` themselves, instead of passing PIDs around and also use it to start buckets named using the registry: ```elixir - def handle_cast({:create, name}, {names, refs}) do - if Map.has_key?(names, name) do - {:noreply, {names, refs}} - else - {:ok, pid} = DynamicSupervisor.start_child(KV.BucketSupervisor, KV.Bucket) - ref = Process.monitor(pid) - refs = Map.put(refs, ref, name) - names = Map.put(names, name, pid) - {:noreply, {names, refs}} - end - end +iex> DynamicSupervisor.start_link(strategy: :one_for_one, name: :dyn_sup) +iex> name = {:via, Registry, {KV, "yet_another_list"}} +iex> DynamicSupervisor.start_child(:dyn_sup, {KV.Bucket, name: name}) +iex> KV.Bucket.put(name, "milk", 1) +:ok +iex> KV.Bucket.get(name, "milk") +1 ``` -That's enough for our tests to pass but there is a resource leakage in our application. When a bucket terminates, the supervisor will start a new bucket in its place. After all, that's the role of the supervisor! +Overall, processes can be named and supervised, regardless if they are supervisors, agents, etc, since all of Elixir standard library was designed around those capabilities. -However, when the supervisor restarts the new bucket, the registry does not know about it. So we will have an empty bucket in the supervisor that nobody can access! To solve this, we want to say that buckets are actually temporary. If they crash, regardless of the reason, they should not be restarted. - -We can do this by passing the `restart: :temporary` option to `use Agent` in `KV.Bucket`: +With all ingredients in place to supervise and name buckets, open up the `lib/kv.ex` module and let's add a new function called `KV.lookup_bucket/1`, which receives a name and either create or returns a bucket for the given name: ```elixir -defmodule KV.Bucket do - use Agent, restart: :temporary -``` +defmodule KV do + use Application -Let's also add a test to `test/kv/bucket_test.exs` that guarantees the bucket is temporary: + @impl true + def start(_type, _args) do + children = [ + {Registry, name: KV, keys: :unique}, + {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one} + ] -```elixir - test "are temporary workers" do - assert Supervisor.child_spec(KV.Bucket, []).restart == :temporary + Supervisor.start_link(children, strategy: :one_for_one) end -``` -Our test uses the `Supervisor.child_spec/2` function to retrieve the child specification out of a module and then assert its restart value is `:temporary`. At this point, you may be wondering why use a supervisor if it never restarts its children. It happens that supervisors provide more than restarts, they are also responsible for guaranteeing proper startup and shutdown, especially in case of crashes in a supervision tree. - -## Supervision trees + @doc """ + Creates a bucket with the given name. + """ + def create_bucket(name) do + DynamicSupervisor.start_child(KV.BucketSupervisor, {KV.Bucket, name: via(name)}) + end -When we added `KV.BucketSupervisor` as a child of `KV.Supervisor`, we began to have supervisors that supervise other supervisors, forming so-called "supervision trees". + @doc """ + Looks up the given bucket. + """ + def lookup_bucket(name) do + GenServer.whereis(via(name)) + end -Every time you add a new child to a supervisor, it is important to evaluate if the supervisor strategy is correct as well as the order of child processes. In this case, we are using `:one_for_one` and the `KV.Registry` is started before `KV.BucketSupervisor`. + defp via(name), do: {:via, Registry, {KV, name}} +end +``` -One flaw that shows up right away is the ordering issue. Since `KV.Registry` invokes `KV.BucketSupervisor`, then the `KV.BucketSupervisor` must be started before `KV.Registry`. Otherwise, it may happen that the registry attempts to reach the bucket supervisor before it has started. +The code is relatively simple. First we changed `start/2` to also start a dynamic supervisor named `KV.BucketSupervisor`. Then, when implemented `KV.create_bucket/1` which receives a bucket and starts with using our registry and dynamic supervisor. And we also added `KV.lookup_bucket/1` that receives the same name and attempts to find its PID. -The second flaw is related to the supervision strategy. If `KV.Registry` dies, all information linking `KV.Bucket` names to bucket processes is lost. Therefore the `KV.BucketSupervisor` and all children must terminate too - otherwise we will have orphan processes. +To make sure it all works as expected, let's write a test. Open up `test/kv_test.exs` and add this: -In light of this observation, we should consider moving to another supervision strategy. The two other candidates are `:one_for_all` and `:rest_for_one`. A supervisor using the `:rest_for_one` strategy will kill and restart child processes which were started *after* the crashed child. In this case, we would want `KV.BucketSupervisor` to terminate if `KV.Registry` terminates. This would require the bucket supervisor to be placed after the registry which violates the ordering constraints we have established two paragraphs above. +```elixir +defmodule KVTest do + use ExUnit.Case, async: true -So our last option is to go all in and pick the `:one_for_all` strategy: the supervisor will kill and restart all of its children processes whenever any one of them dies. This is a completely reasonable approach for our application, since the registry can't work without the bucket supervisor, and the bucket supervisor should terminate without the registry. Let's reimplement `init/1` in `KV.Supervisor` to encode those properties: + test "creates and looks up buckets by any name" do + name = "a unique name that won't be shared" + assert is_nil(KV.lookup_bucket(name)) -```elixir - def init(:ok) do - children = [ - {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}, - {KV.Registry, name: KV.Registry} - ] + assert {:ok, bucket} = KV.create_bucket(name) + assert KV.lookup_bucket(name) == bucket - Supervisor.init(children, strategy: :one_for_all) + assert KV.create_bucket(name) == {:error, {:already_started, bucket}} end +end ``` -There are two topics left before we move on to the next chapter. +The test shows we are creating and locating buckets with any name, making sure we use a unique name to avoid conflicts between tests. + +## The `start_supervised` test helper + +Before we move on, let's do some clean up. -## Shared state in tests +In `test/kv/bucket_test.exs`, we explicitly invoked `KV.Bucket.start_link/1` to start our buckets. However, we now know that we should avoid calling `start_link/1` directly and instead start processes as part of supervision trees. -So far we have been starting one registry per test to ensure they are isolated: +In order to aid testing, `ExUnit` already starts a supervision tree per test and provides the `start_supervised` function to start processes within test-specific supervision tree. One advantage of this approach is that `ExUnit` guarantees any started process is shut down at the end of the test too. Let's rewrite our tests to use it instead: ```elixir -setup do - registry = start_supervised!(KV.Registry) - %{registry: registry} -end -``` +defmodule KV.BucketTest do + use ExUnit.Case, async: true + + test "stores values by key" do + {:ok, bucket} = start_supervised(KV.Bucket) + assert KV.Bucket.get(bucket, "milk") == nil -Since we have changed our registry to use `KV.BucketSupervisor`, our tests are now relying on this shared supervisor even though each test has its own registry. The question is: should we? + KV.Bucket.put(bucket, "milk", 3) + assert KV.Bucket.get(bucket, "milk") == 3 + end + + test "stores values by key on a named process", config do + {:ok, _} = start_supervised({KV.Bucket, name: config.test}) + assert KV.Bucket.get(config.test, "milk") == nil -It depends. It is ok to rely on shared state as long as we depend only on a non-shared partition of this state. Although multiple registries may start buckets on the shared bucket supervisor, those buckets and registries are isolated from each other. We would only run into concurrency issues if we used a function like `DynamicSupervisor.count_children(KV.BucketSupervisor)` which would count all buckets from all registries, potentially giving different results when tests run concurrently. + KV.Bucket.put(config.test, "milk", 3) + assert KV.Bucket.get(config.test, "milk") == 3 + end +end +``` -Since we have relied only on a non-shared partition of the bucket supervisor so far, we don't need to worry about concurrency issues in our test suite. In case it ever becomes a problem, we can start a supervisor per test and pass it as an argument to the registry `start_link` function. +It is a small change, but our tests are now using all of the relevant best practices. Excellent! ## Observer @@ -174,13 +205,10 @@ iex> :observer.start() > When running `iex` inside a project with `iex -S mix`, `observer` won't be available as a dependency. To do so, you will need to call the following functions before: > > ```elixir -> iex> Mix.ensure_application!(:wx) # Not necessary on Erlang/OTP 27+ -> iex> Mix.ensure_application!(:runtime_tools) # Not necessary on Erlang/OTP 27+ -> iex> Mix.ensure_application!(:observer) > iex> :observer.start() > ``` > -> If any of the calls above fail, here is what may have happened: some package managers default to installing a minimized Erlang without WX bindings for GUI support. In some package managers, you may be able to replace the headless Erlang with a more complete package (look for packages named `erlang` vs `erlang-nox` on Debian/Ubuntu/Arch). In others managers, you may need to install a separate `erlang-wx` (or similarly named) package. +> If the call above fails, here is what may have happened: some package managers default to installing a minimized Erlang without WX bindings for GUI support. In some package managers, you may be able to replace the headless Erlang with a more complete package (look for packages named `erlang` vs `erlang-nox` on Debian/Ubuntu/Arch). In others managers, you may need to install a separate `erlang-wx` (or similarly named) package. > > There are conversations to improve this experience in future releases. @@ -193,12 +221,12 @@ In the Applications tab, you will see all applications currently running in your Not only that, as you create new buckets on the terminal, you should see new processes spawned in the supervision tree shown in Observer: ```elixir -iex> KV.Registry.create(KV.Registry, "shopping") -:ok +iex> KV.lookup_bucket("shopping") +#PID<0.89.0> ``` We will leave it up to you to further explore what Observer provides. Note you can double-click any process in the supervision tree to retrieve more information about it, as well as right-click a process to send "a kill signal", a perfect way to emulate failures and see if your supervisor reacts as expected. At the end of the day, tools like Observer are one of the reasons you want to always start processes inside supervision trees, even if they are temporary, to ensure they are always reachable and introspectable. -Now that our buckets are properly linked and supervised, let's see how we can speed things up. +Now that our buckets are named and supervised, we are ready to start our server and start receiving requests. diff --git a/lib/elixir/pages/mix-and-otp/genservers.md b/lib/elixir/pages/mix-and-otp/genservers.md index 94e62ab34ab..30fcf2a7ce7 100644 --- a/lib/elixir/pages/mix-and-otp/genservers.md +++ b/lib/elixir/pages/mix-and-otp/genservers.md @@ -3,46 +3,70 @@ SPDX-FileCopyrightText: 2021 The Elixir Team --> -# Client-server communication with GenServer +# Client-server with GenServer -In the [previous chapter](agents.md), we used agents to represent our buckets. In the [introduction to mix](introduction-to-mix.md), we specified we would like to name each bucket so we can do the following: +To wrap up our distributed key-value store, we will implement a feature where a client can subscribe to a bucket and receive realtime notifications of any modification happening in the bucket, regardless of where in the cluster the bucket is located. -```elixir -CREATE shopping -OK - -PUT shopping milk 1 -OK +We will do by adding a new command, called SUBSCRIBE, to be used like this: -GET shopping milk -1 -OK +```text +SUBSCRIBE shopping +milk SET TO 1 +eggs SET TO 10 +milk DELETED ``` -In the session above we interacted with the "shopping" bucket. +To make this work, we must change our `KV.Bucket` implementation to track subscriptions and emit broadcasts. However, as we will see, we cannot implement such on top of agents, and we will need to rewrite our bucket implementation to a `GenServer`. + +## Links and monitors + +Processes in Elixir are isolated. When they need to communicate, they do so by sending messages. However, how do you know when a process terminates, either because it has completed or due to a crash? -Since agents are processes, each bucket has a process identifier (PID), but buckets do not have a name. Back [in the Process chapter](../getting-started/processes.md), we have learned that we can register processes in Elixir by giving them atom names: +We have two options: links and monitors. + +We have used links extensively. Whenever we started a process, we typically did so by using `start_link` or similar. The idea behind links is that, if any of the processes crash, the other will crash due to the link. We talked about them in the [Process chapter of the Getting Started guide](../getting-started/processes.md). Here is a refresher: ```elixir -iex> Agent.start_link(fn -> %{} end, name: :shopping) -{:ok, #PID<0.43.0>} -iex> KV.Bucket.put(:shopping, "milk", 1) -:ok -iex> KV.Bucket.get(:shopping, "milk") -1 +iex> self() +#PID<0.115.0> +iex> spawn_link(fn -> :nothing_bad_will_happen end) +#PID<0.116.0> +iex> self() +#PID<0.115.0> ``` -However, naming dynamic processes with atoms is a terrible idea! If we use atoms, we would need to convert the bucket name (often received from an external client) to atoms, and **we should never convert user input to atoms**. This is because atoms are not garbage collected. Once an atom is created, it is never reclaimed. Generating atoms from user input would mean the user can inject enough different names to exhaust our system memory! +```elixir +iex> spawn_link(fn -> raise "oops" end) +#PID<0.117.0> -In practice, it is more likely you will reach the Erlang VM limit for the maximum number of atoms before you run out of memory, which will bring your system down regardless. +12:37:33.229 [error] Process #PID<0.117.0> raised an exception +Interactive Elixir (1.18.4) - press Ctrl+C to exit (type h() ENTER for help) +iex> self() +#PID<0.118.0> +``` -Instead of abusing the built-in name facility, we will create our own *process registry* that associates the bucket name to the bucket process. +The reason why we links are so pervasive is because when we start a process inside a supervisor, we want our process to crash if the supervisor terminates. On the other hand, we don't want the supervisor to crash when a child terminates, and therefore supervisors trap exits from links by calling `Process.flag(:trap_exit, true)`. -The registry needs to guarantee that it is always up to date. For example, if one of the bucket processes crashes due to a bug, the registry must notice this change and avoid serving stale entries. In Elixir, we say the registry needs to *monitor* each bucket. Because our *registry* needs to be able to receive and handle ad-hoc messages from the system, the `Agent` API is not enough. +In other words, links create an intrinsic relationship between the processes. If we simply want to track when a process dies, without tying their exit signals to each other, a better solution is to use monitors. When a monitored process terminates, we receive a message in our inbox, regardless of the reason: -We will use a `GenServer` to create a registry process that can monitor the bucket processes. GenServer provides industrial strength functionality for building servers in both Elixir and OTP. +```elixir +iex> pid = spawn(fn -> Process.sleep(5000) end) +#PID<0.119.0> +iex> Process.monitor(pid) +#Reference<0.1076459149.2159017989.118674> +iex> flush() +:ok +# Wait five seconds +iex> flush() +{:DOWN, #Reference<0.1076459149.2159017989.118674>, :process, #PID<0.119.0>, :normal} +:ok +``` + +Once the process terminates, we receive a "DOWN message", represented in a five-element tuple. The last element is the reason why it crashed (`:normal` means it terminated successfully). -Please read the `GenServer` module documentation for an overview if you haven't yet. Once you do so, we are ready to proceed. +Monitors will play a very important role in our subscribe feature. When a client subscribes to a bucket, the bucket will store the client PID and send messages to it on every change. However, if the client terminates (for example because it was disconnected), the bucket must remove the client from its list of subscribers (otherwise the list would keep on growing forever as clients connect and disconnect). + +We chose the `Agent` module to implement our `KV.Bucket` and, unfortunately, agents cannot receive messages. So the first step is to rewrite our `KV.Bucket` to a `GenServer`. The `GenServer` module documentation has a good overview on what they are and how to implement them. Give it a read and then we are ready to proceed. ## GenServer callbacks @@ -84,234 +108,255 @@ def handle_call({:put, key, value}, _from, state) do end ``` -There is quite a bit more ceremony in the GenServer code but, as we will see, it brings some benefits too. - -For now, we will write only the server callbacks for our bucket registering logic, without providing a proper API, which we will do later. - -Create a new file at `lib/kv/registry.ex` with the following contents: +Let's go ahead and rewrite `KV.Bucket` at once. Open up `lib/kv/bucket.ex` and replace its contents with this new version: ```elixir -defmodule KV.Registry do +defmodule KV.Bucket do use GenServer - ## Missing Client API - will add this later + @doc """ + Starts a new bucket. + """ + def start_link(opts) do + GenServer.start_link(__MODULE__, %{}, opts) + end - ## Defining GenServer Callbacks + @doc """ + Gets a value from the `bucket` by `key`. + """ + def get(bucket, key) do + GenServer.call(bucket, {:get, key}) + end - @impl true - def init(:ok) do - {:ok, %{}} + @doc """ + Puts the `value` for the given `key` in the `bucket`. + """ + def put(bucket, key, value) do + GenServer.call(bucket, {:put, key, value}) + end + + @doc """ + Deletes `key` from `bucket`. + + Returns the current value of `key`, if `key` exists. + """ + def delete(bucket, key) do + GenServer.call(bucket, {:delete, key}) end + ### Callbacks + @impl true - def handle_call({:lookup, name}, _from, names) do - {:reply, Map.fetch(names, name), names} + def init(bucket) do + state = %{ + bucket: bucket + } + + {:ok, state} end @impl true - def handle_cast({:create, name}, names) do - if Map.has_key?(names, name) do - {:noreply, names} - else - {:ok, bucket} = KV.Bucket.start_link([]) - {:noreply, Map.put(names, name, bucket)} - end + def handle_call({:get, key}, _from, state) do + value = get_in(state.bucket[key]) + {:reply, value, state} + end + + def handle_call({:put, key, value}, _from, state) do + state = put_in(state.bucket[key], value) + {:reply, :ok, state} + end + + def handle_call({:delete, key}, _from, state) do + {value, state} = pop_in(state.bucket[key]) + {:reply, value, state} end end ``` -There are two types of requests you can send to a GenServer: calls and casts. Calls are synchronous and the server **must** send a response back to such requests. While the server computes the response, the client is **waiting**. Casts are asynchronous: the server won't send a response back and therefore the client won't wait for one. Both requests are messages sent to the server, and will be handled in sequence. In the above implementation, we pattern-match on the `:create` messages, to be handled as cast, and on the `:lookup` messages, to be handled as call. +The first function is `start_link/1`, which starts a new GenServer passing a list of options. `GenServer.start_link/3`, which takes three arguments: -In order to invoke the callbacks above, we need to go through the corresponding `GenServer` functions. Let's start a registry, create a named bucket, and then look it up: +1. The module where the server callbacks are implemented, in this case `__MODULE__` (meaning the current module) -```elixir -iex> {:ok, registry} = GenServer.start_link(KV.Registry, :ok) -{:ok, #PID<0.136.0>} -iex> GenServer.cast(registry, {:create, "shopping"}) -:ok -iex> {:ok, bucket} = GenServer.call(registry, {:lookup, "shopping"}) -{:ok, #PID<0.174.0>} -``` +2. The initialization arguments, in this case the empty bucket `%{}` -Our `KV.Registry` process received a cast with `{:create, "shopping"}` and a call with `{:lookup, "shopping"}`, in this sequence. `GenServer.cast` will immediately return, as soon as the message is sent to the `registry`. The `GenServer.call` on the other hand, is where we would be waiting for an answer, provided by the above `KV.Registry.handle_call` callback. +3. A list of options which can be used to specify things like the name of the server. Once again, we forward the list of options that we receive on `start_link/1` to `GenServer.start_link/3`, as we did for agents -You may also have noticed that we have added `@impl true` before each callback. The `@impl true` informs the compiler that our intention for the subsequent function definition is to define a callback. If by any chance we make a mistake in the function name or in the number of arguments, like we define a `handle_call/2`, the compiler would warn us there isn't any `handle_call/2` to define, and would give us the complete list of known callbacks for the `GenServer` module. +Once started, the GenServer will invoke the `init/1` callback, that receives the second argument given to `GenServer.start_link/3` and returns `{:ok, state}`, where state is a new map. We can already notice how the `GenServer` API makes the client/server segregation more apparent. `start_link/3` happens in the client, while `init/1` is the respective callback that runs on the server. -This is all good and well, but we still want to offer our users an API that allows us to hide our implementation details. +There are two types of requests you can send to a GenServer: calls and casts. Calls are synchronous and the server **must** send a response back to such requests. While the server computes the response, the client is **waiting**. Casts are asynchronous: the server won't send a response back and therefore the client won't wait for one. Both requests are messages sent to the server, and will be handled in sequence. So far we have only used `GenServer.call/2`, to keep the same semantics as the Agent, but we will give `cast` a try when implementing subscriptions. Given we kept the same behaviour, all tests will still pass. -## The Client API +Each request must be implemented as a specific callback. For `call/2` requests, we implement a `handle_call/3` callback that receives the `request`, the process from which we received the request (`_from`), and the current server state (`state`). The `handle_call/3` callback returns a tuple in the format `{:reply, reply, updated_state}`. The first element of the tuple, `:reply`, indicates that the server should send a reply back to the client. The second element, `reply`, is what will be sent to the client while the third, `updated_state` is the new server state. -A GenServer is implemented in two parts: the client API and the server callbacks. You can either combine both parts into a single module or you can separate them into a client module and a server module. The client is any process that invokes the client function. The server is always the process identifier or process name that we will explicitly pass as argument to the client API. Here we'll use a single module for both the server callbacks and the client API. +Another Elixir feature we used in the implementation above are the nested traversal functions: `get_in/1`, `put_in/2`, and `pop_in/1`. Instead of keeping the `bucket` as our GenServer state, we defined a state map with a `bucket` key inside. This will be important as we also need to track subscribers as part of the GenServer state. These new functions make it straight-forward to manipulate data structures nested in other data structures. -Edit the file at `lib/kv/registry.ex`, filling in the blanks for the client API: +With our GenServer in place, let's work on subscription, starting with the tests. -```elixir - ## Client API +## Implementing subscriptions - @doc """ - Starts the registry. - """ - def start_link(opts) do - GenServer.start_link(__MODULE__, :ok, opts) - end +Our new test will subscribe to a bucket and then assert that, as operations are performed against the bucket, we receive messages of said events. - @doc """ - Looks up the bucket pid for `name` stored in `server`. +Open up `test/kv/bucket_test.exs` and key this in: - Returns `{:ok, pid}` if the bucket exists, `:error` otherwise. - """ - def lookup(server, name) do - GenServer.call(server, {:lookup, name}) - end +```elixir + test "subscribes to puts and deletes" do + {:ok, bucket} = start_supervised(KV.Bucket) + KV.Bucket.subscribe(bucket) - @doc """ - Ensures there is a bucket associated with the given `name` in `server`. - """ - def create(server, name) do - GenServer.cast(server, {:create, name}) + KV.Bucket.put(bucket, "milk", 3) + assert_receive {:put, "milk", 3} + + # Also check it works even from another process + spawn(fn -> KV.Bucket.delete(bucket, "milk") end) + assert_receive {:delete, "milk"} end ``` -The first function is `start_link/1`, which starts a new GenServer passing a list of options. `start_link/1` calls out to `GenServer.start_link/3`, which takes three arguments: - -1. The module where the server callbacks are implemented, in this case `__MODULE__` (meaning the current module) - -2. The initialization arguments, in this case the atom `:ok` +In order to make the test pass, we need to implement the `KV.Bucket.subscribe/1`. So let's add these three new functions to `KV.Bucket`: -3. A list of options which can be used to specify things like the name of the server. For now, we forward the list of options that we receive on `start_link/1` to `GenServer.start_link/3` - -The next two functions, `lookup/2` and `create/2`, are responsible for sending these requests to the server. In this case, we have used `{:lookup, name}` and `{:create, name}` respectively. Requests are often specified as tuples, like this, in order to provide more than one "argument" in that first argument slot. It's common to specify the action being requested as the first element of a tuple, and arguments for that action in the remaining elements. Note that the requests must match the first argument to `handle_call/3` or `handle_cast/2`. +```elixir + @doc """ + Subscribes the current process to the bucket. + """ + def subscribe(bucket) do + GenServer.cast(bucket, {:subscribe, self()}) + end -That's it for the client API. On the server side, we can implement a variety of callbacks to guarantee the server initialization, termination, and handling of requests. Those callbacks are optional and for now, we have only implemented the ones we care about. Let's recap. + @impl true + def handle_cast({:subscribe, pid}, state) do + Process.monitor(pid) + state = update_in(state.subscribers, &MapSet.put(&1, pid)) + {:noreply, state} + end -The first is the `init/1` callback, that receives the second argument given to `GenServer.start_link/3` and returns `{:ok, state}`, where state is a new map. We can already notice how the `GenServer` API makes the client/server segregation more apparent. `start_link/3` happens in the client, while `init/1` is the respective callback that runs on the server. + @impl true + def handle_info({:DOWN, _ref, _type, pid, _reason}, state) do + state = update_in(state.subscribers, &MapSet.delete(&1, pid)) + {:noreply, state} + end +``` -For `call/2` requests, we implement a `handle_call/3` callback that receives the `request`, the process from which we received the request (`_from`), and the current server state (`names`). The `handle_call/3` callback returns a tuple in the format `{:reply, reply, new_state}`. The first element of the tuple, `:reply`, indicates that the server should send a reply back to the client. The second element, `reply`, is what will be sent to the client while the third, `new_state` is the new server state. +On subscription, we send a `cast/2` request with the current process identifier and implement its `handle_cast/2` callback that receives the `request` and the current server state. We then proceed to monitor the given `pid` and add it to the list of subscribers, which we are implementing using `MapSet`. The `handle_cast/2` callback returns a tuple in the format `{:noreply, updated_state}`. Note that in a real application we would have probably implemented it with a synchronous call, as it provides back pressure, instead of an asynchronous cast. We are doing it this way to illustrate how to implement a cast callback. -For `cast/2` requests, we implement a `handle_cast/2` callback that receives the `request` and the current server state (`names`). The `handle_cast/2` callback returns a tuple in the format `{:noreply, new_state}`. Note that in a real application we would have probably implemented the callback for `:create` with a synchronous call instead of an asynchronous cast. We are doing it this way to illustrate how to implement a cast callback. +Then, because we have monitored a process, once that process terminates, we will receive a "DOWN message". GenServers handle regular messages using the `handle_info/2` callback, which also typically return `{:noreply, updated_state}`. In this callback, we remove the PID that terminated from our list of subscribers. -There are other tuple formats both `handle_call/3` and `handle_cast/2` callbacks may return. There are other callbacks like `terminate/2` and `code_change/3` that we could implement. You are welcome to explore the full `GenServer` documentation to learn more about those. +We are almost there. We can see both `handle_cast/2` and `handle_info/2` callbacks assume there is a subscribers key in our state with a `MapSet`. So let's add it by updating the existing `init/1` to the following: -For now, let's write some tests to guarantee our GenServer works as expected. +```elixir + @impl true + def init(bucket) do + state = %{ + bucket: bucket, + subscribers: MapSet.new() + } -## Testing a GenServer + {:ok, state} + end +``` -Testing a GenServer is not much different from testing an agent. We will spawn the server on a setup callback and use it throughout our tests. Create a file at `test/kv/registry_test.exs` with the following: +And finally let's update the callbacks for `put/3` and `delete/2` to broadcast messages whenever they are invoked, like this: ```elixir -defmodule KV.RegistryTest do - use ExUnit.Case, async: true - - setup do - registry = start_supervised!(KV.Registry) - %{registry: registry} + def handle_call({:put, key, value}, _from, state) do + state = put_in(state.bucket[key], value) + broadcast(state, {:put, key, value}) + {:reply, :ok, state} end - test "spawns buckets", %{registry: registry} do - assert KV.Registry.lookup(registry, "shopping") == :error - - KV.Registry.create(registry, "shopping") - assert {:ok, bucket} = KV.Registry.lookup(registry, "shopping") + def handle_call({:delete, key}, _from, state) do + {value, state} = pop_in(state.bucket[key]) + broadcast(state, {:delete, key}) + {:reply, value, state} + end - KV.Bucket.put(bucket, "milk", 1) - assert KV.Bucket.get(bucket, "milk") == 1 + defp broadcast(state, message) do + for pid <- state.subscribers do + send(pid, message) + end end -end ``` -Our test case first asserts there are no buckets in our registry, creates a named bucket, looks it up, and asserts it behaves as a bucket. - -There is one important difference between the `setup` block we wrote for `KV.Registry` and the one we wrote for `KV.Bucket`. Instead of starting the registry by hand by calling `KV.Registry.start_link/1`, we instead called the `ExUnit.Callbacks.start_supervised!/2` function, passing the `KV.Registry` module. +There is no need to modify the callback for `get/2`. And that's it, run the tests again, and our new test should pass! -The `start_supervised!` function was injected into our test module by `use ExUnit.Case`. It does the job of starting the `KV.Registry` process, by calling its `start_link/1` function. The advantage of using `start_supervised!` is that ExUnit will guarantee that the registry process will be shutdown **before** the next test starts. In other words, it helps guarantee that the state of one test is not going to interfere with the next one in case they depend on shared resources. +## Wiring it all up -When starting processes during your tests, we should always prefer to use `start_supervised!`. We recommend you to change the `setup` block in `bucket_test.exs` to use `start_supervised!` too. +Now that our bucket deals with subscriptions, we need to expose this new functionality in our server. Let's once again start with the test. -Run the tests and they should all pass! +Open up `test/kv/server_test.exs` and add this new test: -## The need for monitoring +```elixir + test "subscribes to buckets", %{socket: socket, name: name} do + assert send_and_recv(socket, "CREATE #{name}\r\n") == "OK\r\n" + :gen_tcp.send(socket, "SUBSCRIBE #{name}\r\n") -Everything we have done so far could have been implemented with a `Agent`. In this section, we will see one of many things that we can achieve with a GenServer that is not possible with an Agent. + {:ok, other} = :gen_tcp.connect(~c"localhost", 4040, @socket_options) -Let's start with a test that describes how we want the registry to behave if a bucket stops or crashes: + assert send_and_recv(other, "PUT #{name} milk 3\r\n") == "OK\r\n" + assert :gen_tcp.recv(socket, 0, 1000) == {:ok, "milk SET TO 3\r\n"} -```elixir -test "removes buckets on exit", %{registry: registry} do - KV.Registry.create(registry, "shopping") - {:ok, bucket} = KV.Registry.lookup(registry, "shopping") - Agent.stop(bucket) - assert KV.Registry.lookup(registry, "shopping") == :error -end + assert send_and_recv(other, "DELETE #{name} milk\r\n") == "OK\r\n" + assert :gen_tcp.recv(socket, 0, 1000) == {:ok, "milk DELETED\r\n"} + end ``` -The test above will fail on the last assertion as the bucket name remains in the registry even after we stop the bucket process. +The test creates a bucket and subscribes to it. Then it opens up another TCP connection to send commands. For each command sent, we expect the subscribed socket to receive a message. -In order to fix this bug, we need the registry to monitor every bucket it spawns. Once we set up a monitor, the registry will receive a notification every time a bucket process exits, allowing us to clean the registry up. - -Let's first play with monitors by starting a new console with `iex -S mix`: +To make the test pass, we need to change `KV.Command` to parse the new `SUBSCRIBE` command and then run it. Open up `lib/kv/commands.ex` and then first change the `parse/1` definition to the following: ```elixir -iex> {:ok, pid} = KV.Bucket.start_link([]) -{:ok, #PID<0.66.0>} -iex> Process.monitor(pid) -#Reference<0.0.0.551> -iex> Agent.stop(pid) -:ok -iex> flush() -{:DOWN, #Reference<0.0.0.551>, :process, #PID<0.66.0>, :normal} + def parse(line) do + case String.split(line) do + ["SUBSCRIBE", bucket] -> {:ok, {:subscribe, bucket}} + ["CREATE", bucket] -> {:ok, {:create, bucket}} + ["GET", bucket, key] -> {:ok, {:get, bucket, key}} + ["PUT", bucket, key, value] -> {:ok, {:put, bucket, key, value}} + ["DELETE", bucket, key] -> {:ok, {:delete, bucket, key}} + _ -> {:error, :unknown_command} + end + end ``` -Note `Process.monitor(pid)` returns a unique reference that allows us to match upcoming messages to that monitoring reference. After we stop the agent, we can `flush/0` all messages and notice a `:DOWN` message arrived, with the exact reference returned by `monitor`, notifying that the bucket process exited with reason `:normal`. - -Let's reimplement the server callbacks to fix the bug and make the test pass. First, we will modify the GenServer state to two maps: one that contains `name -> pid` and another that holds `ref -> name`. Then we need to monitor the buckets on `handle_cast/2` as well as implement a `handle_info/2` callback to handle the monitoring messages. The full server callbacks implementation is shown below: +We added a new clause that converts "SUBSCRIBE" into a tuple. Now we need to match on this tuple within `run/1`. We can do so by adding a new clause at the bottom of `run/1`, with the following code: ```elixir -## Server callbacks + def run({:subscribe, bucket}, socket) do + lookup(bucket, fn pid -> + KV.Bucket.subscribe(pid) + :inet.setopts(socket, active: true) + receive_messages(socket) + end) + end -@impl true -def init(:ok) do - names = %{} - refs = %{} - {:ok, {names, refs}} -end + defp receive_messages(socket) do + receive do + {:put, key, value} -> + :gen_tcp.send(socket, "#{key} SET TO #{value}\r\n") + receive_messages(socket) -@impl true -def handle_call({:lookup, name}, _from, state) do - {names, _} = state - {:reply, Map.fetch(names, name), state} -end + {:delete, key} -> + :gen_tcp.send(socket, "#{key} DELETED\r\n") + receive_messages(socket) + + {:tcp_closed, ^socket} -> + {:error, :closed} -@impl true -def handle_cast({:create, name}, {names, refs}) do - if Map.has_key?(names, name) do - {:noreply, {names, refs}} - else - {:ok, bucket} = KV.Bucket.start_link([]) - ref = Process.monitor(bucket) - refs = Map.put(refs, ref, name) - names = Map.put(names, name, bucket) - {:noreply, {names, refs}} + # If we receive any message, including socket writes, we discard them + _ -> + receive_messages(socket) + end end -end +``` -@impl true -def handle_info({:DOWN, ref, :process, _pid, _reason}, {names, refs}) do - {name, refs} = Map.pop(refs, ref) - names = Map.delete(names, name) - {:noreply, {names, refs}} -end +Let's go over it by parts. We use the existing `lookup/2` private function to lookup for a bucket. If one is found, we subscribe the current process to the bucket. Then we call `:inet.setopts(socket, active: true)` (which we will explain soon) and `receive_messages/1`. -@impl true -def handle_info(msg, state) do - require Logger - Logger.debug("Unexpected message in KV.Registry: #{inspect(msg)}") - {:noreply, state} -end -``` +`receive_messages/1` awaits for messages from the bucket and then calls itself again, becoming a loop. We match on `{:put, key, value}` and `{:delete, key}` and write to those events to the socket. We also match on `{:tcp_closed, ^socket}`, which is a message that will be delivered if the TCP socket closes, and use it to abort the loop. We discard any other message. + +At this point you may be wondering: where does `{:tcp_closed, ^socket}` come from? + +So far, when receiving messages from the socket, we used `:gen_tcp.recv/3` to perform calls that will block the current process until content is available. This is known as "passive mode". However, we can also ask `:gen_tcp` to stream messages to the current process inbox as they arrive, which is known as "active mode", which is exactly what we configured when we called `:inet.setopts(socket, active: true)`. Those messages have the shape `{:tcp, socket, data}`. When the socket is in active mode and it is closed, it delivers a `{:tcp_closed, socket}` message. Once we receive this message, we exit the loop, which will exit the connection process. Since the bucket is monitoring the process, it will automatically remove the subscription too. You could verify this in practice by adding a `COUNT SUBSCRIPTIONS` command that returns the number of subscribers for a given bucket. -Observe that we were able to considerably change the server implementation without changing any of the client API. That's one of the benefits of explicitly segregating the server and the client. +In practice, many systems would prefer to call `:inet.setopts(socket, active: :once)` to specify only a single TCP message should be delivered to avoid overflowing message queues. Once the message is received, they call `:inet.setopts/2` again. In our case, we are simply discarding anything that arrives over the socket, so setting `active: true` is equally fine. In all scenarios, the benefit of using active mode is that the process can receive TCP messages as well as messages from other processes at the same time, instead of blocking on `:gen_tcp.recv/3`. -Finally, different from the other callbacks, we have defined a "catch-all" clause for `handle_info/2` that discards and logs any unknown message. To understand why, let's move on to the next section. +To wrap it all up, you should give our new feature a try in a distributed setting too. Start two `NODES=... PORT=... iex --sname ... -S mix` instances. In one of them, create a bucket. In the other, subscribe to the same bucket. Once you go back to the first shell, you will see that, even as you send commands to the bucket in one machine, the messages will be streamed to the other one. In other words, our subscription system is also distributed, and all we had to do is to send messages! ## `call`, `cast` or `info`? @@ -319,25 +364,20 @@ So far we have used three callbacks: `handle_call/3`, `handle_cast/2` and `handl 1. `handle_call/3` must be used for synchronous requests. This should be the default choice as waiting for the server reply is a useful back-pressure mechanism. -2. `handle_cast/2` must be used for asynchronous requests, when you don't care about a reply. A cast does not guarantee the server has received the message and, for this reason, should be used sparingly. For example, the `create/2` function we have defined in this chapter should have used `call/2`. We have used `cast/2` for didactic purposes. +2. `handle_cast/2` must be used for asynchronous requests, when you don't care about a reply. A cast does not guarantee the server has received the message and, for this reason, should be used sparingly. For example, the `subscribe/1` function we have defined in this chapter should have used `call/2`. We have used `cast/2` for educational purposes. 3. `handle_info/2` must be used for all other messages a server may receive that are not sent via `GenServer.call/2` or `GenServer.cast/2`, including regular messages sent with `send/2`. The monitoring `:DOWN` messages are an example of this. -Since any message, including the ones sent via `send/2`, go to `handle_info/2`, there is a chance that unexpected messages will arrive to the server. Therefore, if we don't define the catch-all clause, those messages could cause our registry to crash, because no clause would match. We don't need to worry about such cases for `handle_call/3` and `handle_cast/2` though. Calls and casts are only done via the `GenServer` API, so an unknown message is quite likely a developer mistake. - To help developers remember the differences between call, cast and info, the supported return values and more, we have a tiny [GenServer cheat sheet](https://elixir-lang.org/downloads/cheatsheets/gen-server.pdf). -## Monitors or links? +## Agents or GenServers? -We have previously learned about links in the [Process chapter](../getting-started/processes.md). Now, with the registry complete, you may be wondering: when should we use monitors and when should we use links? +Before moving forward to the last chapter, you may be wondering: in the future, should you use an `Agent` or a `GenServer`? -Links are bi-directional. If you link two processes and one of them crashes, the other side will crash too (unless it is trapping exits). A monitor is uni-directional: only the monitoring process will receive notifications about the monitored one. In other words: use links when you want linked crashes, and monitors when you just want to be informed of crashes, exits, and so on. +As we saw throughout this guide, agents are straight-forward to get started but they are limited in what they can do. Agents are effectively a subset of GenServers. In fact, agents are implemented on top of GenServers. As well as supervisors, the `Registry` module, and many other features you will find in both Erlang and Elixir. -Returning to our `handle_cast/2` implementation, you can see the registry is both linking and monitoring the buckets: +In other words, GenServers are the most essential component for building concurrent and fault-tolerant systems in Elixir. They provide a robust and flexible framework for managing state and coordinating interactions between processes. -```elixir -{:ok, bucket} = KV.Bucket.start_link([]) -ref = Process.monitor(bucket) -``` +For those reasons, many adopt a rule of thumb to never use Agents and jump straight into GenServers instead. On the other hand, others are more than fine with using agents to store a bit of state here and there. Either way, you will be fine! -This is a bad idea, as we don't want the registry to crash when a bucket crashes. The proper fix is to actually not link the bucket to the registry. Instead, we will link each bucket to a special type of process called Supervisors, which are explicitly designed to handle failures and crashes. We will learn more about them in the next chapter. +This is the last feature we have implemented for our distributed key-value store. In the next chapter, we will learn how to package our application before shipping it to production. diff --git a/lib/elixir/pages/mix-and-otp/introduction-to-mix.md b/lib/elixir/pages/mix-and-otp/introduction-to-mix.md index 84888ce85a7..80b2bdd2688 100644 --- a/lib/elixir/pages/mix-and-otp/introduction-to-mix.md +++ b/lib/elixir/pages/mix-and-otp/introduction-to-mix.md @@ -9,8 +9,8 @@ In this guide, we will build a complete Elixir application, with its own supervi The requirements for this guide are (see `elixir -v`): - * Elixir 1.15.0 onwards - * Erlang/OTP 24 onwards + * Elixir 1.18.0 onwards + * Erlang/OTP 27 onwards The application works as a distributed key-value store. We are going to organize key-value pairs into buckets and distribute those buckets across multiple nodes. We will also build a simple client that allows us to connect to any of those nodes and send requests such as: @@ -44,7 +44,7 @@ In this chapter, we will create our first project using Mix and explore differen > #### Source code {: .info} > -> The final code for the application built in this guide is in [this repository](https://github.com/josevalim/kv_umbrella) and can be used as a reference. +> The final code for the application built in this guide is in [this repository](https://github.com/josevalim/kv) and can be used as a reference. > #### Is this guide required reading? {: .info} > @@ -82,7 +82,7 @@ Let's take a brief look at those generated files. > #### Executables in the `PATH` {: .info} > -> Mix is an Elixir executable. This means that in order to run `mix`, you need to have both `mix` and `elixir` executables in your PATH. That's what happens when you install Elixir. +> Mix is an Elixir executable. This means that in order to run `mix`, you need to have both `mix` and `elixir` executables in your [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)). That's what happens when you install Elixir. ## Project compilation diff --git a/lib/elixir/pages/mix-and-otp/releases.md b/lib/elixir/pages/mix-and-otp/releases.md new file mode 100644 index 00000000000..526d1bbb35d --- /dev/null +++ b/lib/elixir/pages/mix-and-otp/releases.md @@ -0,0 +1,170 @@ + + +# Releases + +Now that our application is ready, you may be wondering how we can package our application to run in production. After all, all of our code so far depends on Erlang and Elixir versions that are installed in your current system. To achieve this goal, Elixir provides releases. + +A release is a self-contained directory that consists of your application code, all of its dependencies, plus the whole Erlang Virtual Machine (VM) and runtime. Once a release is assembled, it can be packaged and deployed to a target as long as the target runs on the same operating system (OS) distribution and version as the machine that assembled the release. + +To get started, simply run `mix release` while setting `MIX_ENV=prod`: + +```console +$ MIX_ENV=prod mix release +Compiling 4 files (.ex) +Generated kv app +* assembling kv-0.1.0 on MIX_ENV=prod +* using config/runtime.exs to configure the release at runtime + +Release created at _build/prod/rel/kv + + # To start your system + _build/prod/rel/kv/bin/kv start + +Once the release is running: + + # To connect to it remotely + _build/prod/rel/kv/bin/kv remote + + # To stop it gracefully (you may also send SIGINT/SIGTERM) + _build/prod/rel/kv/bin/kv stop + +To list all commands: + + _build/prod/rel/kv/bin/kv +``` + +Excellent! A release was assembled in `_build/prod/rel/kv`. Everything you need to run your application is inside that directory. In particular, there is a `bin/kv` file which is the entry point to your system. It supports multiple commands, such as: + + * `bin/kv start`, `bin/kv start_iex`, `bin/kv restart`, and `bin/kv stop` — for general management of the release + + * `bin/kv rpc COMMAND` and `bin/kv remote` — for running commands on the running system or to connect to the running system + + * `bin/kv eval COMMAND` — to start a fresh system that runs a single command and then shuts down + + * `bin/kv daemon` and `bin/kv daemon_iex` — to start the system as a daemon on Unix-like systems + + * `bin/kv install` — to install the system as a service on Windows machines + +If you run `bin/kv start_iex` inside the release directory, it will start the system using a short name (`--sname`) equal to the release name, which in this case is `kv`. The next step is to start two instances, on different ports and different names, as we did earlier on. But before we do this, let's talk a bit about the benefits of releases. + +## Why releases? + +Releases allow developers to precompile and package all of their code and the runtime into a single unit. The benefits of releases are: + + * Code preloading. The VM has two mechanisms for loading code: interactive and embedded. By default, it runs in the interactive mode which dynamically loads modules when they are used for the first time. The first time your application calls `Enum.map/2`, the VM will find the `Enum` module and load it. There's a downside. When you start a new server in production, it may need to load many other modules, causing the first requests to have an unusual spike in response time. Releases run in embedded mode, which loads all available modules upfront, guaranteeing your system is ready to handle requests after booting. + + * Configuration and customization. Releases give developers fine grained control over system configuration and the VM flags used to start the system. + + * Self-contained. A release does not require the source code to be included in your production artifacts. All of the code is precompiled and packaged. Releases do not even require Erlang or Elixir on your servers, as they include the Erlang VM and its runtime by default. Furthermore, both Erlang and Elixir standard libraries are stripped to bring only the parts you are actually using. + + * Multiple releases. You can assemble different releases with different configuration per application or even with different applications altogether. + +We have written extensive documentation on releases, so [please check the official documentation for more information](`mix release`). For now, we will continue exploring some of the features outlined above. + +## Configuring releases + +Releases also provide built-in hooks for configuring almost every need of the production system: + + * `config/config.exs` — provides build-time application configuration, which is executed before our application compiles. This file often imports configuration files based on the environment, such as `config/dev.exs` and `config/prod.exs`. + + * `config/runtime.exs` — provides runtime application configuration. It is executed every time the release boots and is further extensible via config providers. + + * `rel/env.sh.eex` and `rel/env.bat.eex` — template files that are copied into every release and executed on every command to set up environment variables, including ones specific to the VM, and the general environment. + + * `rel/vm.args.eex` — a template file that is copied into every release and provides static configuration of the Erlang Virtual Machine and other runtime flags. + +In this case, we already have specified a `config/runtime.exs` that deals with both `PORT` and `NODES` environment variables. Furthermore, while releases don't accept a `--sname` parameter, they do allow us to set the name via the `RELEASE_NODE` env var. Therefore, we can start two copies of the system by jumping into `_build/prod/rel/kv` and typing this (remember to adjust `@computer-name` to your actual computer name): + +```console +$ NODES="foo@computer-name,bar@computer-name" PORT=4040 RELEASE_NODE="foo" bin/kv start_iex +``` + +```console +$ NODES="foo@computer-name,bar@computer-name" PORT=4041 RELEASE_NODE="bar" bin/kv start_iex +``` + +To verify it all worked out, you can type `Node.list` in the IEx section and see if it returns the other node. If it doesn't, you can start diagnosing, first by comparing the node names within each `iex>` prompt and calling `Node.connect/1` directly. With applications running, you can `telnet` into them as usual too. + +While the above is enough to get started, you may want to perform advanced configuration based on the environment you are replying to. Releases provide scripts for that, which are great to automate based on host, network, or cloud settings. + +## Operating System scripts + +Every release contains an environment file, named `env.sh` on Unix-like systems and `env.bat` on Windows machines, that executes before the Elixir system starts. In this file, you can execute any OS-level code, such as invoke other applications, set environment variables and so on. Some of those environment variables can even configure how the release itself runs. + +For instance, releases run using short-names (`--sname`). However, if you want to actually run a distributed key-value store in production, you will need multiple nodes and start the release with the `--name` option. We can achieve this by setting the `RELEASE_DISTRIBUTION` environment variable inside the `env.sh` and `env.bat` files. Mix already has a template for said files which we can customize, so let's ask Mix to copy them to our application: + + $ mix release.init + * creating rel/vm.args.eex + * creating rel/remote.vm.args.eex + * creating rel/env.sh.eex + * creating rel/env.bat.eex + +If you open up `rel/env.sh.eex`, you will see: + +```shell +#!/bin/sh + +# # Sets and enables heart (recommended only in daemon mode) +# case $RELEASE_COMMAND in +# daemon*) +# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND" +# export HEART_COMMAND +# export ELIXIR_ERL_OPTIONS="-heart" +# ;; +# *) +# ;; +# esac + +# # Set the release to load code on demand (interactive) instead of preloading (embedded). +# export RELEASE_MODE=interactive + +# # Set the release to work across nodes. +# # RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". +# export RELEASE_DISTRIBUTION=name +# export RELEASE_NODE=<%= @release.name %> +``` + +The steps necessary to work across nodes is already commented out as an example. You can enable full distribution by setting the `RELEASE_DISTRIBUTION` variable to `name`. + +If you are on Windows, you will have to open up `rel/env.bat.eex`, where you will find this: + +```bat +@echo off +rem Set the release to load code on demand (interactive) instead of preloading (embedded). +rem set RELEASE_MODE=interactive + +rem Set the release to work across nodes. +rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none". +rem set RELEASE_DISTRIBUTION=name +rem set RELEASE_NODE=<%= @release.name %> +``` + +Once again, set the `RELEASE_DISTRIBUTION` variable to `name` and you are good to go! + +## VM arguments + +The `rel/vm.args.eex` allows you to specify low-level flags that control how the Erlang VM and its runtime operate. You specify entries as if you were specifying arguments in the command line with code comments also supported. Here is the default generated file: + + ## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html + ## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here + + ## Increase number of concurrent ports/sockets + ##+Q 65536 + + ## Tweak GC to run more often + ##-env ERL_FULLSWEEP_AFTER 10 + +You can see [a complete list of VM arguments and flags in the Erlang documentation](http://www.erlang.org/doc/man/erl.html). + +## Summing up + +Throughout the guide, we have built a very simple distributed key-value store as an opportunity to explore many constructs like generic servers, supervisors, tasks, agents, applications and more. Not only that, we have written tests for the whole application, got familiar with ExUnit, and learned how to use the Mix build tool to accomplish a wide range of tasks. + +If you are looking for a distributed key-value store to use in production, you should definitely look into [Riak](http://riak.com/products/riak-kv/), which also runs in the Erlang VM. In Riak, the buckets are replicated and stored across several nodes to avoid data loss. + +Of course, Elixir can be used for much more than distributed key-value stores. Embedded systems, data-processing and data-ingestion, web applications, audio/video streaming systems, machine learning, and others are many of the different domains Elixir excels at. We hope this guide has prepared you to explore any of those domains or any future domain you may desire to bring Elixir into. + +Happy coding! diff --git a/lib/elixir/pages/mix-and-otp/supervisor-and-application.md b/lib/elixir/pages/mix-and-otp/supervisor-and-application.md index 8c5839b2022..b3b51f5d090 100644 --- a/lib/elixir/pages/mix-and-otp/supervisor-and-application.md +++ b/lib/elixir/pages/mix-and-otp/supervisor-and-application.md @@ -3,142 +3,65 @@ SPDX-FileCopyrightText: 2021 The Elixir Team --> -# Supervision trees and applications +# Registries and supervision trees -In the previous chapter about `GenServer`, we implemented `KV.Registry` to manage buckets. At some point, we started monitoring buckets so we were able to take action whenever a `KV.Bucket` crashed. Although the change was relatively small, it introduced a question which is frequently asked by Elixir developers: what happens when something fails? +In the [previous chapter](agents.md), we used agents to represent our buckets. In the [introduction to mix](introduction-to-mix.md), we specified we would like to name each bucket so we can do the following: -Before we added monitoring, if a bucket crashed, the registry would forever point to a bucket that no longer exists. If a user tried to read or write to the crashed bucket, it would fail. Any attempt at creating a new bucket with the same name would just return the PID of the crashed bucket. In other words, that registry entry for that bucket would forever be in a bad state. Once we added monitoring, the registry automatically removes the entry for the crashed bucket. Trying to lookup the crashed bucket now (correctly) says the bucket does not exist and a user of the system can successfully create a new one if desired. +```text +CREATE shopping +OK -In practice, we are not expecting the processes working as buckets to fail. But, if it does happen, for whatever reason, we can rest assured that our system will continue to work as intended. +PUT shopping milk 1 +OK -If you have prior programming experience, you may be wondering: "could we just guarantee the bucket does not crash in the first place?". As we will see, Elixir developers tend to refer to those practices as "defensive programming". That's because a live production system has dozens of different reasons why something can go wrong. The disk can fail, memory can be corrupted, bugs, the network may stop working for a second, etc. If we were to write software that attempted to protect or circumvent all of those errors, we would spend more time handling failures than writing our own software! - -Therefore, an Elixir developer prefers to "let it crash" or "fail fast". And one of the most common ways we can recover from a failure is by restarting whatever part of the system crashed. - -For example, imagine your computer, router, printer, or whatever device is not working properly. How often do you fix it by restarting it? Once we restart the device, we reset the device back to its initial state, which is well-tested and guaranteed to work. In Elixir, we apply this same approach to software: whenever a process crashes, we start a new process to perform the same job as the crashed process. - -In Elixir, this is done by a Supervisor. A Supervisor is a process that supervises other processes and restarts them whenever they crash. To do so, Supervisors manage the whole life cycle of any supervised processes, including startup and shutdown. - -In this chapter, we will learn how to put those concepts into practice by supervising the `KV.Registry` process. After all, if something goes wrong with the registry, the whole registry is lost and no bucket could ever be found! To address this, we will define a `KV.Supervisor` module that guarantees that our `KV.Registry` is up and running at any given moment. - -At the end of the chapter, we will also talk about Applications. As we will see, Mix has been packaging all of our code into an application, and we will learn how to customize our application to guarantee that our Supervisor and the Registry are up and running whenever our system starts. - -## Our first supervisor - -A supervisor is a process which supervises other processes, which we refer to as child processes. The act of supervising a process includes three distinct responsibilities. The first one is to start child processes. Once a child process is running, the supervisor may restart a child process, either because it terminated abnormally or because a certain condition was reached. For example, a supervisor may restart all children if any child dies. Finally, a supervisor is also responsible for shutting down the child processes when the system is shutting down. Please see the `Supervisor` module for a more in-depth discussion. - -Creating a supervisor is not much different from creating a GenServer. We are going to define a module named `KV.Supervisor`, which will use the Supervisor behaviour, inside the `lib/kv/supervisor.ex` file: - -```elixir -defmodule KV.Supervisor do - use Supervisor - - def start_link(opts) do - Supervisor.start_link(__MODULE__, :ok, opts) - end - - @impl true - def init(:ok) do - children = [ - KV.Registry - ] - - Supervisor.init(children, strategy: :one_for_one) - end -end +GET shopping milk +1 +OK ``` -Our supervisor has a single child so far: `KV.Registry`. After we define a list of children, we call `Supervisor.init/2`, passing the children and the supervision strategy. - -The supervision strategy dictates what happens when one of the children crashes. `:one_for_one` means that if a child dies, it will be the only one restarted. Since we have only one child now, that's all we need. The `Supervisor` behaviour supports several strategies, which we will discuss in this chapter. +In the example session above we interacted with the "shopping" bucket by referencing its name. Therefore, an important feature in our key-value store is to give names to processes. -Once the supervisor starts, it will traverse the list of children and it will invoke the `child_spec/1` function on each module. - -The `child_spec/1` function returns the child specification which describes how to start the process, if the process is a worker or a supervisor, if the process is temporary, transient or permanent and so on. The `child_spec/1` function is automatically defined when we `use Agent`, `use GenServer`, `use Supervisor`, etc. Let's give it a try in the terminal with `iex -S mix`: +We have also learned in the previous chapter we can already name our buckets. For example: ```elixir -iex> KV.Registry.child_spec([]) -%{id: KV.Registry, start: {KV.Registry, :start_link, [[]]}} -``` - -We will learn those details as we move forward on this guide. If you would rather peek ahead, check the `Supervisor` docs. - -After the supervisor retrieves all child specifications, it proceeds to start its children one by one, in the order they were defined, using the information in the `:start` key in the child specification. For our current specification, it will call `KV.Registry.start_link([])`. - -Let's take the supervisor for a spin: - -```elixir -iex> {:ok, sup} = KV.Supervisor.start_link([]) -{:ok, #PID<0.148.0>} -iex> Supervisor.which_children(sup) -[{KV.Registry, #PID<0.150.0>, :worker, [KV.Registry]}] -``` - -So far we have started the supervisor and listed its children. Once the supervisor started, it also started all of its children. - -What happens if we intentionally crash the registry started by the supervisor? Let's do so by sending it a bad input on `call`: - -```elixir -iex> [{_, registry, _, _}] = Supervisor.which_children(sup) -[{KV.Registry, #PID<0.150.0>, :worker, [KV.Registry]}] -iex> GenServer.call(registry, :bad_input) -08:52:57.311 [error] GenServer #PID<0.150.0> terminating -** (FunctionClauseError) no function clause matching in KV.Registry.handle_call/3 -iex> Supervisor.which_children(sup) -[{KV.Registry, #PID<0.157.0>, :worker, [KV.Registry]}] -``` - -Notice how the supervisor automatically started a new registry, with a new PID, in place of the first one once we caused it to crash due to a bad input. - -In the previous chapters, we have always started processes directly. For example, we would call `KV.Registry.start_link([])`, which would return `{:ok, pid}`, and that would allow us to interact with the registry via its `pid`. Now that processes are started by the supervisor, we have to directly ask the supervisor who its children are, and fetch the PID from the returned list of children. In practice, doing so every time would be very expensive. To address this, we often give names to processes, allowing them to be uniquely identified in a single machine from anywhere in our code. - -Let's learn how to do that. - -## Naming processes - -While our application will have many buckets, it will only have a single registry. Therefore, whenever we start the registry, we want to give it a unique name so we can reach out to it from anywhere. We do so by passing a `:name` option to `KV.Registry.start_link/1`. - -Let's slightly change our children definition (in `KV.Supervisor.init/1`) to be a list of tuples instead of a list of atoms: - -```elixir - def init(:ok) do - children = [ - {KV.Registry, name: KV.Registry} - ] +iex> KV.Bucket.start_link(name: :shopping) +{:ok, #PID<0.43.0>} +iex> KV.Bucket.put(:shopping, "milk", 1) +:ok +iex> KV.Bucket.get(:shopping, "milk") +1 ``` -With this in place, the supervisor will now start `KV.Registry` by calling `KV.Registry.start_link(name: KV.Registry)`. +However, naming dynamic processes with atoms is a terrible idea! If we use atoms, we would need to convert the bucket name (often received from an external client) to atoms, and **we should never convert user input to atoms**. This is because atoms are not garbage collected. Once an atom is created, it is never reclaimed. Generating atoms from user input would mean the user can inject enough different names to exhaust our system memory! -If you revisit the `KV.Registry.start_link/1` implementation, you will remember it simply passes the options to GenServer: +In practice, it is more likely you will reach the Erlang VM limit for the maximum number of atoms before you run out of memory, which will bring your system down regardless. -```elixir - def start_link(opts) do - GenServer.start_link(__MODULE__, :ok, opts) - end -``` +Luckily, Elixir (and Erlang) comes with built-in abstractions for naming processes, called name registries, each with different trade-offs which we will explore throughout these guides. -which in turn will register the process with the given name. The `:name` option expects an atom for locally named processes (locally named means it is available to this machine — there are other options, which we won't discuss here). Since module identifiers are atoms (try `i(KV.Registry)` in IEx), we can name a process after the module that implements it, provided there is only one process for that name. This helps when debugging and introspecting the system. +## Local, decentralized, and scalable registry -Let's give the updated supervisor a try inside `iex -S mix`: +Elixir ships with a single-node process registry module aptly called `Registry`. Its main feature is that you can use any Elixir value to name a process, not only atoms. Let's take it for a spin in `iex`: ```elixir -iex> KV.Supervisor.start_link([]) -{:ok, #PID<0.66.0>} -iex> KV.Registry.create(KV.Registry, "shopping") +iex> Registry.start_link(name: KV, keys: :unique) +iex> name = {:via, Registry, {KV, "shopping"}} +iex> KV.Bucket.start_link(name: name) +{:ok, #PID<0.43.0>} +iex> KV.Bucket.put(name, "milk", 1) :ok -iex> KV.Registry.lookup(KV.Registry, "shopping") -{:ok, #PID<0.70.0>} +iex> KV.Bucket.get(name, "milk") +1 ``` -This time the supervisor started a named registry, allowing us to create buckets without having to explicitly fetch the PID from the supervisor. You should also know how to make the registry crash again, without looking up its PID: give it a try. - -> At this point, you may be wondering: should you also locally name bucket processes? Remember buckets are started dynamically based on user input. Since local names MUST be atoms, we would have to dynamically create atoms, which is a bad idea since once an atom is defined, it is never erased nor garbage collected. This means that, if we create atoms dynamically based on user input, we will eventually run out of memory (or to be more precise, the VM will crash because it imposes a hard limit on the number of atoms). This limitation is precisely why we created our own registry (or why one would use Elixir's built-in `Registry` module). +As you can see, instead of passing an atom to the `:name` option, we pass a tuple of shape `{:via, registry_module, {registry_name, process_name}}`, and everything just worked. You could have used anything as the `process_name`, even an integer or a map! That's because all of Elixir built-in behaviours, agents, supervisors, tasks, etc, are compatible with naming registries, as long as you pass them using the "via" tuple format. -We are getting closer and closer to a fully working system. The supervisor automatically starts the registry. But how can we automatically start the supervisor whenever our system starts? To answer this question, let's talk about applications. +Therefore, all we need to do to name our buckets is to start a `Registry`, using `Registry.start_link/1`. But you may be wondering, where exactly should we place that? ## Understanding applications -We have been working inside an application this entire time. Every time we changed a file and ran `mix compile`, we could see a `Generated kv app` message in the compilation output. +Every Elixir project is an application. Elixir itself is defined in an application named `:elixir`. The `ExUnit.Case` module is part of the `:ex_unit` application. And so forth. + +In fact, we have been working inside an application this entire time. Every time we changed a file and ran `mix compile`, we could see a `Generated kv app` message in the compilation output. We can find the generated `.app` file at `_build/dev/lib/kv/ebin/kv.app`. Let's have a look at its contents: @@ -146,8 +69,7 @@ We can find the generated `.app` file at `_build/dev/lib/kv/ebin/kv.app`. Let's {application,kv, [{applications,[kernel,stdlib,elixir,logger]}, {description,"kv"}, - {modules,['Elixir.KV','Elixir.KV.Bucket','Elixir.KV.Registry', - 'Elixir.KV.Supervisor']}, + {modules,['Elixir.KV','Elixir.KV.Bucket']}, {registered,[]}, {vsn,"0.1.0"}]}. ``` @@ -156,7 +78,7 @@ This file contains Erlang terms (written using Erlang syntax). Even though we ar > The `logger` application ships as part of Elixir. We stated that our application needs it by specifying it in the `:extra_applications` list in `mix.exs`. See the [official documentation](`Logger`) for more information. -In a nutshell, an application consists of all the modules defined in the `.app` file, including the `.app` file itself. An application has generally only two directories: `ebin`, for Elixir artifacts, such as `.beam` and `.app` files, and `priv`, with any other artifact or asset you may need in your application. +In a nutshell, an application consists of all the modules defined in the `.app` file, including the `.app` file itself. The application itself is located at the `_build/dev/lib/kv` folder and typically has only two directories: `ebin`, for Elixir artifacts, such as `.beam` and `.app` files, and `priv`, with any other artifact or asset you may need in your application. Although Mix generates and maintains the `.app` file for us, we can customize its contents by adding new entries to the `application/0` function inside the `mix.exs` project file. We are going to do our first customization soon. @@ -196,9 +118,9 @@ iex> Application.ensure_all_started(:kv) {:ok, [:logger, :kv]} ``` -In practice, our tools always start our applications for us, but there is an API available if you need fine-grained control. +In practice, our tools always start our applications for us, and you don't have to worry about the above, but it is good to know how it all works behind the scenes. -## The application callback +### The application callback Whenever we invoke `iex -S mix`, Mix automatically starts our application by calling `Application.start(:kv)`. But can we customize what happens when our application starts? As a matter of fact, we can! To do so, we define an application callback. @@ -213,50 +135,119 @@ The first step is to tell our application definition (for example, our `.app` fi end ``` -The `:mod` option specifies the "application callback module", followed by the arguments to be passed on application start. The application callback module can be any module that implements the `Application` behaviour. +The `:mod` option specifies the "application callback module", followed by the arguments to be passed on application start. The application callback module can be any module that invokes `use Application`. Since we have specified `KV` as the module callback, let's change the `KV` module defined in `lib/kv.ex` to the following: -To implement the `Application` behaviour, we have to `use Application` and define a `start/2` function. The goal of `start/2` is to start a supervisor, which will then start any child services or execute any other code our application may need. Let's use this opportunity to start the `KV.Supervisor` we have implemented earlier in this chapter. +```elixir +defmodule KV do + use Application +end +``` -Since we have specified `KV` as the module callback, let's change the `KV` module defined in `lib/kv.ex` to implement a `start/2` function: +Now run `mix test` and you will see a couple things happening. First of all, you will get a compilation warning: + +```text +Compiling 1 file (.ex) + warning: function start/2 required by behaviour Application is not implemented (in module KV) + │ + 1 │ defmodule KV do + │ ~~~~~~~~~~~~~~~ + │ + └─ lib/kv.ex:1: KV (module) +``` + +This warning is telling us that `use Application` actually defines a behaviour, which expects us to implement to a `start/2` function in our `KV` module. + +Then our application does not even boot because the `start/2` function is not actually implemented: + +```text +18:29:39.109 [notice] Application kv exited: exited in: KV.start(:normal, []) + ** (EXIT) an exception was raised: + ** (UndefinedFunctionError) function KV.start/2 is undefined or private +``` + +Implementing the `start/2` callback is relatively straight-forward, all we need to do is to start a supervision tree, and return `{:ok, root_supervisor_pid}`. The `Supervisor.start_link/2` function does precisely that, it only expects a list of children and the supervision strategy. Let's just pass an empty list of children for now: ```elixir defmodule KV do use Application + # The @impl true annotation says we are implementing a callback @impl true def start(_type, _args) do - # Although we don't use the supervisor name below directly, - # it can be useful when debugging or introspecting the system. - KV.Supervisor.start_link(name: KV.Supervisor) + Supervisor.start_link([], strategy: :one_for_one) end end ``` -> Please note that by doing this, we are breaking the boilerplate test case which tested the `hello` function in `KV`. You can simply remove that test case. +Now run `mix test` again and our app should boot but we should see one failure. When we changed the `KV` module, we broke the boilerplate test case which tested the `KV.hello/0` function. You can simply remove that test case and we are back to a green suite. + +We wrote very little code but we did something incredibly powerful. We now have a function, `KV.start/2` that is invoked whenever your application starts. This gives us the perfect place to start our key-value registry. The `Application` module also allows us to define a `stop/1` callback and other funtionality. You can check the `Application` and `Supervisor` modules for extensive documentation on their uses. + +Let's finally start our registry. + +## Supervision trees -When we `use Application`, we may define a couple of functions, similar to when we used `Supervisor` or `GenServer`. This time we only had to define a `start/2` function. The `Application` behaviour also has a `stop/1` callback, but it is rarely used in practice. You can check the documentation for more information. +Now that we have the `start/2` callback, we can finally go ahead and start our registry. You may be tempted to do it like this: -Now that you have defined an application callback which starts our supervisor, we expect the `KV.Registry` process to be up and running as soon as we start `iex -S mix`. Let's give it another try: +```elixir + def start(_type, _args) do + Registry.start_link(name: KV, keys: :unique) + Supervisor.start_link([], strategy: :one_for_one) + end +``` + +However, this would not be a good idea. In Elixir, we typically start processes inside supervision trees. In fact, we rarely use the `start_link` functions to start processes (except at the root of the supervision tree itself). Instead, do this: ```elixir -iex> KV.Registry.create(KV.Registry, "shopping") + def start(_type, _args) do + children = [ + {Registry, name: KV, keys: :unique} + ] + + Supervisor.start_link(children, strategy: :one_for_one) + end +``` + +A supervisor receives one or more child specifications that tell it exactly how to start each child. A child specification is typically represented by a `{module, options}` pair, as shown above, and often as simply the module name. Sometimes, these children are supervisors themselves, giving us supervision trees. + +Let's take it for a spin and see if we can indeed name our buckets using our new registry. Let's make sure to start a new `iex -S mix` (`recompile()` is not enough, as it does not reload your supervision tree) and then: + +```iex +iex> name = {:via, Registry, {KV, "shopping"}} +iex> KV.Bucket.start_link(name: name) +{:ok, #PID<0.43.0>} +iex> KV.Bucket.put(name, "milk", 1) :ok -iex> KV.Registry.lookup(KV.Registry, "shopping") -{:ok, #PID<0.88.0>} +iex> KV.Bucket.get(name, "milk") +1 ``` -Let's recap what is happening. Whenever we invoke `iex -S mix`, it automatically starts our application by calling `Application.start(:kv)`, which then invokes the application callback. The application callback's job is to start a **supervision tree**. Right now, our supervisor has a single child named `KV.Registry`, started with name `KV.Registry`. Our supervisor could have other children, and some of these children could be their own supervisors with their own children, leading to the so-called supervision trees. +Perfect, this time we didn't need to start the registry inside `iex`, as it was started as part of the application itself. + +By starting processes inside supervisors, we gain important properties such as: + + * **Introspection**: for each application, you can fully introspect and visualize each process in its supervision tree, its memory usage, message queue, etc + + * **Resilience**: when a process fails for an unexpected reason, its supervisor controls if and how those processes should be restarted, leading to self-healing systems + + * **Graceful shutdown**: when your application is shutting down, the children of a supervision tree are terminated in the opposite order they were started, leading to graceful shutdowns ## Projects or applications? -Mix makes a distinction between projects and applications. Based on the contents of our `mix.exs` file, we would say we have a Mix project that defines the `:kv` application. As we will see in later chapters, there are projects that don't define any application. +Mix makes a distinction between projects and applications. Based on the contents of our `mix.exs` file, we would say we have a Mix project that defines the `:kv` application. When we say "project" you should think about Mix. Mix is the tool that manages your project. It knows how to compile your project, test your project and more. It also knows how to compile and start the application relevant to your project. When we talk about applications, we talk about OTP. Applications are the entities that are started and stopped as a whole by the runtime. You can learn more about applications and how they relate to booting and shutting down of your system as a whole in the documentation for the `Application` module. -## Next steps +## Summing up + +We learned important concepts in this chapter: + + * Naming registries allow us to find processes in a given machine (or, as we will see in the future, even in a cluster) + + * Applications bundle our modules, its dependencies, and how code starts and stops -Although this chapter was the first time we implemented a supervisor, it was not the first time we used one! In the previous chapter, when we used `start_supervised!` to start the registry during our tests, `ExUnit` started the registry under a supervisor managed by the ExUnit framework itself. By defining our own supervisor, we provide more structure on how we initialize, shutdown and supervise processes in our applications, aligning our production code and tests with best practices. + * Processes are started as part of supervisors for introspection and fault-tolerance -But we are not done yet. So far we are supervising the registry but our application is also starting buckets. Since buckets are started dynamically, we can use a special type of supervisor called `DynamicSupervisor`, which is optimized to handle such scenarios. Let's explore it next. +In the next chapter, we will tie it all up by making sure all our buckets are named and supervised. To do so, we will learn a new tool called dynamic supervisors. diff --git a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md index 8ae44d38962..fe037ee3dcc 100644 --- a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md +++ b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md @@ -5,7 +5,7 @@ # Task and gen_tcp -In this chapter, we are going to learn how to use Erlang's [`:gen_tcp` module](`:gen_tcp`) to serve requests. This provides a great opportunity to explore Elixir's `Task` module. In future chapters, we will expand our server so that it can actually serve the commands. +In this chapter, we are going to learn how to use Erlang's [`:gen_tcp` module](`:gen_tcp`) to serve requests. This provides a great opportunity to explore Elixir's `Task` module. In future chapters, we will expand our server so that it can actually interact with buckets. ## Echo server @@ -17,10 +17,10 @@ A TCP server, in broad strokes, performs the following steps: 2. Waits for a client connection on that port and accepts it 3. Reads the client request and writes a response back -Let's implement those steps. Move to the `apps/kv_server` application, open up `lib/kv_server.ex`, and add the following functions: +Let's implement those steps. Create a new `lib/kv/server.ex` and add the following functions: ```elixir -defmodule KVServer do +defmodule KV.Server do require Logger def accept(port) do @@ -62,7 +62,7 @@ defmodule KVServer do end ``` -We are going to start our server by calling `KVServer.accept(4040)`, where 4040 is the port. The first step in `accept/1` is to listen to the port until the socket becomes available and then call `loop_acceptor/1`. `loop_acceptor/1` is a loop accepting client connections. For each accepted connection, we call `serve/1`. +We are going to start our server by calling `KV.Server.accept(4040)`, where 4040 is the port. The first step in `accept/1` is to listen to the port until the socket becomes available and then call `loop_acceptor/1`. `loop_acceptor/1` is a loop accepting client connections. For each accepted connection, we call `serve/1`. `serve/1` is another loop that reads a line from the socket and writes those lines back to the socket. Note that the `serve/1` function uses the pipe operator `|>/2` to express this flow of operations. The pipe operator evaluates the left side and passes its result as the first argument to the function on the right side. The example above: @@ -85,7 +85,7 @@ This is pretty much all we need to implement our echo server. Let's give it a tr Start an IEx session inside the `kv_server` application with `iex -S mix`. Inside IEx, run: ```elixir -iex> KVServer.accept(4040) +iex> KV.Server.accept(4040) ``` The server is now running, and you will even notice the console is blocked. Let's use [a `telnet` client](https://en.wikipedia.org/wiki/Telnet) to access our server. There are clients available on most operating systems, and their command lines are generally similar: @@ -109,10 +109,12 @@ My particular telnet client can be exited by typing `ctrl + ]`, typing `quit`, a Once you exit the telnet client, you will likely see an error in the IEx session: - ** (MatchError) no match of right hand side value: {:error, :closed} - (kv_server) lib/kv_server.ex:45: KVServer.read_line/1 - (kv_server) lib/kv_server.ex:37: KVServer.serve/1 - (kv_server) lib/kv_server.ex:30: KVServer.loop_acceptor/1 +```text +** (MatchError) no match of right hand side value: {:error, :closed} + (kv) lib/kv/server.ex:45: KV.Server.read_line/1 + (kv) lib/kv/server.ex:37: KV.Server.serve/1 + (kv) lib/kv/server.ex:30: KV.Server.loop_acceptor/1 +``` That's because we were expecting data from `:gen_tcp.recv/2` but the client closed the connection. We need to handle such cases better in future revisions of our server. @@ -120,36 +122,25 @@ For now, there is a more important bug we need to fix: what happens if our TCP a ## Tasks -We have learned about agents, generic servers, and supervisors. They are all meant to work with multiple messages or manage state. But what do we use when we only need to execute some task and that is it? - -The `Task` module provides this functionality exactly. For example, it has a `Task.start_link/1` function that receives an anonymous function and executes it inside a new process that will be part of a supervision tree. +Whenever you have an existing function and you simply want to execute it when your application starts, the `Task` module is exactly you need. For example, it has a `Task.start_link/1` function that receives an anonymous function and executes it inside a new process that will be part of a supervision tree. -Let's give it a try. Open up `lib/kv_server/application.ex`, and let's change the supervisor in the `start/2` function to the following: +Let's give it a try. Open up `lib/kv.ex` and let's add a new child: ```elixir def start(_type, _args) do children = [ - {Task, fn -> KVServer.accept(4040) end} + {Registry, name: KV, keys: :unique}, + {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}, + {Task, fn -> KV.Server.accept(4040) end} ] - opts = [strategy: :one_for_one, name: KVServer.Supervisor] - Supervisor.start_link(children, opts) + Supervisor.start_link(children, strategy: :one_for_one) end ``` -As usual, we've passed a two-element tuple as a child specification, which in turn will invoke `Task.start_link/1`. - -With this change, we are saying that we want to run `KVServer.accept(4040)` as a task. We are hardcoding the port for now but this could be changed in a few ways, for example, by reading the port out of the system environment when starting the application: - -```elixir -port = String.to_integer(System.get_env("PORT") || "4040") -# ... -{Task, fn -> KVServer.accept(port) end} -``` +With this change, we are saying that we want to run `KV.Server.accept(4040)` as a task. We are hardcoding the port for now but we will make this a configuration in later chapters. As usual, we've passed a two-element tuple as a child specification, which in turn will invoke `Task.start_link/1`. -Insert these changes in your code and now you may start your application using the following command `PORT=4321 mix run --no-halt`, notice how we are passing the port as a variable, but still defaults to `4040` if none is given. - -Now that the server is part of the supervision tree, it should start automatically when we run the application. Start your server, now passing the port, and once again use the `telnet` client to make sure that everything still works: +Now that the server is part of the supervision tree, it should start automatically when we run the application. Run `iex -S mix` to boot the app and use the `telnet` client to make sure that everything still works: ```console $ telnet 127.0.0.1 4321 @@ -178,7 +169,7 @@ HELLOOOOOO? It doesn't seem to work at all. That's because we are serving requests in the same process that are accepting connections. When one client is connected, we can't accept another client. -## Task supervisor +## Adding (flawed) concurrency In order to make our server handle simultaneous connections, we need to have one process working as an acceptor that spawns other processes to serve requests. One solution would be to change: @@ -195,43 +186,49 @@ to also use `Task.start_link/1`: ```elixir defp loop_acceptor(socket) do {:ok, client} = :gen_tcp.accept(socket) - Task.start_link(fn -> serve(client) end) + {:ok, pid} = Task.start_link(fn -> serve(client) end) + :ok = :gen_tcp.controlling_process(client, pid) loop_acceptor(socket) end ``` -We are starting a linked Task directly from the acceptor process. But we've already made this mistake once. Do you remember? +In the new acceptor loop, we are starting a new task every time there is a new client. Now, if you attempt to connect two clients at the same time, it should work! + +Or does it? For example, what happens when you exit one telnet session? The other session should crash! The reason of this crash is two fold: + +1. We have a bug in our server where we don't expect `:gen_tcp.recv/2` to return an `{:error, :closed}` tuple + +2. Because each server task is linked to the acceptor process, if one task crashes, the acceptor process will also crash, taking down all other tasks and clients -This is similar to the mistake we made when we called `KV.Bucket.start_link/1` straight from the registry. That meant a failure in any bucket would bring the whole registry down. +An important rule thumb throughout this guide is to always start processes as children of supervisors. The code above is an excellent example of what happens when we don't. If we don't isolate the different parts of our systems, failures can now cascade through our system, as it would happen in other languages. -The code above would have the same flaw: if we link the `serve(client)` task to the acceptor, a crash when serving a request would bring the acceptor, and consequently all other connections, down. +To fix this, we could use a `DynamicSupervisor`, but tasks also provide a specialized `Task.Supervisor` which has better ergonomics and is optimized for supervising tasks themselves. Let's give it a try. -We fixed the issue for the registry by using a simple one for one supervisor. We are going to use the same tactic here, except that this pattern is so common with tasks that `Task` already comes with a solution: a simple one for one supervisor that starts temporary tasks as part of our supervision tree. +## Adding a task supervisor -Let's change `start/2` once again, to add a supervisor to our tree: +Let's change `start/2` in `lib/kv.ex` once more, to add the task supervisor to our tree: ```elixir def start(_type, _args) do - port = String.to_integer(System.get_env("PORT") || "4040") - children = [ - {Task.Supervisor, name: KVServer.TaskSupervisor}, - {Task, fn -> KVServer.accept(port) end} + {Registry, name: KV, keys: :unique}, + {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}, + {Task.Supervisor, name: KV.ServerSupervisor}, + {Task, fn -> KV.Server.accept(4040) end} ] - opts = [strategy: :one_for_one, name: KVServer.Supervisor] - Supervisor.start_link(children, opts) + Supervisor.start_link(children, strategy: :one_for_one) end ``` -We'll now start a `Task.Supervisor` process with name `KVServer.TaskSupervisor`. Remember, since the acceptor task depends on this supervisor, the supervisor must be started first. +We'll now start a `Task.Supervisor` process with name `KV.TaskSupervisor`. Keep in mind that the order children are started matters. For example, the acceptor must come last because, if it comes first, it means our application can start accepting requests before the `Task.Supervisor` is running or before we can locate buckets. Shutting down an application will also stop the children in reverse order, guaranteeing a clean termination. Now we need to change `loop_acceptor/1` to use `Task.Supervisor` to serve each request: ```elixir defp loop_acceptor(socket) do {:ok, client} = :gen_tcp.accept(socket) - {:ok, pid} = Task.Supervisor.start_child(KVServer.TaskSupervisor, fn -> serve(client) end) + {:ok, pid} = Task.Supervisor.start_child(KV.BucketSupervisor, fn -> serve(client) end) :ok = :gen_tcp.controlling_process(client, pid) loop_acceptor(socket) end @@ -239,76 +236,72 @@ end You might notice that we added a line, `:ok = :gen_tcp.controlling_process(client, pid)`. This makes the child process the "controlling process" of the `client` socket. If we didn't do this, the acceptor would bring down all the clients if it crashed because sockets would be tied to the process that accepted them (which is the default behavior). -Start a new server with `PORT=4040 mix run --no-halt` and we can now open up many concurrent telnet clients. You will also notice that quitting a client does not bring the acceptor down. Excellent! +Now start a new server with `iex -S mix` and try to open up many concurrent telnet clients. You will notice that quitting a client does not bring the acceptor down, even though we haven't fixed the bug in `:gen_tcp.recv/2` yet (which we will address in the next chapter). Excellent! -Here is the full echo server implementation: +## Restart strategies -```elixir -defmodule KVServer do - require Logger +There is one important topic we haven't explored yet with the necessary depth. What happens when a supervised process crashes? - @doc """ - Starts accepting connections on the given `port`. - """ - def accept(port) do - {:ok, socket} = :gen_tcp.listen(port, - [:binary, packet: :line, active: false, reuseaddr: true]) - Logger.info "Accepting connections on port #{port}" - loop_acceptor(socket) - end +In the previous chapter, when we started a bucket and killed it, the supervisor automatically started one in its place: - defp loop_acceptor(socket) do - {:ok, client} = :gen_tcp.accept(socket) - {:ok, pid} = Task.Supervisor.start_child(KVServer.TaskSupervisor, fn -> serve(client) end) - :ok = :gen_tcp.controlling_process(client, pid) - loop_acceptor(socket) - end +```elixir +iex> children = [{KV.Bucket, name: :shopping}] +iex> Supervisor.start_link(children, strategy: :one_for_one) +iex> KV.Bucket.put(:shopping, "milk", 1) +iex> pid = Process.whereis(:shopping) +#PID<0.48.0> +iex> Process.exit(pid, :kill) +true +iex> Process.whereis(:shopping) +#PID<0.50.0> +``` - defp serve(socket) do - socket - |> read_line() - |> write_line(socket) +What exactly happens when a process terminates is part of its child specification. For `KV.Bucket`, we have this: - serve(socket) - end +```elixir +iex> KV.Bucket.child_spec([]) +%{id: KV.Bucket, start: {KV.Bucket, :start_link, [[]]}} +``` - defp read_line(socket) do - {:ok, data} = :gen_tcp.recv(socket, 0) - data - end +However, for tasks, we have this: - defp write_line(line, socket) do - :gen_tcp.send(socket, line) - end -end +```elixir +iex> Task.child_spec(fn -> :ok end) +%{ + id: Task, + restart: :temporary, + start: {Task, :start_link, [#Function<43.39164016/0 in :erl_eval.expr/6>]} +} ``` -Since we have changed the supervisor specification, we need to ask: is our supervision strategy still correct? +Notice that a task says `:restart` is `:temporary`. `KV.Bucket` says nothing, which means it defaults to `:permanent`. `:temporary` means that a process is never restarted, regardless of why it crashed. `:permanent` means a process is always restarted, regardless of the exit reason. There is also `:transient`, which means it won't be restarted as long as it terminates successfully. + +Now we must ask ourselves, are those the correct settings? -In this case, the answer is yes: if the acceptor crashes, there is no need to crash the existing connections. On the other hand, if the task supervisor crashes, there is no need to crash the acceptor too. +For `KV.Bucket`, using `:permanent` seem logical, as should not request the user to recreate a bucket they have previous created. Although currently we would lose the bucket data, in actual system we would add mechanisms to recover it on initialization. However, for tasks, we have used them in two opposing ways in this chapter, which means at least one of them is wrong. -However, there is still one concern left, which are the restart strategies. Tasks, by default, have the `:restart` value set to `:temporary`, which means they are not restarted. This is an excellent default for the connections started via the `Task.Supervisor`, as it makes no sense to restart a failed connection, but it is a bad choice for the acceptor. If the acceptor crashes, we want to bring the acceptor up and running again. +We use a task to start the acceptor. The acceptor is a critical component of our infrastructure. If it crashes, it means we won't accept further requests, and our server would then be useless as no one can connect to it. On the other hand, we also use `Task.Supervisor` to start tasks that deal with each connection. In this case, restarting may not be useful at all, given the reason we crashed could just as well be a connection issue, and attempting to restart over the same connection would lead to further failures. -Let's fix this. We know that for a child of shape `{Task, fun}`, Elixir will invoke `Task.child_spec(fun)` to retrieve the underlying child specification. Therefore, one might imagine that to change the `{Task, fun}` specification to have a `:restart` of `:permanent`, we would need to change the `Task` module. However, that's impossible to do, as the `Task` module is defined as part of Elixir's standard library (and even if it was possible, it is unlikely it would be a good idea). -Luckily, this can be done by using `Supervisor.child_spec/2`, which allows us to configure a child specification with new values. Let's rewrite `start/2` in `KVServer.Application` once more: +Therefore, we want the acceptor to actually run in `:permanent` mode, while we preserve the `Task.Supervisor` as `:temporary`. Luckily Elixir has an API that allows us to change an existing child specification, which we use below. + +Let's change `start/2` in `lib/kv.ex` once more to the following: ```elixir def start(_type, _args) do - port = String.to_integer(System.get_env("PORT") || "4040") - children = [ - {Task.Supervisor, name: KVServer.TaskSupervisor}, - Supervisor.child_spec({Task, fn -> KVServer.accept(port) end}, restart: :permanent) + {Registry, name: KV, keys: :unique}, + {DynamicSupervisor, name: KV.BucketSupervisor, strategy: :one_for_one}, + {Task.Supervisor, name: KV.ServerSupervisor}, + Supervisor.child_spec({Task, fn -> KV.Server.accept(4040) end}, restart: :permanent) ] - opts = [strategy: :one_for_one, name: KVServer.Supervisor] - Supervisor.start_link(children, opts) + Supervisor.start_link(children, strategy: :one_for_one) end ``` Now we have an always running acceptor that starts temporary task processes under an always running task supervisor. -## Wrapping up +## Leveraging the ecosystem In this chapter, we implemented a basic TCP acceptor while exploring concurrency and fault-tolerance. Our acceptor can manage concurrent connections, but it is still not ready for production. Production-ready TCP servers run a pool of acceptors, each with their own supervisor. Elixir's `PartitionSupervisor` might be used to partition and scale the acceptor, but it is out of scope for this guide. In practice, you will use existing packages tailored for this use-case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir). diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index 74571ac0f8d..1676a01078e 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -49,15 +49,13 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/references/unicode-syntax.md", "lib/elixir/pages/mix-and-otp/introduction-to-mix.md", "lib/elixir/pages/mix-and-otp/agents.md", - "lib/elixir/pages/mix-and-otp/genservers.md", "lib/elixir/pages/mix-and-otp/supervisor-and-application.md", "lib/elixir/pages/mix-and-otp/dynamic-supervisor.md", - "lib/elixir/pages/mix-and-otp/erlang-term-storage.md", - "lib/elixir/pages/mix-and-otp/dependencies-and-umbrella-projects.md", "lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md", "lib/elixir/pages/mix-and-otp/docs-tests-and-with.md", - "lib/elixir/pages/mix-and-otp/distributed-tasks.md", - "lib/elixir/pages/mix-and-otp/config-and-releases.md", + "lib/elixir/pages/mix-and-otp/config-and-distribution.md", + "lib/elixir/pages/mix-and-otp/genservers.md", + "lib/elixir/pages/mix-and-otp/releases.md", "lib/elixir/pages/meta-programming/quote-and-unquote.md", "lib/elixir/pages/meta-programming/macros.md", "lib/elixir/pages/meta-programming/domain-specific-languages.md", @@ -73,9 +71,9 @@ canonical = System.fetch_env!("CANONICAL") groups_for_extras: [ "Getting started": ~r"pages/getting-started/.*\.md$", Cheatsheets: ~r"pages/cheatsheets/.*\.cheatmd$", + "Mix & OTP": ~r"pages/mix-and-otp/.*\.md$", "Anti-patterns": ~r"pages/anti-patterns/.*\.md$", "Meta-programming": ~r"pages/meta-programming/.*\.md$", - "Mix & OTP": ~r"pages/mix-and-otp/.*\.md$", References: ~r"pages/references/.*\.md$" ], groups_for_docs: [ diff --git a/lib/mix/lib/mix/project.ex b/lib/mix/lib/mix/project.ex index 7c89b2ba35e..060c533c885 100644 --- a/lib/mix/lib/mix/project.ex +++ b/lib/mix/lib/mix/project.ex @@ -130,6 +130,67 @@ defmodule Mix.Project do makes sure Elixir is not added as a dependency to the generated `.app` file or to the escript generated with `mix escript.build`, and so on. + ## Umbrella projects + + Umbrella projects are a convenience to help you organize and manage multiple + applications. While it provides a degree of separation between applications, + those applications are not fully decoupled, as they share the same configuration + and the same dependencies. + + In an umbrella project, you have an `apps/` folder where you store each application. + Then, instead of each app in the umbrella having its own configuration, build cache, + lockfile and so, they all point to the parent project by specifying the following + configuration in their `mix.exs`: + + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", + + The pattern of keeping multiple applications in the same repository is known as + [monorepo](https://en.wikipedia.org/wiki/Monorepo). Umbrella projects maximize + this pattern by providing conveniences to compile, test and run multiple + applications at once. When an umbrella application needs to depend on another + one, it can be done by passing the `in_umbrella: true` option to your dependency. + If an umbrella application `:foo` depends on its sibling `:bar`, you can specify + this dependency in `foo`'s `mix.exs` file as: + + {:bar, in_umbrella: true} + + ### Undoing umbrellas + + Using umbrella projects can impact how you design and write your software and, + as time passes, they may turn out to be the wrong choice. + If you find yourself in a position where you want to use different configurations + in each application for the same dependency or use different dependency versions, + then it is likely your codebase has grown beyond what umbrellas can provide. + + If you find yourself in this situation, you have two options: + + 1. Convert everything into a single Mix project, which can be done in steps. + First move all files in `lib`, `test`, `priv`, and friends into a single + application, while still keeping the overall umbrella structure and + `mix.exs` files. For example, if your umbrellas has three applications, + `foo`, `bar` and `baz`, where `baz` depends on both `foo` and `bar`, + move all source to `baz`. Then remove `foo` and `bar` one by one, + updating any configuration and removing references to the `:foo` and + `:bar` application names. Until you have only a single application. + + 2. Remove umbrella structure while keeping them as distinct applications. + This is done by moving applications outside of the umbrella + project's `apps/` directory and updating the projects' `mix.exs` files + to no longer set the `build_path`, `config_path`, `deps_path`, and + `lockfile` configurations, guaranteeing each of them have their own + build and dependency structure. + + Keep in mind that umbrellas are one of many options for managing private + packages within your organization. You might: + + 1. Have multiple directories inside the same repository and using `:path` + dependencies (which is essentially the monorepo pattern) + 2. Use private Git repositories and Mix' ability to fetch Git dependencies + 3. Publishing packages to a private [Hex.pm](https://hex.pm/) organization + ## Invoking this module This module contains many functions that return project information and From 2c3edbe1c76a48ba960796d8b330bd266489eef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 11 Jul 2025 16:15:53 +0200 Subject: [PATCH 063/111] Check for type equality --- lib/elixir/test/elixir/module/types/expr_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 473a674ca58..a6e49d2d2b3 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -1034,7 +1034,7 @@ defmodule Module.Types.ExprTest do end test "nested map" do - assert typecheck!([x = %{}], x.foo.bar) == dynamic() + assert typecheck!([x = %{}], x.foo.bar) |> equal?(dynamic()) end test "accessing a field on not a map" do From b7e0d657d1fc2f1976ecd114463dab3ab2e10311 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 12 Jul 2025 14:10:26 +0200 Subject: [PATCH 064/111] Drop :app_properties when rendering dependency in mix (#14645) --- lib/mix/lib/mix/dep.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/lib/mix/dep.ex b/lib/mix/lib/mix/dep.ex index 1dd953b4cd9..1ebb3150df1 100644 --- a/lib/mix/lib/mix/dep.ex +++ b/lib/mix/lib/mix/dep.ex @@ -415,7 +415,7 @@ defmodule Mix.Dep do |> Kernel.++(if manager, do: [manager: manager], else: []) |> Kernel.++(if system_env != [], do: [system_env: system_env], else: []) |> Kernel.++(opts) - |> Keyword.drop([:dest, :build, :lock, :manager, :checkout]) + |> Keyword.drop([:dest, :build, :lock, :manager, :checkout, :app_properties]) info = if req, do: {app, req, opts}, else: {app, opts} "\n > In #{Path.relative_to_cwd(from)}:\n #{inspect(info)}\n" From 8cba20cb3ef5eef40161a5cc2f2bb75f594ad625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Sat, 12 Jul 2025 20:49:19 +0200 Subject: [PATCH 065/111] Clean up unreachable clause of Types.Descr.atom_only? helper (#14647) it's being always called with a map --- lib/elixir/lib/module/types/descr.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index d7c73d87c98..84e61343116 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -972,7 +972,6 @@ defmodule Module.Types.Descr do end end - defp atom_only?(:term), do: false defp atom_only?(descr), do: empty?(Map.delete(descr, :atom)) defp atom_new(as) when is_list(as), do: {:union, :sets.from_list(as, version: 2)} From 9b729ca4c17e6be9edfc47d9e402a085871a1681 Mon Sep 17 00:00:00 2001 From: Vasilis Spilka Date: Sat, 12 Jul 2025 21:58:00 +0200 Subject: [PATCH 066/111] Add printable_limit and limit to IO.inspect doc examples (#14646) --- lib/elixir/lib/io.ex | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/elixir/lib/io.ex b/lib/elixir/lib/io.ex index 123441bd279..36fbe6573c7 100644 --- a/lib/elixir/lib/io.ex +++ b/lib/elixir/lib/io.ex @@ -467,13 +467,15 @@ defmodule IO do ## Examples + The following code: + IO.inspect(<<0, 1, 2>>, width: 40) Prints: <<0, 1, 2>> - We can use the `:label` option to decorate the output: + You can use the `:label` option to decorate the output: IO.inspect(1..100, label: "a wonderful range") @@ -481,18 +483,20 @@ defmodule IO do a wonderful range: 1..100 - The `:label` option is especially useful with pipelines: + Inspect truncates large inputs by default. The `:printable_limit` controls + the limit for strings and other string-like constructs (such as charlists): - [1, 2, 3] - |> IO.inspect(label: "before") - |> Enum.map(&(&1 * 2)) - |> IO.inspect(label: "after") - |> Enum.sum() + "abc" + |> String.duplicate(9001) + |> IO.inspect(printable_limit: :infinity) - Prints: + For containers such as lists, maps, and tuples, the number of entries + is managed by the `:limit` option: - before: [1, 2, 3] - after: [2, 4, 6] + 1..100 + |> Enum.map(& {&1, &1}) + |> Enum.into(%{}) + |> IO.inspect(limit: :infinity) """ @spec inspect(item, inspect_opts) :: item when item: var From f9966230ee2aa1735847a115d62192b4fcd9d796 Mon Sep 17 00:00:00 2001 From: Benjamin Milde Date: Sat, 12 Jul 2025 22:07:34 +0200 Subject: [PATCH 067/111] Docs updates und restructuring (#14636) --- .../alias-require-and-import.md | 79 +------------------ .../getting-started/anonymous-functions.md | 2 +- .../pages/getting-started/case-cond-and-if.md | 21 +++-- .../getting-started/keywords-and-maps.md | 4 +- .../getting-started/modules-and-functions.md | 78 +++++++++++++++++- .../pages/getting-started/pattern-matching.md | 64 +++++++-------- .../pages/references/patterns-and-guards.md | 13 +++ lib/elixir/scripts/elixir_docs.exs | 10 +-- 8 files changed, 145 insertions(+), 126 deletions(-) diff --git a/lib/elixir/pages/getting-started/alias-require-and-import.md b/lib/elixir/pages/getting-started/alias-require-and-import.md index f77b78efae7..31d1564960e 100644 --- a/lib/elixir/pages/getting-started/alias-require-and-import.md +++ b/lib/elixir/pages/getting-started/alias-require-and-import.md @@ -156,83 +156,6 @@ end Since `use` allows any code to run, we can't really know the side-effects of using a module without reading its documentation. Therefore use this function with care and only if strictly required. Don't use `use` where an `import` or `alias` would do. -## Understanding Aliases - -At this point, you may be wondering: what exactly is an Elixir alias and how is it represented? - -An alias in Elixir is a capitalized identifier (like `String`, `Keyword`, etc) which is converted to an atom during compilation. For instance, the `String` alias translates by default to the atom `:"Elixir.String"`: - -```elixir -iex> is_atom(String) -true -iex> to_string(String) -"Elixir.String" -iex> :"Elixir.String" == String -true -``` - -By using the `alias/2` directive, we are changing the atom the alias expands to. - -Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms: - -```elixir -iex> List.flatten([1, [2], 3]) -[1, 2, 3] -iex> :"Elixir.List".flatten([1, [2], 3]) -[1, 2, 3] -``` - -That's the mechanism we use to call Erlang modules: - -```elixir -iex> :lists.flatten([1, [2], 3]) -[1, 2, 3] -``` - -## Module nesting - -Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example: - -```elixir -defmodule Foo do - defmodule Bar do - end -end -``` - -The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope. - -If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above. - -**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as: - -```elixir -defmodule Foo.Bar do -end - -defmodule Foo do - alias Foo.Bar - # Can still access it as `Bar` -end -``` - -Aliasing a nested module does not bring parent modules into scope. Consider the following example: - -```elixir -defmodule Foo do - defmodule Bar do - defmodule Baz do - end - end -end - -alias Foo.Bar.Baz -# The module `Foo.Bar.Baz` is now available as `Baz` -# However, the module `Foo.Bar` is *not* available as `Bar` -``` - -As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic. - ## Multi alias/import/require/use It is possible to `alias`, `import`, `require`, or `use` multiple modules at once. This is particularly useful once we start nesting modules, which is very common when building Elixir applications. For example, imagine you have an application where all modules are nested under `MyApp`, you can alias the modules `MyApp.Foo`, `MyApp.Bar` and `MyApp.Baz` at once as follows: @@ -241,4 +164,4 @@ It is possible to `alias`, `import`, `require`, or `use` multiple modules at onc alias MyApp.{Foo, Bar, Baz} ``` -With this, we have finished our tour of Elixir modules. The next topic to cover is module attributes. +With this, we have finished our tour of Elixir modules. diff --git a/lib/elixir/pages/getting-started/anonymous-functions.md b/lib/elixir/pages/getting-started/anonymous-functions.md index bec912d000e..253ba3e650e 100644 --- a/lib/elixir/pages/getting-started/anonymous-functions.md +++ b/lib/elixir/pages/getting-started/anonymous-functions.md @@ -9,7 +9,7 @@ Anonymous functions allow us to store and pass executable code around as if it w ## Identifying functions and documentation -Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions. +Before we move on to discuss anonymous functions, let's talk about how Elixir identifies named functions – the functions defined in [modules](modules-and-functions.md). Functions in Elixir are identified by both their name and their arity. The arity of a function describes the number of arguments that the function takes. From this point on we will use both the function name and its arity to describe functions throughout the documentation. `trunc/1` identifies the function which is named `trunc` and takes `1` argument, whereas `trunc/2` identifies a different (nonexistent) function with the same name but with an arity of `2`. diff --git a/lib/elixir/pages/getting-started/case-cond-and-if.md b/lib/elixir/pages/getting-started/case-cond-and-if.md index 2da7c568afd..9c354105e5c 100644 --- a/lib/elixir/pages/getting-started/case-cond-and-if.md +++ b/lib/elixir/pages/getting-started/case-cond-and-if.md @@ -100,7 +100,11 @@ iex> if nil do "This will" ``` -This is also a good opportunity to talk about variable scoping in Elixir. If any variable is declared or changed inside [`if`](`if/2`), [`case`](`case/2`), and similar constructs, the declaration and change will only be visible inside the construct. For example: +### Expressions + +Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value. + +This property allows variables to be scoped to individual blocks of code such as [`if`](`if/2`), [`case`](`case/2`), where declarations or changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example: ```elixir iex> x = 1 @@ -113,19 +117,22 @@ iex> x 1 ``` -In said cases, if you want to change a value, you must return the value from the [`if`](`if/2`): +You see the return value of the [`if`](`if/2`) expression as the resulting `2` here. To retain changes made within the [`if`](`if/2`) expression on the outer block you need to assign the returned value to a variable in the outer block. ```elixir iex> x = 1 1 -iex> x = if true do -...> x + 1 -...> else -...> x -...> end +iex> x = +...> if true do +...> x + 1 +...> else +...> x +...> end 2 ``` +With all expressions returning a value there's also no need for alternative constructs, such as ternary operators posing as an alternative to [`if`](`if/2`). Elixir does include an inline notation for [`if`](`if/2`) and, as we will [learn later](keywords-and-maps.md#do-blocks-and-keywords), it is a syntactic variation on `if`'s arguments. + > #### `if` is a macro {: .info} > > An interesting note regarding [`if`](`if/2`) is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information. diff --git a/lib/elixir/pages/getting-started/keywords-and-maps.md b/lib/elixir/pages/getting-started/keywords-and-maps.md index 040a47bedc8..656e84c96ca 100644 --- a/lib/elixir/pages/getting-started/keywords-and-maps.md +++ b/lib/elixir/pages/getting-started/keywords-and-maps.md @@ -131,7 +131,7 @@ iex> if true do In the example above, the `do` and `else` blocks make up a keyword list. They are nothing more than a syntax convenience on top of keyword lists. We can rewrite the above to: ```elixir -iex> if true, do: "This will be seen", else: "This won't" +iex> if(true, do: "This will be seen", else: "This won't") "This will be seen" ``` @@ -225,6 +225,8 @@ These operations have one large benefit in that they raise if the key does not e Elixir developers typically prefer to use the `map.key` syntax and pattern matching instead of the functions in the `Map` module when working with maps because they lead to an assertive style of programming. [This blog post by José Valim](https://dashbit.co/blog/writing-assertive-code-with-elixir) provides insight and examples on how you get more concise and faster software by writing assertive code in Elixir. +In a further chapter you'll learn about ["Structs"](structs.md), which further enforce the idea of a map with predefined keys. + ## Nested data structures Often we will have maps inside maps, or even keywords lists inside maps, and so forth. Elixir provides conveniences for manipulating nested data structures via the `get_in/1`, `put_in/2`, `update_in/2`, and other macros giving the same conveniences you would find in imperative languages while keeping the immutable properties of the language. diff --git a/lib/elixir/pages/getting-started/modules-and-functions.md b/lib/elixir/pages/getting-started/modules-and-functions.md index bfccc32ce7c..888b48c90f8 100644 --- a/lib/elixir/pages/getting-started/modules-and-functions.md +++ b/lib/elixir/pages/getting-started/modules-and-functions.md @@ -12,7 +12,7 @@ iex> String.length("hello") 5 ``` -In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of the module must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): +In order to create our own modules in Elixir, we use the [`defmodule`](`defmodule/2`) macro. The first letter of an module name (an alias, as described further down) must be in uppercase. We use the [`def`](`def/2`) macro to define functions in that module. The first letter of every function must be in lowercase (or underscore): ```elixir iex> defmodule Math do @@ -167,4 +167,78 @@ IO.puts(Concat.join("Hello", "world", "_")) #=> Hello_world When a variable is not used by a function or a clause, we add a leading underscore (`_`) to its name to signal this intent. This rule is also covered in our [Naming Conventions](../references/naming-conventions.md#underscore-_foo) document. -This finishes our short introduction to modules. In the next chapters, we will learn how to use function definitions for recursion and later on explore more functionality related to modules. +## Understanding Aliases + +An alias in Elixir is a capitalized identifier (like `String`, `Keyword`, etc) which is converted to an atom during compilation. For instance, the `String` alias translates by default to the atom `:"Elixir.String"`: + +```elixir +iex> is_atom(String) +true +iex> to_string(String) +"Elixir.String" +iex> :"Elixir.String" == String +true +``` + +By using the `alias/2` directive, we are changing the atom the alias expands to. + +Aliases expand to atoms because in the Erlang Virtual Machine (and consequently Elixir) modules are always represented by atoms. By namespacing +those atoms elixir modules avoid conflicting with existing erlang modules. + +```elixir +iex> List.flatten([1, [2], 3]) +[1, 2, 3] +iex> :"Elixir.List".flatten([1, [2], 3]) +[1, 2, 3] +``` + +That's the mechanism we use to call Erlang modules: + +```elixir +iex> :lists.flatten([1, [2], 3]) +[1, 2, 3] +``` + +## Module nesting + +Now that we have talked about aliases, we can talk about nesting and how it works in Elixir. Consider the following example: + +```elixir +defmodule Foo do + defmodule Bar do + end +end +``` + +The example above will define two modules: `Foo` and `Foo.Bar`. The second can be accessed as `Bar` inside `Foo` as long as they are in the same lexical scope. + +If, later, the `Bar` module is moved outside the `Foo` module definition, it must be referenced by its full name (`Foo.Bar`) or an alias must be set using the `alias` directive discussed above. + +**Note**: in Elixir, you don't have to define the `Foo` module before being able to define the `Foo.Bar` module, as they are effectively independent. The above could also be written as: + +```elixir +defmodule Foo.Bar do +end + +defmodule Foo do + alias Foo.Bar + # Can still access it as `Bar` +end +``` + +Aliasing a nested module does not bring parent modules into scope. Consider the following example: + +```elixir +defmodule Foo do + defmodule Bar do + defmodule Baz do + end + end +end + +alias Foo.Bar.Baz +# The module `Foo.Bar.Baz` is now available as `Baz` +# However, the module `Foo.Bar` is *not* available as `Bar` +``` + +As we will see in later chapters, aliases also play a crucial role in macros, to guarantee they are hygienic. diff --git a/lib/elixir/pages/getting-started/pattern-matching.md b/lib/elixir/pages/getting-started/pattern-matching.md index 8b114791d53..78dde7c0ef2 100644 --- a/lib/elixir/pages/getting-started/pattern-matching.md +++ b/lib/elixir/pages/getting-started/pattern-matching.md @@ -113,6 +113,38 @@ iex> [0 | list] [0, 1, 2, 3] ``` +In some cases, you don't care about a particular value in a pattern. It is a common practice to bind those values to the underscore, `_`. For example, if only the head of the list matters to us, we can assign the tail to underscore: + +```elixir +iex> [head | _] = [1, 2, 3] +[1, 2, 3] +iex> head +1 +``` + +The variable `_` is special in that it can never be read from. Trying to read from it gives a compile error: + +```elixir +iex> _ +** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions +``` + +If a variable is mentioned more than once in a pattern, all references must bind to the same value: + +```elixir +iex> {x, x} = {1, 1} +{1, 1} +iex> {x, x} = {1, 2} +** (MatchError) no match of right hand side value: {1, 2} +``` + +Although pattern matching allows us to build powerful constructs, its usage is limited. For instance, you cannot make function calls on the left side of a match. The following example is invalid: + +```elixir +iex> length([1, [2], 3]) = 3 +** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match +``` + Pattern matching allows developers to easily destructure data types such as tuples and lists. As we will see in the following chapters, it is one of the foundations of recursion in Elixir and applies to other types as well, like maps and binaries. ## The pin operator @@ -168,36 +200,4 @@ iex> {y, 1} = {2, 2} ** (MatchError) no match of right hand side value: {2, 2} ``` -If a variable is mentioned more than once in a pattern, all references must bind to the same value: - -```elixir -iex> {x, x} = {1, 1} -{1, 1} -iex> {x, x} = {1, 2} -** (MatchError) no match of right hand side value: {1, 2} -``` - -In some cases, you don't care about a particular value in a pattern. It is a common practice to bind those values to the underscore, `_`. For example, if only the head of the list matters to us, we can assign the tail to underscore: - -```elixir -iex> [head | _] = [1, 2, 3] -[1, 2, 3] -iex> head -1 -``` - -The variable `_` is special in that it can never be read from. Trying to read from it gives a compile error: - -```elixir -iex> _ -** (CompileError) iex:1: invalid use of _. "_" represents a value to be ignored in a pattern and cannot be used in expressions -``` - -Although pattern matching allows us to build powerful constructs, its usage is limited. For instance, you cannot make function calls on the left side of a match. The following example is invalid: - -```elixir -iex> length([1, [2], 3]) = 3 -** (CompileError) iex:1: cannot invoke remote function :erlang.length/1 inside match -``` - This finishes our introduction to pattern matching. As we will see in the next chapter, pattern matching is very common in many language constructs and they can be further augmented with guards. diff --git a/lib/elixir/pages/references/patterns-and-guards.md b/lib/elixir/pages/references/patterns-and-guards.md index 551fe29b6b1..2029a495ca4 100644 --- a/lib/elixir/pages/references/patterns-and-guards.md +++ b/lib/elixir/pages/references/patterns-and-guards.md @@ -83,6 +83,19 @@ iex> _ ** (CompileError) iex:3: invalid use of _ ``` +A pinned value represents the value itself and not its – even if syntatically equal – pattern. The right hand side is compared to be equal to the pinned value: + +```iex +iex> x = %{} +%{} +iex> {:ok, %{}} = {:ok, %{a: 13}} +{:ok, %{a: 13}} +iex> {:ok, ^x} = {:ok, %{a: 13}} +** (MatchError) no match of right hand side value: {:ok, %{a: 13}} + (stdlib 6.2) erl_eval.erl:667: :erl_eval.expr/6 + iex:2: (file) +``` + ### Literals (numbers and atoms) Atoms and numbers (integers and floats) can appear in patterns and they are always represented as is. For example, an atom will only match an atom if they are the same atom: diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs index 1676a01078e..a25cb8830ff 100644 --- a/lib/elixir/scripts/elixir_docs.exs +++ b/lib/elixir/scripts/elixir_docs.exs @@ -17,17 +17,17 @@ canonical = System.fetch_env!("CANONICAL") "lib/elixir/pages/getting-started/binaries-strings-and-charlists.md", "lib/elixir/pages/getting-started/keywords-and-maps.md", "lib/elixir/pages/getting-started/modules-and-functions.md", - "lib/elixir/pages/getting-started/recursion.md", - "lib/elixir/pages/getting-started/enumerable-and-streams.md", - "lib/elixir/pages/getting-started/processes.md", - "lib/elixir/pages/getting-started/io-and-the-file-system.md", "lib/elixir/pages/getting-started/alias-require-and-import.md", "lib/elixir/pages/getting-started/module-attributes.md", "lib/elixir/pages/getting-started/structs.md", - "lib/elixir/pages/getting-started/protocols.md", + "lib/elixir/pages/getting-started/recursion.md", + "lib/elixir/pages/getting-started/enumerable-and-streams.md", "lib/elixir/pages/getting-started/comprehensions.md", + "lib/elixir/pages/getting-started/protocols.md", "lib/elixir/pages/getting-started/sigils.md", "lib/elixir/pages/getting-started/try-catch-and-rescue.md", + "lib/elixir/pages/getting-started/processes.md", + "lib/elixir/pages/getting-started/io-and-the-file-system.md", "lib/elixir/pages/getting-started/writing-documentation.md", "lib/elixir/pages/getting-started/optional-syntax.md", "lib/elixir/pages/getting-started/erlang-libraries.md", From ac901d9d279a965a456b30c41c4b286bd2dc5cb6 Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Fri, 4 Jul 2025 21:42:56 +0200 Subject: [PATCH 068/111] Remove duplicate for map_difference --- lib/elixir/lib/module/types/descr.ex | 58 ++++++++++++++++------------ 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 84e61343116..6b0c255065a 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -2443,16 +2443,7 @@ defmodule Module.Types.Descr do acc -> try do {tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2) - entry = {tag, fields, negs1 ++ negs2} - - # Imagine a, b, c, where a is closed and b and c are open with - # no keys in common. The result in both cases will be a and we - # want to avoid adding duplicates, especially as intersection - # is a cartesian product. - case :lists.member(entry, acc) do - true -> acc - false -> [entry | acc] - end + prepend_to_map_dnf(tag, fields, negs1 ++ (negs2 -- negs1), acc) catch :empty -> acc end @@ -2567,42 +2558,61 @@ defmodule Module.Types.Descr do if empty?(type), do: throw(:empty), else: type end - defp map_difference(_, dnf) when dnf == @map_top do - 0 - end + defp map_difference(_, dnf) when dnf == @map_top, do: [] + defp map_difference(dnf, dnf), do: [] defp map_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn - # Optimization: we are removing an open map with one field. - {:open, fields2, []}, dnf1 when map_size(fields2) == 1 -> - Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc -> + {:open, fields2, []}, current_dnf when map_size(fields2) == 1 -> + # Optimization: we are removing an open map with one field. + Enum.reduce(current_dnf, [], fn {tag1, fields1, negs1}, acc -> {key, value, _rest} = :maps.next(:maps.iterator(fields2)) t_diff = difference(Map.get(fields1, key, map_key_tag_to_type(tag1)), value) if empty?(t_diff) do acc else - [{tag1, Map.put(fields1, key, t_diff), negs1} | acc] + {tag, pos} = {tag1, Map.put(fields1, key, t_diff)} + entry = {tag, pos, negs1} + + cond do + :lists.member({tag, pos}, negs1) -> acc + :lists.member(entry, acc) -> acc + true -> [entry | acc] + end end end) - {tag2, fields2, negs2}, dnf1 -> - Enum.reduce(dnf1, [], fn {tag1, fields1, negs1}, acc -> - acc = [{tag1, fields1, [{tag2, fields2} | negs1]} | acc] + {tag2, fields2, negs2}, current_dnf -> + Enum.reduce(current_dnf, [], fn {tag1, fields1, negs1}, acc -> + negs = + if :lists.member({tag2, fields2}, negs1) do + negs1 + else + [{tag2, fields2} | negs1] + end + + acc = prepend_to_map_dnf(tag1, fields1, negs, acc) Enum.reduce(negs2, acc, fn {neg_tag2, neg_fields2}, acc -> try do {tag, fields} = map_literal_intersection(tag1, fields1, neg_tag2, neg_fields2) - [{tag, fields, negs1} | acc] + prepend_to_map_dnf(tag, fields, negs1, acc) catch :empty -> acc end end) end) end) - |> case do - [] -> 0 - acc -> acc + end + + defp prepend_to_map_dnf(tag, fields, negs, acc) do + entry = {tag, fields, negs} + + cond do + :lists.member({tag, fields}, negs) -> acc + :lists.member(entry, acc) -> acc + true -> [entry | acc] end end From a719cb3b2146b99bfa0fbed820a7ee48b190798e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 16 Jul 2025 16:39:20 +0200 Subject: [PATCH 069/111] Update checker to v2 as representation has changed --- lib/elixir/lib/module/parallel_checker.ex | 2 +- lib/elixir/src/elixir_erl.erl | 2 +- lib/elixir/test/elixir/module/types/integration_test.exs | 2 +- lib/elixir/test/elixir/protocol/consolidation_test.exs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 2ffdbf69490..2fdd5c3d5d2 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -423,7 +423,7 @@ defmodule Module.ParallelChecker do mode = with {^module, binary, _filename} <- object_code, {:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]), - {:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do + {:elixir_checker_v2, contents} <- :erlang.binary_to_term(chunk) do # The chunk has more information, so that's our preference cache_chunk(table, module, contents) else diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index 9df9b51d0f1..5a012050ff7 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -662,7 +662,7 @@ checker_chunk(Map, Def, ChunkOpts) -> end }, - [{<<"ExCk">>, term_to_binary({elixir_checker_v1, Contents}, ChunkOpts)}]. + [{<<"ExCk">>, term_to_binary({elixir_checker_v2, Contents}, ChunkOpts)}]. prepend_behaviour_info(true, Def) -> [{{behaviour_info, 1}, []} | Def]; prepend_behaviour_info(false, Def) -> Def. diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index be9ff32c2b8..2377cb75ee1 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -1467,7 +1467,7 @@ defmodule Module.Types.IntegrationTest do defp read_chunk(binary) do assert {:ok, {_module, [{~c"ExCk", chunk}]}} = :beam_lib.chunks(binary, [~c"ExCk"]) - assert {:elixir_checker_v1, map} = :erlang.binary_to_term(chunk) + assert {:elixir_checker_v2, map} = :erlang.binary_to_term(chunk) map end diff --git a/lib/elixir/test/elixir/protocol/consolidation_test.exs b/lib/elixir/test/elixir/protocol/consolidation_test.exs index 5364eafc447..fc03f8d31da 100644 --- a/lib/elixir/test/elixir/protocol/consolidation_test.exs +++ b/lib/elixir/test/elixir/protocol/consolidation_test.exs @@ -165,7 +165,7 @@ defmodule Protocol.ConsolidationTest do defp exports(binary) do {:ok, {_, [{~c"ExCk", check_bin}]}} = :beam_lib.chunks(binary, [~c"ExCk"]) - assert {:elixir_checker_v1, contents} = :erlang.binary_to_term(check_bin) + assert {:elixir_checker_v2, contents} = :erlang.binary_to_term(check_bin) Map.new(contents.exports) end From 1f75ade71f2837085db99e45959cfac16a04741f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 16 Jul 2025 22:09:09 +0200 Subject: [PATCH 070/111] Avoid adding lists that match negations --- lib/elixir/lib/module/types/descr.ex | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 6b0c255065a..765e076467b 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -1879,17 +1879,14 @@ defmodule Module.Types.Descr do acc -> inter = intersection(list_type1, list_type2) last = intersection(last_type1, last_type2) + negs = negs1 ++ negs2 - if empty?(inter) or empty?(last) do - acc - else - [{inter, last, negs1 ++ negs2} | acc] + cond do + :lists.member({inter, last}, negs) -> acc + empty?(inter) or empty?(last) -> acc + true -> [{inter, last, negs} | acc] end end - |> case do - [] -> 0 - dnf -> dnf - end end # Computes the difference between two DNF (Disjunctive Normal Form) list types. @@ -1902,7 +1899,7 @@ defmodule Module.Types.Descr do # 3. Base case: adds dnf2 type to negations of dnf1 type # The result may be larger than the initial dnf1, which is maintained in the accumulator. defp list_difference(_, dnf) when dnf == @non_empty_list_top do - 0 + [] end defp list_difference(dnf1, dnf2) do @@ -1916,7 +1913,12 @@ defmodule Module.Types.Descr do Enum.reduce(negs2, [], fn {nt, nlast}, nacc -> t = intersection(t1, nt) last = intersection(last1, nlast) - if empty?(t) or empty?(last), do: nacc, else: [{t, last, negs1} | nacc] + + cond do + :lists.member({t, last}, negs1) -> nacc + empty?(t) or empty?(last) -> nacc + true -> [{t, last, negs1} | nacc] + end end) i = intersection(t1, t2) From 79005e370e75bc3207da00f8a1caf5d0c66747c9 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Thu, 17 Jul 2025 23:42:30 +0200 Subject: [PATCH 071/111] Fix opaqueness violation in Task.Supervisor (#14656) --- lib/elixir/lib/task.ex | 7 +++++++ lib/elixir/lib/task/supervisor.ex | 2 +- .../elixir/fixtures/dialyzer/opaque_inline.ex | 10 ---------- .../test/elixir/fixtures/dialyzer/opaqueness.ex | 17 +++++++++++++++++ lib/elixir/test/elixir/kernel/dialyzer_test.exs | 8 +++++--- 5 files changed, 30 insertions(+), 14 deletions(-) delete mode 100644 lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex create mode 100644 lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex diff --git a/lib/elixir/lib/task.ex b/lib/elixir/lib/task.ex index ef7af6ebd7a..ec881c4450a 100644 --- a/lib/elixir/lib/task.ex +++ b/lib/elixir/lib/task.ex @@ -1444,6 +1444,13 @@ defmodule Task do end end + # exported only to avoid dialyzer opaqueness check in internal Task modules + @doc false + @spec __alias__(pid()) :: Task.ref() + def __alias__(pid) do + build_alias(pid) + end + ## Optimizations defp build_monitor(pid) do diff --git a/lib/elixir/lib/task/supervisor.ex b/lib/elixir/lib/task/supervisor.ex index 59b63f680e0..a296e9bfe03 100644 --- a/lib/elixir/lib/task/supervisor.ex +++ b/lib/elixir/lib/task/supervisor.ex @@ -614,7 +614,7 @@ defmodule Task.Supervisor do case start_child_with_spec(supervisor, [get_owner(owner), :monitor], :temporary, shutdown) do {:ok, pid} -> if link_type == :link, do: Process.link(pid) - alias = :erlang.monitor(:process, pid, alias: :demonitor) + alias = Task.__alias__(pid) send(pid, {owner, alias, alias, get_callers(owner), {module, fun, args}}) %Task{pid: pid, ref: alias, owner: owner, mfa: {module, fun, length(args)}} diff --git a/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex b/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex deleted file mode 100644 index ce224ee523b..00000000000 --- a/lib/elixir/test/elixir/fixtures/dialyzer/opaque_inline.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Dialyzer.OpaqueInline do - @spec bar(MapSet.t()) :: term() - def bar(set) do - set - end - - def foo() do - bar(MapSet.new([1, 2, 3])) - end -end diff --git a/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex b/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex new file mode 100644 index 00000000000..12857f515fe --- /dev/null +++ b/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex @@ -0,0 +1,17 @@ +defmodule Dialyzer.Opaqueness do + @spec bar(MapSet.t()) :: term() + def bar(set) do + set + end + + def foo() do + # inlining of literals should not violate opaqueness check + bar(MapSet.new([1, 2, 3])) + end + + # Task.Supervisor returns a Task.t() containing an opaque Task.ref() + @spec run_task() :: Task.t() + def run_task do + Task.Supervisor.async(SupervisorName, fn -> :ok end) + end +end diff --git a/lib/elixir/test/elixir/kernel/dialyzer_test.exs b/lib/elixir/test/elixir/kernel/dialyzer_test.exs index 615f946b0d9..01aa977ecae 100644 --- a/lib/elixir/test/elixir/kernel/dialyzer_test.exs +++ b/lib/elixir/test/elixir/kernel/dialyzer_test.exs @@ -50,7 +50,9 @@ defmodule Kernel.DialyzerTest do Module, Protocol, String, - String.Chars + String.Chars, + Task, + Task.Supervisor ] files = Enum.map(mods, &:code.which/1) @@ -178,8 +180,8 @@ defmodule Kernel.DialyzerTest do assert_dialyze_no_warnings!(context) end - test "no warning on inlined calls returning opaque", context do - copy_beam!(context, Dialyzer.OpaqueInline) + test "no warning due to opaqueness edge cases", context do + copy_beam!(context, Dialyzer.Opaqueness) assert_dialyze_no_warnings!(context) end From 2308be941e30776dc30b024bf3aeba24e5258afd Mon Sep 17 00:00:00 2001 From: Paul Gideon Dann Date: Thu, 24 Jul 2025 17:59:34 +0100 Subject: [PATCH 072/111] Validate type of :deps_paths option for formatter_for_file/2 (#14669) --- lib/mix/lib/mix/tasks/format.ex | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index 59b937e482b..cbcddaea8f8 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -518,6 +518,12 @@ defmodule Mix.Tasks.Format do defp eval_deps_opts(deps, opts) do deps_paths = opts[:deps_paths] || Mix.Project.deps_paths() + if not is_map(deps_paths) do + Mix.raise( + "Expected :deps_paths to return a map of dependency paths, got: #{inspect(deps_paths)}" + ) + end + for dep <- deps, dep_path = assert_valid_dep_and_fetch_path(dep, deps_paths), dep_dot_formatter = Path.join(dep_path, ".formatter.exs"), From 99cef0686c328ecf5dc9f6070ff6da3af71acf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 26 Jul 2025 10:31:36 +0200 Subject: [PATCH 073/111] Improve ExUnit docs --- lib/ex_unit/lib/ex_unit/case.ex | 23 +++++++++++++---------- lib/ex_unit/lib/ex_unit/filters.ex | 7 ++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex index d2cea8e82cd..f72160711c9 100644 --- a/lib/ex_unit/lib/ex_unit/case.ex +++ b/lib/ex_unit/lib/ex_unit/case.ex @@ -168,18 +168,22 @@ defmodule ExUnit.Case do * `:test_pid` - the PID of the testing process - * `:test_type` - the test type used when printing test results. - It is set by ExUnit to `:test`, `:doctest` and so on, but is customizable. + * `:test_type` - the test type used when printing test results. It can also + be used for filtering. It is set by ExUnit to `:test`, `:doctest`, or + the equivalent type given to `register_test/6` * `:describe` - the describe block the test belongs to (if in a describe) - * `:describe_line` - the line the describe block begins on (if in a describe) + * `:describe_line` - the line the describe block begins on (if in a describe). + It can be used to run all tests in the describe block given by line * `:doctest` - the module or the file being doctested (if a doctest) - * `:doctest_data` - additional metadata about doctests (if a doctest) + * `:doctest_data` - additional metadata about doctests stored in a map, + such as the `:end_line`, available for reflection purposes (if a doctest) - * `:doctest_line` - the line the doctest was defined (if a doctest) + * `:doctest_line` - the line the doctest was defined (if a doctest). + It can be used to run a doctest defined at the given source line The following tags customize how tests behave: @@ -658,12 +662,11 @@ defmodule ExUnit.Case do end @doc """ - Registers a function to run as part of this case. + Registers a function to run as a test for this module. - This is used by third-party projects, like QuickCheck, to - implement macros like `property/3` that works like `test` - but instead defines a property. See `test/3` implementation - for an example of invoking this function. + This is used by third-party projects to implement macros like + `property/3` that works like `test` but instead defines a property. + See `test/3` implementation for an example of invoking this function. The test type will be converted to a string and pluralized for display. You can use `ExUnit.plural_rule/2` to set a custom diff --git a/lib/ex_unit/lib/ex_unit/filters.ex b/lib/ex_unit/lib/ex_unit/filters.ex index 106cd2d56d4..69f9ac71d51 100644 --- a/lib/ex_unit/lib/ex_unit/filters.ex +++ b/lib/ex_unit/lib/ex_unit/filters.ex @@ -196,11 +196,8 @@ defmodule ExUnit.Filters do Tests are first excluded, then included, and then skipped (if any left). If a `:skip` tag is found in `tags`, `{:skipped, message}` is returned if the test - has been left after the `exclude` and `include` filters. Otherwise `{:exclude, message}` - is returned. - - The only exception to this rule is that `:skip` is found in the `include` filter, - `:ok` is returned regardless of whether the test was excluded or not. + remains after the `exclude` and `include` filters. However, if skipped tests are + specifically included, then they will always run. ## Examples From 5bc81aaa0ac9ddab36fbbc300b21acbd90822d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 26 Jul 2025 18:16:49 +0200 Subject: [PATCH 074/111] Enhance OptionParser.ParseError with available options display (#14673) Example output: Expected one of: --count INTEGER (alias: -c) --debug, --no-debug (alias: -d) --files STRING (alias: -f) (may be given more than once) --verbose, --no-verbose (alias: -v) Prompt ====== When we raise ParseError, include all of the options we could potentially accept, alongside their types and aliases. For example, the switches `[foo: :string, bar: :integer]` and `aliases: [b: :bar]`, the error message should say: Expected one of: --foo STRING --bar INTEGER (alias: -b) Furthermore, for types that are :keep (which default to string), you should add: --bar INTEGER (alias: -b) (may be given more than once) And boolean ones accept no arguments, so they should be written as: --baz, --no-baz Sort all of them alphabetically. --- lib/elixir/lib/option_parser.ex | 74 ++++++++++++++-- lib/elixir/test/elixir/option_parser_test.exs | 88 ++++++++++++++++--- lib/mix/test/mix/task_test.exs | 4 +- 3 files changed, 146 insertions(+), 20 deletions(-) diff --git a/lib/elixir/lib/option_parser.ex b/lib/elixir/lib/option_parser.ex index c68619f6736..1094692a7c9 100644 --- a/lib/elixir/lib/option_parser.ex +++ b/lib/elixir/lib/option_parser.ex @@ -282,11 +282,11 @@ defmodule OptionParser do iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer]) ** (OptionParser.ParseError) 1 error found! - --limit : Expected type integer, got "xyz" + --limit : Expected type integer, got "xyz"... iex> OptionParser.parse!(["--unknown", "xyz"], strict: []) ** (OptionParser.ParseError) 1 error found! - --unknown : Unknown option + --unknown : Unknown option... iex> OptionParser.parse!( ...> ["-l", "xyz", "-f", "bar"], @@ -295,7 +295,7 @@ defmodule OptionParser do ...> ) ** (OptionParser.ParseError) 2 errors found! -l : Expected type integer, got "xyz" - -f : Expected type integer, got "bar" + -f : Expected type integer, got "bar"... """ @spec parse!(argv, options) :: {parsed, argv} @@ -354,7 +354,7 @@ defmodule OptionParser do ...> strict: [number: :integer] ...> ) ** (OptionParser.ParseError) 1 error found! - --number : Expected type integer, got "lib" + --number : Expected type integer, got "lib"... iex> OptionParser.parse_head!( ...> ["--verbose", "--source", "lib", "test/enum_test.exs", "--unlock"], @@ -362,7 +362,7 @@ defmodule OptionParser do ...> ) ** (OptionParser.ParseError) 2 errors found! --verbose : Missing argument of type integer - --source : Expected type integer, got "lib" + --source : Expected type integer, got "lib"... """ @spec parse_head!(argv, options) :: {parsed, argv} @@ -863,15 +863,20 @@ defmodule OptionParser do error_count = length(errors) error = if error_count == 1, do: "error", else: "errors" - "#{error_count} #{error} found!\n" <> - Enum.map_join(errors, "\n", &format_error(&1, opts, types)) + slogan = + "#{error_count} #{error} found!\n" <> + Enum.map_join(errors, "\n", &format_error(&1, opts, types)) + + case format_available_options(opts, types) do + "" -> slogan + available_options -> slogan <> "\n\n#{available_options}" + end end defp format_error({option, nil}, opts, types) do if type = get_type(option, opts, types) do if String.contains?(option, "_") do msg = "#{option} : Unknown option" - msg <> ". Did you mean #{String.replace(option, "_", "-")}?" else "#{option} : Missing argument of type #{type}" @@ -917,4 +922,57 @@ defmodule OptionParser do option = String.replace(source, "_", "-") if score < current, do: best, else: {option, score} end + + defp format_available_options(opts, switches) do + reverse_aliases = + opts + |> Keyword.get(:aliases, []) + |> Enum.reduce(%{}, fn {alias, target}, acc -> + Map.update(acc, target, [alias], &[alias | &1]) + end) + + formatted_options = + switches + |> Enum.sort() + |> Enum.map(fn {name, types} -> + types = List.wrap(types) + + case types |> List.delete(:keep) |> List.first(:string) do + :boolean -> + base = "#{to_switch(name)}, #{to_switch(name, "--no-")}" + add_aliases(base, name, reverse_aliases) + + type -> + base = "#{to_switch(name)} #{String.upcase(Atom.to_string(type))}" + base = add_aliases(base, name, reverse_aliases) + + if :keep in types do + base <> " (may be given more than once)" + else + base + end + end + end) + + if formatted_options == [] do + "" + else + "Supported options:\n" <> Enum.map_join(formatted_options, "\n", &(" " <> &1)) + end + end + + defp add_aliases(base, name, reverse_aliases) do + case Map.get(reverse_aliases, name, []) do + [] -> + base + + alias_list -> + alias_str = + alias_list + |> Enum.sort() + |> Enum.map_join(", ", &("-" <> Atom.to_string(&1))) + + base <> " (alias: #{alias_str})" + end + end end diff --git a/lib/elixir/test/elixir/option_parser_test.exs b/lib/elixir/test/elixir/option_parser_test.exs index 89b46968b46..9abb3a50b16 100644 --- a/lib/elixir/test/elixir/option_parser_test.exs +++ b/lib/elixir/test/elixir/option_parser_test.exs @@ -100,21 +100,46 @@ defmodule OptionParserTest do end test "parse!/2 raises an exception for an unknown option using strict" do - msg = "1 error found!\n--doc-bar : Unknown option. Did you mean --docs-bar?" + msg = + """ + 1 error found! + --doc-bar : Unknown option. Did you mean --docs-bar? + + Supported options: + --docs-bar STRING + --source STRING\ + """ assert_raise OptionParser.ParseError, msg, fn -> argv = ["--source", "from_docs/", "--doc-bar", "show"] OptionParser.parse!(argv, strict: [source: :string, docs_bar: :string]) end - assert_raise OptionParser.ParseError, "1 error found!\n--foo : Unknown option", fn -> - argv = ["--source", "from_docs/", "--foo", "show"] - OptionParser.parse!(argv, strict: [source: :string, docs: :string]) - end + assert_raise OptionParser.ParseError, + """ + 1 error found! + --foo : Unknown option + + Supported options: + --docs STRING + --source STRING\ + """, + fn -> + argv = ["--source", "from_docs/", "--foo", "show"] + OptionParser.parse!(argv, strict: [source: :string, docs: :string]) + end end test "parse!/2 raises an exception for an unknown option using strict when it is only off by underscores" do - msg = "1 error found!\n--docs_bar : Unknown option. Did you mean --docs-bar?" + msg = + """ + 1 error found! + --docs_bar : Unknown option. Did you mean --docs-bar? + + Supported options: + --docs-bar STRING + --source STRING\ + """ assert_raise OptionParser.ParseError, msg, fn -> argv = ["--source", "from_docs/", "--docs_bar", "show"] @@ -123,14 +148,57 @@ defmodule OptionParserTest do end test "parse!/2 raises an exception when an option is of the wrong type" do - assert_raise OptionParser.ParseError, fn -> - argv = ["--bad", "opt", "foo", "-o", "bad", "bar"] - OptionParser.parse!(argv, switches: [bad: :integer]) + assert_raise OptionParser.ParseError, + """ + 1 error found! + --bad : Expected type integer, got "opt" + + Supported options: + --bad INTEGER\ + """, + fn -> + argv = ["--bad", "opt", "foo", "-o", "bad", "bar"] + OptionParser.parse!(argv, switches: [bad: :integer]) + end + end + + test "parse!/2 lists all supported options and aliases" do + expected_suggestion = + """ + 1 error found! + --verbos : Unknown option. Did you mean --verbose? + + Supported options: + --count INTEGER (alias: -c) + --debug, --no-debug (alias: -d) + --files STRING (alias: -f) (may be given more than once) + --name STRING (alias: -n) + --verbose, --no-verbose (alias: -v)\ + """ + + assert_raise OptionParser.ParseError, expected_suggestion, fn -> + OptionParser.parse!(["--verbos"], + strict: [ + name: :string, + count: :integer, + verbose: :boolean, + debug: :boolean, + files: :keep + ], + aliases: [n: :name, c: :count, v: :verbose, d: :debug, f: :files] + ) end end test "parse_head!/2 raises an exception when an option is of the wrong type" do - message = "1 error found!\n--number : Expected type integer, got \"lib\"" + message = + """ + 1 error found! + --number : Expected type integer, got "lib" + + Supported options: + --number INTEGER\ + """ assert_raise OptionParser.ParseError, message, fn -> argv = ["--number", "lib", "test/enum_test.exs"] diff --git a/lib/mix/test/mix/task_test.exs b/lib/mix/test/mix/task_test.exs index d0e7315aa54..3cdb9a1e299 100644 --- a/lib/mix/test/mix/task_test.exs +++ b/lib/mix/test/mix/task_test.exs @@ -43,7 +43,7 @@ defmodule Mix.TaskTest do end test "run/2 converts OptionParser.ParseError into Mix errors" do - message = "Could not invoke task \"hello\": 1 error found!\n--unknown : Unknown option" + message = ~r"Could not invoke task \"hello\": 1 error found!\n--unknown : Unknown option" assert_raise Mix.Error, message, fn -> Mix.Task.run("hello", ["--parser", "--unknown"]) @@ -52,7 +52,7 @@ defmodule Mix.TaskTest do Mix.Task.clear() message = - "Could not invoke task \"hello\": 1 error found!\n--int : Expected type integer, got \"foo\"" + ~r"Could not invoke task \"hello\": 1 error found!\n--int : Expected type integer, got \"foo\"" assert_raise Mix.Error, message, fn -> Mix.Task.run("hello", ["--parser", "--int", "foo"]) From f50f35fd1cab2a8ae773837713601bf757d835ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 26 Jul 2025 19:53:15 +0200 Subject: [PATCH 075/111] Add --name-pattern option to `mix test` and regex support to OptionParser (#14674) --- lib/elixir/lib/option_parser.ex | 17 ++++- lib/elixir/test/elixir/option_parser_test.exs | 74 +++++++++++++++++++ lib/mix/lib/mix/tasks/test.ex | 49 ++++++++---- lib/mix/test/mix/tasks/test_test.exs | 30 +++++--- 4 files changed, 143 insertions(+), 27 deletions(-) diff --git a/lib/elixir/lib/option_parser.ex b/lib/elixir/lib/option_parser.ex index 1094692a7c9..6c832cb3c63 100644 --- a/lib/elixir/lib/option_parser.ex +++ b/lib/elixir/lib/option_parser.ex @@ -129,6 +129,7 @@ defmodule OptionParser do * `:integer` - parses the value as an integer * `:float` - parses the value as a float * `:string` - parses the value as a string + * `:regex` - parses the value as a regular expression with Unicode support If a switch can't be parsed according to the given type, it is returned in the invalid options list. @@ -664,7 +665,7 @@ defmodule OptionParser do end defp validate_switch({_name, type_or_type_and_modifiers}) do - valid = [:boolean, :count, :integer, :float, :string, :keep] + valid = [:boolean, :count, :integer, :float, :string, :regex, :keep] invalid = List.wrap(type_or_type_and_modifiers) -- valid if invalid != [] do @@ -704,6 +705,12 @@ defmodule OptionParser do _ -> {true, value} end + :regex in kinds -> + case Regex.compile(value, "u") do + {:ok, regex} -> {false, regex} + {:error, _} -> {true, value} + end + true -> {false, value} end @@ -896,7 +903,13 @@ defmodule OptionParser do defp format_error({option, value}, opts, types) do type = get_type(option, opts, types) - "#{option} : Expected type #{type}, got #{inspect(value)}" + + with :regex <- type, + {:error, {reason, position}} <- Regex.compile(value, "u") do + "#{option} : Invalid regular expression #{inspect(value)}: #{reason} at position #{position}" + else + _ -> "#{option} : Expected type #{type}, got #{inspect(value)}" + end end defp get_type(option, opts, types) do diff --git a/lib/elixir/test/elixir/option_parser_test.exs b/lib/elixir/test/elixir/option_parser_test.exs index 9abb3a50b16..737a3832e26 100644 --- a/lib/elixir/test/elixir/option_parser_test.exs +++ b/lib/elixir/test/elixir/option_parser_test.exs @@ -418,6 +418,80 @@ defmodule OptionParserTest do assert OptionParser.parse(["arg1", "--option", "-43.2"], opts) == {[option: -43.2], ["arg1"], []} end + + test "parses configured regexes" do + assert {[pattern: regex], ["foo"], []} = + OptionParser.parse(["--pattern", "a.*b", "foo"], switches: [pattern: :regex]) + + assert Regex.match?(regex, "aXXXb") + refute Regex.match?(regex, "xyz") + + assert {[pattern: regex], ["foo"], []} = + OptionParser.parse(["--pattern=a.*b", "foo"], switches: [pattern: :regex]) + + assert Regex.match?(regex, "aXXXb") + + # Test Unicode support + assert {[pattern: regex], ["foo"], []} = + OptionParser.parse(["--pattern", "café.*résumé", "foo"], + switches: [pattern: :regex] + ) + + assert Regex.match?(regex, "café test résumé") + refute Regex.match?(regex, "ascii only") + + # Test invalid regex + assert OptionParser.parse(["--pattern", "[invalid", "foo"], switches: [pattern: :regex]) == + {[], ["foo"], [{"--pattern", "[invalid"}]} + end + + test "parses configured regexes with keep" do + argv = ["--pattern", "a.*", "--pattern", "b.*", "foo"] + + assert {[pattern: regex1, pattern: regex2], ["foo"], []} = + OptionParser.parse(argv, switches: [pattern: [:regex, :keep]]) + + assert Regex.match?(regex1, "aXXX") + assert Regex.match?(regex2, "bXXX") + refute Regex.match?(regex1, "bXXX") + refute Regex.match?(regex2, "aXXX") + + argv = ["--pattern=a.*", "foo", "--pattern=b.*", "bar"] + + assert {[pattern: regex1, pattern: regex2], ["foo", "bar"], []} = + OptionParser.parse(argv, switches: [pattern: [:regex, :keep]]) + + assert Regex.match?(regex1, "aXXX") + assert Regex.match?(regex2, "bXXX") + end + + test "correctly handles regex compilation errors" do + opts = [switches: [pattern: :regex]] + + # Invalid regex patterns should be treated as errors + assert OptionParser.parse(["--pattern", "*invalid"], opts) == + {[], [], [{"--pattern", "*invalid"}]} + + assert OptionParser.parse(["--pattern", "[unclosed"], opts) == + {[], [], [{"--pattern", "[unclosed"}]} + + assert OptionParser.parse(["--pattern", "(?invalid)"], opts) == + {[], [], [{"--pattern", "(?invalid)"}]} + end + + test "parse! raises an exception for invalid regex patterns" do + assert_raise OptionParser.ParseError, + ~r/Invalid regular expression \"\[invalid\": missing terminating \] for character class at position \d/, + fn -> + OptionParser.parse!(["--pattern", "[invalid"], switches: [pattern: :regex]) + end + + assert_raise OptionParser.ParseError, + ~r/Invalid regular expression \"\(\?invalid\)\": unrecognized character after \(\? or \(\?\- at position \d/, + fn -> + OptionParser.parse!(["--pattern", "(?invalid)"], switches: [pattern: :regex]) + end + end end describe "next" do diff --git a/lib/mix/lib/mix/tasks/test.ex b/lib/mix/lib/mix/tasks/test.ex index b7cb8cb4f73..0b1fa23e06d 100644 --- a/lib/mix/lib/mix/tasks/test.ex +++ b/lib/mix/lib/mix/tasks/test.ex @@ -153,6 +153,9 @@ defmodule Mix.Tasks.Test do * `--max-requires` - sets the maximum number of test files to compile in parallel. Setting this to 1 will compile test files sequentially. + * `-n`, `--name-pattern` *(since v1.19.0)* - only run tests with names that match + the given regular expression + * `--no-archives-check` - does not check archives * `--no-color` - disables color in the output @@ -475,6 +478,7 @@ defmodule Mix.Tasks.Test do include: :keep, exclude: :keep, seed: :integer, + name_pattern: :regex, only: :keep, compile: :boolean, start: :boolean, @@ -497,11 +501,13 @@ defmodule Mix.Tasks.Test do repeat_until_failure: :integer ] + @aliases [b: :breakpoints, n: :name_pattern] + @cover [output: "cover", tool: Mix.Tasks.Test.Coverage] @impl true def run(args) do - {opts, files} = OptionParser.parse!(args, strict: @switches, aliases: [b: :breakpoints]) + {opts, files} = OptionParser.parse!(args, strict: @switches, aliases: @aliases) if not Mix.Task.recursing?() do do_run(opts, args, files) @@ -700,8 +706,10 @@ defmodule Mix.Tasks.Test do end) excluded == total and Keyword.has_key?(opts, :only) -> - message = "The --only option was given to \"mix test\" but no test was executed" - raise_or_error_at_exit(shell, message, opts) + nothing_executed(shell, "--only", opts) + + excluded == total and Keyword.has_key?(opts, :name_pattern) -> + nothing_executed(shell, "--name-pattern", opts) true -> :ok @@ -756,6 +764,11 @@ defmodule Mix.Tasks.Test do Mix.raise(message) end + defp nothing_executed(shell, option, opts) do + message = "The #{option} option was given to \"mix test\" but no test was executed" + raise_or_error_at_exit(shell, message, opts) + end + defp raise_or_error_at_exit(shell, message, opts) do cond do opts[:raise] -> @@ -858,7 +871,7 @@ defmodule Mix.Tasks.Test do opts |> filter_opts(:include) |> filter_opts(:exclude) - |> filter_opts(:only) + |> filter_only_and_name_pattern() |> formatter_opts() |> color_opts() |> exit_status_opts() @@ -885,24 +898,30 @@ defmodule Mix.Tasks.Test do defp parse_filters(opts, key) do if Keyword.has_key?(opts, key) do ExUnit.Filters.parse(Keyword.get_values(opts, key)) + else + [] end end - defp filter_opts(opts, :only) do - if filters = parse_filters(opts, :only) do - opts - |> Keyword.update(:include, filters, &(filters ++ &1)) - |> Keyword.update(:exclude, [:test], &[:test | &1]) - else - opts + defp filter_only_and_name_pattern(opts) do + only = parse_filters(opts, :only) + name_patterns = opts |> Keyword.get_values(:name_pattern) |> Enum.map(&{:test, &1}) + + case only ++ name_patterns do + [] -> + opts + + filters -> + opts + |> Keyword.update(:include, filters, &(filters ++ &1)) + |> Keyword.update(:exclude, [:test], &[:test | &1]) end end defp filter_opts(opts, key) do - if filters = parse_filters(opts, key) do - Keyword.put(opts, key, filters) - else - opts + case parse_filters(opts, key) do + [] -> opts + filters -> Keyword.put(opts, key, filters) end end diff --git a/lib/mix/test/mix/tasks/test_test.exs b/lib/mix/test/mix/tasks/test_test.exs index f5d792ce473..e1ca04b14f5 100644 --- a/lib/mix/test/mix/tasks/test_test.exs +++ b/lib/mix/test/mix/tasks/test_test.exs @@ -9,31 +9,41 @@ defmodule Mix.Tasks.TestTest do describe "ex_unit_opts/1" do test "returns ex unit options" do - assert ex_unit_opts_from_given(unknown: "ok", seed: 13) == [seed: 13] + assert filtered_ex_unit_opts(unknown: "ok", seed: 13) == [seed: 13] end test "returns includes and excludes" do included = [include: [:focus, key: "val"]] - assert ex_unit_opts_from_given(include: "focus", include: "key:val") == included + assert filtered_ex_unit_opts(include: "focus", include: "key:val") == included excluded = [exclude: [:focus, key: "val"]] - assert ex_unit_opts_from_given(exclude: "focus", exclude: "key:val") == excluded + assert filtered_ex_unit_opts(exclude: "focus", exclude: "key:val") == excluded end test "translates :only into includes and excludes" do - assert ex_unit_opts_from_given(only: "focus") == [include: [:focus], exclude: [:test]] + assert filtered_ex_unit_opts(only: "focus") == [include: [:focus], exclude: [:test]] only = [include: [:focus, :special], exclude: [:test]] - assert ex_unit_opts_from_given(only: "focus", include: "special") == only + assert filtered_ex_unit_opts(only: "focus", include: "special") == only + end + + test "translates :name_pattern into includes and excludes" do + assert [include: [test: hello_regex, test: world_regex], exclude: [:test]] = + filtered_ex_unit_opts(name_pattern: ~r/hello/, name_pattern: ~r/world/) + + assert Regex.match?(hello_regex, "hello") + refute Regex.match?(hello_regex, "world") + refute Regex.match?(world_regex, "hello") + assert Regex.match?(world_regex, "world") end test "translates :color into list containing an enabled key-value pair" do - assert ex_unit_opts_from_given(color: false) == [colors: [enabled: false]] - assert ex_unit_opts_from_given(color: true) == [colors: [enabled: true]] + assert filtered_ex_unit_opts(color: false) == [colors: [enabled: false]] + assert filtered_ex_unit_opts(color: true) == [colors: [enabled: true]] end test "translates :formatter into list of modules" do - assert ex_unit_opts_from_given(formatter: "A.B") == [formatters: [A.B]] + assert filtered_ex_unit_opts(formatter: "A.B") == [formatters: [A.B]] end test "accepts custom :exit_status" do @@ -53,8 +63,8 @@ defmodule Mix.Tasks.TestTest do ex_unit_opts end - defp ex_unit_opts_from_given(passed) do - passed + defp filtered_ex_unit_opts(opts) do + opts |> Keyword.put(:failures_manifest_path, "foo.bar") |> ex_unit_opts() |> Keyword.drop([:failures_manifest_path, :autorun, :exit_status]) From 71e1ddc64e2fc48dabe753e1a4f5dfa55cc7e736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Sun, 27 Jul 2025 19:04:14 +0200 Subject: [PATCH 076/111] Return error on invalid unicode sequences (#14666) --- lib/elixir/src/elixir_parser.yrl | 8 ++- lib/elixir/src/elixir_tokenizer.erl | 101 +++++++++++++++++++++------ lib/elixir/test/elixir/code_test.exs | 37 ++++++++-- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index 4f3bc99a5af..70fa6bd7e7f 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -1016,7 +1016,13 @@ build_bin_string({bin_string, Location, Args}, ExtraMeta) -> {'<<>>', Meta, string_parts(Args)}. build_list_string({list_string, _Location, [H]} = Token, ExtraMeta) when is_binary(H) -> - handle_literal(elixir_utils:characters_to_list(H), Token, ExtraMeta); + try + List = elixir_utils:characters_to_list(H), + handle_literal(List, Token, ExtraMeta) + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + return_error(?location(Token), elixir_utils:characters_to_list(Message), "'") + end; build_list_string({list_string, Location, Args}, ExtraMeta) -> Meta = meta_from_location(Location), MetaWithExtra = diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 578bc340aac..59f6383b577 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -1023,15 +1023,33 @@ is_unnecessary_quote(_Parts, _Scope) -> unsafe_to_atom(Part, Line, Column, #elixir_tokenizer{}) when is_binary(Part) andalso byte_size(Part) > 255; is_list(Part) andalso length(Part) > 255 -> - {error, {?LOC(Line, Column), "atom length must be less than system limit: ", elixir_utils:characters_to_list(Part)}}; + try + PartList = elixir_utils:characters_to_list(Part), + {error, {?LOC(Line, Column), "atom length must be less than system limit: ", PartList}} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end; unsafe_to_atom(Part, Line, Column, #elixir_tokenizer{static_atoms_encoder=StaticAtomsEncoder}) when is_function(StaticAtomsEncoder) -> - Value = elixir_utils:characters_to_binary(Part), - case StaticAtomsEncoder(Value, [{line, Line}, {column, Column}]) of - {ok, Term} -> - {ok, Term}; - {error, Reason} when is_binary(Reason) -> - {error, {?LOC(Line, Column), elixir_utils:characters_to_list(Reason) ++ ": ", elixir_utils:characters_to_list(Part)}} + EncodeResult = try + ValueEncBin = elixir_utils:characters_to_binary(Part), + ValueEncList = elixir_utils:characters_to_list(Part), + {ok, ValueEncBin, ValueEncList} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end, + + case EncodeResult of + {ok, Value, ValueList} -> + case StaticAtomsEncoder(Value, [{line, Line}, {column, Column}]) of + {ok, Term} -> + {ok, Term}; + {error, Reason} when is_binary(Reason) -> + {error, {?LOC(Line, Column), elixir_utils:characters_to_list(Reason) ++ ": ", ValueList}} + end; + EncError -> EncError end; unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{existing_atoms_only=true}) when is_binary(Binary) -> try @@ -1039,9 +1057,14 @@ unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{existing_atoms_only=true} catch error:badarg -> % Check if it's a UTF-8 issue by trying to convert to list - elixir_utils:characters_to_list(Binary), - % If we get here, it's not a UTF-8 issue - {error, {?LOC(Line, Column), "unsafe atom does not exist: ", elixir_utils:characters_to_list(Binary)}} + try + List = elixir_utils:characters_to_list(Binary), + % If we get here, it's not a UTF-8 issue + {error, {?LOC(Line, Column), "unsafe atom does not exist: ", List}} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end end; unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{}) when is_binary(Binary) -> try @@ -1049,9 +1072,14 @@ unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{}) when is_binary(Binary) catch error:badarg -> % Try to convert using elixir_utils to get proper UnicodeConversionError - elixir_utils:characters_to_list(Binary), - % If we get here, it's not a UTF-8 issue, so it's some other badarg - {error, {?LOC(Line, Column), "invalid atom: ", elixir_utils:characters_to_list(Binary)}} + try + List = elixir_utils:characters_to_list(Binary), + % If we get here, it's not a UTF-8 issue, so it's some other badarg + {error, {?LOC(Line, Column), "invalid atom: ", List}} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end end; unsafe_to_atom(List, Line, Column, #elixir_tokenizer{existing_atoms_only=true}) when is_list(List) -> try @@ -1059,9 +1087,14 @@ unsafe_to_atom(List, Line, Column, #elixir_tokenizer{existing_atoms_only=true}) catch error:badarg -> % Try to convert using elixir_utils to get proper UnicodeConversionError - elixir_utils:characters_to_binary(List), - % If we get here, it's not a UTF-8 issue - {error, {?LOC(Line, Column), "unsafe atom does not exist: ", List}} + try + elixir_utils:characters_to_binary(List), + % If we get here, it's not a UTF-8 issue + {error, {?LOC(Line, Column), "unsafe atom does not exist: ", List}} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end end; unsafe_to_atom(List, Line, Column, #elixir_tokenizer{}) when is_list(List) -> try @@ -1069,9 +1102,14 @@ unsafe_to_atom(List, Line, Column, #elixir_tokenizer{}) when is_list(List) -> catch error:badarg -> % Try to convert using elixir_utils to get proper UnicodeConversionError - elixir_utils:characters_to_binary(List), - % If we get here, it's not a UTF-8 issue, so it's some other badarg - {error, {?LOC(Line, Column), "invalid atom: ", List}} + try + elixir_utils:characters_to_binary(List), + % If we get here, it's not a UTF-8 issue, so it's some other badarg + {error, {?LOC(Line, Column), "invalid atom: ", List}} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in atom: ", elixir_utils:characters_to_list(Message)}} + end end. collect_modifiers([H | T], Buffer) when ?is_downcase(H) or ?is_upcase(H) or ?is_digit(H) -> @@ -1095,7 +1133,12 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) -> {Parts1, {ShouldWarn, _}} = lists:mapfoldl(Fun, {false, Line}, Parts0), Parts2 = extract_heredoc_head(Parts1), NewScope = maybe_heredoc_warn(ShouldWarn, Column, InterScope, H), - {ok, NewLine, NewColumn, tokens_to_binary(Parts2), Rest, NewScope}; + try + {ok, NewLine, NewColumn, tokens_to_binary(Parts2), Rest, NewScope} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, interpolation_format(Message, " (for heredoc starting at line ~B)", [Line], Line, Column, [H, H, H], [H, H, H])} + end; {error, Reason} -> {error, interpolation_format(Reason, " (for heredoc starting at line ~B)", [Line], Line, Column, [H, H, H], [H, H, H])} @@ -1166,8 +1209,13 @@ unescape_tokens(Tokens, Line, Column, #elixir_tokenizer{unescape=true}) -> {error, Message, Token} -> {error, {?LOC(Line, Column), Message ++ ". Syntax error after: ", Token}} end; -unescape_tokens(Tokens, _Line, _Column, #elixir_tokenizer{unescape=false}) -> - {ok, tokens_to_binary(Tokens)}. +unescape_tokens(Tokens, Line, Column, #elixir_tokenizer{unescape=false}) -> + try + {ok, tokens_to_binary(Tokens)} + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + {error, {?LOC(Line, Column), "invalid encoding in tokens: ", elixir_utils:characters_to_list(Message)}} + end. tokens_to_binary(Tokens) -> [if is_list(Token) -> elixir_utils:characters_to_binary(Token); true -> Token end @@ -1671,7 +1719,14 @@ tokenize_sigil_contents([H | T] = Original, [S | _] = SigilName, Line, Column, S case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of {NewLine, NewColumn, Parts, Rest, NewScope} -> Indentation = nil, - add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <>); + try + add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <>) + catch + error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> + Sigil = [$~, S, H], + Message = " (for sigil ~ts starting at line ~B)", + interpolation_error(Message, [$~] ++ SigilName ++ Original, Scope, Tokens, Message, [Sigil, Line], Line, Column, [H], [sigil_terminator(H)]) + end; {error, Reason} -> Sigil = [$~, S, H], diff --git a/lib/elixir/test/elixir/code_test.exs b/lib/elixir/test/elixir/code_test.exs index 5be948fa38a..23bc49f0acb 100644 --- a/lib/elixir/test/elixir/code_test.exs +++ b/lib/elixir/test/elixir/code_test.exs @@ -556,24 +556,47 @@ defmodule CodeTest do assert token4 == "\\u" end - test "string_to_quoted raises UnicodeConversionError for invalid UTF-8 in quoted atoms and function calls" do + test "string_to_quoted returns error for invalid UTF-8 in strings" do invalid_utf8_cases = [ + # charlist + "'\\xFF'", + # charlist heredoc + "'''\n\\xFF\\\n'''" + ] + + for code <- invalid_utf8_cases do + assert {:error, {_, message, _}} = Code.string_to_quoted(code) + assert message =~ "invalid encoding starting at <<255>>" + end + end + + test "string_to_quoted returns error for invalid UTF-8 in quoted atoms and function calls" do + invalid_utf8_cases = [ + # charlist + # ~S{'\xFF'}, + # charlist heredoc + # ~s{'''\n\xFF\n'''}, # Quoted atom ~S{:"\xFF"}, ~S{:'\xFF'}, + # Quoted keyword identifier + ~S{["\xFF": 1]}, + ~S{['\xFF': 1]}, # Quoted function call ~S{foo."\xFF"()}, ~S{foo.'\xFF'()} ] for code <- invalid_utf8_cases do - assert_raise UnicodeConversionError, fn -> - Code.string_to_quoted!(code) - end + assert {:error, {_, message, detail}} = Code.string_to_quoted(code) + assert message =~ "invalid encoding in atom: " + assert detail =~ "invalid encoding starting at <<255>>" - assert_raise UnicodeConversionError, fn -> - Code.string_to_quoted!(code, existing_atoms_only: true) - end + assert {:error, {_, message, detail}} = + Code.string_to_quoted(code, existing_atoms_only: true) + + assert message =~ "invalid encoding in atom: " + assert detail =~ "invalid encoding starting at <<255>>" end end From 2c7fa47ad6e7d11de0910da4ceb017ec58abc4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 28 Jul 2025 08:28:51 +0200 Subject: [PATCH 077/111] Remove general catch on sigil token --- lib/elixir/src/elixir_tokenizer.erl | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 59f6383b577..3c38062ec17 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -1055,7 +1055,7 @@ unsafe_to_atom(Binary, Line, Column, #elixir_tokenizer{existing_atoms_only=true} try {ok, binary_to_existing_atom(Binary, utf8)} catch - error:badarg -> + error:badarg -> % Check if it's a UTF-8 issue by trying to convert to list try List = elixir_utils:characters_to_list(Binary), @@ -1719,14 +1719,7 @@ tokenize_sigil_contents([H | T] = Original, [S | _] = SigilName, Line, Column, S case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of {NewLine, NewColumn, Parts, Rest, NewScope} -> Indentation = nil, - try - add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <>) - catch - error:#{'__struct__' := 'Elixir.UnicodeConversionError', message := Message} -> - Sigil = [$~, S, H], - Message = " (for sigil ~ts starting at line ~B)", - interpolation_error(Message, [$~] ++ SigilName ++ Original, Scope, Tokens, Message, [Sigil, Line], Line, Column, [H], [sigil_terminator(H)]) - end; + add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <>); {error, Reason} -> Sigil = [$~, S, H], From 8d216b87cdb630a9eab017f6a8dfb92469ab2204 Mon Sep 17 00:00:00 2001 From: ice_cap <81338316+ice-cap0@users.noreply.github.com> Date: Mon, 4 Aug 2025 07:55:34 +0100 Subject: [PATCH 078/111] Update structs.md (#14683) --- lib/elixir/pages/getting-started/structs.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/elixir/pages/getting-started/structs.md b/lib/elixir/pages/getting-started/structs.md index 7654131b42e..ff817819b0d 100644 --- a/lib/elixir/pages/getting-started/structs.md +++ b/lib/elixir/pages/getting-started/structs.md @@ -76,6 +76,28 @@ iex> %User{} = %{} For more details on creating, updating, and pattern matching structs, see the documentation for `%/2`. +## Dynamic struct updates + +When you need to update structs with data from keyword lists or maps, use `Kernel.struct!/2`: + +```elixir +iex> john = %User{name: "John", age: 27} +%User{age: 27, name: "John"} +iex> updates = [name: "Jane", age: 30] +[name: "Jane", age: 30] +iex> struct!(john, updates) +%User{age: 27, name: "Jane"} +``` + +`struct!/2` will raise an error if you try to set invalid fields: + +```elixir +iex> struct!(john, invalid: "field") +** (KeyError) key :invalid not found in: %User{age: 27, name: "John"} +``` + +Use the map update syntax (`%{john | name: "Jane"}`) when you know the exact fields at compile time. Always use `struct!/2` instead of `Map` functions to preserve struct integrity. + ## Structs are bare maps underneath Structs are simply maps with a "special" field named `__struct__` that holds the name of the struct: From 764235f1daf24777f0b4b1c739083c68bd358bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Wed, 6 Aug 2025 08:49:12 +0200 Subject: [PATCH 079/111] Fix expand crash on invalid multialias root (#14698) --- lib/elixir/src/elixir_expand.erl | 24 ++++++++++++------- .../test/elixir/kernel/expansion_test.exs | 6 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl index 18a8d04cddf..6e81bc70e56 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -574,18 +574,24 @@ escape_map(Map) -> {'%{}', [], lists:sort(maps:to_list(Map))}. expand_multi_alias_call(Kind, Meta, Base, Refs, Opts, S, E) -> {BaseRef, SB, EB} = expand_without_aliases_report(Base, S, E), - Fun = fun - ({'__aliases__', _, Ref}, SR, ER) -> - expand({Kind, Meta, [elixir_aliases:concat([BaseRef | Ref]), Opts]}, SR, ER); + case is_atom(BaseRef) of + true -> + Fun = fun + ({'__aliases__', _, Ref}, SR, ER) -> + expand({Kind, Meta, [elixir_aliases:concat([BaseRef | Ref]), Opts]}, SR, ER); - (Ref, SR, ER) when is_atom(Ref) -> - expand({Kind, Meta, [elixir_aliases:concat([BaseRef, Ref]), Opts]}, SR, ER); + (Ref, SR, ER) when is_atom(Ref) -> + expand({Kind, Meta, [elixir_aliases:concat([BaseRef, Ref]), Opts]}, SR, ER); - (Other, _SR, _ER) -> - file_error(Meta, E, ?MODULE, {expected_compile_time_module, Kind, Other}) - end, + (Other, _SR, _ER) -> + file_error(Meta, E, ?MODULE, {expected_compile_time_module, Kind, Other}) + end, - mapfold(Fun, SB, EB, Refs). + mapfold(Fun, SB, EB, Refs); + + false -> + file_error(Meta, E, ?MODULE, {invalid_alias, Base}) + end. resolve_super(Meta, Arity, E) -> Module = assert_module_scope(Meta, super, E), diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index 9f77db07c13..becb79afb7c 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -118,6 +118,12 @@ defmodule Kernel.ExpansionTest do end) end + test "raises on multi-alias with non-atom base" do + assert_compile_error(~r"invalid alias: \"foo\"", fn -> + expand(quote(do: alias(foo.{Bar, Baz}))) + end) + end + test "invalid options" do assert_compile_error(~r"unsupported option :ops given to alias", fn -> expand(quote(do: alias(Foo, ops: 1))) From 297f1f3edf25c5ccfdc46db0ee7dd11b8de9263c Mon Sep 17 00:00:00 2001 From: Chris Hicks <1351942+mononym@users.noreply.github.com> Date: Wed, 6 Aug 2025 01:04:54 -0700 Subject: [PATCH 080/111] Add options to mix format to allow excluding of files (#14702) --- lib/mix/lib/mix/tasks/format.ex | 11 ++++++++++ lib/mix/test/mix/tasks/format_test.exs | 29 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/mix/lib/mix/tasks/format.ex b/lib/mix/lib/mix/tasks/format.ex index cbcddaea8f8..8d7e13a52b9 100644 --- a/lib/mix/lib/mix/tasks/format.ex +++ b/lib/mix/lib/mix/tasks/format.ex @@ -33,6 +33,10 @@ defmodule Mix.Tasks.Format do to be used by this task. For example, `["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]`. Patterns are expanded with `Path.wildcard/2`. + * `:excludes` (a list of paths and patterns) - specifies the files to exclude from the + list of inputs to this task. For example, `["config/runtime.exs", "test/**/*.{ex,exs}"]`. + Patterns are expanded with `Path.wildcard/2`. + * `:plugins` (a list of modules) (since v1.13.0) - specifies a list of modules to customize how the formatter works. See the "Plugins" section below for more information. @@ -634,9 +638,16 @@ defmodule Mix.Tasks.Format do Mix.raise("Expected :inputs or :subdirectories key in #{dot_formatter}") end + excluded_files = + formatter_opts[:excludes] + |> List.wrap() + |> Enum.flat_map(&Path.wildcard(Path.expand(&1, cwd), match_dot: true)) + |> MapSet.new() + map = for input <- List.wrap(formatter_opts[:inputs]), file <- Path.wildcard(Path.expand(input, cwd), match_dot: true), + file not in excluded_files, do: {file, {dot_formatter, formatter_opts}}, into: %{} diff --git a/lib/mix/test/mix/tasks/format_test.exs b/lib/mix/test/mix/tasks/format_test.exs index dc7d8eecead..d5d17bbace4 100644 --- a/lib/mix/test/mix/tasks/format_test.exs +++ b/lib/mix/test/mix/tasks/format_test.exs @@ -279,6 +279,35 @@ defmodule Mix.Tasks.FormatTest do end) end + test "ignores expanded patterns in excludes from .formatter.exs", context do + in_tmp(context.test, fn -> + File.write!(".formatter.exs", """ + [ + inputs: ["{a,.b}.ex"], + excludes: [".*.{ex,exs}"] + ] + """) + + File.write!("a.ex", """ + foo bar + """) + + File.write!(".b.ex", """ + foo bar + """) + + Mix.Tasks.Format.run([]) + + assert File.read!("a.ex") == """ + foo(bar) + """ + + assert File.read!(".b.ex") == """ + foo bar + """ + end) + end + defmodule Elixir.SigilWPlugin do @behaviour Mix.Tasks.Format From b566edc2abce83a73ca09e824e925bfe832b8721 Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Fri, 8 Aug 2025 01:43:30 -0500 Subject: [PATCH 081/111] ExUnit: Raise explaining what failed on invalid tags (#14707) --- lib/ex_unit/lib/ex_unit/case.ex | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex index f72160711c9..6af10d22b3d 100644 --- a/lib/ex_unit/lib/ex_unit/case.ex +++ b/lib/ex_unit/lib/ex_unit/case.ex @@ -940,9 +940,19 @@ defmodule ExUnit.Case do defp normalize_tags(tags) do Enum.reduce(Enum.reverse(tags), %{}, fn - {key, value}, acc -> Map.put(acc, key, value) - tag, acc when is_atom(tag) -> Map.put(acc, tag, true) - tag, acc when is_list(tag) -> Enum.into(tag, acc) + {key, value}, acc -> + Map.put(acc, key, value) + + tag, acc when is_atom(tag) -> + Map.put(acc, tag, true) + + tag, acc -> + if Keyword.keyword?(tag) do + Enum.into(tag, acc) + else + raise "an invalid value for a tag was used. " <> + "Expected an atom or a keyword list, got: #{inspect(tag)}" + end end) end end From ba0f3935d35b66e6b6a71075481623c7af6b4d97 Mon Sep 17 00:00:00 2001 From: Steve Cohen <55809+scohen@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:25:38 -0700 Subject: [PATCH 082/111] Fix filtering documentation (#14705) The filtering documentation implied that the msg attribute of the logger event map could be a binary, but according to the erlang types (https://www.erlang.org/doc/apps/kernel/logger.html#t:log_event/0) it can't be a binary. This change updates the docs with an example that comports with the actual typing. --- lib/logger/lib/logger.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex index 5591671e8c8..86dcd199569 100644 --- a/lib/logger/lib/logger.ex +++ b/lib/logger/lib/logger.ex @@ -436,8 +436,9 @@ defmodule Logger do defmodule LogFilter do def filter(log_event, _opts) do case log_event do - %{msg: msg} when is_binary(msg) -> - if msg =~ "password" do + %{msg: {:string, msg}} -> + # msg may be a charlist or a binary + if to_string(msg) =~ "password" do :stop else :ignore From 54369ba3e502b24dde60851c68fb412a4cac5e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 15 Aug 2025 19:39:05 +0200 Subject: [PATCH 083/111] Improve docs on DynamicSupervisor blocking operations --- lib/elixir/lib/dynamic_supervisor.ex | 80 ++++++---------------------- 1 file changed, 16 insertions(+), 64 deletions(-) diff --git a/lib/elixir/lib/dynamic_supervisor.ex b/lib/elixir/lib/dynamic_supervisor.ex index d910c684ad9..3ea2f250617 100644 --- a/lib/elixir/lib/dynamic_supervisor.ex +++ b/lib/elixir/lib/dynamic_supervisor.ex @@ -137,67 +137,6 @@ defmodule DynamicSupervisor do A supervisor is bound to the same name registration rules as a `GenServer`. Read more about these rules in the documentation for `GenServer`. - - ## Migrating from Supervisor's :simple_one_for_one - - In case you were using the deprecated `:simple_one_for_one` strategy from - the `Supervisor` module, you can migrate to the `DynamicSupervisor` in - few steps. - - Imagine the given "old" code: - - defmodule MySupervisor do - use Supervisor - - def start_link(init_arg) do - Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) - end - - def start_child(foo, bar, baz) do - # This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz) - Supervisor.start_child(__MODULE__, [foo, bar, baz]) - end - - @impl true - def init(init_arg) do - children = [ - # Or the deprecated: worker(MyWorker, [init_arg]) - %{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}} - ] - - Supervisor.init(children, strategy: :simple_one_for_one) - end - end - - It can be upgraded to the DynamicSupervisor like this: - - defmodule MySupervisor do - use DynamicSupervisor - - def start_link(init_arg) do - DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) - end - - def start_child(foo, bar, baz) do - # If MyWorker is not using the new child specs, we need to pass a map: - # spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}} - spec = {MyWorker, foo: foo, bar: bar, baz: baz} - DynamicSupervisor.start_child(__MODULE__, spec) - end - - @impl true - def init(init_arg) do - DynamicSupervisor.init( - strategy: :one_for_one, - extra_arguments: [init_arg] - ) - end - end - - The difference is that the `DynamicSupervisor` expects the child specification - at the moment `start_child/2` is called, and no longer on the init callback. - If there are any initial arguments given on initialization, such as `[initial_arg]`, - it can be given in the `:extra_arguments` flag on `DynamicSupervisor.init/1`. """ @behaviour GenServer @@ -233,9 +172,10 @@ defmodule DynamicSupervisor do @typedoc """ Return values of `start_child` functions. - Unlike `Supervisor`, this module ignores the child spec ids, so - `{:error, {:already_started, pid}}` is not returned for child specs given with the same id. - `{:error, {:already_started, pid}}` is returned however if a duplicate name is used when using + Unlike `Supervisor`, this module ignores the child spec ids, + so `{:error, {:already_started, pid}}` is not returned for child specs + given with the same id. `{:error, {:already_started, pid}}` is returned + however if a duplicate name is used when using [name registration](`m:GenServer#module-name-registration`). """ @type on_start_child :: @@ -415,6 +355,10 @@ defmodule DynamicSupervisor do `{:error, {:already_started, pid}}` is returned however if a duplicate name is used when using [name registration](`m:GenServer#module-name-registration`). + This function will block the `DynamicSupervisor` until the child initializes. + When starting too many processes dynamically, you may want to use a + `PartitionSupervisor` to split the work across multiple processes. + If the child process start function returns `{:ok, child}` or `{:ok, child, info}`, then child specification and PID are added to the supervisor and this function returns the same value. @@ -518,6 +462,14 @@ defmodule DynamicSupervisor do @doc """ Terminates the given child identified by `pid`. + This function will block the `DynamicSupervisor` until the child + terminates, which may take an arbitrary amount of time if the child + is trapping exits and implements its own terminate callback. + For this reason, it is often better to ask the child process + itself to terminate, often by declaring in its child spec it has + a restart strategy of `:transient` (or `:temporary`) and then + sending it a message to stop with reason `:shutdown`. + If successful, this function returns `:ok`. If there is no process with the given PID, this function returns `{:error, :not_found}`. """ From 99f2c8f2ea79e0a58ee553d134f396192f420efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Aug 2025 09:53:38 +0200 Subject: [PATCH 084/111] Fix docs for Macro.compile_apply/4 --- lib/elixir/lib/macro.ex | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index b09a418f7e1..14ff68d53f7 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -1765,12 +1765,17 @@ defmodule Macro do @doc """ Applies a `mod`, `function`, and `args` at compile-time in `caller`. - This is used when you want to programmatically invoke a macro at - compile-time. + This is used when you want to dynamically invoke a function at + compile-time and force it to be tracked as a compile-time dependency. + For example, this is used by `dbg/1` to force the `dbg_callback` + configuration to be a compile-time dependency. + + If you want to "invoke" a macro instead, remember macros are by + definition compile-time, and you can use `Macro.expand/2`. """ @doc since: "1.16.0" def compile_apply(mod, fun, args, caller) do - :elixir_env.trace({:remote_macro, [], mod, fun, length(args)}, caller) + :elixir_env.trace({:remote_function, [], mod, fun, length(args)}, %{caller | function: nil}) Kernel.apply(mod, fun, args) end From 1778bf211c0e9ab5ff1812ce61489a595d367c1e Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 23 Aug 2025 17:10:40 +0900 Subject: [PATCH 085/111] Inspect ill-formed structs as maps (#14718) --- lib/elixir/lib/inspect.ex | 34 ++++--------------- lib/elixir/lib/inspect/algebra.ex | 22 +++++++++++- .../test/elixir/calendar/date_range_test.exs | 21 ------------ lib/elixir/test/elixir/inspect_test.exs | 7 ++++ lib/elixir/test/elixir/range_test.exs | 8 ----- 5 files changed, 34 insertions(+), 58 deletions(-) diff --git a/lib/elixir/lib/inspect.ex b/lib/elixir/lib/inspect.ex index 7c20bcf8140..ff1c86d728f 100644 --- a/lib/elixir/lib/inspect.ex +++ b/lib/elixir/lib/inspect.ex @@ -662,35 +662,13 @@ end defimpl Inspect, for: Any do def inspect(%module{} = struct, opts) do - try do - module.__info__(:struct) - rescue - _ -> Inspect.Map.inspect_as_map(struct, opts) - else - info -> - if valid_struct?(info, struct) do - info = - for %{field: field} = map <- info, - field != :__exception__, - do: map - - Inspect.Map.inspect_as_struct(struct, Macro.inspect_atom(:literal, module), info, opts) - else - Inspect.Map.inspect_as_map(struct, opts) - end - end - end + info = + for %{field: field} = map <- module.__info__(:struct), + field != :__exception__, + do: map - defp valid_struct?(info, struct), do: valid_struct?(info, struct, map_size(struct) - 1) - - defp valid_struct?([%{field: field} | info], struct, count) when is_map_key(struct, field), - do: valid_struct?(info, struct, count - 1) - - defp valid_struct?([], _struct, 0), - do: true - - defp valid_struct?(_fields, _struct, _count), - do: false + Inspect.Map.inspect_as_struct(struct, Macro.inspect_atom(:literal, module), info, opts) + end def inspect_as_struct(map, name, infos, opts) do open = color_doc("#" <> name <> "<", :map, opts) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 62e039c938c..99b8d5cce55 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -396,7 +396,7 @@ defmodule Inspect.Algebra do def to_doc_with_opts(term, opts) def to_doc_with_opts(%_{} = struct, %Inspect.Opts{inspect_fun: fun} = opts) do - if opts.structs do + if opts.structs and valid_struct?(struct) do try do fun.(struct, opts) rescue @@ -453,6 +453,26 @@ defmodule Inspect.Algebra do fun.(arg, opts) |> pack_opts(opts) end + defp valid_struct?(%module{} = struct) do + try do + module.__info__(:struct) + rescue + _ -> false + else + info -> + valid_struct?(info, struct, map_size(struct) - 1) + end + end + + defp valid_struct?([%{field: field} | info], struct, count) when is_map_key(struct, field), + do: valid_struct?(info, struct, count - 1) + + defp valid_struct?([], _struct, 0), + do: true + + defp valid_struct?(_fields, _struct, _count), + do: false + defp pack_opts({_doc, %Inspect.Opts{}} = doc_opts, _opts), do: doc_opts defp pack_opts(doc, opts), do: {doc, opts} diff --git a/lib/elixir/test/elixir/calendar/date_range_test.exs b/lib/elixir/test/elixir/calendar/date_range_test.exs index 10fbf53df37..39465421ca4 100644 --- a/lib/elixir/test/elixir/calendar/date_range_test.exs +++ b/lib/elixir/test/elixir/calendar/date_range_test.exs @@ -149,27 +149,6 @@ defmodule Date.RangeTest do end describe "old date ranges" do - test "inspect" do - asc = %{ - __struct__: Date.Range, - first: ~D[2021-07-14], - first_in_iso_days: 738_350, - last: ~D[2021-07-17], - last_in_iso_days: 738_353 - } - - desc = %{ - __struct__: Date.Range, - first: ~D[2021-07-17], - first_in_iso_days: 738_353, - last: ~D[2021-07-14], - last_in_iso_days: 738_350 - } - - assert inspect(asc) == "Date.range(~D[2021-07-14], ~D[2021-07-17])" - assert inspect(desc) == "Date.range(~D[2021-07-17], ~D[2021-07-14], -1)" - end - test "enumerable" do asc = %{ __struct__: Date.Range, diff --git a/lib/elixir/test/elixir/inspect_test.exs b/lib/elixir/test/elixir/inspect_test.exs index ddc5cc04bd2..be589836b33 100644 --- a/lib/elixir/test/elixir/inspect_test.exs +++ b/lib/elixir/test/elixir/inspect_test.exs @@ -445,6 +445,13 @@ defmodule Inspect.MapTest do "%{__struct__: Inspect.MapTest.Public, foo: :bar, key: 1}" end + test "public modified struct with defimpl" do + map_set = MapSet.new([1, 2]) + + assert inspect(Map.put(map_set, :foo, :bar), custom_options: [sort_maps: true]) == + "%{__struct__: MapSet, foo: :bar, map: %{1 => [], 2 => []}}" + end + test "private struct" do assert inspect(%{__struct__: Private, key: 1}, custom_options: [sort_maps: true]) == "%{__struct__: Inspect.MapTest.Private, key: 1}" diff --git a/lib/elixir/test/elixir/range_test.exs b/lib/elixir/test/elixir/range_test.exs index 4f8783f261a..d273c992019 100644 --- a/lib/elixir/test/elixir/range_test.exs +++ b/lib/elixir/test/elixir/range_test.exs @@ -200,14 +200,6 @@ defmodule RangeTest do end describe "old ranges" do - test "inspect" do - asc = %{__struct__: Range, first: 1, last: 3} - desc = %{__struct__: Range, first: 3, last: 1} - - assert inspect(asc) == "1..3" - assert inspect(desc) == "3..1//-1" - end - test "enum" do asc = %{__struct__: Range, first: 1, last: 3} desc = %{__struct__: Range, first: 3, last: 1} From ff21a9d601f8dfc8c9774bc0f4e8cbeac34659c2 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sat, 30 Aug 2025 18:15:50 +0900 Subject: [PATCH 086/111] Add __escape__/1 and use it to fix Regex escaping in OTP28.1+ (#14720) Leverages newly added :re.import/1. https://github.com/erlang/otp/pull/9976 --- lib/elixir/lib/kernel.ex | 16 +++---- lib/elixir/lib/macro.ex | 60 ++++++++++++++++++++++++++ lib/elixir/lib/regex.ex | 41 ++++++++++++++++++ lib/elixir/src/elixir_quote.erl | 18 ++++++-- lib/elixir/test/elixir/macro_test.exs | 21 +++++++-- lib/elixir/test/elixir/test_helper.exs | 11 ++++- 6 files changed, 149 insertions(+), 18 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index 7c8e8545494..eca475437d4 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3814,13 +3814,6 @@ defmodule Kernel do {_, doc} when doc_attr? -> do_at_escape(name, doc) - %{__struct__: Regex, source: source, opts: opts} = regex -> - # TODO: Automatically deal with exported regexes - case :erlang.system_info(:otp_release) < [?2, ?8] do - true -> do_at_escape(name, regex) - false -> quote(do: Regex.compile!(unquote(source), unquote(opts))) - end - value -> do_at_escape(name, value) end @@ -6647,13 +6640,14 @@ defmodule Kernel do end defp compile_regex(binary_or_tuple, options) do - # TODO: Remove this when we require Erlang/OTP 28+ - case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do + bin_opts = :binary.list_to_bin(options) + + case is_binary(binary_or_tuple) do true -> - Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options))) + Macro.escape(Regex.compile!(binary_or_tuple, bin_opts)) false -> - quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options)))) + quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(bin_opts))) end end diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 14ff68d53f7..014840b8e91 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -843,6 +843,66 @@ defmodule Macro do `escape/2` is used to escape *values* (either directly passed or variable bound), while `quote/2` produces syntax trees for expressions. + + ## Dealing with references and other runtime values + + Macros work at compile-time and therefore `Macro.escape/1` can only escape values + that are valid during compilation, such as numbers, atoms, tuples, maps, binaries, + etc. + + However, you may have values at compile-time which cannot be escaped, such as + `reference`s and `pid`s, since the process or memory address they point to will + no longer exist once compilation completes. Attempting to escape said values will + raise an exception. This is a common issue when working with NIFs. + + Luckily, Elixir v1.19 introduces a mechanism that allows those values to be escaped, + as long as they are encapsulated by a struct within a module that defines the + `__escape__/1` function. This is possible as long as the reference has a natural + text or binary representation that can be serialized during compilation. + + Let's imagine we have the following struct: + + defmodule WrapperStruct do + defstruct [:ref] + + def new(...), do: %WrapperStruct{ref: ...} + + # efficiently dump to / load from binaries + def dump_to_binary(%WrapperStruct{ref: ref}), do: ... + def load_from_binary(binary), do: %WrapperStruct{ref: ...} + end + + Such a struct could not be used in module attributes or escaped with `Macro.escape/2`: + + defmodule Foo do + @my_struct WrapperStruct.new(...) + def my_struct, do: @my_struct + end + + ** (ArgumentError) cannot inject attribute @my_struct into function/macro because cannot escape #Reference<...> + + To address this, structs can re-define how they should be escaped by defining a custom + `__escape__/1` function which returns the AST. In our example: + + defmodule WrapperStruct do + # ... + + def __escape__(struct) do + # dump to a binary representation at compile-time + binary = dump_to_binary(struct) + quote do + # load from the binary representation at runtime + WrapperStruct.load_from_binary(unquote(Macro.escape(binary))) + end + end + end + + Now, our example above will be expanded as: + + def my_struct, do: WrapperStruct.load_from_binary(<<...>>) + + When implementing `__escape__/1`, you must ensure that the quoted expression + will evaluate to a struct that represents the one given as argument. """ @spec escape(term, escape_opts) :: t() def escape(expr, opts \\ []) do diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 8ca5a226d34..d07f4479c25 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -1000,4 +1000,45 @@ defmodule Regex do defp translate_options(<<>>, acc), do: acc defp translate_options(t, _acc), do: {:error, t} + + @doc false + def __escape__(%{__struct__: Regex} = regex) do + # OTP 28.0 introduced refs in patterns, which can't be used in AST anymore + # OTP 28.1 introduced :re.import/1 which allows us to work with pre-compiled binaries again + + pattern_ast = + cond do + # TODO: Remove this when we require Erlang/OTP 28+ + # Before OTP 28.0, patterns did not contain any refs and could be safely be escaped + :erlang.system_info(:otp_release) < [?2, ?8] -> + Macro.escape(regex.re_pattern) + + # OTP 28.1+ introduced the ability to export and import regexes from compiled binaries + Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) -> + {:ok, exported} = :re.compile(regex.source, [:export] ++ regex.opts) + + quote do + :re.import(unquote(Macro.escape(exported))) + end + + # TODO: Remove this when we require Erlang/OTP 28.1+ + # OTP 28.0 works in degraded mode performance-wise, we need to recompile from the source + true -> + quote do + {:ok, pattern} = + :re.compile(unquote(Macro.escape(regex.source)), unquote(Macro.escape(regex.opts))) + + pattern + end + end + + quote do + %{ + __struct__: unquote(Regex), + re_pattern: unquote(pattern_ast), + source: unquote(Macro.escape(regex.source)), + opts: unquote(Macro.escape(regex.opts)) + } + end + end end diff --git a/lib/elixir/src/elixir_quote.erl b/lib/elixir/src/elixir_quote.erl index 3f872cd7944..99dc12838ef 100644 --- a/lib/elixir/src/elixir_quote.erl +++ b/lib/elixir/src/elixir_quote.erl @@ -3,6 +3,9 @@ %% SPDX-FileCopyrightText: 2012 Plataformatec -module(elixir_quote). + +-feature(maybe_expr, enable). + -export([escape/3, linify/3, linify_with_context_counter/3, build/7, quote/2, has_unquotes/1, fun_to_quoted/1]). -export([dot/5, tail_list/3, list/2, validate_runtime/2, shallow_validate_ast/1]). %% Quote callbacks @@ -164,8 +167,17 @@ do_escape(BitString, _) when is_bitstring(BitString) -> end; do_escape(Map, Q) when is_map(Map) -> - TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))], - {'%{}', [], TT}; + maybe + #{'__struct__' := Module} ?= Map, + true ?= is_atom(Module), + {module, Module} ?= code:ensure_loaded(Module), + true ?= erlang:function_exported(Module, '__escape__', 1), + Module:'__escape__'(Map) + else + _ -> + TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))], + {'%{}', [], TT} + end; do_escape([], _) -> []; @@ -211,7 +223,7 @@ escape_map_key_value(K, V, Map, Q) -> "(it must be defined within a function instead). ", (bad_escape_hint())/binary>>); true -> {do_quote(K, Q), do_quote(V, Q)} - end. + end. find_tuple_ref(Tuple, Index) when Index > tuple_size(Tuple) -> nil; find_tuple_ref(Tuple, Index) -> diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index c3f752eea40..a9fe72a7f19 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -141,11 +141,26 @@ defmodule MacroTest do assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]} end - test "inspects container when a reference cannot be escaped" do - assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn -> - Macro.escape(%{~r/foo/ | re_pattern: {:re_pattern, 0, 0, 0, make_ref()}}) + test "escape container when a reference cannot be escaped" do + assert_raise ArgumentError, ~r"contains a reference", fn -> + Macro.escape(%{re_pattern: {:re_pattern, 0, 0, 0, make_ref()}}) end end + + @tag :re_import + test "escape regex will remove references and replace it by a call to :re.import/1" do + assert { + :%{}, + [], + [ + __struct__: Regex, + re_pattern: + {{:., [], [:re, :import]}, [], [{:{}, [], [:re_exported_pattern | _]}]}, + source: "foo", + opts: [] + ] + } = Macro.escape(~r/foo/) + end end describe "expand_once/2" do diff --git a/lib/elixir/test/elixir/test_helper.exs b/lib/elixir/test/elixir/test_helper.exs index 16df79ae49f..2ce3cd85236 100644 --- a/lib/elixir/test/elixir/test_helper.exs +++ b/lib/elixir/test/elixir/test_helper.exs @@ -132,11 +132,20 @@ cover_exclude = [] end +# OTP 28.1+ +re_import_exclude = + if Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do + [] + else + [:re_import] + end + ExUnit.start( trace: !!System.get_env("TRACE"), exclude: epmd_exclude ++ - os_exclude ++ line_exclude ++ distributed_exclude ++ source_exclude ++ cover_exclude, + os_exclude ++ + line_exclude ++ distributed_exclude ++ source_exclude ++ cover_exclude ++ re_import_exclude, include: line_include, assert_receive_timeout: String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT", "300")) ) From e1f34d09afa46fc4ff22cba660f91afee6ebf5d7 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sun, 31 Aug 2025 15:50:35 +0900 Subject: [PATCH 087/111] Shallow-validate the return of __escape__ (#14736) --- lib/elixir/src/elixir_quote.erl | 7 ++++++- lib/elixir/test/elixir/macro_test.exs | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/elixir/src/elixir_quote.erl b/lib/elixir/src/elixir_quote.erl index 99dc12838ef..4bf4421a447 100644 --- a/lib/elixir/src/elixir_quote.erl +++ b/lib/elixir/src/elixir_quote.erl @@ -172,7 +172,12 @@ do_escape(Map, Q) when is_map(Map) -> true ?= is_atom(Module), {module, Module} ?= code:ensure_loaded(Module), true ?= erlang:function_exported(Module, '__escape__', 1), - Module:'__escape__'(Map) + Expr = Module:'__escape__'(Map), + case shallow_valid_ast(Expr) of + true -> Expr; + false -> argument_error( + <<('Elixir.Kernel':inspect(Module))/binary, ".__escape__/1 returned invalid AST: ", ('Elixir.Kernel':inspect(Expr))/binary>>) + end else _ -> TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))], diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index a9fe72a7f19..aa46513330d 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -161,6 +161,26 @@ defmodule MacroTest do ] } = Macro.escape(~r/foo/) end + + defmodule EscapedStruct do + defstruct [:ast, :ref] + + def __escape__(%{ast: ast}), do: ast + end + + test "escape struct with custom __escape__ (valid AST)" do + struct = %EscapedStruct{ast: {:valid_ast, [], []}, ref: make_ref()} + + assert {:valid_ast, [], []} = Macro.escape(struct) + end + + test "escape struct with custom __escape__ (shallow invalid AST)" do + struct = %EscapedStruct{ast: %{invalid: :ast}, ref: make_ref()} + + assert_raise ArgumentError, + "MacroTest.EscapedStruct.__escape__/1 returned invalid AST: %{invalid: :ast}", + fn -> Macro.escape(struct) end + end end describe "expand_once/2" do From 1fadefe25f2d196d9143bd8424c67a4155a1aef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Samson?= Date: Sun, 31 Aug 2025 09:50:55 +0200 Subject: [PATCH 088/111] Catch-all clause for unbalanced terminators (#14694) --- lib/elixir/lib/code/fragment.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex index 7e5323c5fad..c4dcd2b8e87 100644 --- a/lib/elixir/lib/code/fragment.ex +++ b/lib/elixir/lib/code/fragment.ex @@ -1332,7 +1332,7 @@ defmodule Code.Fragment do defp drop_tokens([{:do, _} | tokens], counter), do: drop_tokens(tokens, counter + 1) defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter) - defp drop_tokens([], 0), do: [] + defp drop_tokens([], _counter), do: [] defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true From 71bb017b616601569909e2588be526dca09199ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 10:04:33 +0200 Subject: [PATCH 089/111] Raise if message in AssertionError is not a binary Closes #14695. --- lib/ex_unit/lib/ex_unit/assertions.ex | 10 ++++++++++ lib/ex_unit/test/ex_unit/assertions_test.exs | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex index 816aafa24f6..9b3a019ace4 100644 --- a/lib/ex_unit/lib/ex_unit/assertions.ex +++ b/lib/ex_unit/lib/ex_unit/assertions.ex @@ -38,6 +38,16 @@ defmodule ExUnit.AssertionError do @no_value end + @impl true + def exception(opts) do + with {:ok, message} when not is_binary(message) <- Keyword.fetch(opts, :message) do + raise ArgumentError, + ":message in ExUnit.Assertion must be a binary, got: #{inspect(message)}" + end + + struct(__MODULE__, opts) + end + @impl true def message(exception) do "\n\n" <> ExUnit.Formatter.format_assertion_error(exception) diff --git a/lib/ex_unit/test/ex_unit/assertions_test.exs b/lib/ex_unit/test/ex_unit/assertions_test.exs index 7254edd5e5a..0574343408a 100644 --- a/lib/ex_unit/test/ex_unit/assertions_test.exs +++ b/lib/ex_unit/test/ex_unit/assertions_test.exs @@ -857,6 +857,22 @@ defmodule ExUnit.AssertionsTest do "assertion" = error.message end + test "assert operator with custom options" do + assert 1 > 2, message: "assertion" + flunk("This should never be tested") + rescue + error in [ExUnit.AssertionError] -> + "assertion" = error.message + end + + test "assert operator with invalid options" do + assert 1 > 2, message: 123 + flunk("This should never be tested") + rescue + error in [ArgumentError] -> + ":message in ExUnit.Assertion must be a binary, got: 123" = error.message + end + test "assert lack of equality" do try do assert "one" != "one" From 6fbc6e08a0117a4d18c43de9e37d3fa710adb76b Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Mon, 18 Aug 2025 22:29:23 +0200 Subject: [PATCH 090/111] Advance line when processing ? followed by and \ Closes #14715. --- lib/elixir/src/elixir_tokenizer.erl | 18 ++++++++++++++++-- lib/elixir/test/elixir/kernel/parser_test.exs | 10 ++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 3c38062ec17..3da98a908ce 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -241,7 +241,14 @@ tokenize([$?, $\\, H | T], Line, Column, Scope, Tokens) -> end, Token = {char, {Line, Column, [$?, $\\, H]}, Char}, - tokenize(T, Line, Column + 3, NewScope, [Token | Tokens]); + case Char of + $\n -> + %% If Char is a literal line feed, we already emit a warning, + %% but we need to bump the line without emitting an EOL token. + tokenize_eol(T, Line, NewScope, [Token | Tokens]); + _ -> + tokenize(T, Line, Column + 3, NewScope, [Token | Tokens]) + end; tokenize([$?, Char | T], Line, Column, Scope, Tokens) -> NewScope = case handle_char(Char) of @@ -253,7 +260,14 @@ tokenize([$?, Char | T], Line, Column, Scope, Tokens) -> Scope end, Token = {char, {Line, Column, [$?, Char]}, Char}, - tokenize(T, Line, Column + 2, NewScope, [Token | Tokens]); + case Char of + $\n -> + %% If Char is a literal line feed, we already emit a warning, + %% but we need to bump the line without emitting an EOL token. + tokenize_eol(T, Line, NewScope, [Token | Tokens]); + _ -> + tokenize(T, Line, Column + 2, NewScope, [Token | Tokens]) + end; % Heredocs diff --git a/lib/elixir/test/elixir/kernel/parser_test.exs b/lib/elixir/test/elixir/kernel/parser_test.exs index 650a1bdd8af..093642134fb 100644 --- a/lib/elixir/test/elixir/kernel/parser_test.exs +++ b/lib/elixir/test/elixir/kernel/parser_test.exs @@ -1429,6 +1429,16 @@ defmodule Kernel.ParserTest do assert_syntax_error(["nofile:1:5:", "syntax error before: ?す"], ~c":ok ?す") end + test "character literals take newlines into account" do + ExUnit.CaptureIO.capture_io(:stderr, fn -> + assert parse!("{?\n}\n{123}") == + {:__block__, [], [{:{}, [line: 1], ~c"\n"}, {:{}, [line: 3], ~c"{"}]} + + assert parse!("{?\\\n}\n{123}") == + {:__block__, [], [{:{}, [line: 1], ~c"\n"}, {:{}, [line: 3], ~c"{"}]} + end) + end + test "numbers are printed correctly in syntax errors" do assert_syntax_error(["nofile:1:5:", ~s/syntax error before: "12"/], ~c":ok 12") assert_syntax_error(["nofile:1:5:", ~s/syntax error before: "0b1"/], ~c":ok 0b1") From 2bb27ea1288901d30d8a2685bed4f2cd1c159b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 10:50:57 +0200 Subject: [PATCH 091/111] Only break newlines if original char is a newline --- lib/elixir/src/elixir_tokenizer.erl | 6 +++--- lib/elixir/test/elixir/kernel/parser_test.exs | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 3da98a908ce..a5b2cba495e 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -241,9 +241,9 @@ tokenize([$?, $\\, H | T], Line, Column, Scope, Tokens) -> end, Token = {char, {Line, Column, [$?, $\\, H]}, Char}, - case Char of + case H of $\n -> - %% If Char is a literal line feed, we already emit a warning, + %% If original char is a literal line feed, we already emit a warning, %% but we need to bump the line without emitting an EOL token. tokenize_eol(T, Line, NewScope, [Token | Tokens]); _ -> @@ -262,7 +262,7 @@ tokenize([$?, Char | T], Line, Column, Scope, Tokens) -> Token = {char, {Line, Column, [$?, Char]}, Char}, case Char of $\n -> - %% If Char is a literal line feed, we already emit a warning, + %% If original char is a literal line feed, we already emit a warning, %% but we need to bump the line without emitting an EOL token. tokenize_eol(T, Line, NewScope, [Token | Tokens]); _ -> diff --git a/lib/elixir/test/elixir/kernel/parser_test.exs b/lib/elixir/test/elixir/kernel/parser_test.exs index 093642134fb..c3a6e734caa 100644 --- a/lib/elixir/test/elixir/kernel/parser_test.exs +++ b/lib/elixir/test/elixir/kernel/parser_test.exs @@ -1434,6 +1434,9 @@ defmodule Kernel.ParserTest do assert parse!("{?\n}\n{123}") == {:__block__, [], [{:{}, [line: 1], ~c"\n"}, {:{}, [line: 3], ~c"{"}]} + assert parse!("{?\\n}\n{123}") == + {:__block__, [], [{:{}, [line: 1], ~c"\n"}, {:{}, [line: 2], ~c"{"}]} + assert parse!("{?\\\n}\n{123}") == {:__block__, [], [{:{}, [line: 1], ~c"\n"}, {:{}, [line: 3], ~c"{"}]} end) From d507502ecde6ec492a01572e0c5f5a377b17d875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 10:56:33 +0200 Subject: [PATCH 092/111] Update bidi/line break character checks according to UX#55 --- lib/elixir/src/elixir_interpolation.erl | 7 +++-- lib/elixir/src/elixir_tokenizer.erl | 19 +++++++------ lib/elixir/src/elixir_tokenizer.hrl | 8 ++++++ lib/elixir/test/elixir/kernel/parser_test.exs | 28 +++++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/lib/elixir/src/elixir_interpolation.erl b/lib/elixir/src/elixir_interpolation.erl index 16670d5f3e0..bb2d2608256 100644 --- a/lib/elixir/src/elixir_interpolation.erl +++ b/lib/elixir/src/elixir_interpolation.erl @@ -89,9 +89,12 @@ extract(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last) -> extract_char(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last) -> case unicode_util:gc(Rest) of - [Char | _] when ?bidi(Char) -> + [Char | _] when ?bidi(Char); ?break(Char) -> Token = io_lib:format("\\u~4.16.0B", [Char]), - Pre = "invalid bidirectional formatting character in string: ", + Pre = if + ?bidi(Char) -> "invalid bidirectional formatting character in string: "; + true -> "invalid line break character in string: " + end, Pos = io_lib:format(". If you want to use such character, use it in its escaped ~ts form instead", [Token]), {error, {?LOC(Line, Column), {Pre, Pos}, Token}}; diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index a5b2cba495e..9badbe51b1b 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -198,8 +198,8 @@ tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) -> tokenize([$# | String], Line, Column, Scope, Tokens) -> case tokenize_comment(String, [$#]) of - {error, Char} -> - error_comment(Char, [$# | String], Line, Column, Scope, Tokens); + {error, Char, Reason} -> + error_comment(Char, Reason, [$# | String], Line, Column, Scope, Tokens); {Rest, Comment} -> preserve_comments(Line, Column, Tokens, Comment, Rest, Scope), tokenize(Rest, Line, Column, Scope, reset_eol(Tokens)) @@ -748,8 +748,8 @@ tokenize_dot(T, Line, Column, DotInfo, Scope, Tokens) -> case strip_horizontal_space(T, 0) of {[$# | R], _} -> case tokenize_comment(R, [$#]) of - {error, Char} -> - error_comment(Char, [$# | R], Line, Column, Scope, Tokens); + {error, Char, Reason} -> + error_comment(Char, Reason, [$# | R], Line, Column, Scope, Tokens); {Rest, Comment} -> preserve_comments(Line, Column, Tokens, Comment, Rest, Scope), @@ -1315,16 +1315,17 @@ tokenize_comment("\r\n" ++ _ = Rest, Acc) -> tokenize_comment("\n" ++ _ = Rest, Acc) -> {Rest, lists:reverse(Acc)}; tokenize_comment([H | _Rest], _) when ?bidi(H) -> - {error, H}; + {error, H, "invalid bidirectional formatting character in comment: "}; +tokenize_comment([H | _Rest], _) when ?break(H) -> + {error, H, "invalid line break character in comment: "}; tokenize_comment([H | Rest], Acc) -> tokenize_comment(Rest, [H | Acc]); tokenize_comment([], Acc) -> {[], lists:reverse(Acc)}. -error_comment(H, Comment, Line, Column, Scope, Tokens) -> - Token = io_lib:format("\\u~4.16.0B", [H]), - Reason = {?LOC(Line, Column), "invalid bidirectional formatting character in comment: ", Token}, - error(Reason, Comment, Scope, Tokens). +error_comment(Char, Reason, Comment, Line, Column, Scope, Tokens) -> + Token = io_lib:format("\\u~4.16.0B", [Char]), + error({?LOC(Line, Column), Reason, Token}, Comment, Scope, Tokens). preserve_comments(Line, Column, Tokens, Comment, Rest, Scope) -> case Scope#elixir_tokenizer.preserve_comments of diff --git a/lib/elixir/src/elixir_tokenizer.hrl b/lib/elixir/src/elixir_tokenizer.hrl index 664e353b8a1..88325989597 100644 --- a/lib/elixir/src/elixir_tokenizer.hrl +++ b/lib/elixir/src/elixir_tokenizer.hrl @@ -33,3 +33,11 @@ C =:= 16#2068; C =:= 16#202C; C =:= 16#2069). + +%% Unsupported newlines +%% https://www.unicode.org/reports/tr55/ +-define(break(C), C =:= 16#000B; + C =:= 16#000C; + C =:= 16#0085; + C =:= 16#2028; + C =:= 16#2029). \ No newline at end of file diff --git a/lib/elixir/test/elixir/kernel/parser_test.exs b/lib/elixir/test/elixir/kernel/parser_test.exs index c3a6e734caa..5c07cc5881b 100644 --- a/lib/elixir/test/elixir/kernel/parser_test.exs +++ b/lib/elixir/test/elixir/kernel/parser_test.exs @@ -982,6 +982,34 @@ defmodule Kernel.ParserTest do ) end + test "invalid newline in source" do + assert_syntax_error( + ["nofile:1:1:", ~s/invalid line break character in comment: \\u2028/], + ~c"# This is a \u2028" + ) + + assert_syntax_error( + ["nofile:1:5:", "invalid line break character in comment: \\u2028"], + ~c"foo. # This is a \u2028" + ) + + assert_syntax_error( + [ + "nofile:1:12:", + "invalid line break character in string: \\u2028. If you want to use such character, use it in its escaped \\u2028 form instead" + ], + ~c"\"this is a \u2028\"" + ) + + assert_syntax_error( + [ + "nofile:1:13:", + "invalid line break character in string: \\u2028. If you want to use such character, use it in its escaped \\u2028 form instead" + ], + ~c"\"this is a \\\u2028\"" + ) + end + test "reserved tokens" do assert_syntax_error(["nofile:1:1:", "reserved token: __aliases__"], ~c"__aliases__") assert_syntax_error(["nofile:1:1:", "reserved token: __block__"], ~c"__block__") From d458fcb9f5f487fccb96d0977fdf27545158cbcf Mon Sep 17 00:00:00 2001 From: Eksperimental Date: Wed, 27 Aug 2025 01:06:14 -0500 Subject: [PATCH 093/111] Fix order in ExUnit results when listing pinned variables (#14723) The pinned variables were returned in a random order (often reversed): test/ex_unit_pinned_variables_order_test.exs:23 match (=) failed The following variables were pinned: var_d = "four" var_c = "three" var_b = "two" var_a = "one" code: assert %{a: ^var_d, b: ^var_c, c: ^var_b, d: ^var_a} = build(var_a, var_b, var_c, var_d) left: %{a: ^var_d, b: ^var_c, c: ^var_b, d: ^var_a} right: %{a: "one", b: "two", c: "three", d: "four"} stacktrace: test/ex_unit_pinned_variables_order_test.exs:29: (test) This fix sorts them alphabetically. This bug was introduced in 884e93391e when the pinned vars were now accumulated in a map (instead of a list). A repo replicating the issue can be found here: - https://github.com/eksperimental-debug/elixir_debug/tree/ex-unit-pinned-variables-order - https://github.com/eksperimental-debug/elixir_debug/blob/ex-unit-pinned-variables-order/ex_unit_pinned_variables_order/test/ex_unit_pinned_variables_order_test.exs --- lib/ex_unit/lib/ex_unit/assertions.ex | 2 +- lib/ex_unit/test/ex_unit/assertions_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex index 9b3a019ace4..897821c4196 100644 --- a/lib/ex_unit/lib/ex_unit/assertions.ex +++ b/lib/ex_unit/lib/ex_unit/assertions.ex @@ -624,7 +624,7 @@ defmodule ExUnit.Assertions do def __pins__(pins) do pins |> Enum.filter(fn {{_, ctx}, _} -> ctx == nil end) - |> Enum.reverse() + |> Enum.sort() |> Enum.map_join(@indent, fn {{name, _}, var} -> "#{name} = #{inspect(var)}" end) |> case do "" -> diff --git a/lib/ex_unit/test/ex_unit/assertions_test.exs b/lib/ex_unit/test/ex_unit/assertions_test.exs index 0574343408a..10ebacb3d57 100644 --- a/lib/ex_unit/test/ex_unit/assertions_test.exs +++ b/lib/ex_unit/test/ex_unit/assertions_test.exs @@ -524,8 +524,8 @@ defmodule ExUnit.AssertionsTest do """ Assertion failed, no matching message after 0ms The following variables were pinned: - status = :valid other_status = :invalid + status = :valid Showing 1 of 1 message in the mailbox\ """ = error.message From a96c04f260f5640e394e79f3147235e451b654fa Mon Sep 17 00:00:00 2001 From: Jesse Stimpson Date: Fri, 22 Aug 2025 09:31:51 -0400 Subject: [PATCH 094/111] Improve docs on iex remote shell halt behaviour (#14721) --- lib/iex/lib/iex.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/iex/lib/iex.ex b/lib/iex/lib/iex.ex index fc5cf9b7b91..b57aa3b2a32 100644 --- a/lib/iex/lib/iex.ex +++ b/lib/iex/lib/iex.ex @@ -323,6 +323,8 @@ defmodule IEx do Connecting an Elixir shell to a remote node without Elixir is **not** supported. + When remsh halts, the Elixir shell process exits with reason `:normal`. + ## The .iex.exs file When starting, IEx looks for a configured path, then for a local `.iex.exs` file From 44c506957304be813aa25549aa0f07110d6b9499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 11:11:53 +0200 Subject: [PATCH 095/111] Update CHANGELOG --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c54f40b4af6..56be9f2d07a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,6 +194,34 @@ These additions offer greater transparency into the components and licenses of e This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosystem Foundation. +## v1.19.0-rc.1 + +### 1. Enhancements + +#### Elixir + + * [Kernel] Raise when U+2028 and U+2029 characters are present in comments and strings to avoid line spoofing attacks + * [Macro] Add `__escape__/1` callback so structs can escape references and other runtime data types in `Macro.escape/1` + * [OptionParser] Support the `:regex` type + * [OptionParser] Enhance parsing error to display available options + +#### Mix + + * [mix format] Add options to mix format to allow excluding of files + * [mix test] Add `--name-pattern` option to `mix test` + * [Mix.install/2] Support the `:compilers` option + +### 2. Bug fixes + +#### Elixir + + * [Code] Return error on invalid unicode sequences in `Code.string_to_quoted/2` instead of raising + * [Kernel] Properly increment metadata newline when `?` is followed ny a literal newline character + +#### ExUnit + + * [ExUnit.Assertions] Fix order in ExUnit results when listing pinned variables + ## v1.19.0-rc.0 (2025-06-09) ### 1. Enhancements From 61603a87258e41d9026d817dedeb1cd384af7df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 11:20:33 +0200 Subject: [PATCH 096/111] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56be9f2d07a..a2492764413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -216,11 +216,17 @@ This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosyst #### Elixir * [Code] Return error on invalid unicode sequences in `Code.string_to_quoted/2` instead of raising + * [Inspect] Inspect ill-formed structs as maps * [Kernel] Properly increment metadata newline when `?` is followed ny a literal newline character #### ExUnit * [ExUnit.Assertions] Fix order in ExUnit results when listing pinned variables + * [ExUnit.Assertions] Raise if attempting to raise an assertion error with invalid message (not a binary) + +#### Mix + + * [mix test] Prevent `mix test` from overriding `:failures_manifest_path` option ## v1.19.0-rc.0 (2025-06-09) From b3e3e8c6aa39b601459b681ca3164edf06de592a Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Sun, 31 Aug 2025 19:20:26 +0900 Subject: [PATCH 097/111] Do not consider variables from pattern in bitstring modifier (#14738) --- lib/elixir/src/elixir_bitstring.erl | 10 +++++++--- lib/elixir/test/elixir/kernel/expansion_test.exs | 11 +++++++++++ lib/elixir/test/elixir/kernel/warning_test.exs | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/elixir/src/elixir_bitstring.erl b/lib/elixir/src/elixir_bitstring.erl index 9f31a583c88..8e9c6f8e5bb 100644 --- a/lib/elixir/src/elixir_bitstring.erl +++ b/lib/elixir/src/elixir_bitstring.erl @@ -30,7 +30,10 @@ expand(Meta, Args, S, E, RequireSize) -> expand(_BitstrMeta, _Fun, [], Acc, S, E, Alignment, _RequireSize) -> {lists:reverse(Acc), Alignment, S, E}; expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment, RequireSize) -> - {ELeft, {SL, OriginalS}, EL} = expand_expr(Left, Fun, S, E), + % We don't want to consider variables added in the Left pattern inside the Right specs + {#elixir_ex{vars=BeforeVars}, _} = S, + + {ELeft, {#elixir_ex{vars=AfterVars} = SL, OriginalS}, EL} = expand_expr(Left, Fun, S, E), validate_expr(ELeft, Meta, E), MatchOrRequireSize = RequireSize or is_match_size(T, EL), @@ -40,10 +43,11 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment, {'^', _, [{_, _, _}]} -> {infer, ELeft}; _ -> required end, - {ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL, OriginalS, EL, ExpectSize), + + {ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL#elixir_ex{vars=BeforeVars}, OriginalS, EL, ExpectSize), EAcc = concat_or_prepend_bitstring(Meta, ELeft, ERight, Acc, ES, MatchOrRequireSize), - expand(BitstrMeta, Fun, T, EAcc, {SS, OriginalS}, ES, alignment(Alignment, EAlignment), RequireSize); + expand(BitstrMeta, Fun, T, EAcc, {SS#elixir_ex{vars=AfterVars}, OriginalS}, ES, alignment(Alignment, EAlignment), RequireSize); expand(BitstrMeta, Fun, [H | T], Acc, S, E, Alignment, RequireSize) -> Meta = extract_meta(H, BitstrMeta), {ELeft, {SS, OriginalS}, ES} = expand_expr(H, Fun, S, E), diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index becb79afb7c..2d247fd8e76 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -2836,6 +2836,17 @@ defmodule Kernel.ExpansionTest do end) end + test "raises for variable used both in pattern and size" do + assert_compile_error(~r/undefined variable "foo"/, fn -> + code = + quote do + fn <> -> :ok end + end + + expand(code, []) + end) + end + test "raises for invalid unit" do message = ~r"unit in bitstring expects an integer as argument, got: :oops" diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index eb0dd65930b..4d7c44eace9 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -2237,6 +2237,20 @@ defmodule Kernel.WarningTest do purge(Sample) end + test "deprecate non-quoted variables in bitstring size modifiers" do + assert_warn_eval( + [ + "the variable \"a\" is accessed inside size(...) of a bitstring but it was defined outside of the match", + "You must precede it with the pin operator" + ], + """ + a = "foo" + <> <> _ = a + "fo" = a + """ + ) + end + defp assert_compile_error(messages, string) do captured = capture_err(fn -> From 0fca9beaf7c467965ebc5ea811c830b44178b452 Mon Sep 17 00:00:00 2001 From: sabiwara Date: Sun, 31 Aug 2025 20:10:59 +0900 Subject: [PATCH 098/111] Remove test for warning disabled on 1.19 --- lib/elixir/test/elixir/kernel/warning_test.exs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index 4d7c44eace9..eb0dd65930b 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -2237,20 +2237,6 @@ defmodule Kernel.WarningTest do purge(Sample) end - test "deprecate non-quoted variables in bitstring size modifiers" do - assert_warn_eval( - [ - "the variable \"a\" is accessed inside size(...) of a bitstring but it was defined outside of the match", - "You must precede it with the pin operator" - ], - """ - a = "foo" - <> <> _ = a - "fo" = a - """ - ) - end - defp assert_compile_error(messages, string) do captured = capture_err(fn -> From 8ac8230e18dbee8538291b40757fd4f864290d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 13:00:30 +0200 Subject: [PATCH 099/111] Properly handle column for 'in' in 'not in' operator Closes #14681. --- lib/elixir/src/elixir_parser.yrl | 5 +++-- lib/elixir/src/elixir_tokenizer.erl | 4 ++-- lib/elixir/test/elixir/kernel/parser_test.exs | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index 70fa6bd7e7f..15dab255cc3 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -752,8 +752,9 @@ build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) w {UOp, Meta, [{'in', Meta, [Left, Right]}]}; build_op(Left, {_Kind, Location, 'not in'}, Right) -> - Meta = meta_from_location(Location), - {'not', Meta, [{'in', Meta, [Left, Right]}]}; + NotMeta = meta_from_location(Location), + InMeta = meta_from_location(element(3, Location)), + {'not', NotMeta, [{'in', InMeta, [Left, Right]}]}; build_op(Left, {_Kind, Location, Op}, Right) -> {Op, newlines_op(Location) ++ meta_from_location(Location), [Left, Right]}. diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 9badbe51b1b..2b8343e59be 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -1670,8 +1670,8 @@ tokenize_keyword(Kind, Rest, Line, Column, Atom, Length, Scope, Tokens) -> _ -> case {Kind, Tokens} of - {in_op, [{unary_op, NotInfo, 'not'} | T]} -> - add_token_with_eol({in_op, NotInfo, 'not in'}, T); + {in_op, [{unary_op, {NotLine, NotColumn, _}, 'not'} | T]} -> + add_token_with_eol({in_op, {NotLine, NotColumn, {Line, Column, nil}}, 'not in'}, T); {_, _} -> add_token_with_eol({Kind, {Line, Column, previous_was_eol(Tokens)}, Atom}, Tokens) diff --git a/lib/elixir/test/elixir/kernel/parser_test.exs b/lib/elixir/test/elixir/kernel/parser_test.exs index 5c07cc5881b..d7b94e4f1a3 100644 --- a/lib/elixir/test/elixir/kernel/parser_test.exs +++ b/lib/elixir/test/elixir/kernel/parser_test.exs @@ -447,6 +447,22 @@ defmodule Kernel.ParserTest do {:ok, {:=, context, [List.to_string(nfd_abba), 1]}} end + test "not in" do + assert Code.string_to_quoted!("a not in b", columns: true) == + {:not, [line: 1, column: 3], + [ + {:in, [line: 1, column: 7], + [{:a, [line: 1, column: 1], nil}, {:b, [line: 1, column: 10], nil}]} + ]} + + assert Code.string_to_quoted!("a not in b", columns: true) == + {:not, [line: 1, column: 3], + [ + {:in, [line: 1, column: 8], + [{:a, [line: 1, column: 1], nil}, {:b, [line: 1, column: 11], nil}]} + ]} + end + test "handles maps and structs" do assert Code.string_to_quoted("%{}", columns: true) == {:ok, {:%{}, [line: 1, column: 1], []}} From f56139aa2ca35d3919dcf2957dbb0225160d1a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 31 Aug 2025 13:06:53 +0200 Subject: [PATCH 100/111] Add closing token metadata to a.{}, closes #14682 --- lib/elixir/src/elixir_parser.yrl | 2 +- lib/elixir/test/elixir/kernel/parser_test.exs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index 15dab255cc3..ce966cfbbff 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -479,7 +479,7 @@ dot_identifier -> matched_expr dot_op identifier : build_dot('$2', '$1', '$3'). dot_alias -> alias : build_alias('$1'). dot_alias -> matched_expr dot_op alias : build_dot_alias('$2', '$1', '$3'). -dot_alias -> matched_expr dot_op open_curly '}' : build_dot_container('$2', '$1', [], []). +dot_alias -> matched_expr dot_op open_curly '}' : build_dot_container('$2', '$1', [], newlines_pair('$3', '$4')). dot_alias -> matched_expr dot_op open_curly container_args close_curly : build_dot_container('$2', '$1', '$4', newlines_pair('$3', '$5')). dot_op_identifier -> op_identifier : '$1'. diff --git a/lib/elixir/test/elixir/kernel/parser_test.exs b/lib/elixir/test/elixir/kernel/parser_test.exs index d7b94e4f1a3..cb082c74681 100644 --- a/lib/elixir/test/elixir/kernel/parser_test.exs +++ b/lib/elixir/test/elixir/kernel/parser_test.exs @@ -702,6 +702,24 @@ defmodule Kernel.ParserTest do ]} end + test "adds opening and closing information for tuples" do + string_to_quoted = &Code.string_to_quoted!(&1, token_metadata: true, columns: true) + + assert string_to_quoted.("{}") == + {:{}, [closing: [line: 1, column: 2], line: 1, column: 1], []} + + assert string_to_quoted.("{123}") == + {:{}, [closing: [line: 1, column: 5], line: 1, column: 1], [123]} + + assert string_to_quoted.("x.{}") == + {{:., [line: 1, column: 2], [{:x, [line: 1, column: 1], nil}, :{}]}, + [closing: [line: 1, column: 4], line: 1, column: 2], []} + + assert string_to_quoted.("x.{123}") == + {{:., [line: 1, column: 2], [{:x, [line: 1, column: 1], nil}, :{}]}, + [closing: [line: 1, column: 7], line: 1, column: 2], [123]} + end + test "adds opening and closing information for empty block" do string_to_quoted = &Code.string_to_quoted!(&1, token_metadata: true, columns: true, emit_warnings: false) From cc797de249953f189a55f3645320e5ad13d839ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Wed, 3 Sep 2025 22:14:09 +0200 Subject: [PATCH 101/111] tighten CI secret scope and move AWS config to environment vars (#14627) * Add `environment: release` to the "publish-to-hex" job so that only workflows explicitly targeting the release environment can read sensitive values. * Gate the job behind `if: ${{ vars.HEX_AWS_REGION }}` to avoid noisy failures in forks where the variable is not configured. * Replace `${{ secrets.HEX_AWS_REGION }}` / `${{ secrets.HEX_AWS_S3_BUCKET }}` references with `${{ vars.* }}`. These are not credentials, so environment-level *variables* are a better fit and keep them readable only by jobs that declare the environment. * Remove Fastly secrets from the job-wide `env:` block and inject them only into the Fastly purge step, following the principle of least privilege. Other steps no longer see these tokens. Restricting secret visibility to an environment and to the exact step that needs them reduces the blast radius of a compromised workflow run, blocks accidental exposure in logs of unrelated steps, and stops forks from obtaining privileged data. --- .github/workflows/release.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4aeec8b9486..8b9537d1eb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -303,14 +303,14 @@ jobs: needs: [build, sign] runs-on: ubuntu-22.04 concurrency: builds-hex-pm + environment: release + # Only run if HEX_AWS_REGION is set (no failing job in forks) + if: "${{ vars.HEX_AWS_REGION }}" env: AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.HEX_AWS_REGION }} - AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }} - FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }} - FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }} - FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }} + AWS_REGION: ${{ vars.HEX_AWS_REGION }} + AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }} OTP_GENERIC_VERSION: "25" steps: - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -417,3 +417,7 @@ jobs: for key in $(cat purge_keys.txt); do purge "${key}" done + env: + FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }} + FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }} + FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }} From d0634c9188659cfb622085af1005e11b5cc489c1 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Fri, 5 Sep 2025 21:24:39 +0900 Subject: [PATCH 102/111] Fix infinite loop: Enum.take/2 with negative index on empty enum (#14747) --- lib/elixir/lib/calendar/date_range.ex | 2 +- lib/elixir/lib/enum.ex | 18 +++++++++++++----- .../test/elixir/calendar/date_range_test.exs | 4 ++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/elixir/lib/calendar/date_range.ex b/lib/elixir/lib/calendar/date_range.ex index a92b9d124f5..5e8c652b781 100644 --- a/lib/elixir/lib/calendar/date_range.ex +++ b/lib/elixir/lib/calendar/date_range.ex @@ -95,7 +95,7 @@ defmodule Date.Range do [date_from_iso_days(current, calendar)] end - defp slice(current, step, remaining, calendar) do + defp slice(current, step, remaining, calendar) when remaining > 1 do [ date_from_iso_days(current, calendar) | slice(current + step, step, remaining - 1, calendar) diff --git a/lib/elixir/lib/enum.ex b/lib/elixir/lib/enum.ex index 4e23ff0a519..08a85a07db2 100644 --- a/lib/elixir/lib/enum.ex +++ b/lib/elixir/lib/enum.ex @@ -3611,9 +3611,14 @@ defmodule Enum do end def take(enumerable, amount) when is_integer(amount) and amount < 0 do - {count, fun} = slice_count_and_fun(enumerable, 1) - first = Kernel.max(amount + count, 0) - fun.(first, count - first, 1) + case slice_count_and_fun(enumerable, 1) do + {0, _fun} -> + [] + + {count, fun} -> + first = Kernel.max(amount + count, 0) + fun.(first, count - first, 1) + end end @doc """ @@ -5118,6 +5123,9 @@ defimpl Enumerable, for: Range do slice(Map.put(range, :step, step)) end - defp slice(_current, _step, 0), do: [] - defp slice(current, step, remaining), do: [current | slice(current + step, step, remaining - 1)] + defp slice(current, _step, 1), do: [current] + + defp slice(current, step, remaining) when remaining > 1 do + [current | slice(current + step, step, remaining - 1)] + end end diff --git a/lib/elixir/test/elixir/calendar/date_range_test.exs b/lib/elixir/test/elixir/calendar/date_range_test.exs index 39465421ca4..fe6cab37e59 100644 --- a/lib/elixir/test/elixir/calendar/date_range_test.exs +++ b/lib/elixir/test/elixir/calendar/date_range_test.exs @@ -101,6 +101,10 @@ defmodule Date.RangeTest do end end + test "Enum.take/1 for empty range with negative step" do + assert Enum.take(@empty_range, -1) == [] + end + test "works with date-like structs" do range = Date.range(~N[2000-01-01 09:00:00], ~U[2000-01-02 09:00:00Z]) assert range.first == ~D[2000-01-01] From c8f8d02cc0a60fb04be40c4606086913279c3f87 Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Tue, 9 Sep 2025 17:51:09 +0900 Subject: [PATCH 103/111] Fix dialyzer opaqueness warnings on module attrs in OTP28 (#14755) * Mark module attributes as generated in case they contain opaque terms * Add and use Macro.escape(ast, generated: true) --- lib/elixir/lib/kernel.ex | 4 +++- lib/elixir/lib/macro.ex | 16 +++++++++++++++- .../test/elixir/fixtures/dialyzer/opaqueness.ex | 7 ++++++- lib/elixir/test/elixir/macro_test.exs | 5 +++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index eca475437d4..b792c3ad8f3 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -3859,7 +3859,9 @@ defmodule Kernel do defp do_at_escape(name, value) do try do - :elixir_quote.escape(value, :none, false) + # mark module attrs as shallow-generated since the ast for their representation + # might contain opaque terms + Macro.escape(value, generated: true) rescue ex in [ArgumentError] -> raise ArgumentError, diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 014840b8e91..1ce25f6859d 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -808,6 +808,10 @@ defmodule Macro do nodes. Note this option changes the semantics of escaped code and it should only be used when escaping ASTs. Defaults to `false`. + * `:generated` - (since v1.19.0) Whether the AST should be considered as generated + by the compiler or not. This means the compiler and tools like Dialyzer may not + emit certain warnings. + As an example for `:prune_metadata`, `ExUnit` stores the AST of every assertion, so when an assertion fails we can show code snippets to users. Without this option, each time the test module is compiled, we would get a @@ -908,7 +912,17 @@ defmodule Macro do def escape(expr, opts \\ []) do unquote = Keyword.get(opts, :unquote, false) kind = if Keyword.get(opts, :prune_metadata, false), do: :prune_metadata, else: :none - :elixir_quote.escape(expr, kind, unquote) + generated = Keyword.get(opts, :generated, false) + + case :elixir_quote.escape(expr, kind, unquote) do + # mark module attrs as shallow-generated since the ast for their representation + # might contain opaque terms + {caller, meta, args} when generated and is_list(meta) -> + {caller, [generated: true] ++ meta, args} + + ast -> + ast + end end # TODO: Deprecate me on Elixir v1.22 diff --git a/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex b/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex index 12857f515fe..d8371c386e5 100644 --- a/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex +++ b/lib/elixir/test/elixir/fixtures/dialyzer/opaqueness.ex @@ -4,11 +4,16 @@ defmodule Dialyzer.Opaqueness do set end - def foo() do + def inlined do # inlining of literals should not violate opaqueness check bar(MapSet.new([1, 2, 3])) end + @my_set MapSet.new([1, 2, 3]) + def module_attr do + bar(@my_set) + end + # Task.Supervisor returns a Task.t() containing an opaque Task.ref() @spec run_task() :: Task.t() def run_task do diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index aa46513330d..ac1be32a100 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -84,6 +84,11 @@ defmodule MacroTest do assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest} end + test "with generated" do + assert Macro.escape(%{a: {}}, generated: true) == + {:%{}, [generated: true], [a: {:{}, [], []}]} + end + defp eval_escaped(contents) do {eval, []} = Code.eval_quoted(Macro.escape(contents, unquote: true)) eval From 51ac9bc324cdbcafce789a08c8fd0c5dadcf6984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 9 Sep 2025 15:30:54 +0200 Subject: [PATCH 104/111] Accept any enumerable in Logger.metadata/1 --- lib/logger/lib/logger.ex | 20 ++++++++++---------- lib/logger/test/logger_test.exs | 9 ++++----- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex index 86dcd199569..2294f799efe 100644 --- a/lib/logger/lib/logger.ex +++ b/lib/logger/lib/logger.ex @@ -585,24 +585,24 @@ defmodule Logger do end @doc """ - Alters the current process metadata according to the given keyword list. + Alters the current process metadata according to the given enumerable. - This function will merge the given keyword list into the existing metadata, + This function will merge the given enumerable into the existing metadata, with the exception of setting a key to `nil`, which will remove that key from the metadata. Note some metadata keys are reserved and cannot be overridden. See [the module documentation](#module-metadata) for more information. """ - @spec metadata(metadata) :: :ok - def metadata(keyword) do + @spec metadata(Enumerable.t({atom(), term()})) :: :ok + def metadata(enumerable) do case :logger.get_process_metadata() do :undefined -> - reset_metadata(keyword) + reset_metadata(enumerable) map when is_map(map) -> metadata = - Enum.reduce(keyword, map, fn + Enum.reduce(enumerable, map, fn {k, nil}, acc -> Map.delete(acc, k) {k, v}, acc -> Map.put(acc, k, v) end) @@ -626,11 +626,11 @@ defmodule Logger do end @doc """ - Resets the current process metadata to the given keyword list. + Resets the current process metadata to the given enumerable. """ - @spec reset_metadata(metadata) :: :ok - def reset_metadata(keyword \\ []) do - :ok = :logger.set_process_metadata(filter_out_nils(keyword)) + @spec reset_metadata(Enumerable.t({atom(), term()})) :: :ok + def reset_metadata(enumerable \\ []) do + :ok = :logger.set_process_metadata(filter_out_nils(enumerable)) end defp filter_out_nils(keyword) do diff --git a/lib/logger/test/logger_test.exs b/lib/logger/test/logger_test.exs index 776e3a3635a..c5bac9ecffc 100644 --- a/lib/logger/test/logger_test.exs +++ b/lib/logger/test/logger_test.exs @@ -190,15 +190,14 @@ defmodule LoggerTest do assert Logger.metadata() == [data: true] assert Logger.metadata(data: true) == :ok assert Logger.metadata() == [data: true] - assert Logger.metadata(meta: 1) == :ok - metadata = Logger.metadata() - assert Enum.sort(metadata) == [data: true, meta: 1] - assert Logger.metadata(data: nil) == :ok + assert Logger.metadata(%{meta: 1}) == :ok + assert Enum.sort(Logger.metadata()) == [data: true, meta: 1] + assert Logger.metadata(%{data: nil}) == :ok assert Logger.metadata() == [meta: 1] assert Logger.reset_metadata(meta: 2) == :ok assert Logger.metadata() == [meta: 2] - assert Logger.reset_metadata(data: true, app: nil) == :ok + assert Logger.reset_metadata(%{data: true, app: nil}) == :ok assert Logger.metadata() == [data: true] assert Logger.reset_metadata() == :ok assert Logger.metadata() == [] From bbcd7f3094bce347d31499ed6e27d6a4c4abeb66 Mon Sep 17 00:00:00 2001 From: Nathan Long Date: Tue, 9 Sep 2025 15:53:07 -0400 Subject: [PATCH 105/111] ExUnit sets a process label for each test (#14758) --- lib/ex_unit/lib/ex_unit/runner.ex | 1 + lib/ex_unit/test/ex_unit_test.exs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/ex_unit/lib/ex_unit/runner.ex b/lib/ex_unit/lib/ex_unit/runner.ex index ff4c3b218fc..8c8356d1f5c 100644 --- a/lib/ex_unit/lib/ex_unit/runner.ex +++ b/lib/ex_unit/lib/ex_unit/runner.ex @@ -435,6 +435,7 @@ defmodule ExUnit.Runner do context ) do spawn_monitor(fn -> + Process.set_label({test.case, test.name}) ExUnit.OnExitHandler.register(self()) generate_test_seed(seed, test, rand_algorithm) context = context |> Map.merge(test.tags) |> Map.put(:test_pid, self()) diff --git a/lib/ex_unit/test/ex_unit_test.exs b/lib/ex_unit/test/ex_unit_test.exs index 336b0bd4b85..540f8b53790 100644 --- a/lib/ex_unit/test/ex_unit_test.exs +++ b/lib/ex_unit/test/ex_unit_test.exs @@ -1234,6 +1234,14 @@ defmodule ExUnitTest do end end + test "sets process label for each test" do + # TODO: Remove check once we support Erlang/OTP 27+ + if function_exported?(:proc_lib, :get_label, 1) do + label = apply(:proc_lib, :get_label, [self()]) + assert label == {ExUnitTest, :"test sets process label for each test"} + end + end + defp configure_and_reload_on_exit(opts) do old_opts = ExUnit.configuration() From 7f45befed6c63be4b6e0cbb027c22ee805c7fb2f Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Wed, 10 Sep 2025 07:45:02 +0900 Subject: [PATCH 106/111] Have mix test fail if warnings and --warnings-as-errors (#14756) --- .../compatibility-and-deprecations.md | 2 +- lib/mix/lib/mix/tasks/test.ex | 16 ++++++++++------ lib/mix/test/mix/tasks/test_test.exs | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md index f44bf9acd0d..1d5e9118d89 100644 --- a/lib/elixir/pages/references/compatibility-and-deprecations.md +++ b/lib/elixir/pages/references/compatibility-and-deprecations.md @@ -34,7 +34,7 @@ Although we expect the vast majority of programs to remain compatible over time, * Bugs: if an API has undesired behavior, a program that depends on the buggy behavior may break if the bug is fixed. We reserve the right to fix such bugs. - * Compiler front-end: improvements may be done to the compiler, introducing new warnings for ambiguous modes and providing more detailed error messages. Those can lead to compilation errors (when running with `--warning-as-errors`) or tooling failures when asserting on specific error messages (although one should avoid such). We reserve the right to do such improvements. + * Compiler front-end: improvements may be done to the compiler, introducing new warnings for ambiguous modes and providing more detailed error messages. Those can lead to compilation errors (when running with `--warnings-as-errors`) or tooling failures when asserting on specific error messages (although one should avoid such). We reserve the right to do such improvements. * Imports: new functions may be added to the `Kernel` module, which is auto-imported. They may collide with local functions defined in your modules. Collisions can be resolved in a backwards compatible fashion using `import Kernel, except: [...]` with a list of all functions you don't want to be imported from `Kernel`. We reserve the right to do such additions. diff --git a/lib/mix/lib/mix/tasks/test.ex b/lib/mix/lib/mix/tasks/test.ex index 0b1fa23e06d..af1e494e00e 100644 --- a/lib/mix/lib/mix/tasks/test.ex +++ b/lib/mix/lib/mix/tasks/test.ex @@ -666,10 +666,12 @@ defmodule Mix.Tasks.Test do warn_files != [] && warn_misnamed_test_files(warn_files) try do - Enum.each(test_paths, &require_test_helper(shell, &1)) + helper_warned? = Enum.any?(test_paths, &require_test_helper(shell, &1)) # test_opts always wins because those are given via args ExUnit.configure(ex_unit_opts |> merge_helper_opts() |> Keyword.merge(test_opts)) - CT.require_and_run(matched_test_files, test_paths, test_elixirc_options, opts) + + {CT.require_and_run(matched_test_files, test_paths, test_elixirc_options, opts), + helper_warned?} catch kind, reason -> # Also mark the whole suite as failed @@ -677,12 +679,13 @@ defmodule Mix.Tasks.Test do ExUnit.Filters.fail_all!(file) :erlang.raise(kind, reason, __STACKTRACE__) else - {:ok, %{excluded: excluded, failures: failures, warnings?: warnings?, total: total}} -> + {{:ok, %{excluded: excluded, failures: failures, warnings?: warnings?, total: total}}, + helper_warned?} -> Mix.shell(shell) cover && cover.() cond do - warnings_as_errors? and warnings? and failures == 0 -> + warnings_as_errors? and (warnings? or helper_warned?) and failures == 0 -> message = "\nERROR! Test suite aborted after successful execution due to warnings while using the --warnings-as-errors option" @@ -715,7 +718,7 @@ defmodule Mix.Tasks.Test do :ok end - :noop -> + {:noop, _} -> cond do opts[:stale] -> Mix.shell().info("No stale tests") @@ -1024,7 +1027,8 @@ defmodule Mix.Tasks.Test do file = Path.join(dir, "test_helper.exs") if File.exists?(file) do - Code.require_file(file) + {_result, warnings} = Code.with_diagnostics([log: true], fn -> Code.require_file(file) end) + warnings != [] else raise_with_shell( shell, diff --git a/lib/mix/test/mix/tasks/test_test.exs b/lib/mix/test/mix/tasks/test_test.exs index e1ca04b14f5..3c92a5a2ea8 100644 --- a/lib/mix/test/mix/tasks/test_test.exs +++ b/lib/mix/test/mix/tasks/test_test.exs @@ -593,6 +593,25 @@ defmodule Mix.Tasks.TestTest do assert output =~ "2 failures" end) end + + test "fail with exit status 1 if warning in test_helper.exs" do + in_fixture("test_stale", fn -> + File.write!("test/test_helper.exs", """ + unused_var = 123 + + ExUnit.start() + """) + + msg = + "Test suite aborted after successful execution due to warnings while using the --warnings-as-errors option" + + {output, exit_status} = mix_code(["test", "--warnings-as-errors"]) + + assert output =~ "variable \"unused_var\" is unused" + assert output =~ msg + assert exit_status == 1 + end) + end end describe "--exit-status" do From fd9dbf8490bf43bf02ce022ff8b1370f81a30e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 10 Sep 2025 09:09:44 +0200 Subject: [PATCH 107/111] Update Unicode to version 17.0.0 (#14760) This is an automated commit created by the Maintenance project https://github.com/eksperimental/maintenance Please read the release notes by visiting . --- lib/elixir/lib/string.ex | 2 +- lib/elixir/pages/references/unicode-syntax.md | 26 +- .../elixir/kernel/string_tokenizer_test.exs | 4 +- lib/elixir/unicode/IdentifierType.txt | 3435 +++++++++++++++-- lib/elixir/unicode/PropList.txt | 72 +- lib/elixir/unicode/PropertyValueAliases.txt | 35 +- lib/elixir/unicode/ScriptExtensions.txt | 38 +- lib/elixir/unicode/Scripts.txt | 142 +- lib/elixir/unicode/SpecialCasing.txt | 14 +- lib/elixir/unicode/UnicodeData.txt | 489 ++- lib/elixir/unicode/confusables.txt | 499 ++- lib/elixir/unicode/tokenizer.ex | 6 +- lib/elixir/unicode/unicode.ex | 2 +- 13 files changed, 4179 insertions(+), 585 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 73d59f31901..44aaffe088c 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -22,7 +22,7 @@ defmodule String do "hello world" The functions in this module act according to - [The Unicode Standard, Version 16.0.0](http://www.unicode.org/versions/Unicode16.0.0/). + [The Unicode Standard, Version 17.0.0](http://www.unicode.org/versions/Unicode17.0.0/). ## Interpolation diff --git a/lib/elixir/pages/references/unicode-syntax.md b/lib/elixir/pages/references/unicode-syntax.md index e2de2ad98fc..e0b05cca3c5 100644 --- a/lib/elixir/pages/references/unicode-syntax.md +++ b/lib/elixir/pages/references/unicode-syntax.md @@ -48,7 +48,7 @@ For the technical details, see the next sections that cover the technical Unicod ## Unicode Annex #31 -Elixir implements the requirements outlined in the [Unicode Annex #31](https://unicode.org/reports/tr31/), version 15.0. +Elixir implements the requirements outlined in the [Unicode Annex #31](https://unicode.org/reports/tr31/), version 17.0. ### R1. Default Identifiers @@ -112,23 +112,21 @@ Choosing requirement R4 automatically excludes requirements R5, R6, and R7. ## Unicode Technical Standard #39 -Elixir conforms to the clauses outlined in the [Unicode Technical Standard #39](https://unicode.org/reports/tr39/) on Security, version 15.0. +Elixir conforms to the clauses outlined in the [Unicode Technical Standard #39](https://unicode.org/reports/tr39/) on Security, version 17.0. ### C1. General Security Profile for Identifiers -Elixir will not allow tokenization of identifiers with codepoints in `\p{Identifier_Status=Restricted}`. +Elixir will not allow tokenization of identifiers with codepoints in `\p{Identifier_Status=Restricted}`, except for the outlined 'Additional normalizations' section below. > An implementation following the General Security Profile does not permit any characters in \p{Identifier_Status=Restricted}, ... -For instance, the 'HANGUL FILLER' (`ㅤ`) character, which is often invisible, is an uncommon codepoint and will trigger this warning. - -See the note below about additional normalizations, which can perform automatic replacement of some Restricted identifiers. +For instance, the 'HANGUL FILLER' (`ㅤ`) character, which is often invisible, is an uncommon codepoint and will trigger a warning. ### C2. Confusable detection Elixir will warn on identifiers that look the same, but aren't. Examples: in `а = a = 1`, the two 'a' characters are Cyrillic and Latin, and could be confused for each other; in `力 = カ = 1`, both are Japanese, but different codepoints, in different scripts of that writing system. Confusable identifiers can lead to hard-to-catch bugs (say, due to copy-pasted code) and can be unsafe, so we will warn about identifiers within a single file that could be confused with each other. -We use the means described in Section 4, 'Confusable Detection', with one noted modification +We use the means described in Section 4, 'Confusable Detection', with one noted modification: > Alternatively, it shall declare that it uses a modification, and provide a precise list of character mappings that are added to or removed from the provided ones. @@ -136,9 +134,9 @@ Elixir will not warn on confusability for identifiers made up exclusively of cha ### C3. Mixed Script Detection -Elixir will not allow tokenization of mixed-script identifiers unless it is via chunks separated by an underscore, like `http_сервер`. We use the means described in Section 5.1, Mixed-Script Detection, to determine if script mixing is occurring, with the modification documented in the section 'Additional Normalizations', below. +Elixir will not allow tokenization of mixed-script identifiers unless it is via chunks separated by an underscore, like `http_сервер`. We use the means described in Section 5.1, Mixed-Script Detection, to determine if script mixing is occurring, with the 'Additional Normalizations' documented in. -Examples: Elixir allows an identifiers like `幻ㄒㄧㄤ`, even though it includes characters from multiple 'scripts', because those scripts all 'resolve' to Japanese when applying the resolution rules from UTS 39 5.1. When mixing Latin and Japanese scripts, underscores are necessary, as in `:T_シャツ` (the Japanese word for 't-shirt' with an additional underscore separating the letter T). +Examples: Elixir allows an identifiers like `幻한`, even though it includes characters from multiple 'scripts', as Han characters may be mixed with Japanese and Korean, according to the rules from UTS 39 5.1. When mixing Latin and Japanese scripts, underscores are necessary, as in `:T_シャツ` (the Japanese word for 't-shirt' with an additional underscore separating the letter T). Elixir does not allow code like `if аdmin, do: :ok, else: :err`, where the scriptset for the 'a' character is {Cyrillic} but all other characters have scriptsets of {Latin}. The scriptsets fail to resolve and a descriptive error is shown. @@ -148,16 +146,14 @@ Elixir does not allow code like `if аdmin, do: :ok, else: :err`, where the scri 'C5 - Mixed number detection' conformance is inapplicable as Elixir does not support Unicode numbers. -### Addition normalizations and documented UTS 39 modifications +### Addition Normalizations As of Elixir 1.14, some codepoints in `\p{Identifier_Status=Restricted}` are *normalized* to other, unrestricted codepoints. -Initially this is only done to translate MICRO SIGN `µ` to Greek lowercase mu, `μ`. - -This is not a modification of UTS39 clauses C1 (General Security Profile) or C2 (Confusability Detection); however, it is a documented modification of C3, 'Mixed-Script detection'. +This is currently only applied to translate MICRO SIGN (`µ`) to Greek lowercase mu (`μ`). -Mixed-script detection is modified by these normalizations to the extent that the normalized codepoint is given the union of scriptsets from both characters. +The normalization avoids confusability and the mixed-script detection is modified to the extent that the normalized codepoint is given the union of scriptsets from both characters. - * For instance, in the example of MICRO => MU, Micro was a 'Common'-script character -- the same script given to the '_' underscore codepoint -- and thus the normalized character's scriptset will be {Greek, Common}. 'Common' intersects with all non-empty scriptsets, and thus the normalized character can be used in tokens written in any script without causing script mixing. + * For instance, in the example of MICRO => MU, MICRO was a 'Common'-script character - the same script given to the '_' underscore codepoint - and thus the normalized character's scriptset will be {Greek, Common}. 'Common' intersects with all non-empty scriptsets, and thus the normalized character can be used in tokens written in any script without causing script mixing. * The code points normalized in this fashion are those that are in use in the community, and judged not likely to cause issues with unsafe script mixing. For instance, the MICRO or MU codepoint may be used in an atom or variable dealing with microseconds. diff --git a/lib/elixir/test/elixir/kernel/string_tokenizer_test.exs b/lib/elixir/test/elixir/kernel/string_tokenizer_test.exs index d084e728d75..316b5d1de41 100644 --- a/lib/elixir/test/elixir/kernel/string_tokenizer_test.exs +++ b/lib/elixir/test/elixir/kernel/string_tokenizer_test.exs @@ -134,8 +134,8 @@ defmodule Kernel.StringTokenizerTest do test "allows legitimate script mixing" do # Mixed script with supersets, numbers, and underscores - assert Code.eval_string("幻ㄒㄧㄤ = 1") == {1, [幻ㄒㄧㄤ: 1]} - assert Code.eval_string("幻ㄒㄧㄤ1 = 1") == {1, [幻ㄒㄧㄤ1: 1]} + assert Code.eval_string("幻한 = 1") == {1, [幻한: 1]} + assert Code.eval_string("幻한1 = 1") == {1, [幻한1: 1]} assert Code.eval_string("__सवव_1? = 1") == {1, [__सवव_1?: 1]} # Elixir's normalizations combine scriptsets of the 'from' and 'to' characters, diff --git a/lib/elixir/unicode/IdentifierType.txt b/lib/elixir/unicode/IdentifierType.txt index 145d40c5981..90b6f116f4b 100644 --- a/lib/elixir/unicode/IdentifierType.txt +++ b/lib/elixir/unicode/IdentifierType.txt @@ -1,18 +1,21 @@ # IdentifierType.txt -# Date: 2024-08-14, 23:39:57 GMT -# © 2024 Unicode®, Inc. +# Date: 2025-08-04, 21:58:43 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Unicode Security Mechanisms for UTS #39 -# Version: 16.0.0 +# Version: 17.0.0 # # For documentation and usage, see https://www.unicode.org/reports/tr39 # # Format # # Field 0: code point -# Field 1: set of Identifier_Type values (see Table 1 of http://www.unicode.org/reports/tr39) +# Field 1: set of Identifier_Type values +# See the "Identifier_Status and Identifier_Type" table of UTS #39: +# https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type + # # For the purpose of regular expressions, the property Identifier_Type is defined as # mapping each code point to a set of enumerated values. @@ -38,153 +41,143 @@ 0061..007A ; Recommended # 1.1 [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z 00C0..00D6 ; Recommended # 1.1 [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS 00D8..00F6 ; Recommended # 1.1 [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS -00F8..0131 ; Recommended # 1.1 [58] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER DOTLESS I -0134..013E ; Recommended # 1.1 [11] LATIN CAPITAL LETTER J WITH CIRCUMFLEX..LATIN SMALL LETTER L WITH CARON +00F8..0113 ; Recommended # 1.1 [28] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER E WITH MACRON +0116..012B ; Recommended # 1.1 [22] LATIN CAPITAL LETTER E WITH DOT ABOVE..LATIN SMALL LETTER I WITH MACRON +012E..0131 ; Recommended # 1.1 [4] LATIN CAPITAL LETTER I WITH OGONEK..LATIN SMALL LETTER DOTLESS I +0134..0137 ; Recommended # 1.1 [4] LATIN CAPITAL LETTER J WITH CIRCUMFLEX..LATIN SMALL LETTER K WITH CEDILLA +0139..013E ; Recommended # 1.1 [6] LATIN CAPITAL LETTER L WITH ACUTE..LATIN SMALL LETTER L WITH CARON 0141..0148 ; Recommended # 1.1 [8] LATIN CAPITAL LETTER L WITH STROKE..LATIN SMALL LETTER N WITH CARON -014A..017E ; Recommended # 1.1 [53] LATIN CAPITAL LETTER ENG..LATIN SMALL LETTER Z WITH CARON -018F ; Recommended # 1.1 LATIN CAPITAL LETTER SCHWA +014A..014D ; Recommended # 1.1 [4] LATIN CAPITAL LETTER ENG..LATIN SMALL LETTER O WITH MACRON +0150..0155 ; Recommended # 1.1 [6] LATIN CAPITAL LETTER O WITH DOUBLE ACUTE..LATIN SMALL LETTER R WITH ACUTE +0158..0161 ; Recommended # 1.1 [10] LATIN CAPITAL LETTER R WITH CARON..LATIN SMALL LETTER S WITH CARON +0164..017E ; Recommended # 1.1 [27] LATIN CAPITAL LETTER T WITH CARON..LATIN SMALL LETTER Z WITH CARON +0181 ; Recommended # 1.1 LATIN CAPITAL LETTER B WITH HOOK +0186 ; Recommended # 1.1 LATIN CAPITAL LETTER OPEN O +0189..018A ; Recommended # 1.1 [2] LATIN CAPITAL LETTER AFRICAN D..LATIN CAPITAL LETTER D WITH HOOK +018E..0192 ; Recommended # 1.1 [5] LATIN CAPITAL LETTER REVERSED E..LATIN SMALL LETTER F WITH HOOK +0194 ; Recommended # 1.1 LATIN CAPITAL LETTER GAMMA +0196..0199 ; Recommended # 1.1 [4] LATIN CAPITAL LETTER IOTA..LATIN SMALL LETTER K WITH HOOK +019D ; Recommended # 1.1 LATIN CAPITAL LETTER N WITH LEFT HOOK 01A0..01A1 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER O WITH HORN..LATIN SMALL LETTER O WITH HORN 01AF..01B0 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER U WITH HORN..LATIN SMALL LETTER U WITH HORN -01CD..01DC ; Recommended # 1.1 [16] LATIN CAPITAL LETTER A WITH CARON..LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE -01DE..01E3 ; Recommended # 1.1 [6] LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON..LATIN SMALL LETTER AE WITH MACRON -01E6..01F0 ; Recommended # 1.1 [11] LATIN CAPITAL LETTER G WITH CARON..LATIN SMALL LETTER J WITH CARON -01F4..01F5 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER G WITH ACUTE..LATIN SMALL LETTER G WITH ACUTE +01B2..01B4 ; Recommended # 1.1 [3] LATIN CAPITAL LETTER V WITH HOOK..LATIN SMALL LETTER Y WITH HOOK +01B7 ; Recommended # 1.1 LATIN CAPITAL LETTER EZH +01CD..01D4 ; Recommended # 1.1 [8] LATIN CAPITAL LETTER A WITH CARON..LATIN SMALL LETTER U WITH CARON +01DD ; Recommended # 1.1 LATIN SMALL LETTER TURNED E +01E6..01E9 ; Recommended # 1.1 [4] LATIN CAPITAL LETTER G WITH CARON..LATIN SMALL LETTER K WITH CARON +01EE..01EF ; Recommended # 1.1 [2] LATIN CAPITAL LETTER EZH WITH CARON..LATIN SMALL LETTER EZH WITH CARON 01F8..01F9 ; Recommended # 3.0 [2] LATIN CAPITAL LETTER N WITH GRAVE..LATIN SMALL LETTER N WITH GRAVE -01FA..0217 ; Recommended # 1.1 [30] LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE..LATIN SMALL LETTER U WITH INVERTED BREVE 0218..021B ; Recommended # 3.0 [4] LATIN CAPITAL LETTER S WITH COMMA BELOW..LATIN SMALL LETTER T WITH COMMA BELOW -021E..021F ; Recommended # 3.0 [2] LATIN CAPITAL LETTER H WITH CARON..LATIN SMALL LETTER H WITH CARON -0226..0233 ; Recommended # 3.0 [14] LATIN CAPITAL LETTER A WITH DOT ABOVE..LATIN SMALL LETTER Y WITH MACRON +0244 ; Recommended # 5.0 LATIN CAPITAL LETTER U BAR +024C..024D ; Recommended # 5.0 [2] LATIN CAPITAL LETTER R WITH STROKE..LATIN SMALL LETTER R WITH STROKE +0253..0254 ; Recommended # 1.1 [2] LATIN SMALL LETTER B WITH HOOK..LATIN SMALL LETTER OPEN O +0256..0257 ; Recommended # 1.1 [2] LATIN SMALL LETTER D WITH TAIL..LATIN SMALL LETTER D WITH HOOK 0259 ; Recommended # 1.1 LATIN SMALL LETTER SCHWA -02BB..02BC ; Recommended # 1.1 [2] MODIFIER LETTER TURNED COMMA..MODIFIER LETTER APOSTROPHE -02EC ; Recommended # 3.0 MODIFIER LETTER VOICING +025B ; Recommended # 1.1 LATIN SMALL LETTER OPEN E +0263 ; Recommended # 1.1 LATIN SMALL LETTER GAMMA +0268..0269 ; Recommended # 1.1 [2] LATIN SMALL LETTER I WITH STROKE..LATIN SMALL LETTER IOTA +0272 ; Recommended # 1.1 LATIN SMALL LETTER N WITH LEFT HOOK +0289 ; Recommended # 1.1 LATIN SMALL LETTER U BAR +028B ; Recommended # 1.1 LATIN SMALL LETTER V WITH HOOK +0292 ; Recommended # 1.1 LATIN SMALL LETTER EZH 0300..0304 ; Recommended # 1.1 [5] COMBINING GRAVE ACCENT..COMBINING MACRON 0306..030C ; Recommended # 1.1 [7] COMBINING BREVE..COMBINING CARON -030F..0311 ; Recommended # 1.1 [3] COMBINING DOUBLE GRAVE ACCENT..COMBINING INVERTED BREVE -0313..0314 ; Recommended # 1.1 [2] COMBINING COMMA ABOVE..COMBINING REVERSED COMMA ABOVE 031B ; Recommended # 1.1 COMBINING HORN -0323..0328 ; Recommended # 1.1 [6] COMBINING DOT BELOW..COMBINING OGONEK -032D..032E ; Recommended # 1.1 [2] COMBINING CIRCUMFLEX ACCENT BELOW..COMBINING BREVE BELOW -0330..0331 ; Recommended # 1.1 [2] COMBINING TILDE BELOW..COMBINING MACRON BELOW -0335 ; Recommended # 1.1 COMBINING SHORT STROKE OVERLAY -0338..0339 ; Recommended # 1.1 [2] COMBINING LONG SOLIDUS OVERLAY..COMBINING RIGHT HALF RING BELOW -0342 ; Recommended # 1.1 COMBINING GREEK PERISPOMENI -0345 ; Recommended # 1.1 COMBINING GREEK YPOGEGRAMMENI -037B..037D ; Recommended # 5.0 [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL +0323 ; Recommended # 1.1 COMBINING DOT BELOW +0326..0328 ; Recommended # 1.1 [3] COMBINING COMMA BELOW..COMBINING OGONEK +0331 ; Recommended # 1.1 COMBINING MACRON BELOW 0386 ; Recommended # 1.1 GREEK CAPITAL LETTER ALPHA WITH TONOS 0388..038A ; Recommended # 1.1 [3] GREEK CAPITAL LETTER EPSILON WITH TONOS..GREEK CAPITAL LETTER IOTA WITH TONOS 038C ; Recommended # 1.1 GREEK CAPITAL LETTER OMICRON WITH TONOS 038E..03A1 ; Recommended # 1.1 [20] GREEK CAPITAL LETTER UPSILON WITH TONOS..GREEK CAPITAL LETTER RHO 03A3..03CE ; Recommended # 1.1 [44] GREEK CAPITAL LETTER SIGMA..GREEK SMALL LETTER OMEGA WITH TONOS -03FC..03FF ; Recommended # 4.1 [4] GREEK RHO WITH STROKE SYMBOL..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL -0400 ; Recommended # 3.0 CYRILLIC CAPITAL LETTER IE WITH GRAVE 0401..040C ; Recommended # 1.1 [12] CYRILLIC CAPITAL LETTER IO..CYRILLIC CAPITAL LETTER KJE -040D ; Recommended # 3.0 CYRILLIC CAPITAL LETTER I WITH GRAVE 040E..044F ; Recommended # 1.1 [66] CYRILLIC CAPITAL LETTER SHORT U..CYRILLIC SMALL LETTER YA -0450 ; Recommended # 3.0 CYRILLIC SMALL LETTER IE WITH GRAVE 0451..045C ; Recommended # 1.1 [12] CYRILLIC SMALL LETTER IO..CYRILLIC SMALL LETTER KJE -045D ; Recommended # 3.0 CYRILLIC SMALL LETTER I WITH GRAVE 045E..045F ; Recommended # 1.1 [2] CYRILLIC SMALL LETTER SHORT U..CYRILLIC SMALL LETTER DZHE -048A..048B ; Recommended # 3.2 [2] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER SHORT I WITH TAIL -048C..048F ; Recommended # 3.0 [4] CYRILLIC CAPITAL LETTER SEMISOFT SIGN..CYRILLIC SMALL LETTER ER WITH TICK -0490..04C4 ; Recommended # 1.1 [53] CYRILLIC CAPITAL LETTER GHE WITH UPTURN..CYRILLIC SMALL LETTER KA WITH HOOK -04C5..04C6 ; Recommended # 3.2 [2] CYRILLIC CAPITAL LETTER EL WITH TAIL..CYRILLIC SMALL LETTER EL WITH TAIL -04C7..04C8 ; Recommended # 1.1 [2] CYRILLIC CAPITAL LETTER EN WITH HOOK..CYRILLIC SMALL LETTER EN WITH HOOK -04C9..04CA ; Recommended # 3.2 [2] CYRILLIC CAPITAL LETTER EN WITH TAIL..CYRILLIC SMALL LETTER EN WITH TAIL -04CB..04CC ; Recommended # 1.1 [2] CYRILLIC CAPITAL LETTER KHAKASSIAN CHE..CYRILLIC SMALL LETTER KHAKASSIAN CHE -04CD..04CE ; Recommended # 3.2 [2] CYRILLIC CAPITAL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER EM WITH TAIL +0490..049B ; Recommended # 1.1 [12] CYRILLIC CAPITAL LETTER GHE WITH UPTURN..CYRILLIC SMALL LETTER KA WITH DESCENDER +049E..04A5 ; Recommended # 1.1 [8] CYRILLIC CAPITAL LETTER KA WITH STROKE..CYRILLIC SMALL LIGATURE EN GHE +04A8..04B7 ; Recommended # 1.1 [16] CYRILLIC CAPITAL LETTER ABKHASIAN HA..CYRILLIC SMALL LETTER CHE WITH DESCENDER +04BA..04C0 ; Recommended # 1.1 [7] CYRILLIC CAPITAL LETTER SHHA..CYRILLIC LETTER PALOCHKA 04CF ; Recommended # 5.0 CYRILLIC SMALL LETTER PALOCHKA -04D0..04EB ; Recommended # 1.1 [28] CYRILLIC CAPITAL LETTER A WITH BREVE..CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS -04EC..04ED ; Recommended # 3.0 [2] CYRILLIC CAPITAL LETTER E WITH DIAERESIS..CYRILLIC SMALL LETTER E WITH DIAERESIS +04D0..04D9 ; Recommended # 1.1 [10] CYRILLIC CAPITAL LETTER A WITH BREVE..CYRILLIC SMALL LETTER SCHWA +04DC..04E9 ; Recommended # 1.1 [14] CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS..CYRILLIC SMALL LETTER BARRED O 04EE..04F5 ; Recommended # 1.1 [8] CYRILLIC CAPITAL LETTER U WITH MACRON..CYRILLIC SMALL LETTER CHE WITH DIAERESIS -04F6..04F7 ; Recommended # 4.1 [2] CYRILLIC CAPITAL LETTER GHE WITH DESCENDER..CYRILLIC SMALL LETTER GHE WITH DESCENDER 04F8..04F9 ; Recommended # 1.1 [2] CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS..CYRILLIC SMALL LETTER YERU WITH DIAERESIS -04FA..04FF ; Recommended # 5.0 [6] CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK..CYRILLIC SMALL LETTER HA WITH STROKE -0510..0513 ; Recommended # 5.0 [4] CYRILLIC CAPITAL LETTER REVERSED ZE..CYRILLIC SMALL LETTER EL WITH HOOK -0514..0523 ; Recommended # 5.1 [16] CYRILLIC CAPITAL LETTER LHA..CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK 0524..0525 ; Recommended # 5.2 [2] CYRILLIC CAPITAL LETTER PE WITH DESCENDER..CYRILLIC SMALL LETTER PE WITH DESCENDER -0526..0527 ; Recommended # 6.0 [2] CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER..CYRILLIC SMALL LETTER SHHA WITH DESCENDER -0528..0529 ; Recommended # 7.0 [2] CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK..CYRILLIC SMALL LETTER EN WITH LEFT HOOK -052E..052F ; Recommended # 7.0 [2] CYRILLIC CAPITAL LETTER EL WITH DESCENDER..CYRILLIC SMALL LETTER EL WITH DESCENDER 0531..0556 ; Recommended # 1.1 [38] ARMENIAN CAPITAL LETTER AYB..ARMENIAN CAPITAL LETTER FEH -0559 ; Recommended # 1.1 ARMENIAN MODIFIER LETTER LEFT HALF RING 0561..0586 ; Recommended # 1.1 [38] ARMENIAN SMALL LETTER AYB..ARMENIAN SMALL LETTER FEH -05B4 ; Recommended # 1.1 HEBREW POINT HIRIQ 05D0..05EA ; Recommended # 1.1 [27] HEBREW LETTER ALEF..HEBREW LETTER TAV -05EF ; Recommended # 11.0 HEBREW YOD TRIANGLE -05F0..05F2 ; Recommended # 1.1 [3] HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW LIGATURE YIDDISH DOUBLE YOD 0620 ; Recommended # 6.0 ARABIC LETTER KASHMIRI YEH 0621..063A ; Recommended # 1.1 [26] ARABIC LETTER HAMZA..ARABIC LETTER GHAIN -063B..063F ; Recommended # 5.1 [5] ARABIC LETTER KEHEH WITH TWO DOTS ABOVE..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE +063D ; Recommended # 5.1 ARABIC LETTER FARSI YEH WITH INVERTED V 0641..0652 ; Recommended # 1.1 [18] ARABIC LETTER FEH..ARABIC SUKUN -0653..0655 ; Recommended # 3.0 [3] ARABIC MADDAH ABOVE..ARABIC HAMZA BELOW +0654..0655 ; Recommended # 3.0 [2] ARABIC HAMZA ABOVE..ARABIC HAMZA BELOW 0660..0669 ; Recommended # 1.1 [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE -0670..0672 ; Recommended # 1.1 [3] ARABIC LETTER SUPERSCRIPT ALEF..ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE +0670 ; Recommended # 1.1 ARABIC LETTER SUPERSCRIPT ALEF +0672 ; Recommended # 1.1 ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE 0674 ; Recommended # 1.1 ARABIC LETTER HIGH HAMZA -0679..068D ; Recommended # 1.1 [21] ARABIC LETTER TTEH..ARABIC LETTER DDAHAL -068F..06A0 ; Recommended # 1.1 [18] ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS..ARABIC LETTER AIN WITH THREE DOTS ABOVE -06A2..06B7 ; Recommended # 1.1 [22] ARABIC LETTER FEH WITH DOT MOVED BELOW..ARABIC LETTER LAM WITH THREE DOTS ABOVE -06B8..06B9 ; Recommended # 3.0 [2] ARABIC LETTER LAM WITH THREE DOTS BELOW..ARABIC LETTER NOON WITH DOT BELOW +0679..068F ; Recommended # 1.1 [23] ARABIC LETTER TTEH..ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS +0691..069A ; Recommended # 1.1 [10] ARABIC LETTER RREH..ARABIC LETTER SEEN WITH DOT BELOW AND DOT ABOVE +069F..06A0 ; Recommended # 1.1 [2] ARABIC LETTER TAH WITH THREE DOTS ABOVE..ARABIC LETTER AIN WITH THREE DOTS ABOVE +06A2 ; Recommended # 1.1 ARABIC LETTER FEH WITH DOT MOVED BELOW +06A4..06AB ; Recommended # 1.1 [8] ARABIC LETTER VEH..ARABIC LETTER KAF WITH RING +06AD..06B1 ; Recommended # 1.1 [5] ARABIC LETTER NG..ARABIC LETTER NGOEH +06B3 ; Recommended # 1.1 ARABIC LETTER GUEH +06B5..06B7 ; Recommended # 1.1 [3] ARABIC LETTER LAM WITH SMALL V..ARABIC LETTER LAM WITH THREE DOTS ABOVE 06BA..06BE ; Recommended # 1.1 [5] ARABIC LETTER NOON GHUNNA..ARABIC LETTER HEH DOACHASHMEE -06BF ; Recommended # 3.0 ARABIC LETTER TCHEH WITH DOT ABOVE 06C0..06CE ; Recommended # 1.1 [15] ARABIC LETTER HEH WITH YEH ABOVE..ARABIC LETTER YEH WITH SMALL V 06CF ; Recommended # 3.0 ARABIC LETTER WAW WITH DOT ABOVE 06D0..06D3 ; Recommended # 1.1 [4] ARABIC LETTER E..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE 06D5 ; Recommended # 1.1 ARABIC LETTER AE -06E5..06E6 ; Recommended # 1.1 [2] ARABIC SMALL WAW..ARABIC SMALL YEH 06EE..06EF ; Recommended # 4.0 [2] ARABIC LETTER DAL WITH INVERTED V..ARABIC LETTER REH WITH INVERTED V 06F0..06F9 ; Recommended # 1.1 [10] EXTENDED ARABIC-INDIC DIGIT ZERO..EXTENDED ARABIC-INDIC DIGIT NINE -06FA..06FC ; Recommended # 3.0 [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW 06FF ; Recommended # 4.0 ARABIC LETTER HEH WITH INVERTED V -0750..076D ; Recommended # 4.1 [30] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER SEEN WITH TWO DOTS VERTICALLY ABOVE -076E..077F ; Recommended # 5.1 [18] ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE +0751..0752 ; Recommended # 4.1 [2] ARABIC LETTER BEH WITH DOT BELOW AND THREE DOTS ABOVE..ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW +0756 ; Recommended # 4.1 ARABIC LETTER BEH WITH SMALL V +0760 ; Recommended # 4.1 ARABIC LETTER FEH WITH TWO DOTS BELOW +0762..0763 ; Recommended # 4.1 [2] ARABIC LETTER KEHEH WITH DOT ABOVE..ARABIC LETTER KEHEH WITH THREE DOTS ABOVE +0766..0768 ; Recommended # 4.1 [3] ARABIC LETTER MEEM WITH DOT BELOW..ARABIC LETTER NOON WITH SMALL TAH +076A ; Recommended # 4.1 ARABIC LETTER LAM WITH BAR +076E..0771 ; Recommended # 5.1 [4] ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH BELOW..ARABIC LETTER REH WITH SMALL ARABIC LETTER TAH AND TWO DOTS 0780..07B0 ; Recommended # 3.0 [49] THAANA LETTER HAA..THAANA SUKUN 07B1 ; Recommended # 3.2 THAANA LETTER NAA -0870..0887 ; Recommended # 14.0 [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; Recommended # 14.0 [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +088F ; Recommended # 17.0 ARABIC LETTER NOON WITH RING ABOVE 08A0 ; Recommended # 6.1 ARABIC LETTER BEH WITH SMALL V BELOW -08A1 ; Recommended # 7.0 ARABIC LETTER BEH WITH HAMZA ABOVE -08A2..08AC ; Recommended # 6.1 [11] ARABIC LETTER JEEM WITH TWO DOTS ABOVE..ARABIC LETTER ROHINGYA YEH -08B2 ; Recommended # 7.0 ARABIC LETTER ZAIN WITH INVERTED V ABOVE -08B5 ; Recommended # 14.0 ARABIC LETTER QAF WITH DOT BELOW AND NO DOTS ABOVE -08B6..08BD ; Recommended # 9.0 [8] ARABIC LETTER BEH WITH SMALL MEEM ABOVE..ARABIC LETTER AFRICAN NOON -08BE..08C7 ; Recommended # 13.0 [10] ARABIC LETTER PEH WITH SMALL V..ARABIC LETTER LAM WITH SMALL ARABIC LETTER TAH ABOVE -08C8..08C9 ; Recommended # 14.0 [2] ARABIC LETTER GRAF..ARABIC SMALL FARSI YEH +08A2..08A9 ; Recommended # 6.1 [8] ARABIC LETTER JEEM WITH TWO DOTS ABOVE..ARABIC LETTER YEH WITH TWO DOTS BELOW AND DOT ABOVE +08BB..08BD ; Recommended # 9.0 [3] ARABIC LETTER AFRICAN FEH..ARABIC LETTER AFRICAN NOON +08BE..08C2 ; Recommended # 13.0 [5] ARABIC LETTER PEH WITH SMALL V..ARABIC LETTER KEHEH WITH SMALL V +08C7 ; Recommended # 13.0 ARABIC LETTER LAM WITH SMALL ARABIC LETTER TAH ABOVE 0901..0903 ; Recommended # 1.1 [3] DEVANAGARI SIGN CANDRABINDU..DEVANAGARI SIGN VISARGA -0904 ; Recommended # 4.0 DEVANAGARI LETTER SHORT A -0905..0939 ; Recommended # 1.1 [53] DEVANAGARI LETTER A..DEVANAGARI LETTER HA +0905..090B ; Recommended # 1.1 [7] DEVANAGARI LETTER A..DEVANAGARI LETTER VOCALIC R +090D..0928 ; Recommended # 1.1 [28] DEVANAGARI LETTER CANDRA E..DEVANAGARI LETTER NA +092A..0933 ; Recommended # 1.1 [10] DEVANAGARI LETTER PA..DEVANAGARI LETTER LLA +0935..0939 ; Recommended # 1.1 [5] DEVANAGARI LETTER VA..DEVANAGARI LETTER HA 093A..093B ; Recommended # 6.0 [2] DEVANAGARI VOWEL SIGN OE..DEVANAGARI VOWEL SIGN OOE -093C..094D ; Recommended # 1.1 [18] DEVANAGARI SIGN NUKTA..DEVANAGARI SIGN VIRAMA +093C ; Recommended # 1.1 DEVANAGARI SIGN NUKTA +093E..0943 ; Recommended # 1.1 [6] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN VOCALIC R +0945..094D ; Recommended # 1.1 [9] DEVANAGARI VOWEL SIGN CANDRA E..DEVANAGARI SIGN VIRAMA 094F ; Recommended # 6.0 DEVANAGARI VOWEL SIGN AW -0950 ; Recommended # 1.1 DEVANAGARI OM 0956..0957 ; Recommended # 6.0 [2] DEVANAGARI VOWEL SIGN UE..DEVANAGARI VOWEL SIGN UUE -0960..0963 ; Recommended # 1.1 [4] DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOWEL SIGN VOCALIC LL 0966..096F ; Recommended # 1.1 [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE -0971..0972 ; Recommended # 5.1 [2] DEVANAGARI SIGN HIGH SPACING DOT..DEVANAGARI LETTER CANDRA A +0972 ; Recommended # 5.1 DEVANAGARI LETTER CANDRA A 0973..0977 ; Recommended # 6.0 [5] DEVANAGARI LETTER OE..DEVANAGARI LETTER UUE -0979..097A ; Recommended # 5.2 [2] DEVANAGARI LETTER ZHA..DEVANAGARI LETTER HEAVY YA 097B..097C ; Recommended # 5.0 [2] DEVANAGARI LETTER GGA..DEVANAGARI LETTER JJA -097D ; Recommended # 4.1 DEVANAGARI LETTER GLOTTAL STOP 097E..097F ; Recommended # 5.0 [2] DEVANAGARI LETTER DDDA..DEVANAGARI LETTER BBA 0981..0983 ; Recommended # 1.1 [3] BENGALI SIGN CANDRABINDU..BENGALI SIGN VISARGA -0985..098C ; Recommended # 1.1 [8] BENGALI LETTER A..BENGALI LETTER VOCALIC L +0985..098B ; Recommended # 1.1 [7] BENGALI LETTER A..BENGALI LETTER VOCALIC R 098F..0990 ; Recommended # 1.1 [2] BENGALI LETTER E..BENGALI LETTER AI 0993..09A8 ; Recommended # 1.1 [22] BENGALI LETTER O..BENGALI LETTER NA 09AA..09B0 ; Recommended # 1.1 [7] BENGALI LETTER PA..BENGALI LETTER RA 09B2 ; Recommended # 1.1 BENGALI LETTER LA 09B6..09B9 ; Recommended # 1.1 [4] BENGALI LETTER SHA..BENGALI LETTER HA 09BC ; Recommended # 1.1 BENGALI SIGN NUKTA -09BD ; Recommended # 4.0 BENGALI SIGN AVAGRAHA 09BE..09C4 ; Recommended # 1.1 [7] BENGALI VOWEL SIGN AA..BENGALI VOWEL SIGN VOCALIC RR 09C7..09C8 ; Recommended # 1.1 [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI 09CB..09CD ; Recommended # 1.1 [3] BENGALI VOWEL SIGN O..BENGALI SIGN VIRAMA 09CE ; Recommended # 4.1 BENGALI LETTER KHANDA TA -09D7 ; Recommended # 1.1 BENGALI AU LENGTH MARK -09E0..09E3 ; Recommended # 1.1 [4] BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIGN VOCALIC LL 09E6..09F1 ; Recommended # 1.1 [12] BENGALI DIGIT ZERO..BENGALI LETTER RA WITH LOWER DIAGONAL -09FE ; Recommended # 11.0 BENGALI SANDHI MARK -0A01 ; Recommended # 4.0 GURMUKHI SIGN ADAK BINDI 0A02 ; Recommended # 1.1 GURMUKHI SIGN BINDI -0A03 ; Recommended # 4.0 GURMUKHI SIGN VISARGA 0A05..0A0A ; Recommended # 1.1 [6] GURMUKHI LETTER A..GURMUKHI LETTER UU 0A0F..0A10 ; Recommended # 1.1 [2] GURMUKHI LETTER EE..GURMUKHI LETTER AI 0A13..0A28 ; Recommended # 1.1 [22] GURMUKHI LETTER OO..GURMUKHI LETTER NA @@ -197,8 +190,8 @@ 0A47..0A48 ; Recommended # 1.1 [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI 0A4B..0A4D ; Recommended # 1.1 [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA 0A5C ; Recommended # 1.1 GURMUKHI LETTER RRA -0A66..0A74 ; Recommended # 1.1 [15] GURMUKHI DIGIT ZERO..GURMUKHI EK ONKAR -0A81..0A83 ; Recommended # 1.1 [3] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN VISARGA +0A70..0A71 ; Recommended # 1.1 [2] GURMUKHI TIPPI..GURMUKHI ADDAK +0A82..0A83 ; Recommended # 1.1 [2] GUJARATI SIGN ANUSVARA..GUJARATI SIGN VISARGA 0A85..0A8B ; Recommended # 1.1 [7] GUJARATI LETTER A..GUJARATI LETTER VOCALIC R 0A8C ; Recommended # 4.0 GUJARATI LETTER VOCALIC L 0A8D ; Recommended # 1.1 GUJARATI VOWEL CANDRA E @@ -207,31 +200,26 @@ 0AAA..0AB0 ; Recommended # 1.1 [7] GUJARATI LETTER PA..GUJARATI LETTER RA 0AB2..0AB3 ; Recommended # 1.1 [2] GUJARATI LETTER LA..GUJARATI LETTER LLA 0AB5..0AB9 ; Recommended # 1.1 [5] GUJARATI LETTER VA..GUJARATI LETTER HA -0ABC..0AC5 ; Recommended # 1.1 [10] GUJARATI SIGN NUKTA..GUJARATI VOWEL SIGN CANDRA E +0ABC ; Recommended # 1.1 GUJARATI SIGN NUKTA +0ABE..0AC5 ; Recommended # 1.1 [8] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN CANDRA E 0AC7..0AC9 ; Recommended # 1.1 [3] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN CANDRA O 0ACB..0ACD ; Recommended # 1.1 [3] GUJARATI VOWEL SIGN O..GUJARATI SIGN VIRAMA -0AD0 ; Recommended # 1.1 GUJARATI OM -0AE0 ; Recommended # 1.1 GUJARATI LETTER VOCALIC RR -0AE1..0AE3 ; Recommended # 4.0 [3] GUJARATI LETTER VOCALIC LL..GUJARATI VOWEL SIGN VOCALIC LL 0AE6..0AEF ; Recommended # 1.1 [10] GUJARATI DIGIT ZERO..GUJARATI DIGIT NINE -0AFA..0AFF ; Recommended # 10.0 [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE 0B01..0B03 ; Recommended # 1.1 [3] ORIYA SIGN CANDRABINDU..ORIYA SIGN VISARGA -0B05..0B0C ; Recommended # 1.1 [8] ORIYA LETTER A..ORIYA LETTER VOCALIC L +0B05..0B0B ; Recommended # 1.1 [7] ORIYA LETTER A..ORIYA LETTER VOCALIC R 0B0F..0B10 ; Recommended # 1.1 [2] ORIYA LETTER E..ORIYA LETTER AI 0B13..0B28 ; Recommended # 1.1 [22] ORIYA LETTER O..ORIYA LETTER NA 0B2A..0B30 ; Recommended # 1.1 [7] ORIYA LETTER PA..ORIYA LETTER RA 0B32..0B33 ; Recommended # 1.1 [2] ORIYA LETTER LA..ORIYA LETTER LLA -0B35 ; Recommended # 4.0 ORIYA LETTER VA 0B36..0B39 ; Recommended # 1.1 [4] ORIYA LETTER SHA..ORIYA LETTER HA -0B3C..0B43 ; Recommended # 1.1 [8] ORIYA SIGN NUKTA..ORIYA VOWEL SIGN VOCALIC R +0B3C ; Recommended # 1.1 ORIYA SIGN NUKTA +0B3E..0B43 ; Recommended # 1.1 [6] ORIYA VOWEL SIGN AA..ORIYA VOWEL SIGN VOCALIC R 0B47..0B48 ; Recommended # 1.1 [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI 0B4B..0B4D ; Recommended # 1.1 [3] ORIYA VOWEL SIGN O..ORIYA SIGN VIRAMA -0B55 ; Recommended # 13.0 ORIYA SIGN OVERLINE -0B56..0B57 ; Recommended # 1.1 [2] ORIYA AI LENGTH MARK..ORIYA AU LENGTH MARK -0B5F..0B61 ; Recommended # 1.1 [3] ORIYA LETTER YYA..ORIYA LETTER VOCALIC LL -0B66..0B6F ; Recommended # 1.1 [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0B56 ; Recommended # 1.1 ORIYA AI LENGTH MARK +0B5F ; Recommended # 1.1 ORIYA LETTER YYA 0B71 ; Recommended # 4.0 ORIYA LETTER WA -0B82..0B83 ; Recommended # 1.1 [2] TAMIL SIGN ANUSVARA..TAMIL SIGN VISARGA +0B83 ; Recommended # 1.1 TAMIL SIGN VISARGA 0B85..0B8A ; Recommended # 1.1 [6] TAMIL LETTER A..TAMIL LETTER UU 0B8E..0B90 ; Recommended # 1.1 [3] TAMIL LETTER E..TAMIL LETTER AI 0B92..0B95 ; Recommended # 1.1 [4] TAMIL LETTER O..TAMIL LETTER KA @@ -246,67 +234,43 @@ 0BBE..0BC2 ; Recommended # 1.1 [5] TAMIL VOWEL SIGN AA..TAMIL VOWEL SIGN UU 0BC6..0BC8 ; Recommended # 1.1 [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI 0BCA..0BCD ; Recommended # 1.1 [4] TAMIL VOWEL SIGN O..TAMIL SIGN VIRAMA -0BD0 ; Recommended # 5.1 TAMIL OM -0BD7 ; Recommended # 1.1 TAMIL AU LENGTH MARK -0BE6 ; Recommended # 4.1 TAMIL DIGIT ZERO -0BE7..0BEF ; Recommended # 1.1 [9] TAMIL DIGIT ONE..TAMIL DIGIT NINE -0C01..0C03 ; Recommended # 1.1 [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA -0C04 ; Recommended # 11.0 TELUGU SIGN COMBINING ANUSVARA ABOVE -0C05..0C0C ; Recommended # 1.1 [8] TELUGU LETTER A..TELUGU LETTER VOCALIC L +0C02..0C03 ; Recommended # 1.1 [2] TELUGU SIGN ANUSVARA..TELUGU SIGN VISARGA +0C05..0C0B ; Recommended # 1.1 [7] TELUGU LETTER A..TELUGU LETTER VOCALIC R 0C0E..0C10 ; Recommended # 1.1 [3] TELUGU LETTER E..TELUGU LETTER AI 0C12..0C28 ; Recommended # 1.1 [23] TELUGU LETTER O..TELUGU LETTER NA -0C2A..0C33 ; Recommended # 1.1 [10] TELUGU LETTER PA..TELUGU LETTER LLA +0C2A..0C30 ; Recommended # 1.1 [7] TELUGU LETTER PA..TELUGU LETTER RA +0C32..0C33 ; Recommended # 1.1 [2] TELUGU LETTER LA..TELUGU LETTER LLA 0C35..0C39 ; Recommended # 1.1 [5] TELUGU LETTER VA..TELUGU LETTER HA -0C3C ; Recommended # 14.0 TELUGU SIGN NUKTA -0C3D ; Recommended # 5.1 TELUGU SIGN AVAGRAHA 0C3E..0C44 ; Recommended # 1.1 [7] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN VOCALIC RR 0C46..0C48 ; Recommended # 1.1 [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI 0C4A..0C4D ; Recommended # 1.1 [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA -0C55..0C56 ; Recommended # 1.1 [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK -0C5D ; Recommended # 14.0 TELUGU LETTER NAKAARA POLLU -0C60..0C61 ; Recommended # 1.1 [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL -0C66..0C6F ; Recommended # 1.1 [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE -0C80 ; Recommended # 9.0 KANNADA SIGN SPACING CANDRABINDU 0C82..0C83 ; Recommended # 1.1 [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA -0C85..0C8C ; Recommended # 1.1 [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L +0C85..0C8B ; Recommended # 1.1 [7] KANNADA LETTER A..KANNADA LETTER VOCALIC R 0C8E..0C90 ; Recommended # 1.1 [3] KANNADA LETTER E..KANNADA LETTER AI 0C92..0CA8 ; Recommended # 1.1 [23] KANNADA LETTER O..KANNADA LETTER NA -0CAA..0CB3 ; Recommended # 1.1 [10] KANNADA LETTER PA..KANNADA LETTER LLA +0CAA..0CB0 ; Recommended # 1.1 [7] KANNADA LETTER PA..KANNADA LETTER RA +0CB2..0CB3 ; Recommended # 1.1 [2] KANNADA LETTER LA..KANNADA LETTER LLA 0CB5..0CB9 ; Recommended # 1.1 [5] KANNADA LETTER VA..KANNADA LETTER HA -0CBC..0CBD ; Recommended # 4.0 [2] KANNADA SIGN NUKTA..KANNADA SIGN AVAGRAHA -0CBE..0CC4 ; Recommended # 1.1 [7] KANNADA VOWEL SIGN AA..KANNADA VOWEL SIGN VOCALIC RR +0CBE..0CC3 ; Recommended # 1.1 [6] KANNADA VOWEL SIGN AA..KANNADA VOWEL SIGN VOCALIC R 0CC6..0CC8 ; Recommended # 1.1 [3] KANNADA VOWEL SIGN E..KANNADA VOWEL SIGN AI 0CCA..0CCD ; Recommended # 1.1 [4] KANNADA VOWEL SIGN O..KANNADA SIGN VIRAMA -0CD5..0CD6 ; Recommended # 1.1 [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD ; Recommended # 14.0 KANNADA LETTER NAKAARA POLLU -0CE0..0CE1 ; Recommended # 1.1 [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL -0CE2..0CE3 ; Recommended # 5.0 [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; Recommended # 1.1 [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE -0CF1..0CF2 ; Recommended # 5.0 [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA -0CF3 ; Recommended # 15.0 KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT -0D00 ; Recommended # 10.0 MALAYALAM SIGN COMBINING ANUSVARA ABOVE 0D02..0D03 ; Recommended # 1.1 [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA -0D05..0D0C ; Recommended # 1.1 [8] MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC L +0D05..0D0B ; Recommended # 1.1 [7] MALAYALAM LETTER A..MALAYALAM LETTER VOCALIC R 0D0E..0D10 ; Recommended # 1.1 [3] MALAYALAM LETTER E..MALAYALAM LETTER AI 0D12..0D28 ; Recommended # 1.1 [23] MALAYALAM LETTER O..MALAYALAM LETTER NA -0D29 ; Recommended # 6.0 MALAYALAM LETTER NNNA 0D2A..0D39 ; Recommended # 1.1 [16] MALAYALAM LETTER PA..MALAYALAM LETTER HA -0D3A ; Recommended # 6.0 MALAYALAM LETTER TTTA -0D3D ; Recommended # 5.1 MALAYALAM SIGN AVAGRAHA 0D3E..0D43 ; Recommended # 1.1 [6] MALAYALAM VOWEL SIGN AA..MALAYALAM VOWEL SIGN VOCALIC R 0D46..0D48 ; Recommended # 1.1 [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI -0D4A..0D4D ; Recommended # 1.1 [4] MALAYALAM VOWEL SIGN O..MALAYALAM SIGN VIRAMA -0D4E ; Recommended # 6.0 MALAYALAM LETTER DOT REPH -0D54..0D56 ; Recommended # 9.0 [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL +0D4A..0D4B ; Recommended # 1.1 [2] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN OO +0D4D ; Recommended # 1.1 MALAYALAM SIGN VIRAMA 0D57 ; Recommended # 1.1 MALAYALAM AU LENGTH MARK -0D60..0D61 ; Recommended # 1.1 [2] MALAYALAM LETTER VOCALIC RR..MALAYALAM LETTER VOCALIC LL -0D66..0D6F ; Recommended # 1.1 [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE 0D7A..0D7F ; Recommended # 5.1 [6] MALAYALAM LETTER CHILLU NN..MALAYALAM LETTER CHILLU K 0D82..0D83 ; Recommended # 3.0 [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA -0D85..0D8E ; Recommended # 3.0 [10] SINHALA LETTER AYANNA..SINHALA LETTER IRUUYANNA +0D85..0D8D ; Recommended # 3.0 [9] SINHALA LETTER AYANNA..SINHALA LETTER IRUYANNA 0D91..0D96 ; Recommended # 3.0 [6] SINHALA LETTER EYANNA..SINHALA LETTER AUYANNA -0D9A..0DA5 ; Recommended # 3.0 [12] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER TAALUJA SANYOOGA NAAKSIKYAYA -0DA7..0DB1 ; Recommended # 3.0 [11] SINHALA LETTER ALPAPRAANA TTAYANNA..SINHALA LETTER DANTAJA NAYANNA +0D9A..0D9D ; Recommended # 3.0 [4] SINHALA LETTER ALPAPRAANA KAYANNA..SINHALA LETTER MAHAAPRAANA GAYANNA +0D9F..0DB1 ; Recommended # 3.0 [19] SINHALA LETTER SANYAKA GAYANNA..SINHALA LETTER DANTAJA NAYANNA 0DB3..0DBB ; Recommended # 3.0 [9] SINHALA LETTER SANYAKA DAYANNA..SINHALA LETTER RAYANNA 0DBD ; Recommended # 3.0 SINHALA LETTER DANTAJA LAYANNA 0DC0..0DC6 ; Recommended # 3.0 [7] SINHALA LETTER VAYANNA..SINHALA LETTER FAYANNA @@ -317,56 +281,39 @@ 0DF2 ; Recommended # 3.0 SINHALA VOWEL SIGN DIGA GAETTA-PILLA 0E01..0E32 ; Recommended # 1.1 [50] THAI CHARACTER KO KAI..THAI CHARACTER SARA AA 0E34..0E3A ; Recommended # 1.1 [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU -0E40..0E4E ; Recommended # 1.1 [15] THAI CHARACTER SARA E..THAI CHARACTER YAMAKKAN +0E40..0E4D ; Recommended # 1.1 [14] THAI CHARACTER SARA E..THAI CHARACTER NIKHAHIT 0E50..0E59 ; Recommended # 1.1 [10] THAI DIGIT ZERO..THAI DIGIT NINE 0E81..0E82 ; Recommended # 1.1 [2] LAO LETTER KO..LAO LETTER KHO SUNG 0E84 ; Recommended # 1.1 LAO LETTER KHO TAM -0E86 ; Recommended # 12.0 LAO LETTER PALI GHA 0E87..0E88 ; Recommended # 1.1 [2] LAO LETTER NGO..LAO LETTER CO -0E89 ; Recommended # 12.0 LAO LETTER PALI CHA 0E8A ; Recommended # 1.1 LAO LETTER SO TAM -0E8C ; Recommended # 12.0 LAO LETTER PALI JHA 0E8D ; Recommended # 1.1 LAO LETTER NYO -0E8E..0E93 ; Recommended # 12.0 [6] LAO LETTER PALI NYA..LAO LETTER PALI NNA 0E94..0E97 ; Recommended # 1.1 [4] LAO LETTER DO..LAO LETTER THO TAM -0E98 ; Recommended # 12.0 LAO LETTER PALI DHA 0E99..0E9F ; Recommended # 1.1 [7] LAO LETTER NO..LAO LETTER FO SUNG -0EA0 ; Recommended # 12.0 LAO LETTER PALI BHA 0EA1..0EA3 ; Recommended # 1.1 [3] LAO LETTER MO..LAO LETTER LO LING 0EA5 ; Recommended # 1.1 LAO LETTER LO LOOT 0EA7 ; Recommended # 1.1 LAO LETTER WO -0EA8..0EA9 ; Recommended # 12.0 [2] LAO LETTER SANSKRIT SHA..LAO LETTER SANSKRIT SSA 0EAA..0EAB ; Recommended # 1.1 [2] LAO LETTER SO SUNG..LAO LETTER HO SUNG -0EAC ; Recommended # 12.0 LAO LETTER PALI LLA -0EAD..0EB2 ; Recommended # 1.1 [6] LAO LETTER O..LAO VOWEL SIGN AA +0EAD..0EAE ; Recommended # 1.1 [2] LAO LETTER O..LAO LETTER HO TAM +0EB0..0EB2 ; Recommended # 1.1 [3] LAO VOWEL SIGN A..LAO VOWEL SIGN AA 0EB4..0EB9 ; Recommended # 1.1 [6] LAO VOWEL SIGN I..LAO VOWEL SIGN UU -0EBA ; Recommended # 12.0 LAO SIGN PALI VIRAMA 0EBB..0EBD ; Recommended # 1.1 [3] LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN NYO 0EC0..0EC4 ; Recommended # 1.1 [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI 0EC6 ; Recommended # 1.1 LAO KO LA 0EC8..0ECD ; Recommended # 1.1 [6] LAO TONE MAI EK..LAO NIGGAHITA -0ECE ; Recommended # 15.0 LAO YAMAKKAN 0ED0..0ED9 ; Recommended # 1.1 [10] LAO DIGIT ZERO..LAO DIGIT NINE -0EDE..0EDF ; Recommended # 6.1 [2] LAO LETTER KHMU GO..LAO LETTER KHMU NYO -0F00 ; Recommended # 2.0 TIBETAN SYLLABLE OM 0F20..0F29 ; Recommended # 2.0 [10] TIBETAN DIGIT ZERO..TIBETAN DIGIT NINE -0F35 ; Recommended # 2.0 TIBETAN MARK NGAS BZUNG NYI ZLA -0F37 ; Recommended # 2.0 TIBETAN MARK NGAS BZUNG SGOR RTAGS -0F3E..0F42 ; Recommended # 2.0 [5] TIBETAN SIGN YAR TSHES..TIBETAN LETTER GA +0F40..0F42 ; Recommended # 2.0 [3] TIBETAN LETTER KA..TIBETAN LETTER GA 0F44..0F47 ; Recommended # 2.0 [4] TIBETAN LETTER NGA..TIBETAN LETTER JA 0F49..0F4C ; Recommended # 2.0 [4] TIBETAN LETTER NYA..TIBETAN LETTER DDA 0F4E..0F51 ; Recommended # 2.0 [4] TIBETAN LETTER NNA..TIBETAN LETTER DA 0F53..0F56 ; Recommended # 2.0 [4] TIBETAN LETTER NA..TIBETAN LETTER BA 0F58..0F5B ; Recommended # 2.0 [4] TIBETAN LETTER MA..TIBETAN LETTER DZA 0F5D..0F68 ; Recommended # 2.0 [12] TIBETAN LETTER WA..TIBETAN LETTER A -0F6A ; Recommended # 3.0 TIBETAN LETTER FIXED-FORM RA -0F6B..0F6C ; Recommended # 5.1 [2] TIBETAN LETTER KKA..TIBETAN LETTER RRA 0F71..0F72 ; Recommended # 2.0 [2] TIBETAN VOWEL SIGN AA..TIBETAN VOWEL SIGN I 0F74 ; Recommended # 2.0 TIBETAN VOWEL SIGN U 0F7A..0F80 ; Recommended # 2.0 [7] TIBETAN VOWEL SIGN E..TIBETAN VOWEL SIGN REVERSED I -0F82..0F84 ; Recommended # 2.0 [3] TIBETAN SIGN NYI ZLA NAA DA..TIBETAN MARK HALANTA -0F86..0F8B ; Recommended # 2.0 [6] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN GRU MED RGYINGS -0F8C..0F8F ; Recommended # 6.0 [4] TIBETAN SIGN INVERTED MCHU CAN..TIBETAN SUBJOINED SIGN INVERTED MCHU CAN +0F84 ; Recommended # 2.0 TIBETAN MARK HALANTA 0F90..0F92 ; Recommended # 2.0 [3] TIBETAN SUBJOINED LETTER KA..TIBETAN SUBJOINED LETTER GA 0F94..0F95 ; Recommended # 2.0 [2] TIBETAN SUBJOINED LETTER NGA..TIBETAN SUBJOINED LETTER CA 0F96 ; Recommended # 3.0 TIBETAN SUBJOINED LETTER CHA @@ -376,11 +323,9 @@ 0FA3..0FA6 ; Recommended # 2.0 [4] TIBETAN SUBJOINED LETTER NA..TIBETAN SUBJOINED LETTER BA 0FA8..0FAB ; Recommended # 2.0 [4] TIBETAN SUBJOINED LETTER MA..TIBETAN SUBJOINED LETTER DZA 0FAD ; Recommended # 2.0 TIBETAN SUBJOINED LETTER WA -0FAE..0FB0 ; Recommended # 3.0 [3] TIBETAN SUBJOINED LETTER ZHA..TIBETAN SUBJOINED LETTER -A 0FB1..0FB7 ; Recommended # 2.0 [7] TIBETAN SUBJOINED LETTER YA..TIBETAN SUBJOINED LETTER HA 0FB8 ; Recommended # 3.0 TIBETAN SUBJOINED LETTER A 0FBA..0FBC ; Recommended # 3.0 [3] TIBETAN SUBJOINED LETTER FIXED-FORM WA..TIBETAN SUBJOINED LETTER FIXED-FORM RA -0FC6 ; Recommended # 3.0 TIBETAN SYMBOL PADMA GDAN 1000..1021 ; Recommended # 3.0 [34] MYANMAR LETTER KA..MYANMAR LETTER A 1022 ; Recommended # 5.1 MYANMAR LETTER SHAN A 1023..1027 ; Recommended # 3.0 [5] MYANMAR LETTER I..MYANMAR LETTER E @@ -392,17 +337,13 @@ 1036..1039 ; Recommended # 3.0 [4] MYANMAR SIGN ANUSVARA..MYANMAR SIGN VIRAMA 103A..103F ; Recommended # 5.1 [6] MYANMAR SIGN ASAT..MYANMAR LETTER GREAT SA 1040..1049 ; Recommended # 3.0 [10] MYANMAR DIGIT ZERO..MYANMAR DIGIT NINE -1050..1059 ; Recommended # 3.0 [10] MYANMAR LETTER SHA..MYANMAR VOWEL SIGN VOCALIC LL -105A..1099 ; Recommended # 5.1 [64] MYANMAR LETTER MON NGA..MYANMAR SHAN DIGIT NINE -109A..109D ; Recommended # 5.2 [4] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON AI +105A..1064 ; Recommended # 5.1 [11] MYANMAR LETTER MON NGA..MYANMAR TONE MARK SGAW KAREN KE PHO +1075..108A ; Recommended # 5.1 [22] MYANMAR LETTER SHAN KA..MYANMAR SIGN SHAN TONE-6 +108F ; Recommended # 5.1 MYANMAR SIGN RUMAI PALAUNG TONE-5 10C7 ; Recommended # 6.1 GEORGIAN CAPITAL LETTER YN 10CD ; Recommended # 6.1 GEORGIAN CAPITAL LETTER AEN 10D0..10F0 ; Recommended # 1.1 [33] GEORGIAN LETTER AN..GEORGIAN LETTER HAE -10F7..10F8 ; Recommended # 3.2 [2] GEORGIAN LETTER YN..GEORGIAN LETTER ELIFI -10F9..10FA ; Recommended # 4.1 [2] GEORGIAN LETTER TURNED GAN..GEORGIAN LETTER AIN -10FD..10FF ; Recommended # 6.1 [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN 1200..1206 ; Recommended # 3.0 [7] ETHIOPIC SYLLABLE HA..ETHIOPIC SYLLABLE HO -1207 ; Recommended # 4.1 ETHIOPIC SYLLABLE HOA 1208..1246 ; Recommended # 3.0 [63] ETHIOPIC SYLLABLE LA..ETHIOPIC SYLLABLE QO 1247 ; Recommended # 4.1 ETHIOPIC SYLLABLE QOA 1248 ; Recommended # 3.0 ETHIOPIC SYLLABLE QWA @@ -411,11 +352,9 @@ 1258 ; Recommended # 3.0 ETHIOPIC SYLLABLE QHWA 125A..125D ; Recommended # 3.0 [4] ETHIOPIC SYLLABLE QHWI..ETHIOPIC SYLLABLE QHWE 1260..1286 ; Recommended # 3.0 [39] ETHIOPIC SYLLABLE BA..ETHIOPIC SYLLABLE XO -1287 ; Recommended # 4.1 ETHIOPIC SYLLABLE XOA 1288 ; Recommended # 3.0 ETHIOPIC SYLLABLE XWA 128A..128D ; Recommended # 3.0 [4] ETHIOPIC SYLLABLE XWI..ETHIOPIC SYLLABLE XWE 1290..12AE ; Recommended # 3.0 [31] ETHIOPIC SYLLABLE NA..ETHIOPIC SYLLABLE KO -12AF ; Recommended # 4.1 ETHIOPIC SYLLABLE KOA 12B0 ; Recommended # 3.0 ETHIOPIC SYLLABLE KWA 12B2..12B5 ; Recommended # 3.0 [4] ETHIOPIC SYLLABLE KWI..ETHIOPIC SYLLABLE KWE 12B8..12BE ; Recommended # 3.0 [7] ETHIOPIC SYLLABLE KXA..ETHIOPIC SYLLABLE KXO @@ -426,161 +365,1316 @@ 12D0..12D6 ; Recommended # 3.0 [7] ETHIOPIC SYLLABLE PHARYNGEAL A..ETHIOPIC SYLLABLE PHARYNGEAL O 12D8..12EE ; Recommended # 3.0 [23] ETHIOPIC SYLLABLE ZA..ETHIOPIC SYLLABLE YO 12EF ; Recommended # 4.1 ETHIOPIC SYLLABLE YOA -12F0..130E ; Recommended # 3.0 [31] ETHIOPIC SYLLABLE DA..ETHIOPIC SYLLABLE GO -130F ; Recommended # 4.1 ETHIOPIC SYLLABLE GOA +12F0..12F7 ; Recommended # 3.0 [8] ETHIOPIC SYLLABLE DA..ETHIOPIC SYLLABLE DWA +1300..130E ; Recommended # 3.0 [15] ETHIOPIC SYLLABLE JA..ETHIOPIC SYLLABLE GO 1310 ; Recommended # 3.0 ETHIOPIC SYLLABLE GWA 1312..1315 ; Recommended # 3.0 [4] ETHIOPIC SYLLABLE GWI..ETHIOPIC SYLLABLE GWE 1318..131E ; Recommended # 3.0 [7] ETHIOPIC SYLLABLE GGA..ETHIOPIC SYLLABLE GGO -131F ; Recommended # 4.1 ETHIOPIC SYLLABLE GGWAA 1320..1346 ; Recommended # 3.0 [39] ETHIOPIC SYLLABLE THA..ETHIOPIC SYLLABLE TZO -1347 ; Recommended # 4.1 ETHIOPIC SYLLABLE TZOA -1348..135A ; Recommended # 3.0 [19] ETHIOPIC SYLLABLE FA..ETHIOPIC SYLLABLE FYA -135D..135E ; Recommended # 6.0 [2] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING VOWEL LENGTH MARK -135F ; Recommended # 4.1 ETHIOPIC COMBINING GEMINATION MARK -1380..138F ; Recommended # 4.1 [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE -1780..17A2 ; Recommended # 3.0 [35] KHMER LETTER KA..KHMER LETTER QA +1348..1359 ; Recommended # 3.0 [18] ETHIOPIC SYLLABLE FA..ETHIOPIC SYLLABLE MYA +1780..179C ; Recommended # 3.0 [29] KHMER LETTER KA..KHMER LETTER VO +179F..17A2 ; Recommended # 3.0 [4] KHMER LETTER SA..KHMER LETTER QA 17A5..17A7 ; Recommended # 3.0 [3] KHMER INDEPENDENT VOWEL QI..KHMER INDEPENDENT VOWEL QU -17A9..17B3 ; Recommended # 3.0 [11] KHMER INDEPENDENT VOWEL QUU..KHMER INDEPENDENT VOWEL QAU +17AA..17B3 ; Recommended # 3.0 [10] KHMER INDEPENDENT VOWEL QUUV..KHMER INDEPENDENT VOWEL QAU 17B6..17CD ; Recommended # 3.0 [24] KHMER VOWEL SIGN AA..KHMER SIGN TOANDAKHIAT 17D0 ; Recommended # 3.0 KHMER SIGN SAMYOK SANNYA 17D2 ; Recommended # 3.0 KHMER SIGN COENG -17D7 ; Recommended # 3.0 KHMER SIGN LEK TOO -17DC ; Recommended # 3.0 KHMER SIGN AVAKRAHASANYA 17E0..17E9 ; Recommended # 3.0 [10] KHMER DIGIT ZERO..KHMER DIGIT NINE 1C90..1CBA ; Recommended # 11.0 [43] GEORGIAN MTAVRULI CAPITAL LETTER AN..GEORGIAN MTAVRULI CAPITAL LETTER AIN 1CBD..1CBF ; Recommended # 11.0 [3] GEORGIAN MTAVRULI CAPITAL LETTER AEN..GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN -1E00..1E99 ; Recommended # 1.1 [154] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER Y WITH RING ABOVE +1E0C..1E0D ; Recommended # 1.1 [2] LATIN CAPITAL LETTER D WITH DOT BELOW..LATIN SMALL LETTER D WITH DOT BELOW +1E12..1E13 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW..LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW +1E20..1E21 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER G WITH MACRON..LATIN SMALL LETTER G WITH MACRON +1E24..1E25 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER H WITH DOT BELOW..LATIN SMALL LETTER H WITH DOT BELOW +1E36..1E37 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER L WITH DOT BELOW..LATIN SMALL LETTER L WITH DOT BELOW +1E3C..1E3F ; Recommended # 1.1 [4] LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW..LATIN SMALL LETTER M WITH ACUTE +1E42..1E4B ; Recommended # 1.1 [10] LATIN CAPITAL LETTER M WITH DOT BELOW..LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW +1E5A..1E5B ; Recommended # 1.1 [2] LATIN CAPITAL LETTER R WITH DOT BELOW..LATIN SMALL LETTER R WITH DOT BELOW +1E62..1E63 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER S WITH DOT BELOW..LATIN SMALL LETTER S WITH DOT BELOW +1E6C..1E6D ; Recommended # 1.1 [2] LATIN CAPITAL LETTER T WITH DOT BELOW..LATIN SMALL LETTER T WITH DOT BELOW +1E70..1E71 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW..LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW +1E8C..1E8D ; Recommended # 1.1 [2] LATIN CAPITAL LETTER X WITH DIAERESIS..LATIN SMALL LETTER X WITH DIAERESIS +1E92..1E93 ; Recommended # 1.1 [2] LATIN CAPITAL LETTER Z WITH DOT BELOW..LATIN SMALL LETTER Z WITH DOT BELOW 1E9E ; Recommended # 5.1 LATIN CAPITAL LETTER SHARP S 1EA0..1EF9 ; Recommended # 1.1 [90] LATIN CAPITAL LETTER A WITH DOT BELOW..LATIN SMALL LETTER Y WITH TILDE -1F00..1F15 ; Recommended # 1.1 [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA -1F18..1F1D ; Recommended # 1.1 [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA -1F20..1F45 ; Recommended # 1.1 [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA -1F48..1F4D ; Recommended # 1.1 [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA -1F50..1F57 ; Recommended # 1.1 [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI -1F59 ; Recommended # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA -1F5B ; Recommended # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA -1F5D ; Recommended # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA -1F5F..1F70 ; Recommended # 1.1 [18] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER ALPHA WITH VARIA -1F72 ; Recommended # 1.1 GREEK SMALL LETTER EPSILON WITH VARIA -1F74 ; Recommended # 1.1 GREEK SMALL LETTER ETA WITH VARIA -1F76 ; Recommended # 1.1 GREEK SMALL LETTER IOTA WITH VARIA -1F78 ; Recommended # 1.1 GREEK SMALL LETTER OMICRON WITH VARIA -1F7A ; Recommended # 1.1 GREEK SMALL LETTER UPSILON WITH VARIA -1F7C ; Recommended # 1.1 GREEK SMALL LETTER OMEGA WITH VARIA -1F80..1FB4 ; Recommended # 1.1 [53] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI -1FB6..1FBA ; Recommended # 1.1 [5] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH VARIA -1FBC ; Recommended # 1.1 GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI -1FC2..1FC4 ; Recommended # 1.1 [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI -1FC6..1FC8 ; Recommended # 1.1 [3] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER EPSILON WITH VARIA -1FCA ; Recommended # 1.1 GREEK CAPITAL LETTER ETA WITH VARIA -1FCC ; Recommended # 1.1 GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI -1FD0..1FD2 ; Recommended # 1.1 [3] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA -1FD6..1FDA ; Recommended # 1.1 [5] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH VARIA -1FE0..1FE2 ; Recommended # 1.1 [3] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA -1FE4..1FEA ; Recommended # 1.1 [7] GREEK SMALL LETTER RHO WITH PSILI..GREEK CAPITAL LETTER UPSILON WITH VARIA +1FA0..1FAF ; Recommended # 1.1 [16] GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FB2..1FB4 ; Recommended # 1.1 [3] GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI 1FEC ; Recommended # 1.1 GREEK CAPITAL LETTER RHO WITH DASIA -1FF2..1FF4 ; Recommended # 1.1 [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI -1FF6..1FF8 ; Recommended # 1.1 [3] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMICRON WITH VARIA -1FFA ; Recommended # 1.1 GREEK CAPITAL LETTER OMEGA WITH VARIA -1FFC ; Recommended # 1.1 GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI -2D27 ; Recommended # 6.1 GEORGIAN SMALL LETTER YN -2D2D ; Recommended # 6.1 GEORGIAN SMALL LETTER AEN -2D80..2D96 ; Recommended # 4.1 [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE -2DA0..2DA6 ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO -2DA8..2DAE ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO -2DB0..2DB6 ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO -2DB8..2DBE ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO -2DC0..2DC6 ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO -2DC8..2DCE ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO -2DD0..2DD6 ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO -2DD8..2DDE ; Recommended # 4.1 [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO 3005..3007 ; Recommended # 1.1 [3] IDEOGRAPHIC ITERATION MARK..IDEOGRAPHIC NUMBER ZERO 3041..3094 ; Recommended # 1.1 [84] HIRAGANA LETTER SMALL A..HIRAGANA LETTER VU 3095..3096 ; Recommended # 3.2 [2] HIRAGANA LETTER SMALL KA..HIRAGANA LETTER SMALL KE -3099..309A ; Recommended # 1.1 [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK 309D..309E ; Recommended # 1.1 [2] HIRAGANA ITERATION MARK..HIRAGANA VOICED ITERATION MARK 30A1..30FA ; Recommended # 1.1 [90] KATAKANA LETTER SMALL A..KATAKANA LETTER VO 30FC..30FE ; Recommended # 1.1 [3] KATAKANA-HIRAGANA PROLONGED SOUND MARK..KATAKANA VOICED ITERATION MARK -3105..312C ; Recommended # 1.1 [40] BOPOMOFO LETTER B..BOPOMOFO LETTER GN -312D ; Recommended # 5.1 BOPOMOFO LETTER IH -312F ; Recommended # 11.0 BOPOMOFO LETTER NN -31A0..31B7 ; Recommended # 3.0 [24] BOPOMOFO LETTER BU..BOPOMOFO FINAL LETTER H -31B8..31BA ; Recommended # 6.0 [3] BOPOMOFO LETTER GH..BOPOMOFO LETTER ZY -31BB..31BF ; Recommended # 13.0 [5] BOPOMOFO FINAL LETTER G..BOPOMOFO LETTER AH -3400..4DB5 ; Recommended # 3.0 [6582] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-4DB5 -4DB6..4DBF ; Recommended # 13.0 [10] CJK UNIFIED IDEOGRAPH-4DB6..CJK UNIFIED IDEOGRAPH-4DBF -4E00..9FA5 ; Recommended # 1.1 [20902] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-9FA5 -9FA6..9FBB ; Recommended # 4.1 [22] CJK UNIFIED IDEOGRAPH-9FA6..CJK UNIFIED IDEOGRAPH-9FBB -9FBC..9FC3 ; Recommended # 5.1 [8] CJK UNIFIED IDEOGRAPH-9FBC..CJK UNIFIED IDEOGRAPH-9FC3 -9FC4..9FCB ; Recommended # 5.2 [8] CJK UNIFIED IDEOGRAPH-9FC4..CJK UNIFIED IDEOGRAPH-9FCB -9FCC ; Recommended # 6.1 CJK UNIFIED IDEOGRAPH-9FCC -9FCD..9FD5 ; Recommended # 8.0 [9] CJK UNIFIED IDEOGRAPH-9FCD..CJK UNIFIED IDEOGRAPH-9FD5 -9FD6..9FEA ; Recommended # 10.0 [21] CJK UNIFIED IDEOGRAPH-9FD6..CJK UNIFIED IDEOGRAPH-9FEA -9FEB..9FEF ; Recommended # 11.0 [5] CJK UNIFIED IDEOGRAPH-9FEB..CJK UNIFIED IDEOGRAPH-9FEF -9FF0..9FFC ; Recommended # 13.0 [13] CJK UNIFIED IDEOGRAPH-9FF0..CJK UNIFIED IDEOGRAPH-9FFC -9FFD..9FFF ; Recommended # 14.0 [3] CJK UNIFIED IDEOGRAPH-9FFD..CJK UNIFIED IDEOGRAPH-9FFF -A67F ; Recommended # 5.1 CYRILLIC PAYEROK -A717..A71A ; Recommended # 5.0 [4] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOWER RIGHT CORNER ANGLE -A71B..A71F ; Recommended # 5.1 [5] MODIFIER LETTER RAISED UP ARROW..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK -A788 ; Recommended # 5.1 MODIFIER LETTER LOW CIRCUMFLEX ACCENT +3447 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3447 +3473 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3473 +34E4 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-34E4 +3577 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3577 +359E ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-359E +35A1 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35A1 +35AD ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35AD +35BF ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35BF +35CE ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35CE +35F3 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35F3 +35FE ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-35FE +360E ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-360E +361A ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-361A +3918 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3918 +3960 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3960 +396E ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-396E +39CF..39D0 ; Recommended # 3.0 [2] CJK UNIFIED IDEOGRAPH-39CF..CJK UNIFIED IDEOGRAPH-39D0 +39DB ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-39DB +39DF ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-39DF +39F8 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-39F8 +39FE ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-39FE +3A18 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3A18 +3A52 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3A52 +3A5C ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3A5C +3A67 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3A67 +3A73 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3A73 +3B39 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3B39 +3B4E ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3B4E +3BA3 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3BA3 +3C6E ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3C6E +3CE0 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3CE0 +3DE7 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3DE7 +3DEB ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3DEB +3E74 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3E74 +3ED0 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-3ED0 +4056 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4056 +4065 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4065 +406A ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-406A +40BB ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-40BB +40DF ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-40DF +4137 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4137 +415F ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-415F +4337 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4337 +43AC ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-43AC +43B1 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-43B1 +43D3 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-43D3 +43DD ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-43DD +4443 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4443 +44D6 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-44D6 +44EA ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-44EA +4606 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4606 +464C ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-464C +4661 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4661 +4723 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4723 +4729 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4729 +477C ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-477C +478D ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-478D +47F4 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-47F4 +4882 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4882 +4947 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4947 +497A ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-497A +497D ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-497D +4982..4983 ; Recommended # 3.0 [2] CJK UNIFIED IDEOGRAPH-4982..CJK UNIFIED IDEOGRAPH-4983 +4985..4986 ; Recommended # 3.0 [2] CJK UNIFIED IDEOGRAPH-4985..CJK UNIFIED IDEOGRAPH-4986 +499B ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-499B +499F ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-499F +49B6..49B7 ; Recommended # 3.0 [2] CJK UNIFIED IDEOGRAPH-49B6..CJK UNIFIED IDEOGRAPH-49B7 +4A12 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4A12 +4AB8 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4AB8 +4C77 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4C77 +4C7D ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4C7D +4C81 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4C81 +4C85 ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4C85 +4C9D..4CA3 ; Recommended # 3.0 [7] CJK UNIFIED IDEOGRAPH-4C9D..CJK UNIFIED IDEOGRAPH-4CA3 +4D13..4D19 ; Recommended # 3.0 [7] CJK UNIFIED IDEOGRAPH-4D13..CJK UNIFIED IDEOGRAPH-4D19 +4DAE ; Recommended # 3.0 CJK UNIFIED IDEOGRAPH-4DAE +4E00..4E11 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-4E00..CJK UNIFIED IDEOGRAPH-4E11 +4E13..4E28 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-4E13..CJK UNIFIED IDEOGRAPH-4E28 +4E2A..4E67 ; Recommended # 1.1 [62] CJK UNIFIED IDEOGRAPH-4E2A..CJK UNIFIED IDEOGRAPH-4E67 +4E69..4E78 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-4E69..CJK UNIFIED IDEOGRAPH-4E78 +4E7A..4E95 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-4E7A..CJK UNIFIED IDEOGRAPH-4E95 +4E97..4EA2 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-4E97..CJK UNIFIED IDEOGRAPH-4EA2 +4EA4..4EBB ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-4EA4..CJK UNIFIED IDEOGRAPH-4EBB +4EBD..4ECB ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-4EBD..CJK UNIFIED IDEOGRAPH-4ECB +4ECD..4EE6 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-4ECD..CJK UNIFIED IDEOGRAPH-4EE6 +4EE8..4EF7 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-4EE8..CJK UNIFIED IDEOGRAPH-4EF7 +4EFB ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-4EFB +4EFD ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-4EFD +4EFF..4F06 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-4EFF..CJK UNIFIED IDEOGRAPH-4F06 +4F08..4F15 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-4F08..CJK UNIFIED IDEOGRAPH-4F15 +4F17..4F27 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-4F17..CJK UNIFIED IDEOGRAPH-4F27 +4F29..4F30 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-4F29..CJK UNIFIED IDEOGRAPH-4F30 +4F32..4F34 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-4F32..CJK UNIFIED IDEOGRAPH-4F34 +4F36 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-4F36 +4F38..4F3F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-4F38..CJK UNIFIED IDEOGRAPH-4F3F +4F41..4F43 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-4F41..CJK UNIFIED IDEOGRAPH-4F43 +4F45..4F70 ; Recommended # 1.1 [44] CJK UNIFIED IDEOGRAPH-4F45..CJK UNIFIED IDEOGRAPH-4F70 +4F72..4F8B ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-4F72..CJK UNIFIED IDEOGRAPH-4F8B +4F8D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-4F8D +4F8F..4FA1 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-4F8F..CJK UNIFIED IDEOGRAPH-4FA1 +4FA3..4FBC ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-4FA3..CJK UNIFIED IDEOGRAPH-4FBC +4FBE..4FC5 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-4FBE..CJK UNIFIED IDEOGRAPH-4FC5 +4FC7 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-4FC7 +4FC9..4FCB ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-4FC9..CJK UNIFIED IDEOGRAPH-4FCB +4FCD..4FE1 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-4FCD..CJK UNIFIED IDEOGRAPH-4FE1 +4FE3..4FFB ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-4FE3..CJK UNIFIED IDEOGRAPH-4FFB +4FFE..500F ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-4FFE..CJK UNIFIED IDEOGRAPH-500F +5011..5033 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-5011..CJK UNIFIED IDEOGRAPH-5033 +5035..5037 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5035..CJK UNIFIED IDEOGRAPH-5037 +5039..503C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5039..CJK UNIFIED IDEOGRAPH-503C +503E..5041 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-503E..CJK UNIFIED IDEOGRAPH-5041 +5043..5051 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-5043..CJK UNIFIED IDEOGRAPH-5051 +5053..5057 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5053..CJK UNIFIED IDEOGRAPH-5057 +5059..507B ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-5059..CJK UNIFIED IDEOGRAPH-507B +507D..5080 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-507D..CJK UNIFIED IDEOGRAPH-5080 +5082..5092 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-5082..CJK UNIFIED IDEOGRAPH-5092 +5094..5096 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5094..CJK UNIFIED IDEOGRAPH-5096 +5098..509E ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5098..CJK UNIFIED IDEOGRAPH-509E +50A2..50B8 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-50A2..CJK UNIFIED IDEOGRAPH-50B8 +50BA..50C2 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-50BA..CJK UNIFIED IDEOGRAPH-50C2 +50C4..50D7 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-50C4..CJK UNIFIED IDEOGRAPH-50D7 +50D9..50DE ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-50D9..CJK UNIFIED IDEOGRAPH-50DE +50E0 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-50E0 +50E3..50EA ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-50E3..CJK UNIFIED IDEOGRAPH-50EA +50EC..50F3 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-50EC..CJK UNIFIED IDEOGRAPH-50F3 +50F5..50F6 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-50F5..CJK UNIFIED IDEOGRAPH-50F6 +50F8..511A ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-50F8..CJK UNIFIED IDEOGRAPH-511A +511C..5127 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-511C..CJK UNIFIED IDEOGRAPH-5127 +5129..512A ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5129..CJK UNIFIED IDEOGRAPH-512A +512C..5141 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-512C..CJK UNIFIED IDEOGRAPH-5141 +5143..5149 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5143..CJK UNIFIED IDEOGRAPH-5149 +514B..514E ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-514B..CJK UNIFIED IDEOGRAPH-514E +5150..5152 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5150..CJK UNIFIED IDEOGRAPH-5152 +5154..5157 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5154..CJK UNIFIED IDEOGRAPH-5157 +5159..515F ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5159..CJK UNIFIED IDEOGRAPH-515F +5161..5163 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5161..CJK UNIFIED IDEOGRAPH-5163 +5165..5171 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-5165..CJK UNIFIED IDEOGRAPH-5171 +5173..517D ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5173..CJK UNIFIED IDEOGRAPH-517D +517F..5182 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-517F..CJK UNIFIED IDEOGRAPH-5182 +5185..518D ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5185..CJK UNIFIED IDEOGRAPH-518D +518F..51A0 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-518F..CJK UNIFIED IDEOGRAPH-51A0 +51A2 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-51A2 +51A4..51AC ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-51A4..CJK UNIFIED IDEOGRAPH-51AC +51AE..51B7 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-51AE..CJK UNIFIED IDEOGRAPH-51B7 +51B9 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-51B9 +51BB..51C1 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-51BB..CJK UNIFIED IDEOGRAPH-51C1 +51C3..51D1 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-51C3..CJK UNIFIED IDEOGRAPH-51D1 +51D4..51DE ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-51D4..CJK UNIFIED IDEOGRAPH-51DE +51E0..51EB ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-51E0..CJK UNIFIED IDEOGRAPH-51EB +51ED ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-51ED +51EF..51F1 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-51EF..CJK UNIFIED IDEOGRAPH-51F1 +51F3..5252 ; Recommended # 1.1 [96] CJK UNIFIED IDEOGRAPH-51F3..CJK UNIFIED IDEOGRAPH-5252 +5254..5265 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-5254..CJK UNIFIED IDEOGRAPH-5265 +5267..5278 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-5267..CJK UNIFIED IDEOGRAPH-5278 +527A..5284 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-527A..CJK UNIFIED IDEOGRAPH-5284 +5286..528D ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5286..CJK UNIFIED IDEOGRAPH-528D +528F..52C3 ; Recommended # 1.1 [53] CJK UNIFIED IDEOGRAPH-528F..CJK UNIFIED IDEOGRAPH-52C3 +52C5..52C7 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-52C5..CJK UNIFIED IDEOGRAPH-52C7 +52C9..52CB ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-52C9..CJK UNIFIED IDEOGRAPH-52CB +52CD ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-52CD +52CF..52D0 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-52CF..CJK UNIFIED IDEOGRAPH-52D0 +52D2..52D3 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-52D2..CJK UNIFIED IDEOGRAPH-52D3 +52D5..52E0 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-52D5..CJK UNIFIED IDEOGRAPH-52E0 +52E2..52E4 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-52E2..CJK UNIFIED IDEOGRAPH-52E4 +52E6..52ED ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-52E6..CJK UNIFIED IDEOGRAPH-52ED +52EF..5302 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-52EF..CJK UNIFIED IDEOGRAPH-5302 +5305..5317 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-5305..CJK UNIFIED IDEOGRAPH-5317 +5319..531A ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5319..CJK UNIFIED IDEOGRAPH-531A +531C..531D ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-531C..CJK UNIFIED IDEOGRAPH-531D +531F..5326 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-531F..CJK UNIFIED IDEOGRAPH-5326 +5328 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5328 +532A..5331 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-532A..CJK UNIFIED IDEOGRAPH-5331 +5333..5334 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5333..CJK UNIFIED IDEOGRAPH-5334 +5337..5341 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5337..CJK UNIFIED IDEOGRAPH-5341 +5343..535A ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-5343..CJK UNIFIED IDEOGRAPH-535A +535C ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-535C +535E..5369 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-535E..CJK UNIFIED IDEOGRAPH-5369 +536B..536C ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-536B..CJK UNIFIED IDEOGRAPH-536C +536E..537F ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-536E..CJK UNIFIED IDEOGRAPH-537F +5381..53A0 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-5381..CJK UNIFIED IDEOGRAPH-53A0 +53A2..53A9 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-53A2..CJK UNIFIED IDEOGRAPH-53A9 +53AC..53AE ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-53AC..CJK UNIFIED IDEOGRAPH-53AE +53B0..53B9 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-53B0..CJK UNIFIED IDEOGRAPH-53B9 +53BB..53C4 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-53BB..CJK UNIFIED IDEOGRAPH-53C4 +53C6..53CE ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-53C6..CJK UNIFIED IDEOGRAPH-53CE +53D0..53DC ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-53D0..CJK UNIFIED IDEOGRAPH-53DC +53DF..53E6 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-53DF..CJK UNIFIED IDEOGRAPH-53E6 +53E8..53FE ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-53E8..CJK UNIFIED IDEOGRAPH-53FE +5401..5419 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-5401..CJK UNIFIED IDEOGRAPH-5419 +541B..5421 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-541B..CJK UNIFIED IDEOGRAPH-5421 +5423..544B ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-5423..CJK UNIFIED IDEOGRAPH-544B +544D..545C ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-544D..CJK UNIFIED IDEOGRAPH-545C +545E..5468 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-545E..CJK UNIFIED IDEOGRAPH-5468 +546A..5489 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-546A..CJK UNIFIED IDEOGRAPH-5489 +548B..54B4 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-548B..CJK UNIFIED IDEOGRAPH-54B4 +54B6..54F5 ; Recommended # 1.1 [64] CJK UNIFIED IDEOGRAPH-54B6..CJK UNIFIED IDEOGRAPH-54F5 +54F7..5514 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-54F7..CJK UNIFIED IDEOGRAPH-5514 +5516..5517 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5516..CJK UNIFIED IDEOGRAPH-5517 +551A..5546 ; Recommended # 1.1 [45] CJK UNIFIED IDEOGRAPH-551A..CJK UNIFIED IDEOGRAPH-5546 +5548..555F ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-5548..CJK UNIFIED IDEOGRAPH-555F +5561..5579 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-5561..CJK UNIFIED IDEOGRAPH-5579 +557B..55DF ; Recommended # 1.1 [101] CJK UNIFIED IDEOGRAPH-557B..CJK UNIFIED IDEOGRAPH-55DF +55E1..55F7 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-55E1..CJK UNIFIED IDEOGRAPH-55F7 +55F9..5609 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-55F9..CJK UNIFIED IDEOGRAPH-5609 +560C..561F ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-560C..CJK UNIFIED IDEOGRAPH-561F +5621..562A ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-5621..CJK UNIFIED IDEOGRAPH-562A +562C..5636 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-562C..CJK UNIFIED IDEOGRAPH-5636 +5638..563B ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5638..CJK UNIFIED IDEOGRAPH-563B +563D..5643 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-563D..CJK UNIFIED IDEOGRAPH-5643 +5645..564A ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-5645..CJK UNIFIED IDEOGRAPH-564A +564C..5650 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-564C..CJK UNIFIED IDEOGRAPH-5650 +5652..5655 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5652..CJK UNIFIED IDEOGRAPH-5655 +5657..565E ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5657..CJK UNIFIED IDEOGRAPH-565E +5660 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5660 +5662..5674 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-5662..CJK UNIFIED IDEOGRAPH-5674 +5676..567C ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5676..CJK UNIFIED IDEOGRAPH-567C +567E..5687 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-567E..CJK UNIFIED IDEOGRAPH-5687 +5689..568A ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5689..CJK UNIFIED IDEOGRAPH-568A +568C..5695 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-568C..CJK UNIFIED IDEOGRAPH-5695 +5697..569D ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5697..CJK UNIFIED IDEOGRAPH-569D +569F..56B9 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-569F..CJK UNIFIED IDEOGRAPH-56B9 +56BB..56CE ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-56BB..CJK UNIFIED IDEOGRAPH-56CE +56D0..56D8 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-56D0..CJK UNIFIED IDEOGRAPH-56D8 +56DA..56E5 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-56DA..CJK UNIFIED IDEOGRAPH-56E5 +56E7..56F5 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-56E7..CJK UNIFIED IDEOGRAPH-56F5 +56F7 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-56F7 +56F9..56FA ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-56F9..CJK UNIFIED IDEOGRAPH-56FA +56FD..5704 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-56FD..CJK UNIFIED IDEOGRAPH-5704 +5706..5710 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5706..CJK UNIFIED IDEOGRAPH-5710 +5712..5716 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5712..CJK UNIFIED IDEOGRAPH-5716 +5718..5720 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5718..CJK UNIFIED IDEOGRAPH-5720 +5722..5723 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5722..CJK UNIFIED IDEOGRAPH-5723 +5725..573C ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-5725..CJK UNIFIED IDEOGRAPH-573C +573E..5742 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-573E..CJK UNIFIED IDEOGRAPH-5742 +5744..5747 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5744..CJK UNIFIED IDEOGRAPH-5747 +5749..5754 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-5749..CJK UNIFIED IDEOGRAPH-5754 +5757 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5757 +5759..5762 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-5759..CJK UNIFIED IDEOGRAPH-5762 +5764..5777 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-5764..CJK UNIFIED IDEOGRAPH-5777 +5779..5780 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5779..CJK UNIFIED IDEOGRAPH-5780 +5782..5786 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5782..CJK UNIFIED IDEOGRAPH-5786 +5788..5795 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-5788..CJK UNIFIED IDEOGRAPH-5795 +5797..57A7 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-5797..CJK UNIFIED IDEOGRAPH-57A7 +57A9..57C9 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-57A9..CJK UNIFIED IDEOGRAPH-57C9 +57CB..57D0 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-57CB..CJK UNIFIED IDEOGRAPH-57D0 +57D2..57DA ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-57D2..CJK UNIFIED IDEOGRAPH-57DA +57DC..5816 ; Recommended # 1.1 [59] CJK UNIFIED IDEOGRAPH-57DC..CJK UNIFIED IDEOGRAPH-5816 +5819..584F ; Recommended # 1.1 [55] CJK UNIFIED IDEOGRAPH-5819..CJK UNIFIED IDEOGRAPH-584F +5851..5855 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5851..CJK UNIFIED IDEOGRAPH-5855 +5857..585F ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5857..CJK UNIFIED IDEOGRAPH-585F +5861..5865 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5861..CJK UNIFIED IDEOGRAPH-5865 +5868..5876 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-5868..CJK UNIFIED IDEOGRAPH-5876 +5878..5894 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-5878..CJK UNIFIED IDEOGRAPH-5894 +5896..58A9 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-5896..CJK UNIFIED IDEOGRAPH-58A9 +58AB..58B5 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-58AB..CJK UNIFIED IDEOGRAPH-58B5 +58B7..58BF ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-58B7..CJK UNIFIED IDEOGRAPH-58BF +58C1..58C2 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-58C1..CJK UNIFIED IDEOGRAPH-58C2 +58C5..58CC ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-58C5..CJK UNIFIED IDEOGRAPH-58CC +58CE..58CF ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-58CE..CJK UNIFIED IDEOGRAPH-58CF +58D1..58E0 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-58D1..CJK UNIFIED IDEOGRAPH-58E0 +58E2..58E5 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-58E2..CJK UNIFIED IDEOGRAPH-58E5 +58E7..58F4 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-58E7..CJK UNIFIED IDEOGRAPH-58F4 +58F6..5900 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-58F6..CJK UNIFIED IDEOGRAPH-5900 +5902..5904 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5902..CJK UNIFIED IDEOGRAPH-5904 +5906..5907 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5906..CJK UNIFIED IDEOGRAPH-5907 +5909..5910 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5909..CJK UNIFIED IDEOGRAPH-5910 +5912 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5912 +5914..5922 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-5914..CJK UNIFIED IDEOGRAPH-5922 +5924..5932 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-5924..CJK UNIFIED IDEOGRAPH-5932 +5934..5935 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5934..CJK UNIFIED IDEOGRAPH-5935 +5937..5958 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-5937..CJK UNIFIED IDEOGRAPH-5958 +595A ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-595A +595C..59B6 ; Recommended # 1.1 [91] CJK UNIFIED IDEOGRAPH-595C..CJK UNIFIED IDEOGRAPH-59B6 +59B8..59E6 ; Recommended # 1.1 [47] CJK UNIFIED IDEOGRAPH-59B8..CJK UNIFIED IDEOGRAPH-59E6 +59E8..5A23 ; Recommended # 1.1 [60] CJK UNIFIED IDEOGRAPH-59E8..CJK UNIFIED IDEOGRAPH-5A23 +5A25 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5A25 +5A27..5A2B ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5A27..CJK UNIFIED IDEOGRAPH-5A2B +5A2D..5A2F ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5A2D..CJK UNIFIED IDEOGRAPH-5A2F +5A31..5A53 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-5A31..CJK UNIFIED IDEOGRAPH-5A53 +5A55..5A58 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5A55..CJK UNIFIED IDEOGRAPH-5A58 +5A5A..5A6E ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-5A5A..CJK UNIFIED IDEOGRAPH-5A6E +5A70 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5A70 +5A72..5A86 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-5A72..CJK UNIFIED IDEOGRAPH-5A86 +5A88..5A8C ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5A88..CJK UNIFIED IDEOGRAPH-5A8C +5A8E..5AAA ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-5A8E..CJK UNIFIED IDEOGRAPH-5AAA +5AAC..5AD2 ; Recommended # 1.1 [39] CJK UNIFIED IDEOGRAPH-5AAC..CJK UNIFIED IDEOGRAPH-5AD2 +5AD4..5AEE ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-5AD4..CJK UNIFIED IDEOGRAPH-5AEE +5AF1..5B09 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-5AF1..CJK UNIFIED IDEOGRAPH-5B09 +5B0B..5B0C ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5B0B..CJK UNIFIED IDEOGRAPH-5B0C +5B0E..5B38 ; Recommended # 1.1 [43] CJK UNIFIED IDEOGRAPH-5B0E..CJK UNIFIED IDEOGRAPH-5B38 +5B3A..5B45 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-5B3A..CJK UNIFIED IDEOGRAPH-5B45 +5B47..5B4E ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5B47..CJK UNIFIED IDEOGRAPH-5B4E +5B50..5B51 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5B50..CJK UNIFIED IDEOGRAPH-5B51 +5B53..5B5F ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-5B53..CJK UNIFIED IDEOGRAPH-5B5F +5B62..5B6E ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-5B62..CJK UNIFIED IDEOGRAPH-5B6E +5B70..5B78 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5B70..CJK UNIFIED IDEOGRAPH-5B78 +5B7A..5B7D ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5B7A..CJK UNIFIED IDEOGRAPH-5B7D +5B7F..5B85 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5B7F..CJK UNIFIED IDEOGRAPH-5B85 +5B87..5B8F ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5B87..CJK UNIFIED IDEOGRAPH-5B8F +5B91..5BA8 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-5B91..CJK UNIFIED IDEOGRAPH-5BA8 +5BAA..5BB1 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5BAA..CJK UNIFIED IDEOGRAPH-5BB1 +5BB3..5BB6 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5BB3..CJK UNIFIED IDEOGRAPH-5BB6 +5BB8..5BBB ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5BB8..CJK UNIFIED IDEOGRAPH-5BBB +5BBD..5BC7 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5BBD..CJK UNIFIED IDEOGRAPH-5BC7 +5BC9..5BD9 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-5BC9..CJK UNIFIED IDEOGRAPH-5BD9 +5BDB..5BFF ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-5BDB..CJK UNIFIED IDEOGRAPH-5BFF +5C01..5C1A ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-5C01..CJK UNIFIED IDEOGRAPH-5C1A +5C1C..5C22 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5C1C..CJK UNIFIED IDEOGRAPH-5C22 +5C24..5C25 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5C24..CJK UNIFIED IDEOGRAPH-5C25 +5C27..5C28 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5C27..CJK UNIFIED IDEOGRAPH-5C28 +5C2A..5C35 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-5C2A..CJK UNIFIED IDEOGRAPH-5C35 +5C37..5C59 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-5C37..CJK UNIFIED IDEOGRAPH-5C59 +5C5B..5C84 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-5C5B..CJK UNIFIED IDEOGRAPH-5C84 +5C86..5CB3 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-5C86..CJK UNIFIED IDEOGRAPH-5CB3 +5CB5..5CB8 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-5CB5..CJK UNIFIED IDEOGRAPH-5CB8 +5CBA..5CD4 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-5CBA..CJK UNIFIED IDEOGRAPH-5CD4 +5CD6..5CDC ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5CD6..CJK UNIFIED IDEOGRAPH-5CDC +5CDE..5CF4 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-5CDE..CJK UNIFIED IDEOGRAPH-5CF4 +5CF6..5D2A ; Recommended # 1.1 [53] CJK UNIFIED IDEOGRAPH-5CF6..CJK UNIFIED IDEOGRAPH-5D2A +5D2C..5D2E ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5D2C..CJK UNIFIED IDEOGRAPH-5D2E +5D30..5D3A ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5D30..CJK UNIFIED IDEOGRAPH-5D3A +5D3C..5D52 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-5D3C..CJK UNIFIED IDEOGRAPH-5D52 +5D54..5D56 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-5D54..CJK UNIFIED IDEOGRAPH-5D56 +5D58..5D5F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5D58..CJK UNIFIED IDEOGRAPH-5D5F +5D61..5D82 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-5D61..CJK UNIFIED IDEOGRAPH-5D82 +5D84..5D95 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-5D84..CJK UNIFIED IDEOGRAPH-5D95 +5D97..5DA2 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-5D97..CJK UNIFIED IDEOGRAPH-5DA2 +5DA5..5DAA ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-5DA5..CJK UNIFIED IDEOGRAPH-5DAA +5DAC..5DB2 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-5DAC..CJK UNIFIED IDEOGRAPH-5DB2 +5DB4..5DB8 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5DB4..CJK UNIFIED IDEOGRAPH-5DB8 +5DBA..5DC3 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-5DBA..CJK UNIFIED IDEOGRAPH-5DC3 +5DC5..5DD6 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-5DC5..CJK UNIFIED IDEOGRAPH-5DD6 +5DD8..5DD9 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-5DD8..CJK UNIFIED IDEOGRAPH-5DD9 +5DDB ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5DDB +5DDD..5DF5 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-5DDD..CJK UNIFIED IDEOGRAPH-5DF5 +5DF7..5E11 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-5DF7..CJK UNIFIED IDEOGRAPH-5E11 +5E13..5E47 ; Recommended # 1.1 [53] CJK UNIFIED IDEOGRAPH-5E13..CJK UNIFIED IDEOGRAPH-5E47 +5E49..5E50 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5E49..CJK UNIFIED IDEOGRAPH-5E50 +5E52..5E91 ; Recommended # 1.1 [64] CJK UNIFIED IDEOGRAPH-5E52..CJK UNIFIED IDEOGRAPH-5E91 +5E93..5EB9 ; Recommended # 1.1 [39] CJK UNIFIED IDEOGRAPH-5E93..CJK UNIFIED IDEOGRAPH-5EB9 +5EBB..5EBF ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-5EBB..CJK UNIFIED IDEOGRAPH-5EBF +5EC1..5EEA ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-5EC1..CJK UNIFIED IDEOGRAPH-5EEA +5EEC..5EF8 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-5EEC..CJK UNIFIED IDEOGRAPH-5EF8 +5EFA..5F0D ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-5EFA..CJK UNIFIED IDEOGRAPH-5F0D +5F0F..5F3A ; Recommended # 1.1 [44] CJK UNIFIED IDEOGRAPH-5F0F..CJK UNIFIED IDEOGRAPH-5F3A +5F3C ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-5F3C +5F3E..5F8E ; Recommended # 1.1 [81] CJK UNIFIED IDEOGRAPH-5F3E..CJK UNIFIED IDEOGRAPH-5F8E +5F90..5F99 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-5F90..CJK UNIFIED IDEOGRAPH-5F99 +5F9B..5FA2 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-5F9B..CJK UNIFIED IDEOGRAPH-5FA2 +5FA5..5FAF ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5FA5..CJK UNIFIED IDEOGRAPH-5FAF +5FB1..5FC1 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-5FB1..CJK UNIFIED IDEOGRAPH-5FC1 +5FC3..5FCD ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-5FC3..CJK UNIFIED IDEOGRAPH-5FCD +5FCF..5FDA ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-5FCF..CJK UNIFIED IDEOGRAPH-5FDA +5FDC..5FE1 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-5FDC..CJK UNIFIED IDEOGRAPH-5FE1 +5FE3..5FEB ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-5FE3..CJK UNIFIED IDEOGRAPH-5FEB +5FED..5FFB ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-5FED..CJK UNIFIED IDEOGRAPH-5FFB +5FFD..6022 ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-5FFD..CJK UNIFIED IDEOGRAPH-6022 +6024..6055 ; Recommended # 1.1 [50] CJK UNIFIED IDEOGRAPH-6024..CJK UNIFIED IDEOGRAPH-6055 +6057..6060 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-6057..CJK UNIFIED IDEOGRAPH-6060 +6062..6070 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-6062..CJK UNIFIED IDEOGRAPH-6070 +6072..6073 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6072..CJK UNIFIED IDEOGRAPH-6073 +6075..6090 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-6075..CJK UNIFIED IDEOGRAPH-6090 +6092 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6092 +6094..60A4 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6094..CJK UNIFIED IDEOGRAPH-60A4 +60A6..60D1 ; Recommended # 1.1 [44] CJK UNIFIED IDEOGRAPH-60A6..CJK UNIFIED IDEOGRAPH-60D1 +60D3..60D5 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-60D3..CJK UNIFIED IDEOGRAPH-60D5 +60D7..60DD ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-60D7..CJK UNIFIED IDEOGRAPH-60DD +60DF..60E4 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-60DF..CJK UNIFIED IDEOGRAPH-60E4 +60E6..60FC ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-60E6..CJK UNIFIED IDEOGRAPH-60FC +60FE..6101 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-60FE..CJK UNIFIED IDEOGRAPH-6101 +6103..6106 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6103..CJK UNIFIED IDEOGRAPH-6106 +6108..6110 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-6108..CJK UNIFIED IDEOGRAPH-6110 +6112..611D ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-6112..CJK UNIFIED IDEOGRAPH-611D +611F..6130 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-611F..CJK UNIFIED IDEOGRAPH-6130 +6132 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6132 +6134 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6134 +6136..6137 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6136..CJK UNIFIED IDEOGRAPH-6137 +613A..615F ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-613A..CJK UNIFIED IDEOGRAPH-615F +6161..617A ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-6161..CJK UNIFIED IDEOGRAPH-617A +617C..617E ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-617C..CJK UNIFIED IDEOGRAPH-617E +6180..6185 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-6180..CJK UNIFIED IDEOGRAPH-6185 +6187..6196 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-6187..CJK UNIFIED IDEOGRAPH-6196 +6198..619B ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6198..CJK UNIFIED IDEOGRAPH-619B +619D..61B8 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-619D..CJK UNIFIED IDEOGRAPH-61B8 +61BA ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-61BA +61BC..61D2 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-61BC..CJK UNIFIED IDEOGRAPH-61D2 +61D4 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-61D4 +61D6..61EB ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-61D6..CJK UNIFIED IDEOGRAPH-61EB +61ED..61EE ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-61ED..CJK UNIFIED IDEOGRAPH-61EE +61F0..6204 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-61F0..CJK UNIFIED IDEOGRAPH-6204 +6206..6234 ; Recommended # 1.1 [47] CJK UNIFIED IDEOGRAPH-6206..CJK UNIFIED IDEOGRAPH-6234 +6236..6238 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6236..CJK UNIFIED IDEOGRAPH-6238 +623A..6256 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-623A..CJK UNIFIED IDEOGRAPH-6256 +6258..628C ; Recommended # 1.1 [53] CJK UNIFIED IDEOGRAPH-6258..CJK UNIFIED IDEOGRAPH-628C +628E..629C ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-628E..CJK UNIFIED IDEOGRAPH-629C +629E..62DD ; Recommended # 1.1 [64] CJK UNIFIED IDEOGRAPH-629E..CJK UNIFIED IDEOGRAPH-62DD +62DF..62E9 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-62DF..CJK UNIFIED IDEOGRAPH-62E9 +62EB..6309 ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-62EB..CJK UNIFIED IDEOGRAPH-6309 +630B..6316 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-630B..CJK UNIFIED IDEOGRAPH-6316 +6318..6330 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-6318..CJK UNIFIED IDEOGRAPH-6330 +6332..6336 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-6332..CJK UNIFIED IDEOGRAPH-6336 +6338..635A ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-6338..CJK UNIFIED IDEOGRAPH-635A +635C..638A ; Recommended # 1.1 [47] CJK UNIFIED IDEOGRAPH-635C..CJK UNIFIED IDEOGRAPH-638A +638C..6392 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-638C..CJK UNIFIED IDEOGRAPH-6392 +6394..63D0 ; Recommended # 1.1 [61] CJK UNIFIED IDEOGRAPH-6394..CJK UNIFIED IDEOGRAPH-63D0 +63D2..643A ; Recommended # 1.1 [105] CJK UNIFIED IDEOGRAPH-63D2..CJK UNIFIED IDEOGRAPH-643A +643D..6448 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-643D..CJK UNIFIED IDEOGRAPH-6448 +644A..6459 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-644A..CJK UNIFIED IDEOGRAPH-6459 +645B..647D ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-645B..CJK UNIFIED IDEOGRAPH-647D +647F..6485 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-647F..CJK UNIFIED IDEOGRAPH-6485 +6487..64A0 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-6487..CJK UNIFIED IDEOGRAPH-64A0 +64A2..64AE ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-64A2..CJK UNIFIED IDEOGRAPH-64AE +64B0..64B5 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-64B0..CJK UNIFIED IDEOGRAPH-64B5 +64B7..64C7 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-64B7..CJK UNIFIED IDEOGRAPH-64C7 +64C9..64D4 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-64C9..CJK UNIFIED IDEOGRAPH-64D4 +64D6..64ED ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-64D6..CJK UNIFIED IDEOGRAPH-64ED +64EF..64F4 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-64EF..CJK UNIFIED IDEOGRAPH-64F4 +64F6..64F8 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-64F6..CJK UNIFIED IDEOGRAPH-64F8 +64FA..6501 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-64FA..CJK UNIFIED IDEOGRAPH-6501 +6503..6509 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6503..CJK UNIFIED IDEOGRAPH-6509 +650B..651E ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-650B..CJK UNIFIED IDEOGRAPH-651E +6520..6527 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-6520..CJK UNIFIED IDEOGRAPH-6527 +6529..653F ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-6529..CJK UNIFIED IDEOGRAPH-653F +6541 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6541 +6543..6559 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-6543..CJK UNIFIED IDEOGRAPH-6559 +655B..655E ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-655B..CJK UNIFIED IDEOGRAPH-655E +6560..657C ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-6560..CJK UNIFIED IDEOGRAPH-657C +657E..6589 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-657E..CJK UNIFIED IDEOGRAPH-6589 +658B..6599 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-658B..CJK UNIFIED IDEOGRAPH-6599 +659B..65B4 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-659B..CJK UNIFIED IDEOGRAPH-65B4 +65B6..65BD ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-65B6..CJK UNIFIED IDEOGRAPH-65BD +65BF..65C7 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-65BF..CJK UNIFIED IDEOGRAPH-65C7 +65CA..65D0 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-65CA..CJK UNIFIED IDEOGRAPH-65D0 +65D2..65D7 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-65D2..CJK UNIFIED IDEOGRAPH-65D7 +65D9..65DB ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-65D9..CJK UNIFIED IDEOGRAPH-65DB +65DD..65E3 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-65DD..CJK UNIFIED IDEOGRAPH-65E3 +65E5..65E9 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-65E5..CJK UNIFIED IDEOGRAPH-65E9 +65EB..65F8 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-65EB..CJK UNIFIED IDEOGRAPH-65F8 +65FA..65FD ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-65FA..CJK UNIFIED IDEOGRAPH-65FD +65FF..6616 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-65FF..CJK UNIFIED IDEOGRAPH-6616 +6618..662B ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-6618..CJK UNIFIED IDEOGRAPH-662B +662D..6636 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-662D..CJK UNIFIED IDEOGRAPH-6636 +6639..6647 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-6639..CJK UNIFIED IDEOGRAPH-6647 +6649..664C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6649..CJK UNIFIED IDEOGRAPH-664C +664E..665F ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-664E..CJK UNIFIED IDEOGRAPH-665F +6661..6662 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6661..CJK UNIFIED IDEOGRAPH-6662 +6664..6691 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-6664..CJK UNIFIED IDEOGRAPH-6691 +6693..669B ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-6693..CJK UNIFIED IDEOGRAPH-669B +669D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-669D +669F..66AB ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-669F..CJK UNIFIED IDEOGRAPH-66AB +66AE..66CF ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-66AE..CJK UNIFIED IDEOGRAPH-66CF +66D1..66D2 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-66D1..CJK UNIFIED IDEOGRAPH-66D2 +66D4..66D6 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-66D4..CJK UNIFIED IDEOGRAPH-66D6 +66D8..66DE ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-66D8..CJK UNIFIED IDEOGRAPH-66DE +66E0..66EE ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-66E0..CJK UNIFIED IDEOGRAPH-66EE +66F0..6701 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-66F0..CJK UNIFIED IDEOGRAPH-6701 +6703..6706 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6703..CJK UNIFIED IDEOGRAPH-6706 +6708..6718 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6708..CJK UNIFIED IDEOGRAPH-6718 +671A..6723 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-671A..CJK UNIFIED IDEOGRAPH-6723 +6725..6728 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6725..CJK UNIFIED IDEOGRAPH-6728 +672A..6766 ; Recommended # 1.1 [61] CJK UNIFIED IDEOGRAPH-672A..CJK UNIFIED IDEOGRAPH-6766 +6768..6787 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-6768..CJK UNIFIED IDEOGRAPH-6787 +6789..6795 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-6789..CJK UNIFIED IDEOGRAPH-6795 +6797..67BC ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-6797..CJK UNIFIED IDEOGRAPH-67BC +67BE ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-67BE +67C0..67D4 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-67C0..CJK UNIFIED IDEOGRAPH-67D4 +67D6 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-67D6 +67D8..67F8 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-67D8..CJK UNIFIED IDEOGRAPH-67F8 +67FA..6800 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-67FA..CJK UNIFIED IDEOGRAPH-6800 +6802..6814 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-6802..CJK UNIFIED IDEOGRAPH-6814 +6816..6826 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6816..CJK UNIFIED IDEOGRAPH-6826 +6828..682F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-6828..CJK UNIFIED IDEOGRAPH-682F +6831..6857 ; Recommended # 1.1 [39] CJK UNIFIED IDEOGRAPH-6831..CJK UNIFIED IDEOGRAPH-6857 +6859 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6859 +685B..685D ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-685B..CJK UNIFIED IDEOGRAPH-685D +685F..6879 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-685F..CJK UNIFIED IDEOGRAPH-6879 +687B..6894 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-687B..CJK UNIFIED IDEOGRAPH-6894 +6896..6898 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6896..CJK UNIFIED IDEOGRAPH-6898 +689A..68A4 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-689A..CJK UNIFIED IDEOGRAPH-68A4 +68A6..68B7 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-68A6..CJK UNIFIED IDEOGRAPH-68B7 +68B9..68C2 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-68B9..CJK UNIFIED IDEOGRAPH-68C2 +68C4..68D8 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-68C4..CJK UNIFIED IDEOGRAPH-68D8 +68DA..68E1 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-68DA..CJK UNIFIED IDEOGRAPH-68E1 +68E3..68E4 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-68E3..CJK UNIFIED IDEOGRAPH-68E4 +68E6..6908 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-68E6..CJK UNIFIED IDEOGRAPH-6908 +690A..693D ; Recommended # 1.1 [52] CJK UNIFIED IDEOGRAPH-690A..CJK UNIFIED IDEOGRAPH-693D +693F..694C ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-693F..CJK UNIFIED IDEOGRAPH-694C +694E..699E ; Recommended # 1.1 [81] CJK UNIFIED IDEOGRAPH-694E..CJK UNIFIED IDEOGRAPH-699E +69A0..69A1 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-69A0..CJK UNIFIED IDEOGRAPH-69A1 +69A3..69BF ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-69A3..CJK UNIFIED IDEOGRAPH-69BF +69C1..69D0 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-69C1..CJK UNIFIED IDEOGRAPH-69D0 +69D3..69D4 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-69D3..CJK UNIFIED IDEOGRAPH-69D4 +69D8..6A02 ; Recommended # 1.1 [43] CJK UNIFIED IDEOGRAPH-69D8..CJK UNIFIED IDEOGRAPH-6A02 +6A04..6A1B ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-6A04..CJK UNIFIED IDEOGRAPH-6A1B +6A1D..6A23 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6A1D..CJK UNIFIED IDEOGRAPH-6A23 +6A25..6A36 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-6A25..CJK UNIFIED IDEOGRAPH-6A36 +6A38..6A49 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-6A38..CJK UNIFIED IDEOGRAPH-6A49 +6A4B..6A5B ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6A4B..CJK UNIFIED IDEOGRAPH-6A5B +6A5D..6A6D ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6A5D..CJK UNIFIED IDEOGRAPH-6A6D +6A6F ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6A6F +6A71..6A85 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-6A71..CJK UNIFIED IDEOGRAPH-6A85 +6A87..6A89 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6A87..CJK UNIFIED IDEOGRAPH-6A89 +6A8B..6A8E ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6A8B..CJK UNIFIED IDEOGRAPH-6A8E +6A90..6A98 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-6A90..CJK UNIFIED IDEOGRAPH-6A98 +6A9A..6A9C ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6A9A..CJK UNIFIED IDEOGRAPH-6A9C +6A9E..6AB0 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-6A9E..CJK UNIFIED IDEOGRAPH-6AB0 +6AB2..6ABD ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-6AB2..CJK UNIFIED IDEOGRAPH-6ABD +6ABF ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6ABF +6AC1..6AC3 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6AC1..CJK UNIFIED IDEOGRAPH-6AC3 +6AC5..6AC8 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6AC5..CJK UNIFIED IDEOGRAPH-6AC8 +6ACA..6AD7 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-6ACA..CJK UNIFIED IDEOGRAPH-6AD7 +6AD9..6AE8 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-6AD9..CJK UNIFIED IDEOGRAPH-6AE8 +6AEA..6B0D ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-6AEA..CJK UNIFIED IDEOGRAPH-6B0D +6B0F..6B1A ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-6B0F..CJK UNIFIED IDEOGRAPH-6B1A +6B1C..6B2D ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-6B1C..CJK UNIFIED IDEOGRAPH-6B2D +6B2F..6B34 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-6B2F..CJK UNIFIED IDEOGRAPH-6B34 +6B36..6B3F ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-6B36..CJK UNIFIED IDEOGRAPH-6B3F +6B41..6B56 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-6B41..CJK UNIFIED IDEOGRAPH-6B56 +6B59..6B5C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6B59..CJK UNIFIED IDEOGRAPH-6B5C +6B5E..6B67 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-6B5E..CJK UNIFIED IDEOGRAPH-6B67 +6B69..6B6B ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6B69..CJK UNIFIED IDEOGRAPH-6B6B +6B6D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6B6D +6B6F..6B70 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6B6F..CJK UNIFIED IDEOGRAPH-6B70 +6B72..6B74 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6B72..CJK UNIFIED IDEOGRAPH-6B74 +6B76..6B7C ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6B76..CJK UNIFIED IDEOGRAPH-6B7C +6B7E..6BB7 ; Recommended # 1.1 [58] CJK UNIFIED IDEOGRAPH-6B7E..CJK UNIFIED IDEOGRAPH-6BB7 +6BB9..6BE8 ; Recommended # 1.1 [48] CJK UNIFIED IDEOGRAPH-6BB9..CJK UNIFIED IDEOGRAPH-6BE8 +6BEA..6BF0 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6BEA..CJK UNIFIED IDEOGRAPH-6BF0 +6BF2..6BF3 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6BF2..CJK UNIFIED IDEOGRAPH-6BF3 +6BF5..6BF9 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-6BF5..CJK UNIFIED IDEOGRAPH-6BF9 +6BFB..6C09 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-6BFB..CJK UNIFIED IDEOGRAPH-6C09 +6C0B..6C1B ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6C0B..CJK UNIFIED IDEOGRAPH-6C1B +6C1D..6C2C ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-6C1D..CJK UNIFIED IDEOGRAPH-6C2C +6C2E..6C3B ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-6C2E..CJK UNIFIED IDEOGRAPH-6C3B +6C3D..6C44 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-6C3D..CJK UNIFIED IDEOGRAPH-6C44 +6C46..6C6B ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-6C46..CJK UNIFIED IDEOGRAPH-6C6B +6C6D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6C6D +6C6F..6C9F ; Recommended # 1.1 [49] CJK UNIFIED IDEOGRAPH-6C6F..CJK UNIFIED IDEOGRAPH-6C9F +6CA1..6CD7 ; Recommended # 1.1 [55] CJK UNIFIED IDEOGRAPH-6CA1..CJK UNIFIED IDEOGRAPH-6CD7 +6CD9..6CF3 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-6CD9..CJK UNIFIED IDEOGRAPH-6CF3 +6CF5..6D01 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-6CF5..CJK UNIFIED IDEOGRAPH-6D01 +6D03..6D1B ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-6D03..CJK UNIFIED IDEOGRAPH-6D1B +6D1D..6D23 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6D1D..CJK UNIFIED IDEOGRAPH-6D23 +6D25..6D70 ; Recommended # 1.1 [76] CJK UNIFIED IDEOGRAPH-6D25..CJK UNIFIED IDEOGRAPH-6D70 +6D72..6D80 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-6D72..CJK UNIFIED IDEOGRAPH-6D80 +6D82..6D95 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-6D82..CJK UNIFIED IDEOGRAPH-6D95 +6D97..6DAF ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-6D97..CJK UNIFIED IDEOGRAPH-6DAF +6DB2..6DB5 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6DB2..CJK UNIFIED IDEOGRAPH-6DB5 +6DB7..6DFD ; Recommended # 1.1 [71] CJK UNIFIED IDEOGRAPH-6DB7..CJK UNIFIED IDEOGRAPH-6DFD +6E00 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6E00 +6E03..6E05 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-6E03..CJK UNIFIED IDEOGRAPH-6E05 +6E07..6E11 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-6E07..CJK UNIFIED IDEOGRAPH-6E11 +6E13..6E17 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-6E13..CJK UNIFIED IDEOGRAPH-6E17 +6E19..6E29 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-6E19..CJK UNIFIED IDEOGRAPH-6E29 +6E2B..6E4B ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-6E2B..CJK UNIFIED IDEOGRAPH-6E4B +6E4D..6E6B ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-6E4D..CJK UNIFIED IDEOGRAPH-6E6B +6E6D..6E7A ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-6E6D..CJK UNIFIED IDEOGRAPH-6E7A +6E7E..6E8A ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-6E7E..CJK UNIFIED IDEOGRAPH-6E8A +6E8C..6E94 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-6E8C..CJK UNIFIED IDEOGRAPH-6E94 +6E96..6EDA ; Recommended # 1.1 [69] CJK UNIFIED IDEOGRAPH-6E96..CJK UNIFIED IDEOGRAPH-6EDA +6EDC..6EE2 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6EDC..CJK UNIFIED IDEOGRAPH-6EE2 +6EE4..6F03 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-6EE4..CJK UNIFIED IDEOGRAPH-6F03 +6F05..6F0A ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-6F05..CJK UNIFIED IDEOGRAPH-6F0A +6F0C..6F41 ; Recommended # 1.1 [54] CJK UNIFIED IDEOGRAPH-6F0C..CJK UNIFIED IDEOGRAPH-6F41 +6F43..6F47 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-6F43..CJK UNIFIED IDEOGRAPH-6F47 +6F49 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6F49 +6F4B..6F78 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-6F4B..CJK UNIFIED IDEOGRAPH-6F78 +6F7A..6F97 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-6F7A..CJK UNIFIED IDEOGRAPH-6F97 +6F99 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-6F99 +6F9B..6F9E ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-6F9B..CJK UNIFIED IDEOGRAPH-6F9E +6FA0..6FB6 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-6FA0..CJK UNIFIED IDEOGRAPH-6FB6 +6FB8..6FC4 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-6FB8..CJK UNIFIED IDEOGRAPH-6FC4 +6FC6..6FCF ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-6FC6..CJK UNIFIED IDEOGRAPH-6FCF +6FD1..6FD2 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-6FD1..CJK UNIFIED IDEOGRAPH-6FD2 +6FD4..6FF4 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-6FD4..CJK UNIFIED IDEOGRAPH-6FF4 +6FF6..6FFC ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-6FF6..CJK UNIFIED IDEOGRAPH-6FFC +6FFE..700F ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-6FFE..CJK UNIFIED IDEOGRAPH-700F +7011..7012 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7011..CJK UNIFIED IDEOGRAPH-7012 +7014..7046 ; Recommended # 1.1 [51] CJK UNIFIED IDEOGRAPH-7014..CJK UNIFIED IDEOGRAPH-7046 +7048..704A ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7048..CJK UNIFIED IDEOGRAPH-704A +704C..704D ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-704C..CJK UNIFIED IDEOGRAPH-704D +704F..7071 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-704F..CJK UNIFIED IDEOGRAPH-7071 +7074..707A ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-7074..CJK UNIFIED IDEOGRAPH-707A +707C..7080 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-707C..CJK UNIFIED IDEOGRAPH-7080 +7082..708C ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7082..CJK UNIFIED IDEOGRAPH-708C +708E..7096 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-708E..CJK UNIFIED IDEOGRAPH-7096 +7098..709A ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7098..CJK UNIFIED IDEOGRAPH-709A +709C..70A9 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-709C..CJK UNIFIED IDEOGRAPH-70A9 +70AB..70B1 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-70AB..CJK UNIFIED IDEOGRAPH-70B1 +70B3..70B5 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-70B3..CJK UNIFIED IDEOGRAPH-70B5 +70B7..70D4 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-70B7..CJK UNIFIED IDEOGRAPH-70D4 +70D6..70FD ; Recommended # 1.1 [40] CJK UNIFIED IDEOGRAPH-70D6..CJK UNIFIED IDEOGRAPH-70FD +70FF..7107 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-70FF..CJK UNIFIED IDEOGRAPH-7107 +7109..7123 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-7109..CJK UNIFIED IDEOGRAPH-7123 +7125..7132 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-7125..CJK UNIFIED IDEOGRAPH-7132 +7135..7156 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7135..CJK UNIFIED IDEOGRAPH-7156 +7158..716A ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-7158..CJK UNIFIED IDEOGRAPH-716A +716C ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-716C +716E..718C ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-716E..CJK UNIFIED IDEOGRAPH-718C +718E..7195 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-718E..CJK UNIFIED IDEOGRAPH-7195 +7197..71A5 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-7197..CJK UNIFIED IDEOGRAPH-71A5 +71A7..71AA ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-71A7..CJK UNIFIED IDEOGRAPH-71AA +71AC..71B5 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-71AC..CJK UNIFIED IDEOGRAPH-71B5 +71B7..71CB ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-71B7..CJK UNIFIED IDEOGRAPH-71CB +71CD..71D2 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-71CD..CJK UNIFIED IDEOGRAPH-71D2 +71D4..71F2 ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-71D4..CJK UNIFIED IDEOGRAPH-71F2 +71F4..71F9 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-71F4..CJK UNIFIED IDEOGRAPH-71F9 +71FB..720A ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-71FB..CJK UNIFIED IDEOGRAPH-720A +720C..7210 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-720C..CJK UNIFIED IDEOGRAPH-7210 +7212..7214 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7212..CJK UNIFIED IDEOGRAPH-7214 +7216 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7216 +7218..721F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-7218..CJK UNIFIED IDEOGRAPH-721F +7221..7223 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7221..CJK UNIFIED IDEOGRAPH-7223 +7226..722E ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7226..CJK UNIFIED IDEOGRAPH-722E +7230..7233 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7230..CJK UNIFIED IDEOGRAPH-7233 +7235..7244 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-7235..CJK UNIFIED IDEOGRAPH-7244 +7246..724D ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-7246..CJK UNIFIED IDEOGRAPH-724D +724F ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-724F +7251..7254 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7251..CJK UNIFIED IDEOGRAPH-7254 +7256..72AA ; Recommended # 1.1 [85] CJK UNIFIED IDEOGRAPH-7256..CJK UNIFIED IDEOGRAPH-72AA +72AC..72BD ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-72AC..CJK UNIFIED IDEOGRAPH-72BD +72BF..7301 ; Recommended # 1.1 [67] CJK UNIFIED IDEOGRAPH-72BF..CJK UNIFIED IDEOGRAPH-7301 +7303..730F ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-7303..CJK UNIFIED IDEOGRAPH-730F +7311..7327 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-7311..CJK UNIFIED IDEOGRAPH-7327 +7329..7352 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-7329..CJK UNIFIED IDEOGRAPH-7352 +7354..739B ; Recommended # 1.1 [72] CJK UNIFIED IDEOGRAPH-7354..CJK UNIFIED IDEOGRAPH-739B +739D..73C0 ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-739D..CJK UNIFIED IDEOGRAPH-73C0 +73C2..73F2 ; Recommended # 1.1 [49] CJK UNIFIED IDEOGRAPH-73C2..CJK UNIFIED IDEOGRAPH-73F2 +73F4..73FA ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-73F4..CJK UNIFIED IDEOGRAPH-73FA +73FC..7417 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-73FC..CJK UNIFIED IDEOGRAPH-7417 +7419..7438 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-7419..CJK UNIFIED IDEOGRAPH-7438 +743A..743D ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-743A..CJK UNIFIED IDEOGRAPH-743D +743F..7446 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-743F..CJK UNIFIED IDEOGRAPH-7446 +7448 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7448 +744A..7457 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-744A..CJK UNIFIED IDEOGRAPH-7457 +7459..747A ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7459..CJK UNIFIED IDEOGRAPH-747A +747C..7483 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-747C..CJK UNIFIED IDEOGRAPH-7483 +7485..7495 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7485..CJK UNIFIED IDEOGRAPH-7495 +7497..749C ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7497..CJK UNIFIED IDEOGRAPH-749C +749E..74C6 ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-749E..CJK UNIFIED IDEOGRAPH-74C6 +74C8 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-74C8 +74CA..74CB ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-74CA..CJK UNIFIED IDEOGRAPH-74CB +74CD..74EA ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-74CD..CJK UNIFIED IDEOGRAPH-74EA +74EC..751F ; Recommended # 1.1 [52] CJK UNIFIED IDEOGRAPH-74EC..CJK UNIFIED IDEOGRAPH-751F +7521..7540 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-7521..CJK UNIFIED IDEOGRAPH-7540 +7542..7551 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-7542..CJK UNIFIED IDEOGRAPH-7551 +7553..7554 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7553..CJK UNIFIED IDEOGRAPH-7554 +7556..755D ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-7556..CJK UNIFIED IDEOGRAPH-755D +755F..7560 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-755F..CJK UNIFIED IDEOGRAPH-7560 +7562..7570 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-7562..CJK UNIFIED IDEOGRAPH-7570 +7572..757A ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7572..CJK UNIFIED IDEOGRAPH-757A +757C..7584 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-757C..CJK UNIFIED IDEOGRAPH-7584 +7586..75A8 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-7586..CJK UNIFIED IDEOGRAPH-75A8 +75AA..75B6 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-75AA..CJK UNIFIED IDEOGRAPH-75B6 +75B8..75DB ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-75B8..CJK UNIFIED IDEOGRAPH-75DB +75DD..75ED ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-75DD..CJK UNIFIED IDEOGRAPH-75ED +75EF..762B ; Recommended # 1.1 [61] CJK UNIFIED IDEOGRAPH-75EF..CJK UNIFIED IDEOGRAPH-762B +762D..7643 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-762D..CJK UNIFIED IDEOGRAPH-7643 +7646..7650 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7646..CJK UNIFIED IDEOGRAPH-7650 +7652..7654 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7652..CJK UNIFIED IDEOGRAPH-7654 +7656..7672 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-7656..CJK UNIFIED IDEOGRAPH-7672 +7674..768C ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-7674..CJK UNIFIED IDEOGRAPH-768C +768E..76A0 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-768E..CJK UNIFIED IDEOGRAPH-76A0 +76A3..76A4 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-76A3..CJK UNIFIED IDEOGRAPH-76A4 +76A6..76A7 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-76A6..CJK UNIFIED IDEOGRAPH-76A7 +76A9..76B2 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-76A9..CJK UNIFIED IDEOGRAPH-76B2 +76B4..76B5 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-76B4..CJK UNIFIED IDEOGRAPH-76B5 +76B7..76C0 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-76B7..CJK UNIFIED IDEOGRAPH-76C0 +76C2..76CA ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-76C2..CJK UNIFIED IDEOGRAPH-76CA +76CC..76D8 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-76CC..CJK UNIFIED IDEOGRAPH-76D8 +76DA..76EA ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-76DA..CJK UNIFIED IDEOGRAPH-76EA +76EC..76FF ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-76EC..CJK UNIFIED IDEOGRAPH-76FF +7701 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7701 +7703..770D ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7703..CJK UNIFIED IDEOGRAPH-770D +770F..7720 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-770F..CJK UNIFIED IDEOGRAPH-7720 +7722..772A ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7722..CJK UNIFIED IDEOGRAPH-772A +772C..773E ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-772C..CJK UNIFIED IDEOGRAPH-773E +7740..7741 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7740..CJK UNIFIED IDEOGRAPH-7741 +7743..7763 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-7743..CJK UNIFIED IDEOGRAPH-7763 +7765..7795 ; Recommended # 1.1 [49] CJK UNIFIED IDEOGRAPH-7765..CJK UNIFIED IDEOGRAPH-7795 +7797..77A3 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-7797..CJK UNIFIED IDEOGRAPH-77A3 +77A5..77BD ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-77A5..CJK UNIFIED IDEOGRAPH-77BD +77BF..77C0 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-77BF..CJK UNIFIED IDEOGRAPH-77C0 +77C2..77D1 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-77C2..CJK UNIFIED IDEOGRAPH-77D1 +77D3..77DC ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-77D3..CJK UNIFIED IDEOGRAPH-77DC +77DE..77E3 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-77DE..CJK UNIFIED IDEOGRAPH-77E3 +77E5 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-77E5 +77E7..77F3 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-77E7..CJK UNIFIED IDEOGRAPH-77F3 +77F6..7823 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-77F6..CJK UNIFIED IDEOGRAPH-7823 +7825..7835 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7825..CJK UNIFIED IDEOGRAPH-7835 +7837..7841 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7837..CJK UNIFIED IDEOGRAPH-7841 +7843..7845 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7843..CJK UNIFIED IDEOGRAPH-7845 +7847..784A ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7847..CJK UNIFIED IDEOGRAPH-784A +784C..7875 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-784C..CJK UNIFIED IDEOGRAPH-7875 +7877..7887 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7877..CJK UNIFIED IDEOGRAPH-7887 +7889..78C1 ; Recommended # 1.1 [57] CJK UNIFIED IDEOGRAPH-7889..CJK UNIFIED IDEOGRAPH-78C1 +78C3..78C6 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-78C3..CJK UNIFIED IDEOGRAPH-78C6 +78C8..78D1 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-78C8..CJK UNIFIED IDEOGRAPH-78D1 +78D3..78EF ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-78D3..CJK UNIFIED IDEOGRAPH-78EF +78F1..78F7 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-78F1..CJK UNIFIED IDEOGRAPH-78F7 +78F9..78FF ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-78F9..CJK UNIFIED IDEOGRAPH-78FF +7901..7907 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-7901..CJK UNIFIED IDEOGRAPH-7907 +7909..790C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7909..CJK UNIFIED IDEOGRAPH-790C +790E..7914 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-790E..CJK UNIFIED IDEOGRAPH-7914 +7916..791E ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7916..CJK UNIFIED IDEOGRAPH-791E +7921..7931 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7921..CJK UNIFIED IDEOGRAPH-7931 +7933..7935 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7933..CJK UNIFIED IDEOGRAPH-7935 +7937..7958 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7937..CJK UNIFIED IDEOGRAPH-7958 +795A..796B ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-795A..CJK UNIFIED IDEOGRAPH-796B +796D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-796D +796F..7974 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-796F..CJK UNIFIED IDEOGRAPH-7974 +7977..7985 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-7977..CJK UNIFIED IDEOGRAPH-7985 +7988..799D ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-7988..CJK UNIFIED IDEOGRAPH-799D +799F..79A8 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-799F..CJK UNIFIED IDEOGRAPH-79A8 +79AA..79BB ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-79AA..CJK UNIFIED IDEOGRAPH-79BB +79BD..79C3 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-79BD..CJK UNIFIED IDEOGRAPH-79C3 +79C5..79C6 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-79C5..CJK UNIFIED IDEOGRAPH-79C6 +79C8..79CB ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-79C8..CJK UNIFIED IDEOGRAPH-79CB +79CD..79D3 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-79CD..CJK UNIFIED IDEOGRAPH-79D3 +79D5..79D6 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-79D5..CJK UNIFIED IDEOGRAPH-79D6 +79D8..7A00 ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-79D8..CJK UNIFIED IDEOGRAPH-7A00 +7A02..7A06 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-7A02..CJK UNIFIED IDEOGRAPH-7A06 +7A08 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7A08 +7A0A..7A2B ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7A0A..CJK UNIFIED IDEOGRAPH-7A2B +7A2D..7A37 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7A2D..CJK UNIFIED IDEOGRAPH-7A37 +7A39 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7A39 +7A3B..7A63 ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-7A3B..CJK UNIFIED IDEOGRAPH-7A63 +7A65..7A69 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-7A65..CJK UNIFIED IDEOGRAPH-7A69 +7A6B..7A6E ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7A6B..CJK UNIFIED IDEOGRAPH-7A6E +7A70..7A81 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-7A70..CJK UNIFIED IDEOGRAPH-7A81 +7A83..7A99 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-7A83..CJK UNIFIED IDEOGRAPH-7A99 +7A9C..7AB8 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-7A9C..CJK UNIFIED IDEOGRAPH-7AB8 +7ABA ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7ABA +7ABE..7AC1 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7ABE..CJK UNIFIED IDEOGRAPH-7AC1 +7AC3..7AC5 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7AC3..CJK UNIFIED IDEOGRAPH-7AC5 +7AC7..7AE8 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7AC7..CJK UNIFIED IDEOGRAPH-7AE8 +7AEA..7AF4 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7AEA..CJK UNIFIED IDEOGRAPH-7AF4 +7AF6..7AFB ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7AF6..CJK UNIFIED IDEOGRAPH-7AFB +7AFD..7B06 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-7AFD..CJK UNIFIED IDEOGRAPH-7B06 +7B08..7B1E ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-7B08..CJK UNIFIED IDEOGRAPH-7B1E +7B20..7B26 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-7B20..CJK UNIFIED IDEOGRAPH-7B26 +7B28 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7B28 +7B2A..7B41 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-7B2A..CJK UNIFIED IDEOGRAPH-7B41 +7B43..7B52 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-7B43..CJK UNIFIED IDEOGRAPH-7B52 +7B54..7BA2 ; Recommended # 1.1 [79] CJK UNIFIED IDEOGRAPH-7B54..CJK UNIFIED IDEOGRAPH-7BA2 +7BA4 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7BA4 +7BA6..7BAF ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-7BA6..CJK UNIFIED IDEOGRAPH-7BAF +7BB1 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7BB1 +7BB3..7BF9 ; Recommended # 1.1 [71] CJK UNIFIED IDEOGRAPH-7BB3..CJK UNIFIED IDEOGRAPH-7BF9 +7BFB..7C1A ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-7BFB..CJK UNIFIED IDEOGRAPH-7C1A +7C1C..7C2D ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-7C1C..CJK UNIFIED IDEOGRAPH-7C2D +7C30..7C51 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-7C30..CJK UNIFIED IDEOGRAPH-7C51 +7C53..7C54 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7C53..CJK UNIFIED IDEOGRAPH-7C54 +7C56..7C5C ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-7C56..CJK UNIFIED IDEOGRAPH-7C5C +7C5E..7C75 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-7C5E..CJK UNIFIED IDEOGRAPH-7C75 +7C77..7C86 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-7C77..CJK UNIFIED IDEOGRAPH-7C86 +7C88..7C92 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7C88..CJK UNIFIED IDEOGRAPH-7C92 +7C94..7C99 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7C94..CJK UNIFIED IDEOGRAPH-7C99 +7C9B..7CAB ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7C9B..CJK UNIFIED IDEOGRAPH-7CAB +7CAD..7CD2 ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-7CAD..CJK UNIFIED IDEOGRAPH-7CD2 +7CD4..7CD9 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7CD4..CJK UNIFIED IDEOGRAPH-7CD9 +7CDC..7CE0 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-7CDC..CJK UNIFIED IDEOGRAPH-7CE0 +7CE2 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7CE2 +7CE4 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7CE4 +7CE7..7CFB ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-7CE7..CJK UNIFIED IDEOGRAPH-7CFB +7CFD..7CFE ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7CFD..CJK UNIFIED IDEOGRAPH-7CFE +7D00..7D22 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-7D00..CJK UNIFIED IDEOGRAPH-7D22 +7D24..7D29 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7D24..CJK UNIFIED IDEOGRAPH-7D29 +7D2B..7D2C ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7D2B..CJK UNIFIED IDEOGRAPH-7D2C +7D2E..7D47 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-7D2E..CJK UNIFIED IDEOGRAPH-7D47 +7D49..7D4C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7D49..CJK UNIFIED IDEOGRAPH-7D4C +7D4E..7D59 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-7D4E..CJK UNIFIED IDEOGRAPH-7D59 +7D5B..7D63 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7D5B..CJK UNIFIED IDEOGRAPH-7D63 +7D65..7D77 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-7D65..CJK UNIFIED IDEOGRAPH-7D77 +7D79..7D81 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-7D79..CJK UNIFIED IDEOGRAPH-7D81 +7D83..7D94 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-7D83..CJK UNIFIED IDEOGRAPH-7D94 +7D96..7D97 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-7D96..CJK UNIFIED IDEOGRAPH-7D97 +7D99..7DA3 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7D99..CJK UNIFIED IDEOGRAPH-7DA3 +7DA5..7DA7 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-7DA5..CJK UNIFIED IDEOGRAPH-7DA7 +7DA9..7DCC ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-7DA9..CJK UNIFIED IDEOGRAPH-7DCC +7DCE..7DD2 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-7DCE..CJK UNIFIED IDEOGRAPH-7DD2 +7DD4..7DE4 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7DD4..CJK UNIFIED IDEOGRAPH-7DE4 +7DE6..7DEA ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-7DE6..CJK UNIFIED IDEOGRAPH-7DEA +7DEC..7DFC ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-7DEC..CJK UNIFIED IDEOGRAPH-7DFC +7E00..7E17 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-7E00..CJK UNIFIED IDEOGRAPH-7E17 +7E19..7E5A ; Recommended # 1.1 [66] CJK UNIFIED IDEOGRAPH-7E19..CJK UNIFIED IDEOGRAPH-7E5A +7E5C..7E63 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-7E5C..CJK UNIFIED IDEOGRAPH-7E63 +7E65..7E9C ; Recommended # 1.1 [56] CJK UNIFIED IDEOGRAPH-7E65..CJK UNIFIED IDEOGRAPH-7E9C +7E9E..7F3A ; Recommended # 1.1 [157] CJK UNIFIED IDEOGRAPH-7E9E..CJK UNIFIED IDEOGRAPH-7F3A +7F3D..7F40 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7F3D..CJK UNIFIED IDEOGRAPH-7F40 +7F42..7F45 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-7F42..CJK UNIFIED IDEOGRAPH-7F45 +7F47..7F58 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-7F47..CJK UNIFIED IDEOGRAPH-7F58 +7F5A..7F83 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-7F5A..CJK UNIFIED IDEOGRAPH-7F83 +7F85..7F8F ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-7F85..CJK UNIFIED IDEOGRAPH-7F8F +7F91..7F96 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7F91..CJK UNIFIED IDEOGRAPH-7F96 +7F98 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-7F98 +7F9A..7FB3 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-7F9A..CJK UNIFIED IDEOGRAPH-7FB3 +7FB5..7FD5 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-7FB5..CJK UNIFIED IDEOGRAPH-7FD5 +7FD7..7FDC ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7FD7..CJK UNIFIED IDEOGRAPH-7FDC +7FDE..7FE3 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-7FDE..CJK UNIFIED IDEOGRAPH-7FE3 +7FE5..8009 ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-7FE5..CJK UNIFIED IDEOGRAPH-8009 +800B..802E ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-800B..CJK UNIFIED IDEOGRAPH-802E +8030..803B ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-8030..CJK UNIFIED IDEOGRAPH-803B +803D..803F ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-803D..CJK UNIFIED IDEOGRAPH-803F +8041..8065 ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-8041..CJK UNIFIED IDEOGRAPH-8065 +8067..8087 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-8067..CJK UNIFIED IDEOGRAPH-8087 +8089..808D ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-8089..CJK UNIFIED IDEOGRAPH-808D +808F..8093 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-808F..CJK UNIFIED IDEOGRAPH-8093 +8095..80A5 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-8095..CJK UNIFIED IDEOGRAPH-80A5 +80A9..80B2 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-80A9..CJK UNIFIED IDEOGRAPH-80B2 +80B4..80B8 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-80B4..CJK UNIFIED IDEOGRAPH-80B8 +80BA..80DE ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-80BA..CJK UNIFIED IDEOGRAPH-80DE +80E0..8102 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-80E0..CJK UNIFIED IDEOGRAPH-8102 +8105..8133 ; Recommended # 1.1 [47] CJK UNIFIED IDEOGRAPH-8105..CJK UNIFIED IDEOGRAPH-8133 +8136..8183 ; Recommended # 1.1 [78] CJK UNIFIED IDEOGRAPH-8136..CJK UNIFIED IDEOGRAPH-8183 +8185..818F ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-8185..CJK UNIFIED IDEOGRAPH-818F +8191..8195 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-8191..CJK UNIFIED IDEOGRAPH-8195 +8197..81CA ; Recommended # 1.1 [52] CJK UNIFIED IDEOGRAPH-8197..CJK UNIFIED IDEOGRAPH-81CA +81CC..81E3 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-81CC..CJK UNIFIED IDEOGRAPH-81E3 +81E5..81EE ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-81E5..CJK UNIFIED IDEOGRAPH-81EE +81F1..8212 ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-81F1..CJK UNIFIED IDEOGRAPH-8212 +8214..8223 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-8214..CJK UNIFIED IDEOGRAPH-8223 +8225..8240 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-8225..CJK UNIFIED IDEOGRAPH-8240 +8242..8264 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-8242..CJK UNIFIED IDEOGRAPH-8264 +8266..828B ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-8266..CJK UNIFIED IDEOGRAPH-828B +828D..82B1 ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-828D..CJK UNIFIED IDEOGRAPH-82B1 +82B3..82E1 ; Recommended # 1.1 [47] CJK UNIFIED IDEOGRAPH-82B3..CJK UNIFIED IDEOGRAPH-82E1 +82E3..82FB ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-82E3..CJK UNIFIED IDEOGRAPH-82FB +82FD..8309 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-82FD..CJK UNIFIED IDEOGRAPH-8309 +830B..830F ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-830B..CJK UNIFIED IDEOGRAPH-830F +8311..832F ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-8311..CJK UNIFIED IDEOGRAPH-832F +8331..8354 ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-8331..CJK UNIFIED IDEOGRAPH-8354 +8356..83BD ; Recommended # 1.1 [104] CJK UNIFIED IDEOGRAPH-8356..CJK UNIFIED IDEOGRAPH-83BD +83BF..83E5 ; Recommended # 1.1 [39] CJK UNIFIED IDEOGRAPH-83BF..CJK UNIFIED IDEOGRAPH-83E5 +83E7..83EC ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-83E7..CJK UNIFIED IDEOGRAPH-83EC +83EE..8413 ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-83EE..CJK UNIFIED IDEOGRAPH-8413 +8415 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-8415 +8418..841E ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-8418..CJK UNIFIED IDEOGRAPH-841E +8420..8457 ; Recommended # 1.1 [56] CJK UNIFIED IDEOGRAPH-8420..CJK UNIFIED IDEOGRAPH-8457 +8459..8482 ; Recommended # 1.1 [42] CJK UNIFIED IDEOGRAPH-8459..CJK UNIFIED IDEOGRAPH-8482 +8484..8494 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-8484..CJK UNIFIED IDEOGRAPH-8494 +8496..84B6 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-8496..CJK UNIFIED IDEOGRAPH-84B6 +84B8..84C2 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-84B8..CJK UNIFIED IDEOGRAPH-84C2 +84C4..84EC ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-84C4..CJK UNIFIED IDEOGRAPH-84EC +84EE..8504 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-84EE..CJK UNIFIED IDEOGRAPH-8504 +8506..850F ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8506..CJK UNIFIED IDEOGRAPH-850F +8511..8531 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-8511..CJK UNIFIED IDEOGRAPH-8531 +8534..854B ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-8534..CJK UNIFIED IDEOGRAPH-854B +854D..854F ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-854D..CJK UNIFIED IDEOGRAPH-854F +8551..857E ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-8551..CJK UNIFIED IDEOGRAPH-857E +8580..8592 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-8580..CJK UNIFIED IDEOGRAPH-8592 +8594..85B1 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-8594..CJK UNIFIED IDEOGRAPH-85B1 +85B3..85BA ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-85B3..CJK UNIFIED IDEOGRAPH-85BA +85BC..85CB ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-85BC..CJK UNIFIED IDEOGRAPH-85CB +85CD..85ED ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-85CD..CJK UNIFIED IDEOGRAPH-85ED +85EF..85F2 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-85EF..CJK UNIFIED IDEOGRAPH-85F2 +85F4..85FB ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-85F4..CJK UNIFIED IDEOGRAPH-85FB +85FD..8602 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-85FD..CJK UNIFIED IDEOGRAPH-8602 +8604..860C ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-8604..CJK UNIFIED IDEOGRAPH-860C +860F ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-860F +8611..8614 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8611..CJK UNIFIED IDEOGRAPH-8614 +8616..861C ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-8616..CJK UNIFIED IDEOGRAPH-861C +861E..8636 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-861E..CJK UNIFIED IDEOGRAPH-8636 +8638..8656 ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-8638..CJK UNIFIED IDEOGRAPH-8656 +8658..8674 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-8658..CJK UNIFIED IDEOGRAPH-8674 +8676..8688 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-8676..CJK UNIFIED IDEOGRAPH-8688 +868A..8691 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-868A..CJK UNIFIED IDEOGRAPH-8691 +8693..869F ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-8693..CJK UNIFIED IDEOGRAPH-869F +86A1..86A5 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-86A1..CJK UNIFIED IDEOGRAPH-86A5 +86A7..86D4 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-86A7..CJK UNIFIED IDEOGRAPH-86D4 +86D6..86DF ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-86D6..CJK UNIFIED IDEOGRAPH-86DF +86E1..86E6 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-86E1..CJK UNIFIED IDEOGRAPH-86E6 +86E8..86FC ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-86E8..CJK UNIFIED IDEOGRAPH-86FC +86FE..871C ; Recommended # 1.1 [31] CJK UNIFIED IDEOGRAPH-86FE..CJK UNIFIED IDEOGRAPH-871C +871E..872E ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-871E..CJK UNIFIED IDEOGRAPH-872E +8730..873C ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-8730..CJK UNIFIED IDEOGRAPH-873C +873E..8744 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-873E..CJK UNIFIED IDEOGRAPH-8744 +8746..8770 ; Recommended # 1.1 [43] CJK UNIFIED IDEOGRAPH-8746..CJK UNIFIED IDEOGRAPH-8770 +8772..878D ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-8772..CJK UNIFIED IDEOGRAPH-878D +878F..8798 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-878F..CJK UNIFIED IDEOGRAPH-8798 +879A..87D9 ; Recommended # 1.1 [64] CJK UNIFIED IDEOGRAPH-879A..CJK UNIFIED IDEOGRAPH-87D9 +87DB..87EF ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-87DB..CJK UNIFIED IDEOGRAPH-87EF +87F1..8806 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-87F1..CJK UNIFIED IDEOGRAPH-8806 +8808..8811 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8808..CJK UNIFIED IDEOGRAPH-8811 +8813..882C ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-8813..CJK UNIFIED IDEOGRAPH-882C +882E..8839 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-882E..CJK UNIFIED IDEOGRAPH-8839 +883B..8846 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-883B..CJK UNIFIED IDEOGRAPH-8846 +8848..8857 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-8848..CJK UNIFIED IDEOGRAPH-8857 +8859..885B ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8859..CJK UNIFIED IDEOGRAPH-885B +885D..885E ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-885D..CJK UNIFIED IDEOGRAPH-885E +8860..8879 ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-8860..CJK UNIFIED IDEOGRAPH-8879 +887B..88E5 ; Recommended # 1.1 [107] CJK UNIFIED IDEOGRAPH-887B..CJK UNIFIED IDEOGRAPH-88E5 +88E7..88E8 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-88E7..CJK UNIFIED IDEOGRAPH-88E8 +88EA..88EC ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-88EA..CJK UNIFIED IDEOGRAPH-88EC +88EE..8902 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-88EE..CJK UNIFIED IDEOGRAPH-8902 +8904..890E ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-8904..CJK UNIFIED IDEOGRAPH-890E +8910..8923 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-8910..CJK UNIFIED IDEOGRAPH-8923 +8925..8964 ; Recommended # 1.1 [64] CJK UNIFIED IDEOGRAPH-8925..CJK UNIFIED IDEOGRAPH-8964 +8966..8974 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-8966..CJK UNIFIED IDEOGRAPH-8974 +8976..897C ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-8976..CJK UNIFIED IDEOGRAPH-897C +897E..898C ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-897E..CJK UNIFIED IDEOGRAPH-898C +898E..898F ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-898E..CJK UNIFIED IDEOGRAPH-898F +8991..8993 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8991..CJK UNIFIED IDEOGRAPH-8993 +8995..8998 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8995..CJK UNIFIED IDEOGRAPH-8998 +899A..89AF ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-899A..CJK UNIFIED IDEOGRAPH-89AF +89B1..89B3 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-89B1..CJK UNIFIED IDEOGRAPH-89B3 +89B5..89BA ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-89B5..CJK UNIFIED IDEOGRAPH-89BA +89BD..89ED ; Recommended # 1.1 [49] CJK UNIFIED IDEOGRAPH-89BD..CJK UNIFIED IDEOGRAPH-89ED +89EF..89F4 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-89EF..CJK UNIFIED IDEOGRAPH-89F4 +89F6..89F8 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-89F6..CJK UNIFIED IDEOGRAPH-89F8 +89FA..89FC ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-89FA..CJK UNIFIED IDEOGRAPH-89FC +89FE..8A04 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-89FE..CJK UNIFIED IDEOGRAPH-8A04 +8A07..8A13 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-8A07..CJK UNIFIED IDEOGRAPH-8A13 +8A15..8A18 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8A15..CJK UNIFIED IDEOGRAPH-8A18 +8A1A..8A1F ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8A1A..CJK UNIFIED IDEOGRAPH-8A1F +8A22..8A2A ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-8A22..CJK UNIFIED IDEOGRAPH-8A2A +8A2C..8A3C ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-8A2C..CJK UNIFIED IDEOGRAPH-8A3C +8A3E..8A4A ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-8A3E..CJK UNIFIED IDEOGRAPH-8A4A +8A4C..8A63 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-8A4C..CJK UNIFIED IDEOGRAPH-8A63 +8A65..8A77 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-8A65..CJK UNIFIED IDEOGRAPH-8A77 +8A79..8A7C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8A79..CJK UNIFIED IDEOGRAPH-8A7C +8A7E..8A87 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8A7E..CJK UNIFIED IDEOGRAPH-8A87 +8A89..8A9E ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-8A89..CJK UNIFIED IDEOGRAPH-8A9E +8AA0..8AAE ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-8AA0..CJK UNIFIED IDEOGRAPH-8AAE +8AB0..8AB6 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-8AB0..CJK UNIFIED IDEOGRAPH-8AB6 +8AB8..8ACF ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-8AB8..CJK UNIFIED IDEOGRAPH-8ACF +8AD1..8AEB ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-8AD1..CJK UNIFIED IDEOGRAPH-8AEB +8AED..8B28 ; Recommended # 1.1 [60] CJK UNIFIED IDEOGRAPH-8AED..CJK UNIFIED IDEOGRAPH-8B28 +8B2A..8B31 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-8B2A..CJK UNIFIED IDEOGRAPH-8B31 +8B33..8B37 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-8B33..CJK UNIFIED IDEOGRAPH-8B37 +8B39..8B3E ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8B39..CJK UNIFIED IDEOGRAPH-8B3E +8B40..8B60 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-8B40..CJK UNIFIED IDEOGRAPH-8B60 +8B63..8B68 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8B63..CJK UNIFIED IDEOGRAPH-8B68 +8B6A..8B74 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-8B6A..CJK UNIFIED IDEOGRAPH-8B74 +8B76..8B7B ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8B76..CJK UNIFIED IDEOGRAPH-8B7B +8B7D..8B80 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8B7D..CJK UNIFIED IDEOGRAPH-8B80 +8B82..8B86 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-8B82..CJK UNIFIED IDEOGRAPH-8B86 +8B88..8B8C ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-8B88..CJK UNIFIED IDEOGRAPH-8B8C +8B8E ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-8B8E +8B90..8B9A ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-8B90..CJK UNIFIED IDEOGRAPH-8B9A +8B9C..8C37 ; Recommended # 1.1 [156] CJK UNIFIED IDEOGRAPH-8B9C..CJK UNIFIED IDEOGRAPH-8C37 +8C39..8C3F ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-8C39..CJK UNIFIED IDEOGRAPH-8C3F +8C41..8C43 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8C41..CJK UNIFIED IDEOGRAPH-8C43 +8C45..8C50 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-8C45..CJK UNIFIED IDEOGRAPH-8C50 +8C54..8C57 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-8C54..CJK UNIFIED IDEOGRAPH-8C57 +8C59..8C73 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-8C59..CJK UNIFIED IDEOGRAPH-8C73 +8C75..8C7E ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8C75..CJK UNIFIED IDEOGRAPH-8C7E +8C80..8C82 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8C80..CJK UNIFIED IDEOGRAPH-8C82 +8C84..8C86 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8C84..CJK UNIFIED IDEOGRAPH-8C86 +8C88..8C8A ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-8C88..CJK UNIFIED IDEOGRAPH-8C8A +8C8C..8C9A ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-8C8C..CJK UNIFIED IDEOGRAPH-8C9A +8C9C..8CA5 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8C9C..CJK UNIFIED IDEOGRAPH-8CA5 +8CA7..8CCA ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-8CA7..CJK UNIFIED IDEOGRAPH-8CCA +8CCC..8CD5 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-8CCC..CJK UNIFIED IDEOGRAPH-8CD5 +8CD7 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-8CD7 +8CD9..8CE8 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-8CD9..CJK UNIFIED IDEOGRAPH-8CE8 +8CEA..8CF6 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-8CEA..CJK UNIFIED IDEOGRAPH-8CF6 +8CF8..8D00 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-8CF8..CJK UNIFIED IDEOGRAPH-8D00 +8D02..8D10 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-8D02..CJK UNIFIED IDEOGRAPH-8D10 +8D13..8D7B ; Recommended # 1.1 [105] CJK UNIFIED IDEOGRAPH-8D13..CJK UNIFIED IDEOGRAPH-8D7B +8D7D..8DA5 ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-8D7D..CJK UNIFIED IDEOGRAPH-8DA5 +8DA7..8DBF ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-8DA7..CJK UNIFIED IDEOGRAPH-8DBF +8DC1..8DE4 ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-8DC1..CJK UNIFIED IDEOGRAPH-8DE4 +8DE6..8E00 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-8DE6..CJK UNIFIED IDEOGRAPH-8E00 +8E02..8E0A ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-8E02..CJK UNIFIED IDEOGRAPH-8E0A +8E0C..8E31 ; Recommended # 1.1 [38] CJK UNIFIED IDEOGRAPH-8E0C..CJK UNIFIED IDEOGRAPH-8E31 +8E33..8E45 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-8E33..CJK UNIFIED IDEOGRAPH-8E45 +8E47..8E4E ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-8E47..CJK UNIFIED IDEOGRAPH-8E4E +8E50..8E6D ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-8E50..CJK UNIFIED IDEOGRAPH-8E6D +8E6F..8E74 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8E6F..CJK UNIFIED IDEOGRAPH-8E74 +8E76 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-8E76 +8E78 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-8E78 +8E7A..8E9A ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-8E7A..CJK UNIFIED IDEOGRAPH-8E9A +8E9C..8EA1 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8E9C..CJK UNIFIED IDEOGRAPH-8EA1 +8EA3..8EB2 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-8EA3..CJK UNIFIED IDEOGRAPH-8EB2 +8EB4..8EB5 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-8EB4..CJK UNIFIED IDEOGRAPH-8EB5 +8EB8..8EC0 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-8EB8..CJK UNIFIED IDEOGRAPH-8EC0 +8EC2..8EC3 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-8EC2..CJK UNIFIED IDEOGRAPH-8EC3 +8EC5..8ED8 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-8EC5..CJK UNIFIED IDEOGRAPH-8ED8 +8EDA..8EEF ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-8EDA..CJK UNIFIED IDEOGRAPH-8EEF +8EF1..8F0E ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-8EF1..CJK UNIFIED IDEOGRAPH-8F0E +8F10..8F2C ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-8F10..CJK UNIFIED IDEOGRAPH-8F2C +8F2E..8F39 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-8F2E..CJK UNIFIED IDEOGRAPH-8F39 +8F3B..8F40 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8F3B..CJK UNIFIED IDEOGRAPH-8F40 +8F42..8F9C ; Recommended # 1.1 [91] CJK UNIFIED IDEOGRAPH-8F42..CJK UNIFIED IDEOGRAPH-8F9C +8F9E..8FA3 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8F9E..CJK UNIFIED IDEOGRAPH-8FA3 +8FA5..8FB2 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-8FA5..CJK UNIFIED IDEOGRAPH-8FB2 +8FB4..8FC2 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-8FB4..CJK UNIFIED IDEOGRAPH-8FC2 +8FC4..8FC9 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-8FC4..CJK UNIFIED IDEOGRAPH-8FC9 +8FCB..8FE6 ; Recommended # 1.1 [28] CJK UNIFIED IDEOGRAPH-8FCB..CJK UNIFIED IDEOGRAPH-8FE6 +8FE8..9029 ; Recommended # 1.1 [66] CJK UNIFIED IDEOGRAPH-8FE8..CJK UNIFIED IDEOGRAPH-9029 +902B ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-902B +902D..9036 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-902D..CJK UNIFIED IDEOGRAPH-9036 +9038..903F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-9038..CJK UNIFIED IDEOGRAPH-903F +9041..9045 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9041..CJK UNIFIED IDEOGRAPH-9045 +9047..90AA ; Recommended # 1.1 [100] CJK UNIFIED IDEOGRAPH-9047..CJK UNIFIED IDEOGRAPH-90AA +90AC..90CB ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-90AC..CJK UNIFIED IDEOGRAPH-90CB +90CE..90D1 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-90CE..CJK UNIFIED IDEOGRAPH-90D1 +90D3..90F5 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-90D3..CJK UNIFIED IDEOGRAPH-90F5 +90F7..9109 ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-90F7..CJK UNIFIED IDEOGRAPH-9109 +910B..913B ; Recommended # 1.1 [49] CJK UNIFIED IDEOGRAPH-910B..CJK UNIFIED IDEOGRAPH-913B +913E..9158 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-913E..CJK UNIFIED IDEOGRAPH-9158 +915A..917A ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-915A..CJK UNIFIED IDEOGRAPH-917A +917C..9194 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-917C..CJK UNIFIED IDEOGRAPH-9194 +9196..9197 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9196..CJK UNIFIED IDEOGRAPH-9197 +9199..91A8 ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-9199..CJK UNIFIED IDEOGRAPH-91A8 +91AA..91BE ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-91AA..CJK UNIFIED IDEOGRAPH-91BE +91C0..91C3 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-91C0..CJK UNIFIED IDEOGRAPH-91C3 +91C5..91DF ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-91C5..CJK UNIFIED IDEOGRAPH-91DF +91E1..91EE ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-91E1..CJK UNIFIED IDEOGRAPH-91EE +91F0..9212 ; Recommended # 1.1 [35] CJK UNIFIED IDEOGRAPH-91F0..CJK UNIFIED IDEOGRAPH-9212 +9214..921E ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-9214..CJK UNIFIED IDEOGRAPH-921E +9220..9221 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9220..CJK UNIFIED IDEOGRAPH-9221 +9223..9242 ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-9223..CJK UNIFIED IDEOGRAPH-9242 +9244..9268 ; Recommended # 1.1 [37] CJK UNIFIED IDEOGRAPH-9244..CJK UNIFIED IDEOGRAPH-9268 +926B..9280 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-926B..CJK UNIFIED IDEOGRAPH-9280 +9282..9283 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9282..CJK UNIFIED IDEOGRAPH-9283 +9285..929D ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-9285..CJK UNIFIED IDEOGRAPH-929D +929F..92BC ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-929F..CJK UNIFIED IDEOGRAPH-92BC +92BE..92D3 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-92BE..CJK UNIFIED IDEOGRAPH-92D3 +92D5..92DA ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-92D5..CJK UNIFIED IDEOGRAPH-92DA +92DC..92E1 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-92DC..CJK UNIFIED IDEOGRAPH-92E1 +92E3..931B ; Recommended # 1.1 [57] CJK UNIFIED IDEOGRAPH-92E3..CJK UNIFIED IDEOGRAPH-931B +931D..932F ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-931D..CJK UNIFIED IDEOGRAPH-932F +9332..9361 ; Recommended # 1.1 [48] CJK UNIFIED IDEOGRAPH-9332..CJK UNIFIED IDEOGRAPH-9361 +9363..9367 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9363..CJK UNIFIED IDEOGRAPH-9367 +9369..936A ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9369..CJK UNIFIED IDEOGRAPH-936A +936C..936E ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-936C..CJK UNIFIED IDEOGRAPH-936E +9370..9372 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9370..CJK UNIFIED IDEOGRAPH-9372 +9374..9377 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9374..CJK UNIFIED IDEOGRAPH-9377 +9379..937E ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9379..CJK UNIFIED IDEOGRAPH-937E +9380 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9380 +9382..938A ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-9382..CJK UNIFIED IDEOGRAPH-938A +938C..939B ; Recommended # 1.1 [16] CJK UNIFIED IDEOGRAPH-938C..CJK UNIFIED IDEOGRAPH-939B +939D..939F ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-939D..CJK UNIFIED IDEOGRAPH-939F +93A1..93AA ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-93A1..CJK UNIFIED IDEOGRAPH-93AA +93AC..93BA ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-93AC..CJK UNIFIED IDEOGRAPH-93BA +93BC..93DF ; Recommended # 1.1 [36] CJK UNIFIED IDEOGRAPH-93BC..CJK UNIFIED IDEOGRAPH-93DF +93E1..93F2 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-93E1..CJK UNIFIED IDEOGRAPH-93F2 +93F4..9401 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-93F4..CJK UNIFIED IDEOGRAPH-9401 +9403..9416 ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-9403..CJK UNIFIED IDEOGRAPH-9416 +9418..941B ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9418..CJK UNIFIED IDEOGRAPH-941B +941D ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-941D +9420..9423 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9420..CJK UNIFIED IDEOGRAPH-9423 +9425..9442 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-9425..CJK UNIFIED IDEOGRAPH-9442 +9444..944D ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-9444..CJK UNIFIED IDEOGRAPH-944D +944F..946B ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-944F..CJK UNIFIED IDEOGRAPH-946B +946D..947A ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-946D..CJK UNIFIED IDEOGRAPH-947A +947C..9577 ; Recommended # 1.1 [252] CJK UNIFIED IDEOGRAPH-947C..CJK UNIFIED IDEOGRAPH-9577 +957A..957D ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-957A..CJK UNIFIED IDEOGRAPH-957D +957F..9584 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-957F..CJK UNIFIED IDEOGRAPH-9584 +9586..9596 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9586..CJK UNIFIED IDEOGRAPH-9596 +9598..95B2 ; Recommended # 1.1 [27] CJK UNIFIED IDEOGRAPH-9598..CJK UNIFIED IDEOGRAPH-95B2 +95B5..95B7 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-95B5..CJK UNIFIED IDEOGRAPH-95B7 +95B9..95C0 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-95B9..CJK UNIFIED IDEOGRAPH-95C0 +95C2..95D8 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-95C2..CJK UNIFIED IDEOGRAPH-95D8 +95DA..95DC ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-95DA..CJK UNIFIED IDEOGRAPH-95DC +95DE..9624 ; Recommended # 1.1 [71] CJK UNIFIED IDEOGRAPH-95DE..CJK UNIFIED IDEOGRAPH-9624 +9627..9628 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9627..CJK UNIFIED IDEOGRAPH-9628 +962A..963D ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-962A..CJK UNIFIED IDEOGRAPH-963D +963F..9655 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-963F..CJK UNIFIED IDEOGRAPH-9655 +9658..9678 ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-9658..CJK UNIFIED IDEOGRAPH-9678 +967A ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-967A +967C..967E ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-967C..CJK UNIFIED IDEOGRAPH-967E +9680 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9680 +9683..968B ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-9683..CJK UNIFIED IDEOGRAPH-968B +968D..9695 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-968D..CJK UNIFIED IDEOGRAPH-9695 +9697..9699 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9697..CJK UNIFIED IDEOGRAPH-9699 +969B..969C ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-969B..CJK UNIFIED IDEOGRAPH-969C +969E ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-969E +96A0..96AA ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-96A0..CJK UNIFIED IDEOGRAPH-96AA +96AC..96AE ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-96AC..CJK UNIFIED IDEOGRAPH-96AE +96B0..96B4 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-96B0..CJK UNIFIED IDEOGRAPH-96B4 +96B6..96E3 ; Recommended # 1.1 [46] CJK UNIFIED IDEOGRAPH-96B6..CJK UNIFIED IDEOGRAPH-96E3 +96E5 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-96E5 +96E8..96FB ; Recommended # 1.1 [20] CJK UNIFIED IDEOGRAPH-96E8..CJK UNIFIED IDEOGRAPH-96FB +96FD..9713 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-96FD..CJK UNIFIED IDEOGRAPH-9713 +9715..9716 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9715..CJK UNIFIED IDEOGRAPH-9716 +9718..9719 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9718..CJK UNIFIED IDEOGRAPH-9719 +971C..9732 ; Recommended # 1.1 [23] CJK UNIFIED IDEOGRAPH-971C..CJK UNIFIED IDEOGRAPH-9732 +9735..9736 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9735..CJK UNIFIED IDEOGRAPH-9736 +9738..973F ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-9738..CJK UNIFIED IDEOGRAPH-973F +9742..974C ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-9742..CJK UNIFIED IDEOGRAPH-974C +974E..9756 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-974E..CJK UNIFIED IDEOGRAPH-9756 +9758..9762 ; Recommended # 1.1 [11] CJK UNIFIED IDEOGRAPH-9758..CJK UNIFIED IDEOGRAPH-9762 +9764..9774 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9764..CJK UNIFIED IDEOGRAPH-9774 +9776..9786 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9776..CJK UNIFIED IDEOGRAPH-9786 +9788 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9788 +978A..979A ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-978A..CJK UNIFIED IDEOGRAPH-979A +979C..97A8 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-979C..CJK UNIFIED IDEOGRAPH-97A8 +97AA..97AF ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-97AA..CJK UNIFIED IDEOGRAPH-97AF +97B2..97B4 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-97B2..CJK UNIFIED IDEOGRAPH-97B4 +97B6..97BD ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-97B6..CJK UNIFIED IDEOGRAPH-97BD +97BF ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-97BF +97C1..97D1 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-97C1..CJK UNIFIED IDEOGRAPH-97D1 +97D3..97FB ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-97D3..CJK UNIFIED IDEOGRAPH-97FB +97FD..981E ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-97FD..CJK UNIFIED IDEOGRAPH-981E +9820..9824 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9820..CJK UNIFIED IDEOGRAPH-9824 +9826..9829 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9826..CJK UNIFIED IDEOGRAPH-9829 +982B..9832 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-982B..CJK UNIFIED IDEOGRAPH-9832 +9834..9839 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9834..CJK UNIFIED IDEOGRAPH-9839 +983B..983D ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-983B..CJK UNIFIED IDEOGRAPH-983D +983F..9841 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-983F..CJK UNIFIED IDEOGRAPH-9841 +9843..9846 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9843..CJK UNIFIED IDEOGRAPH-9846 +9848..9855 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-9848..CJK UNIFIED IDEOGRAPH-9855 +9857..9865 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-9857..CJK UNIFIED IDEOGRAPH-9865 +9867 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9867 +9869..98B6 ; Recommended # 1.1 [78] CJK UNIFIED IDEOGRAPH-9869..CJK UNIFIED IDEOGRAPH-98B6 +98B8..98C9 ; Recommended # 1.1 [18] CJK UNIFIED IDEOGRAPH-98B8..CJK UNIFIED IDEOGRAPH-98C9 +98CB..98E3 ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-98CB..CJK UNIFIED IDEOGRAPH-98E3 +98E5..98EB ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-98E5..CJK UNIFIED IDEOGRAPH-98EB +98ED..98F0 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-98ED..CJK UNIFIED IDEOGRAPH-98F0 +98F2..98F7 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-98F2..CJK UNIFIED IDEOGRAPH-98F7 +98F9..98FA ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-98F9..CJK UNIFIED IDEOGRAPH-98FA +98FC..9918 ; Recommended # 1.1 [29] CJK UNIFIED IDEOGRAPH-98FC..CJK UNIFIED IDEOGRAPH-9918 +991A..993A ; Recommended # 1.1 [33] CJK UNIFIED IDEOGRAPH-991A..CJK UNIFIED IDEOGRAPH-993A +993C..9943 ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-993C..CJK UNIFIED IDEOGRAPH-9943 +9945..9959 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-9945..CJK UNIFIED IDEOGRAPH-9959 +995B..995C ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-995B..CJK UNIFIED IDEOGRAPH-995C +995E..99BE ; Recommended # 1.1 [97] CJK UNIFIED IDEOGRAPH-995E..CJK UNIFIED IDEOGRAPH-99BE +99C0..99DF ; Recommended # 1.1 [32] CJK UNIFIED IDEOGRAPH-99C0..CJK UNIFIED IDEOGRAPH-99DF +99E1..99E5 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-99E1..CJK UNIFIED IDEOGRAPH-99E5 +99E7..99EA ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-99E7..CJK UNIFIED IDEOGRAPH-99EA +99EC..99F4 ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-99EC..CJK UNIFIED IDEOGRAPH-99F4 +99F6..9A0F ; Recommended # 1.1 [26] CJK UNIFIED IDEOGRAPH-99F6..CJK UNIFIED IDEOGRAPH-9A0F +9A11..9A16 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9A11..CJK UNIFIED IDEOGRAPH-9A16 +9A19..9A3A ; Recommended # 1.1 [34] CJK UNIFIED IDEOGRAPH-9A19..CJK UNIFIED IDEOGRAPH-9A3A +9A3C..9A50 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-9A3C..CJK UNIFIED IDEOGRAPH-9A50 +9A52..9A57 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9A52..CJK UNIFIED IDEOGRAPH-9A57 +9A59..9A5C ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9A59..CJK UNIFIED IDEOGRAPH-9A5C +9A5E..9A62 ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9A5E..CJK UNIFIED IDEOGRAPH-9A62 +9A64..9AA8 ; Recommended # 1.1 [69] CJK UNIFIED IDEOGRAPH-9A64..CJK UNIFIED IDEOGRAPH-9AA8 +9AAA..9ABC ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-9AAA..CJK UNIFIED IDEOGRAPH-9ABC +9ABE..9AC7 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-9ABE..CJK UNIFIED IDEOGRAPH-9AC7 +9AC9..9AD6 ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-9AC9..CJK UNIFIED IDEOGRAPH-9AD6 +9AD8..9ADF ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-9AD8..CJK UNIFIED IDEOGRAPH-9ADF +9AE1..9AE3 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9AE1..CJK UNIFIED IDEOGRAPH-9AE3 +9AE5..9AE7 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9AE5..CJK UNIFIED IDEOGRAPH-9AE7 +9AEA..9AEF ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9AEA..CJK UNIFIED IDEOGRAPH-9AEF +9AF1..9AFF ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-9AF1..CJK UNIFIED IDEOGRAPH-9AFF +9B01 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9B01 +9B03..9B08 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9B03..CJK UNIFIED IDEOGRAPH-9B08 +9B0A..9B13 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-9B0A..CJK UNIFIED IDEOGRAPH-9B13 +9B15..9B1A ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9B15..CJK UNIFIED IDEOGRAPH-9B1A +9B1C..9B33 ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-9B1C..CJK UNIFIED IDEOGRAPH-9B33 +9B35..9B3C ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-9B35..CJK UNIFIED IDEOGRAPH-9B3C +9B3E..9B3F ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9B3E..CJK UNIFIED IDEOGRAPH-9B3F +9B41..9B4F ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-9B41..CJK UNIFIED IDEOGRAPH-9B4F +9B51..9B56 ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9B51..CJK UNIFIED IDEOGRAPH-9B56 +9B58..9B61 ; Recommended # 1.1 [10] CJK UNIFIED IDEOGRAPH-9B58..CJK UNIFIED IDEOGRAPH-9B61 +9B63..9B71 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-9B63..CJK UNIFIED IDEOGRAPH-9B71 +9B73..9B88 ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-9B73..CJK UNIFIED IDEOGRAPH-9B88 +9B8A..9B8B ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9B8A..CJK UNIFIED IDEOGRAPH-9B8B +9B8D..9B98 ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-9B8D..CJK UNIFIED IDEOGRAPH-9B98 +9B9A..9BC1 ; Recommended # 1.1 [40] CJK UNIFIED IDEOGRAPH-9B9A..CJK UNIFIED IDEOGRAPH-9BC1 +9BC3..9BF5 ; Recommended # 1.1 [51] CJK UNIFIED IDEOGRAPH-9BC3..CJK UNIFIED IDEOGRAPH-9BF5 +9BF7..9BFF ; Recommended # 1.1 [9] CJK UNIFIED IDEOGRAPH-9BF7..CJK UNIFIED IDEOGRAPH-9BFF +9C02 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9C02 +9C04..9C41 ; Recommended # 1.1 [62] CJK UNIFIED IDEOGRAPH-9C04..CJK UNIFIED IDEOGRAPH-9C41 +9C43..9C4E ; Recommended # 1.1 [12] CJK UNIFIED IDEOGRAPH-9C43..CJK UNIFIED IDEOGRAPH-9C4E +9C50 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9C50 +9C52..9C60 ; Recommended # 1.1 [15] CJK UNIFIED IDEOGRAPH-9C52..CJK UNIFIED IDEOGRAPH-9C60 +9C62..9C63 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9C62..CJK UNIFIED IDEOGRAPH-9C63 +9C65..9C7A ; Recommended # 1.1 [22] CJK UNIFIED IDEOGRAPH-9C65..CJK UNIFIED IDEOGRAPH-9C7A +9C7C..9D0B ; Recommended # 1.1 [144] CJK UNIFIED IDEOGRAPH-9C7C..CJK UNIFIED IDEOGRAPH-9D0B +9D0E..9D10 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9D0E..CJK UNIFIED IDEOGRAPH-9D10 +9D12..9D26 ; Recommended # 1.1 [21] CJK UNIFIED IDEOGRAPH-9D12..CJK UNIFIED IDEOGRAPH-9D26 +9D28..9D34 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-9D28..CJK UNIFIED IDEOGRAPH-9D34 +9D36..9D3B ; Recommended # 1.1 [6] CJK UNIFIED IDEOGRAPH-9D36..CJK UNIFIED IDEOGRAPH-9D3B +9D3D..9D6C ; Recommended # 1.1 [48] CJK UNIFIED IDEOGRAPH-9D3D..CJK UNIFIED IDEOGRAPH-9D6C +9D6E..9D94 ; Recommended # 1.1 [39] CJK UNIFIED IDEOGRAPH-9D6E..CJK UNIFIED IDEOGRAPH-9D94 +9D96..9DAD ; Recommended # 1.1 [24] CJK UNIFIED IDEOGRAPH-9D96..CJK UNIFIED IDEOGRAPH-9DAD +9DAF..9DBC ; Recommended # 1.1 [14] CJK UNIFIED IDEOGRAPH-9DAF..CJK UNIFIED IDEOGRAPH-9DBC +9DBE..9DBF ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9DBE..CJK UNIFIED IDEOGRAPH-9DBF +9DC1..9DE9 ; Recommended # 1.1 [41] CJK UNIFIED IDEOGRAPH-9DC1..CJK UNIFIED IDEOGRAPH-9DE9 +9DEB..9DFB ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9DEB..CJK UNIFIED IDEOGRAPH-9DFB +9DFD..9E0D ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9DFD..CJK UNIFIED IDEOGRAPH-9E0D +9E0F..9E15 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-9E0F..CJK UNIFIED IDEOGRAPH-9E15 +9E17..9E1B ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9E17..CJK UNIFIED IDEOGRAPH-9E1B +9E1D..9E7A ; Recommended # 1.1 [94] CJK UNIFIED IDEOGRAPH-9E1D..CJK UNIFIED IDEOGRAPH-9E7A +9E7C..9E8E ; Recommended # 1.1 [19] CJK UNIFIED IDEOGRAPH-9E7C..CJK UNIFIED IDEOGRAPH-9E8E +9E91..9E97 ; Recommended # 1.1 [7] CJK UNIFIED IDEOGRAPH-9E91..CJK UNIFIED IDEOGRAPH-9E97 +9E99..9E9D ; Recommended # 1.1 [5] CJK UNIFIED IDEOGRAPH-9E99..CJK UNIFIED IDEOGRAPH-9E9D +9E9F..9EA1 ; Recommended # 1.1 [3] CJK UNIFIED IDEOGRAPH-9E9F..CJK UNIFIED IDEOGRAPH-9EA1 +9EA3..9EAA ; Recommended # 1.1 [8] CJK UNIFIED IDEOGRAPH-9EA3..CJK UNIFIED IDEOGRAPH-9EAA +9EAD..9EB0 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9EAD..CJK UNIFIED IDEOGRAPH-9EB0 +9EB2..9EEB ; Recommended # 1.1 [58] CJK UNIFIED IDEOGRAPH-9EB2..CJK UNIFIED IDEOGRAPH-9EEB +9EED..9EF0 ; Recommended # 1.1 [4] CJK UNIFIED IDEOGRAPH-9EED..CJK UNIFIED IDEOGRAPH-9EF0 +9EF2..9F02 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9EF2..CJK UNIFIED IDEOGRAPH-9F02 +9F04..9F10 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-9F04..CJK UNIFIED IDEOGRAPH-9F10 +9F12..9F13 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9F12..CJK UNIFIED IDEOGRAPH-9F13 +9F15..9F25 ; Recommended # 1.1 [17] CJK UNIFIED IDEOGRAPH-9F15..CJK UNIFIED IDEOGRAPH-9F25 +9F27..9F44 ; Recommended # 1.1 [30] CJK UNIFIED IDEOGRAPH-9F27..CJK UNIFIED IDEOGRAPH-9F44 +9F46..9F52 ; Recommended # 1.1 [13] CJK UNIFIED IDEOGRAPH-9F46..CJK UNIFIED IDEOGRAPH-9F52 +9F54..9F6C ; Recommended # 1.1 [25] CJK UNIFIED IDEOGRAPH-9F54..CJK UNIFIED IDEOGRAPH-9F6C +9F6E..9FA0 ; Recommended # 1.1 [51] CJK UNIFIED IDEOGRAPH-9F6E..CJK UNIFIED IDEOGRAPH-9FA0 +9FA2 ; Recommended # 1.1 CJK UNIFIED IDEOGRAPH-9FA2 +9FA4..9FA5 ; Recommended # 1.1 [2] CJK UNIFIED IDEOGRAPH-9FA4..CJK UNIFIED IDEOGRAPH-9FA5 A78D ; Recommended # 6.0 LATIN CAPITAL LETTER TURNED H -A792..A793 ; Recommended # 6.1 [2] LATIN CAPITAL LETTER C WITH BAR..LATIN SMALL LETTER C WITH BAR A7AA ; Recommended # 6.1 LATIN CAPITAL LETTER H WITH HOOK -A7C0..A7C1 ; Recommended # 14.0 [2] LATIN CAPITAL LETTER OLD POLISH O..LATIN SMALL LETTER OLD POLISH O -A7C2..A7C6 ; Recommended # 12.0 [5] LATIN CAPITAL LETTER ANGLICANA W..LATIN CAPITAL LETTER Z WITH PALATAL HOOK -A7C7..A7CA ; Recommended # 13.0 [4] LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY -A7D0..A7D1 ; Recommended # 14.0 [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Recommended # 14.0 LATIN SMALL LETTER DOUBLE THORN -A7D5..A7D9 ; Recommended # 14.0 [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S -A9E7..A9FE ; Recommended # 7.0 [24] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING BHA -AA60..AA76 ; Recommended # 5.2 [23] MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM KHAMTI HM -AA7A..AA7B ; Recommended # 5.2 [2] MYANMAR LETTER AITON RA..MYANMAR SIGN PAO KAREN TONE -AA7C..AA7F ; Recommended # 7.0 [4] MYANMAR SIGN TAI LAING TONE-2..MYANMAR LETTER SHWE PALAUNG SHA -AB01..AB06 ; Recommended # 6.0 [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO -AB09..AB0E ; Recommended # 6.0 [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO -AB11..AB16 ; Recommended # 6.0 [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO -AB20..AB26 ; Recommended # 6.0 [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO -AB28..AB2E ; Recommended # 6.0 [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO -AB66..AB67 ; Recommended # 12.0 [2] LATIN SMALL LETTER DZ DIGRAPH WITH RETROFLEX HOOK..LATIN SMALL LETTER TS DIGRAPH WITH RETROFLEX HOOK +AA7B ; Recommended # 5.2 MYANMAR SIGN PAO KAREN TONE AC00..D7A3 ; Recommended # 2.0 [11172] HANGUL SYLLABLE GA..HANGUL SYLLABLE HIH -FA0E..FA0F ; Recommended # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F -FA11 ; Recommended # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA11 -FA13..FA14 ; Recommended # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14 -FA1F ; Recommended # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA1F -FA21 ; Recommended # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA21 -FA23..FA24 ; Recommended # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24 -FA27..FA29 ; Recommended # 1.1 [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29 11301 ; Recommended # 7.0 GRANTHA SIGN CANDRABINDU 11303 ; Recommended # 7.0 GRANTHA SIGN VISARGA -1133B ; Recommended # 11.0 COMBINING BINDU BELOW 1133C ; Recommended # 7.0 GRANTHA SIGN NUKTA -16FF0..16FF1 ; Recommended # 13.0 [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -1B11F..1B122 ; Recommended # 14.0 [4] HIRAGANA LETTER ARCHAIC WU..KATAKANA LETTER ARCHAIC WU -1B132 ; Recommended # 15.0 HIRAGANA LETTER SMALL KO -1B150..1B152 ; Recommended # 12.0 [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO -1B155 ; Recommended # 15.0 KATAKANA LETTER SMALL KO -1B164..1B167 ; Recommended # 12.0 [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N -1DF00..1DF1E ; Recommended # 14.0 [31] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER S WITH CURL -1DF25..1DF2A ; Recommended # 15.0 [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK -1E08F ; Recommended # 15.0 COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I 1E7E0..1E7E6 ; Recommended # 14.0 [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; Recommended # 14.0 [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; Recommended # 14.0 [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE 1E7F0..1E7FE ; Recommended # 14.0 [15] ETHIOPIC SYLLABLE GURAGE QWI..ETHIOPIC SYLLABLE GURAGE PWEE -20000..2A6D6 ; Recommended # 3.1 [42711] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6D6 -2A6D7..2A6DD ; Recommended # 13.0 [7] CJK UNIFIED IDEOGRAPH-2A6D7..CJK UNIFIED IDEOGRAPH-2A6DD -2A6DE..2A6DF ; Recommended # 14.0 [2] CJK UNIFIED IDEOGRAPH-2A6DE..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B734 ; Recommended # 5.2 [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734 -2B735..2B738 ; Recommended # 14.0 [4] CJK UNIFIED IDEOGRAPH-2B735..CJK UNIFIED IDEOGRAPH-2B738 -2B739 ; Recommended # 15.0 CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Recommended # 6.0 [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Recommended # 8.0 [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 -2CEB0..2EBE0 ; Recommended # 10.0 [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 -2EBF0..2EE5D ; Recommended # 15.1 [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D -30000..3134A ; Recommended # 13.0 [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Recommended # 15.0 [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF - -# Total code points: 112761 +2070E ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2070E +20731 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20731 +20779 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20779 +20C53 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20C53 +20C78 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20C78 +20C96 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20C96 +20CCF ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20CCF +20CD5 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20CD5 +20D15 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20D15 +20D7C ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20D7C +20D7F ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20D7F +20E0E..20E0F ; Recommended # 3.1 [2] CJK UNIFIED IDEOGRAPH-20E0E..CJK UNIFIED IDEOGRAPH-20E0F +20E77 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20E77 +20E9D ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20E9D +20EA2 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20EA2 +20ED7 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20ED7 +20EF9..20EFA ; Recommended # 3.1 [2] CJK UNIFIED IDEOGRAPH-20EF9..CJK UNIFIED IDEOGRAPH-20EFA +20F2D..20F2E ; Recommended # 3.1 [2] CJK UNIFIED IDEOGRAPH-20F2D..CJK UNIFIED IDEOGRAPH-20F2E +20F4C ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20F4C +20FB4 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20FB4 +20FBC ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20FBC +20FEA ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-20FEA +2105C ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2105C +2106F ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2106F +21075..21076 ; Recommended # 3.1 [2] CJK UNIFIED IDEOGRAPH-21075..CJK UNIFIED IDEOGRAPH-21076 +2107B ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2107B +210C1 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-210C1 +210C9 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-210C9 +211D9 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-211D9 +220C7 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-220C7 +227B5 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-227B5 +22AD5 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22AD5 +22B43 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22B43 +22BCA ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22BCA +22C51 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22C51 +22C55 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22C55 +22CC2 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22CC2 +22D08 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22D08 +22D4C ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22D4C +22D67 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22D67 +22EB3 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-22EB3 +23CB7 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-23CB7 +244D3 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-244D3 +24DB8 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-24DB8 +24DEA ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-24DEA +2512B ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2512B +26258 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-26258 +267CC ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-267CC +269F2 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-269F2 +269FA ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-269FA +27A3E ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-27A3E +2815D ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-2815D +28207 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-28207 +282E2 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-282E2 +28CCA ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-28CCA +28CCD ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-28CCD +28CD2 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-28CD2 +29D98 ; Recommended # 3.1 CJK UNIFIED IDEOGRAPH-29D98 + +# Total code points: 33773 # Identifier_Type: Inclusion @@ -588,7 +1682,7 @@ FA27..FA29 ; Recommended # 1.1 [3] CJK COMPATIBILITY ID 002D..002E ; Inclusion # 1.1 [2] HYPHEN-MINUS..FULL STOP 003A ; Inclusion # 1.1 COLON 00B7 ; Inclusion # 1.1 MIDDLE DOT -0375 ; Inclusion # 1.1 GREEK LOWER NUMERAL SIGN +02BB..02BC ; Inclusion # 1.1 [2] MODIFIER LETTER TURNED COMMA..MODIFIER LETTER APOSTROPHE 058A ; Inclusion # 3.0 ARMENIAN HYPHEN 05F3..05F4 ; Inclusion # 1.1 [2] HEBREW PUNCTUATION GERESH..HEBREW PUNCTUATION GERSHAYIM 06FD..06FE ; Inclusion # 3.0 [2] ARABIC SIGN SINDHI AMPERSAND..ARABIC SIGN SINDHI POSTPOSITION MEN @@ -599,7 +1693,7 @@ FA27..FA29 ; Recommended # 1.1 [3] CJK COMPATIBILITY ID 30A0 ; Inclusion # 3.2 KATAKANA-HIRAGANA DOUBLE HYPHEN 30FB ; Inclusion # 1.1 KATAKANA MIDDLE DOT -# Total code points: 17 +# Total code points: 18 # Identifier_Type: Limited_Use @@ -649,6 +1743,12 @@ FA27..FA29 ; Recommended # 1.1 [3] CJK COMPATIBILITY ID 2D30..2D65 ; Limited_Use # 4.1 [54] TIFINAGH LETTER YA..TIFINAGH LETTER YAZZ 2D66..2D67 ; Limited_Use # 6.1 [2] TIFINAGH LETTER YE..TIFINAGH LETTER YO 2D7F ; Limited_Use # 6.0 TIFINAGH CONSONANT JOINER +3105..312C ; Limited_Use # 1.1 [40] BOPOMOFO LETTER B..BOPOMOFO LETTER GN +312D ; Limited_Use # 5.1 BOPOMOFO LETTER IH +312F ; Limited_Use # 11.0 BOPOMOFO LETTER NN +31A0..31B7 ; Limited_Use # 3.0 [24] BOPOMOFO LETTER BU..BOPOMOFO FINAL LETTER H +31B8..31BA ; Limited_Use # 6.0 [3] BOPOMOFO LETTER GH..BOPOMOFO LETTER ZY +31BB..31BF ; Limited_Use # 13.0 [5] BOPOMOFO FINAL LETTER G..BOPOMOFO LETTER AH A000..A48C ; Limited_Use # 3.0 [1165] YI SYLLABLE IT..YI SYLLABLE YYR A4D0..A4FD ; Limited_Use # 5.2 [46] LISU LETTER BA..LISU LETTER TONE MYA JEU A500..A60C ; Limited_Use # 5.1 [269] VAI SYLLABLE EE..VAI SYLLABLE LENGTHENER @@ -687,12 +1787,6 @@ ABF0..ABF9 ; Limited_Use # 5.2 [10] MEETEI MAYEK DIGIT Z 1145F ; Limited_Use # 12.0 NEWA LETTER VEDIC ANUSVARA 11460..11461 ; Limited_Use # 13.0 [2] NEWA SIGN JIHVAMULIYA..NEWA SIGN UPADHMANIYA 11AB0..11ABF ; Limited_Use # 14.0 [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA -11D60..11D65 ; Limited_Use # 11.0 [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU -11D67..11D68 ; Limited_Use # 11.0 [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI -11D6A..11D8E ; Limited_Use # 11.0 [37] GUNJALA GONDI LETTER OO..GUNJALA GONDI VOWEL SIGN UU -11D90..11D91 ; Limited_Use # 11.0 [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI -11D93..11D98 ; Limited_Use # 11.0 [6] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI OM -11DA0..11DA9 ; Limited_Use # 11.0 [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE 11FB0 ; Limited_Use # 13.0 LISU LETTER YHA 16800..16A38 ; Limited_Use # 6.0 [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ 16F00..16F44 ; Limited_Use # 6.1 [69] MIAO LETTER PA..MIAO LETTER HHA @@ -710,7 +1804,7 @@ ABF0..ABF9 ; Limited_Use # 5.2 [10] MEETEI MAYEK DIGIT Z 1E94B ; Limited_Use # 12.0 ADLAM NASALIZATION MARK 1E950..1E959 ; Limited_Use # 9.0 [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE -# Total code points: 5033 +# Total code points: 5044 # Identifier_Type: Limited_Use Uncommon_Use @@ -730,13 +1824,15 @@ A9CF ; Limited_Use Uncommon_Use # 5.2 JAVANESE PANGRANGKEP 07E8..07EA ; Limited_Use Obsolete # 5.0 [3] NKO LETTER JONA JA..NKO LETTER JONA RA 07FA ; Limited_Use Obsolete # 5.0 NKO LAJANYALAN +312E ; Limited_Use Obsolete # 10.0 BOPOMOFO LETTER O WITH DOT ABOVE A610..A612 ; Limited_Use Obsolete # 5.1 [3] VAI SYLLABLE NDOLE FA..VAI SYLLABLE NDOLE SOO A62A..A62B ; Limited_Use Obsolete # 5.1 [2] VAI SYLLABLE NDOLE MA..VAI SYLLABLE NDOLE DO -# Total code points: 9 +# Total code points: 10 # Identifier_Type: Limited_Use Not_XID +02EA..02EB ; Limited_Use Not_XID # 3.0 [2] MODIFIER LETTER YIN DEPARTING TONE MARK..MODIFIER LETTER YANG DEPARTING TONE MARK 0700..070D ; Limited_Use Not_XID # 3.0 [14] SYRIAC END OF PARAGRAPH..SYRIAC HARKLEAN ASTERISCUS 070F ; Limited_Use Not_XID # 3.0 SYRIAC ABBREVIATION MARK 07F6..07F9 ; Limited_Use Not_XID # 5.0 [4] NKO SYMBOL OO DENNEN..NKO EXCLAMATION MARK @@ -789,24 +1885,42 @@ ABEB ; Limited_Use Not_XID # 5.2 MEETEI MAYEK CHEIKHE 1E2FF ; Limited_Use Not_XID # 12.0 WANCHO NGUN SIGN 1E95E..1E95F ; Limited_Use Not_XID # 9.0 [2] ADLAM INITIAL EXCLAMATION MARK..ADLAM INITIAL QUESTION MARK -# Total code points: 207 +# Total code points: 209 # Identifier_Type: Uncommon_Use -0181..018C ; Uncommon_Use # 1.1 [12] LATIN CAPITAL LETTER B WITH HOOK..LATIN SMALL LETTER D WITH TOPBAR -018E ; Uncommon_Use # 1.1 LATIN CAPITAL LETTER REVERSED E -0190..019F ; Uncommon_Use # 1.1 [16] LATIN CAPITAL LETTER OPEN E..LATIN CAPITAL LETTER O WITH MIDDLE TILDE +0114..0115 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER E WITH BREVE..LATIN SMALL LETTER E WITH BREVE +012C..012D ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER I WITH BREVE..LATIN SMALL LETTER I WITH BREVE +014E..014F ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER O WITH BREVE..LATIN SMALL LETTER O WITH BREVE +0156..0157 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER R WITH CEDILLA..LATIN SMALL LETTER R WITH CEDILLA +0162..0163 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER T WITH CEDILLA..LATIN SMALL LETTER T WITH CEDILLA +0182..0185 ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER B WITH TOPBAR..LATIN SMALL LETTER TONE SIX +0187..0188 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER C WITH HOOK..LATIN SMALL LETTER C WITH HOOK +018B..018C ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER D WITH TOPBAR..LATIN SMALL LETTER D WITH TOPBAR +0193 ; Uncommon_Use # 1.1 LATIN CAPITAL LETTER G WITH HOOK +0195 ; Uncommon_Use # 1.1 LATIN SMALL LETTER HV +019A..019C ; Uncommon_Use # 1.1 [3] LATIN SMALL LETTER L WITH BAR..LATIN CAPITAL LETTER TURNED M +019E..019F ; Uncommon_Use # 1.1 [2] LATIN SMALL LETTER N WITH LONG RIGHT LEG..LATIN CAPITAL LETTER O WITH MIDDLE TILDE 01A2..01A9 ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER OI..LATIN CAPITAL LETTER ESH 01AC..01AE ; Uncommon_Use # 1.1 [3] LATIN CAPITAL LETTER T WITH HOOK..LATIN CAPITAL LETTER T WITH RETROFLEX HOOK -01B1..01B8 ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER UPSILON..LATIN CAPITAL LETTER EZH REVERSED +01B1 ; Uncommon_Use # 1.1 LATIN CAPITAL LETTER UPSILON +01B5..01B6 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER Z WITH STROKE..LATIN SMALL LETTER Z WITH STROKE +01B8 ; Uncommon_Use # 1.1 LATIN CAPITAL LETTER EZH REVERSED 01BC..01BD ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER TONE FIVE..LATIN SMALL LETTER TONE FIVE -01DD ; Uncommon_Use # 1.1 LATIN SMALL LETTER TURNED E -01E4..01E5 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER G WITH STROKE..LATIN SMALL LETTER G WITH STROKE +01D5..01DC ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON..LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE +01DE..01E5 ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON..LATIN SMALL LETTER G WITH STROKE +01EA..01ED ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER O WITH OGONEK..LATIN SMALL LETTER O WITH OGONEK AND MACRON +01F0 ; Uncommon_Use # 1.1 LATIN SMALL LETTER J WITH CARON +01F4..01F5 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER G WITH ACUTE..LATIN SMALL LETTER G WITH ACUTE +01FA..01FF ; Uncommon_Use # 1.1 [6] LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE..LATIN SMALL LETTER O WITH STROKE AND ACUTE +021E..021F ; Uncommon_Use # 3.0 [2] LATIN CAPITAL LETTER H WITH CARON..LATIN SMALL LETTER H WITH CARON 0220 ; Uncommon_Use # 3.2 LATIN CAPITAL LETTER N WITH LONG RIGHT LEG 0221 ; Uncommon_Use # 4.0 LATIN SMALL LETTER D WITH CURL -0222..0225 ; Uncommon_Use # 3.0 [4] LATIN CAPITAL LETTER OU..LATIN SMALL LETTER Z WITH HOOK +0222..0233 ; Uncommon_Use # 3.0 [18] LATIN CAPITAL LETTER OU..LATIN SMALL LETTER Y WITH MACRON 0237..0241 ; Uncommon_Use # 4.1 [11] LATIN SMALL LETTER DOTLESS J..LATIN CAPITAL LETTER GLOTTAL STOP -0242..024F ; Uncommon_Use # 5.0 [14] LATIN SMALL LETTER GLOTTAL STOP..LATIN SMALL LETTER Y WITH STROKE +0242..0243 ; Uncommon_Use # 5.0 [2] LATIN SMALL LETTER GLOTTAL STOP..LATIN CAPITAL LETTER B WITH STROKE +0245..024B ; Uncommon_Use # 5.0 [7] LATIN CAPITAL LETTER TURNED V..LATIN SMALL LETTER Q WITH HOOK TAIL +024E..024F ; Uncommon_Use # 5.0 [2] LATIN CAPITAL LETTER Y WITH STROKE..LATIN SMALL LETTER Y WITH STROKE 0305 ; Uncommon_Use # 1.1 COMBINING OVERLINE 030D ; Uncommon_Use # 1.1 COMBINING VERTICAL LINE ABOVE 0316 ; Uncommon_Use # 1.1 COMBINING GRAVE ACCENT BELOW @@ -815,27 +1929,69 @@ ABEB ; Limited_Use Not_XID # 5.2 MEETEI MAYEK CHEIKHE 0334 ; Uncommon_Use # 1.1 COMBINING TILDE OVERLAY 0336 ; Uncommon_Use # 1.1 COMBINING LONG STROKE OVERLAY 0358 ; Uncommon_Use # 4.1 COMBINING DOT ABOVE RIGHT +0400 ; Uncommon_Use # 3.0 CYRILLIC CAPITAL LETTER IE WITH GRAVE +040D ; Uncommon_Use # 3.0 CYRILLIC CAPITAL LETTER I WITH GRAVE +0450 ; Uncommon_Use # 3.0 CYRILLIC SMALL LETTER IE WITH GRAVE +045D ; Uncommon_Use # 3.0 CYRILLIC SMALL LETTER I WITH GRAVE +048A..048B ; Uncommon_Use # 3.2 [2] CYRILLIC CAPITAL LETTER SHORT I WITH TAIL..CYRILLIC SMALL LETTER SHORT I WITH TAIL +048C..048F ; Uncommon_Use # 3.0 [4] CYRILLIC CAPITAL LETTER SEMISOFT SIGN..CYRILLIC SMALL LETTER ER WITH TICK +04C1..04C4 ; Uncommon_Use # 1.1 [4] CYRILLIC CAPITAL LETTER ZHE WITH BREVE..CYRILLIC SMALL LETTER KA WITH HOOK +04C5..04C6 ; Uncommon_Use # 3.2 [2] CYRILLIC CAPITAL LETTER EL WITH TAIL..CYRILLIC SMALL LETTER EL WITH TAIL +04C7..04C8 ; Uncommon_Use # 1.1 [2] CYRILLIC CAPITAL LETTER EN WITH HOOK..CYRILLIC SMALL LETTER EN WITH HOOK +04C9..04CA ; Uncommon_Use # 3.2 [2] CYRILLIC CAPITAL LETTER EN WITH TAIL..CYRILLIC SMALL LETTER EN WITH TAIL +04CB..04CC ; Uncommon_Use # 1.1 [2] CYRILLIC CAPITAL LETTER KHAKASSIAN CHE..CYRILLIC SMALL LETTER KHAKASSIAN CHE +04CD..04CE ; Uncommon_Use # 3.2 [2] CYRILLIC CAPITAL LETTER EM WITH TAIL..CYRILLIC SMALL LETTER EM WITH TAIL +04DA..04DB ; Uncommon_Use # 1.1 [2] CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS..CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS +04EA..04EB ; Uncommon_Use # 1.1 [2] CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS..CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS +04EC..04ED ; Uncommon_Use # 3.0 [2] CYRILLIC CAPITAL LETTER E WITH DIAERESIS..CYRILLIC SMALL LETTER E WITH DIAERESIS +04F6..04F7 ; Uncommon_Use # 4.1 [2] CYRILLIC CAPITAL LETTER GHE WITH DESCENDER..CYRILLIC SMALL LETTER GHE WITH DESCENDER +04FA..04FF ; Uncommon_Use # 5.0 [6] CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK..CYRILLIC SMALL LETTER HA WITH STROKE +0510..0513 ; Uncommon_Use # 5.0 [4] CYRILLIC CAPITAL LETTER REVERSED ZE..CYRILLIC SMALL LETTER EL WITH HOOK 0591..05A1 ; Uncommon_Use # 2.0 [17] HEBREW ACCENT ETNAHTA..HEBREW ACCENT PAZER 05A3..05AF ; Uncommon_Use # 2.0 [13] HEBREW ACCENT MUNAH..HEBREW MARK MASORA CIRCLE -05B0..05B3 ; Uncommon_Use # 1.1 [4] HEBREW POINT SHEVA..HEBREW POINT HATAF QAMATS -05B5..05B9 ; Uncommon_Use # 1.1 [5] HEBREW POINT TSERE..HEBREW POINT HOLAM +05B0..05B9 ; Uncommon_Use # 1.1 [10] HEBREW POINT SHEVA..HEBREW POINT HOLAM 05BA ; Uncommon_Use # 5.0 HEBREW POINT HOLAM HASER FOR VAV 05BB..05BD ; Uncommon_Use # 1.1 [3] HEBREW POINT QUBUTS..HEBREW POINT METEG 05BF ; Uncommon_Use # 1.1 HEBREW POINT RAFE 05C1..05C2 ; Uncommon_Use # 1.1 [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT 05C4 ; Uncommon_Use # 2.0 HEBREW MARK UPPER DOT +05EF ; Uncommon_Use # 11.0 HEBREW YOD TRIANGLE +05F0..05F2 ; Uncommon_Use # 1.1 [3] HEBREW LIGATURE YIDDISH DOUBLE VAV..HEBREW LIGATURE YIDDISH DOUBLE YOD 0610..0615 ; Uncommon_Use # 4.0 [6] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL HIGH TAH 0616..061A ; Uncommon_Use # 5.1 [5] ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH..ARABIC SMALL KASRA 0656..0658 ; Uncommon_Use # 4.0 [3] ARABIC SUBSCRIPT ALEF..ARABIC MARK NOON GHUNNA 0659..065E ; Uncommon_Use # 4.1 [6] ARABIC ZWARAKAY..ARABIC FATHA WITH TWO DOTS 065F ; Uncommon_Use # 6.0 ARABIC WAVY HAMZA BELOW +069B..069E ; Uncommon_Use # 1.1 [4] ARABIC LETTER SEEN WITH THREE DOTS BELOW..ARABIC LETTER SAD WITH THREE DOTS ABOVE +06A1 ; Uncommon_Use # 1.1 ARABIC LETTER DOTLESS FEH +06A3 ; Uncommon_Use # 1.1 ARABIC LETTER FEH WITH DOT BELOW +06B2 ; Uncommon_Use # 1.1 ARABIC LETTER GAF WITH TWO DOTS BELOW +06B4 ; Uncommon_Use # 1.1 ARABIC LETTER GAF WITH THREE DOTS ABOVE +06B8..06B9 ; Uncommon_Use # 3.0 [2] ARABIC LETTER LAM WITH THREE DOTS BELOW..ARABIC LETTER NOON WITH DOT BELOW +06BF ; Uncommon_Use # 3.0 ARABIC LETTER TCHEH WITH DOT ABOVE 06D6..06DC ; Uncommon_Use # 1.1 [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN 06DF..06E4 ; Uncommon_Use # 1.1 [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA 06E7..06E8 ; Uncommon_Use # 1.1 [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON 06EA..06ED ; Uncommon_Use # 1.1 [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM +06FA..06FC ; Uncommon_Use # 3.0 [3] ARABIC LETTER SHEEN WITH DOT BELOW..ARABIC LETTER GHAIN WITH DOT BELOW +0750 ; Uncommon_Use # 4.1 ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW +0753..0755 ; Uncommon_Use # 4.1 [3] ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW AND TWO DOTS ABOVE..ARABIC LETTER BEH WITH INVERTED SMALL V BELOW +0757..075F ; Uncommon_Use # 4.1 [9] ARABIC LETTER HAH WITH TWO DOTS ABOVE..ARABIC LETTER AIN WITH TWO DOTS VERTICALLY ABOVE +0761 ; Uncommon_Use # 4.1 ARABIC LETTER FEH WITH THREE DOTS POINTING UPWARDS BELOW +0764..0765 ; Uncommon_Use # 4.1 [2] ARABIC LETTER KEHEH WITH THREE DOTS POINTING UPWARDS BELOW..ARABIC LETTER MEEM WITH DOT ABOVE +0769 ; Uncommon_Use # 4.1 ARABIC LETTER NOON WITH SMALL V +076B..076D ; Uncommon_Use # 4.1 [3] ARABIC LETTER REH WITH TWO DOTS VERTICALLY ABOVE..ARABIC LETTER SEEN WITH TWO DOTS VERTICALLY ABOVE +0772..077D ; Uncommon_Use # 5.1 [12] ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH ABOVE..ARABIC LETTER SEEN WITH EXTENDED ARABIC-INDIC DIGIT FOUR ABOVE +0889..088D ; Uncommon_Use # 14.0 [5] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW 0897 ; Uncommon_Use # 16.0 ARABIC PEPET 0898..089F ; Uncommon_Use # 14.0 [8] ARABIC SMALL HIGH WORD AL-JUZ..ARABIC HALF MADDA OVER MADDA +08A1 ; Uncommon_Use # 7.0 ARABIC LETTER BEH WITH HAMZA ABOVE +08AA..08AC ; Uncommon_Use # 6.1 [3] ARABIC LETTER REH WITH LOOP..ARABIC LETTER ROHINGYA YEH +08B2 ; Uncommon_Use # 7.0 ARABIC LETTER ZAIN WITH INVERTED V ABOVE 08B3..08B4 ; Uncommon_Use # 8.0 [2] ARABIC LETTER AIN WITH THREE DOTS BELOW..ARABIC LETTER KAF WITH DOT BELOW +08B6..08BA ; Uncommon_Use # 9.0 [5] ARABIC LETTER BEH WITH SMALL MEEM ABOVE..ARABIC LETTER YEH WITH TWO DOTS BELOW AND SMALL NOON ABOVE +08C3..08C6 ; Uncommon_Use # 13.0 [4] ARABIC LETTER GHAIN WITH THREE DOTS ABOVE..ARABIC LETTER JEEM WITH THREE DOTS BELOW +08C8 ; Uncommon_Use # 14.0 ARABIC LETTER GRAF 08CA..08D2 ; Uncommon_Use # 14.0 [9] ARABIC SMALL HIGH FARSI YEH..ARABIC LARGE ROUND DOT INSIDE CIRCLE BELOW 08D3 ; Uncommon_Use # 11.0 ARABIC SMALL LOW WAW 08D4..08E1 ; Uncommon_Use # 9.0 [14] ARABIC SMALL HIGH WORD AR-RUB..ARABIC SMALL HIGH SIGN SAFHA @@ -843,60 +1999,1461 @@ ABEB ; Limited_Use Not_XID # 5.2 MEETEI MAYEK CHEIKHE 08E4..08FE ; Uncommon_Use # 6.1 [27] ARABIC CURLY FATHA..ARABIC DAMMA WITH DOT 08FF ; Uncommon_Use # 7.0 ARABIC MARK SIDEWAYS NOON GHUNNA 0900 ; Uncommon_Use # 5.2 DEVANAGARI SIGN INVERTED CANDRABINDU +0904 ; Uncommon_Use # 4.0 DEVANAGARI LETTER SHORT A +0929 ; Uncommon_Use # 1.1 DEVANAGARI LETTER NNNA +0934 ; Uncommon_Use # 1.1 DEVANAGARI LETTER LLLA +0944 ; Uncommon_Use # 1.1 DEVANAGARI VOWEL SIGN VOCALIC RR 0955 ; Uncommon_Use # 5.2 DEVANAGARI VOWEL SIGN CANDRA LONG E +0979..097A ; Uncommon_Use # 5.2 [2] DEVANAGARI LETTER ZHA..DEVANAGARI LETTER HEAVY YA +098C ; Uncommon_Use # 1.1 BENGALI LETTER VOCALIC L +09D7 ; Uncommon_Use # 1.1 BENGALI AU LENGTH MARK +09FE ; Uncommon_Use # 11.0 BENGALI SANDHI MARK +0A01 ; Uncommon_Use # 4.0 GURMUKHI SIGN ADAK BINDI +0A03 ; Uncommon_Use # 4.0 GURMUKHI SIGN VISARGA 0A51 ; Uncommon_Use # 5.1 GURMUKHI SIGN UDAAT +0A66..0A6F ; Uncommon_Use # 1.1 [10] GURMUKHI DIGIT ZERO..GURMUKHI DIGIT NINE +0A72..0A73 ; Uncommon_Use # 1.1 [2] GURMUKHI IRI..GURMUKHI URA 0A75 ; Uncommon_Use # 5.1 GURMUKHI SIGN YAKASH +0A81 ; Uncommon_Use # 1.1 GUJARATI SIGN CANDRABINDU 0AF9 ; Uncommon_Use # 8.0 GUJARATI LETTER ZHA +0AFA..0AFF ; Uncommon_Use # 10.0 [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE +0B0C ; Uncommon_Use # 1.1 ORIYA LETTER VOCALIC L +0B35 ; Uncommon_Use # 4.0 ORIYA LETTER VA 0B44 ; Uncommon_Use # 5.1 ORIYA VOWEL SIGN VOCALIC RR +0B55 ; Uncommon_Use # 13.0 ORIYA SIGN OVERLINE +0B57 ; Uncommon_Use # 1.1 ORIYA AU LENGTH MARK 0B62..0B63 ; Uncommon_Use # 5.1 [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL +0B66..0B6F ; Uncommon_Use # 1.1 [10] ORIYA DIGIT ZERO..ORIYA DIGIT NINE +0BD7 ; Uncommon_Use # 1.1 TAMIL AU LENGTH MARK +0BE6 ; Uncommon_Use # 4.1 TAMIL DIGIT ZERO +0BE7..0BEF ; Uncommon_Use # 1.1 [9] TAMIL DIGIT ONE..TAMIL DIGIT NINE +0C01 ; Uncommon_Use # 1.1 TELUGU SIGN CANDRABINDU +0C04 ; Uncommon_Use # 11.0 TELUGU SIGN COMBINING ANUSVARA ABOVE +0C0C ; Uncommon_Use # 1.1 TELUGU LETTER VOCALIC L +0C31 ; Uncommon_Use # 1.1 TELUGU LETTER RRA +0C3C ; Uncommon_Use # 14.0 TELUGU SIGN NUKTA +0C55..0C56 ; Uncommon_Use # 1.1 [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C5A ; Uncommon_Use # 8.0 TELUGU LETTER RRRA +0C5D ; Uncommon_Use # 14.0 TELUGU LETTER NAKAARA POLLU 0C62..0C63 ; Uncommon_Use # 5.1 [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL +0C66..0C6F ; Uncommon_Use # 1.1 [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE +0C80 ; Uncommon_Use # 9.0 KANNADA SIGN SPACING CANDRABINDU +0CBC ; Uncommon_Use # 4.0 KANNADA SIGN NUKTA +0CC4 ; Uncommon_Use # 1.1 KANNADA VOWEL SIGN VOCALIC RR +0CD5..0CD6 ; Uncommon_Use # 1.1 [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK +0CDD ; Uncommon_Use # 14.0 KANNADA LETTER NAKAARA POLLU +0CF3 ; Uncommon_Use # 15.0 KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT +0D00 ; Uncommon_Use # 10.0 MALAYALAM SIGN COMBINING ANUSVARA ABOVE +0D0C ; Uncommon_Use # 1.1 MALAYALAM LETTER VOCALIC L +0D29 ; Uncommon_Use # 6.0 MALAYALAM LETTER NNNA 0D44 ; Uncommon_Use # 5.1 MALAYALAM VOWEL SIGN VOCALIC RR +0D54..0D56 ; Uncommon_Use # 9.0 [3] MALAYALAM LETTER CHILLU M..MALAYALAM LETTER CHILLU LLL 0D62..0D63 ; Uncommon_Use # 5.1 [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL +0D66..0D6F ; Uncommon_Use # 1.1 [10] MALAYALAM DIGIT ZERO..MALAYALAM DIGIT NINE +0D8E ; Uncommon_Use # 3.0 SINHALA LETTER IRUUYANNA +0DE6..0DEF ; Uncommon_Use # 7.0 [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0E4E ; Uncommon_Use # 1.1 THAI CHARACTER YAMAKKAN +0E86 ; Uncommon_Use # 12.0 LAO LETTER PALI GHA +0E89 ; Uncommon_Use # 12.0 LAO LETTER PALI CHA +0E8C ; Uncommon_Use # 12.0 LAO LETTER PALI JHA +0E8E..0E93 ; Uncommon_Use # 12.0 [6] LAO LETTER PALI NYA..LAO LETTER PALI NNA +0E98 ; Uncommon_Use # 12.0 LAO LETTER PALI DHA +0EA0 ; Uncommon_Use # 12.0 LAO LETTER PALI BHA +0EA8..0EA9 ; Uncommon_Use # 12.0 [2] LAO LETTER SANSKRIT SHA..LAO LETTER SANSKRIT SSA +0EAC ; Uncommon_Use # 12.0 LAO LETTER PALI LLA +0EBA ; Uncommon_Use # 12.0 LAO SIGN PALI VIRAMA +0ECE ; Uncommon_Use # 15.0 LAO YAMAKKAN +0EDE..0EDF ; Uncommon_Use # 6.1 [2] LAO LETTER KHMU GO..LAO LETTER KHMU NYO 0F39 ; Uncommon_Use # 2.0 TIBETAN MARK TSA -PHRU +0F6B..0F6C ; Uncommon_Use # 5.1 [2] TIBETAN LETTER KKA..TIBETAN LETTER RRA +0FAE..0FB0 ; Uncommon_Use # 3.0 [3] TIBETAN SUBJOINED LETTER ZHA..TIBETAN SUBJOINED LETTER -A +1065..1074 ; Uncommon_Use # 5.1 [16] MYANMAR LETTER WESTERN PWO KAREN THA..MYANMAR VOWEL SIGN KAYAH EE +108B..108E ; Uncommon_Use # 5.1 [4] MYANMAR SIGN SHAN COUNCIL TONE-2..MYANMAR LETTER RUMAI PALAUNG FA +1090..1099 ; Uncommon_Use # 5.1 [10] MYANMAR SHAN DIGIT ZERO..MYANMAR SHAN DIGIT NINE +109A..109D ; Uncommon_Use # 5.2 [4] MYANMAR SIGN KHAMTI TONE-1..MYANMAR VOWEL SIGN AITON AI +10F7..10F8 ; Uncommon_Use # 3.2 [2] GEORGIAN LETTER YN..GEORGIAN LETTER ELIFI +10FD..10FF ; Uncommon_Use # 6.1 [3] GEORGIAN LETTER AEN..GEORGIAN LETTER LABIAL SIGN +1207 ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE HOA +1287 ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE XOA +12AF ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE KOA +12F8..12FF ; Uncommon_Use # 3.0 [8] ETHIOPIC SYLLABLE DDA..ETHIOPIC SYLLABLE DDWA +130F ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE GOA +131F ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE GGWAA +1347 ; Uncommon_Use # 4.1 ETHIOPIC SYLLABLE TZOA +135A ; Uncommon_Use # 3.0 ETHIOPIC SYLLABLE FYA +135D..135E ; Uncommon_Use # 6.0 [2] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING VOWEL LENGTH MARK +135F ; Uncommon_Use # 4.1 ETHIOPIC COMBINING GEMINATION MARK +1380..138F ; Uncommon_Use # 4.1 [16] ETHIOPIC SYLLABLE SEBATBEIT MWA..ETHIOPIC SYLLABLE PWE +179D..179E ; Uncommon_Use # 3.0 [2] KHMER LETTER SHA..KHMER LETTER SSO +17A9 ; Uncommon_Use # 3.0 KHMER INDEPENDENT VOWEL QUU +17D7 ; Uncommon_Use # 3.0 KHMER SIGN LEK TOO 1AC1..1ACE ; Uncommon_Use # 14.0 [14] COMBINING LEFT PARENTHESIS ABOVE LEFT..COMBINING LATIN SMALL LETTER INSULAR T 1C89..1C8A ; Uncommon_Use # 16.0 [2] CYRILLIC CAPITAL LETTER TJE..CYRILLIC SMALL LETTER TJE +1E02..1E0B ; Uncommon_Use # 1.1 [10] LATIN CAPITAL LETTER B WITH DOT ABOVE..LATIN SMALL LETTER D WITH DOT ABOVE +1E0E..1E11 ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER D WITH LINE BELOW..LATIN SMALL LETTER D WITH CEDILLA +1E14..1E17 ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER E WITH MACRON AND GRAVE..LATIN SMALL LETTER E WITH MACRON AND ACUTE +1E1C..1E1F ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE..LATIN SMALL LETTER F WITH DOT ABOVE +1E22..1E23 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER H WITH DOT ABOVE..LATIN SMALL LETTER H WITH DOT ABOVE +1E26..1E29 ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER H WITH DIAERESIS..LATIN SMALL LETTER H WITH CEDILLA +1E2E..1E35 ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE..LATIN SMALL LETTER K WITH LINE BELOW +1E38..1E3B ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON..LATIN SMALL LETTER L WITH LINE BELOW +1E40..1E41 ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER M WITH DOT ABOVE..LATIN SMALL LETTER M WITH DOT ABOVE +1E4C..1E59 ; Uncommon_Use # 1.1 [14] LATIN CAPITAL LETTER O WITH TILDE AND ACUTE..LATIN SMALL LETTER R WITH DOT ABOVE +1E5C..1E61 ; Uncommon_Use # 1.1 [6] LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON..LATIN SMALL LETTER S WITH DOT ABOVE +1E64..1E6B ; Uncommon_Use # 1.1 [8] LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE..LATIN SMALL LETTER T WITH DOT ABOVE +1E6E..1E6F ; Uncommon_Use # 1.1 [2] LATIN CAPITAL LETTER T WITH LINE BELOW..LATIN SMALL LETTER T WITH LINE BELOW +1E78..1E8B ; Uncommon_Use # 1.1 [20] LATIN CAPITAL LETTER U WITH TILDE AND ACUTE..LATIN SMALL LETTER X WITH DOT ABOVE +1E8E..1E91 ; Uncommon_Use # 1.1 [4] LATIN CAPITAL LETTER Y WITH DOT ABOVE..LATIN SMALL LETTER Z WITH CIRCUMFLEX +1E94..1E99 ; Uncommon_Use # 1.1 [6] LATIN CAPITAL LETTER Z WITH LINE BELOW..LATIN SMALL LETTER Y WITH RING ABOVE 2054 ; Uncommon_Use # 4.0 INVERTED UNDERTIE 2C68..2C6C ; Uncommon_Use # 5.0 [5] LATIN SMALL LETTER H WITH DESCENDER..LATIN SMALL LETTER Z WITH DESCENDER +2D80..2D96 ; Uncommon_Use # 4.1 [23] ETHIOPIC SYLLABLE LOA..ETHIOPIC SYLLABLE GGWE +2DA0..2DA6 ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE SSA..ETHIOPIC SYLLABLE SSO +2DA8..2DAE ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE CCA..ETHIOPIC SYLLABLE CCO +2DB0..2DB6 ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE ZZA..ETHIOPIC SYLLABLE ZZO +2DB8..2DBE ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE CCHA..ETHIOPIC SYLLABLE CCHO +2DC0..2DC6 ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE QYA..ETHIOPIC SYLLABLE QYO +2DC8..2DCE ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE KYA..ETHIOPIC SYLLABLE KYO +2DD0..2DD6 ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE XYA..ETHIOPIC SYLLABLE XYO +2DD8..2DDE ; Uncommon_Use # 4.1 [7] ETHIOPIC SYLLABLE GYA..ETHIOPIC SYLLABLE GYO +3099..309A ; Uncommon_Use # 1.1 [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK +3400..3446 ; Uncommon_Use # 3.0 [71] CJK UNIFIED IDEOGRAPH-3400..CJK UNIFIED IDEOGRAPH-3446 +3448..3472 ; Uncommon_Use # 3.0 [43] CJK UNIFIED IDEOGRAPH-3448..CJK UNIFIED IDEOGRAPH-3472 +3474..34E3 ; Uncommon_Use # 3.0 [112] CJK UNIFIED IDEOGRAPH-3474..CJK UNIFIED IDEOGRAPH-34E3 +34E5..3576 ; Uncommon_Use # 3.0 [146] CJK UNIFIED IDEOGRAPH-34E5..CJK UNIFIED IDEOGRAPH-3576 +3578..359D ; Uncommon_Use # 3.0 [38] CJK UNIFIED IDEOGRAPH-3578..CJK UNIFIED IDEOGRAPH-359D +359F..35A0 ; Uncommon_Use # 3.0 [2] CJK UNIFIED IDEOGRAPH-359F..CJK UNIFIED IDEOGRAPH-35A0 +35A2..35AC ; Uncommon_Use # 3.0 [11] CJK UNIFIED IDEOGRAPH-35A2..CJK UNIFIED IDEOGRAPH-35AC +35AE..35BE ; Uncommon_Use # 3.0 [17] CJK UNIFIED IDEOGRAPH-35AE..CJK UNIFIED IDEOGRAPH-35BE +35C0..35CD ; Uncommon_Use # 3.0 [14] CJK UNIFIED IDEOGRAPH-35C0..CJK UNIFIED IDEOGRAPH-35CD +35CF..35F2 ; Uncommon_Use # 3.0 [36] CJK UNIFIED IDEOGRAPH-35CF..CJK UNIFIED IDEOGRAPH-35F2 +35F4..35FD ; Uncommon_Use # 3.0 [10] CJK UNIFIED IDEOGRAPH-35F4..CJK UNIFIED IDEOGRAPH-35FD +35FF..360D ; Uncommon_Use # 3.0 [15] CJK UNIFIED IDEOGRAPH-35FF..CJK UNIFIED IDEOGRAPH-360D +360F..3619 ; Uncommon_Use # 3.0 [11] CJK UNIFIED IDEOGRAPH-360F..CJK UNIFIED IDEOGRAPH-3619 +361B..3917 ; Uncommon_Use # 3.0 [765] CJK UNIFIED IDEOGRAPH-361B..CJK UNIFIED IDEOGRAPH-3917 +3919..395F ; Uncommon_Use # 3.0 [71] CJK UNIFIED IDEOGRAPH-3919..CJK UNIFIED IDEOGRAPH-395F +3961..396D ; Uncommon_Use # 3.0 [13] CJK UNIFIED IDEOGRAPH-3961..CJK UNIFIED IDEOGRAPH-396D +396F..39CE ; Uncommon_Use # 3.0 [96] CJK UNIFIED IDEOGRAPH-396F..CJK UNIFIED IDEOGRAPH-39CE +39D1..39DA ; Uncommon_Use # 3.0 [10] CJK UNIFIED IDEOGRAPH-39D1..CJK UNIFIED IDEOGRAPH-39DA +39DC..39DE ; Uncommon_Use # 3.0 [3] CJK UNIFIED IDEOGRAPH-39DC..CJK UNIFIED IDEOGRAPH-39DE +39E0..39F7 ; Uncommon_Use # 3.0 [24] CJK UNIFIED IDEOGRAPH-39E0..CJK UNIFIED IDEOGRAPH-39F7 +39F9..39FD ; Uncommon_Use # 3.0 [5] CJK UNIFIED IDEOGRAPH-39F9..CJK UNIFIED IDEOGRAPH-39FD +39FF..3A17 ; Uncommon_Use # 3.0 [25] CJK UNIFIED IDEOGRAPH-39FF..CJK UNIFIED IDEOGRAPH-3A17 +3A19..3A51 ; Uncommon_Use # 3.0 [57] CJK UNIFIED IDEOGRAPH-3A19..CJK UNIFIED IDEOGRAPH-3A51 +3A53..3A5B ; Uncommon_Use # 3.0 [9] CJK UNIFIED IDEOGRAPH-3A53..CJK UNIFIED IDEOGRAPH-3A5B +3A5D..3A66 ; Uncommon_Use # 3.0 [10] CJK UNIFIED IDEOGRAPH-3A5D..CJK UNIFIED IDEOGRAPH-3A66 +3A68..3A72 ; Uncommon_Use # 3.0 [11] CJK UNIFIED IDEOGRAPH-3A68..CJK UNIFIED IDEOGRAPH-3A72 +3A74..3B38 ; Uncommon_Use # 3.0 [197] CJK UNIFIED IDEOGRAPH-3A74..CJK UNIFIED IDEOGRAPH-3B38 +3B3A..3B4D ; Uncommon_Use # 3.0 [20] CJK UNIFIED IDEOGRAPH-3B3A..CJK UNIFIED IDEOGRAPH-3B4D +3B4F..3BA2 ; Uncommon_Use # 3.0 [84] CJK UNIFIED IDEOGRAPH-3B4F..CJK UNIFIED IDEOGRAPH-3BA2 +3BA4..3C6D ; Uncommon_Use # 3.0 [202] CJK UNIFIED IDEOGRAPH-3BA4..CJK UNIFIED IDEOGRAPH-3C6D +3C6F..3CDF ; Uncommon_Use # 3.0 [113] CJK UNIFIED IDEOGRAPH-3C6F..CJK UNIFIED IDEOGRAPH-3CDF +3CE1..3DE6 ; Uncommon_Use # 3.0 [262] CJK UNIFIED IDEOGRAPH-3CE1..CJK UNIFIED IDEOGRAPH-3DE6 +3DE8..3DEA ; Uncommon_Use # 3.0 [3] CJK UNIFIED IDEOGRAPH-3DE8..CJK UNIFIED IDEOGRAPH-3DEA +3DEC..3E73 ; Uncommon_Use # 3.0 [136] CJK UNIFIED IDEOGRAPH-3DEC..CJK UNIFIED IDEOGRAPH-3E73 +3E75..3ECF ; Uncommon_Use # 3.0 [91] CJK UNIFIED IDEOGRAPH-3E75..CJK UNIFIED IDEOGRAPH-3ECF +3ED1..4055 ; Uncommon_Use # 3.0 [389] CJK UNIFIED IDEOGRAPH-3ED1..CJK UNIFIED IDEOGRAPH-4055 +4057..4064 ; Uncommon_Use # 3.0 [14] CJK UNIFIED IDEOGRAPH-4057..CJK UNIFIED IDEOGRAPH-4064 +4066..4069 ; Uncommon_Use # 3.0 [4] CJK UNIFIED IDEOGRAPH-4066..CJK UNIFIED IDEOGRAPH-4069 +406B..40BA ; Uncommon_Use # 3.0 [80] CJK UNIFIED IDEOGRAPH-406B..CJK UNIFIED IDEOGRAPH-40BA +40BC..40DE ; Uncommon_Use # 3.0 [35] CJK UNIFIED IDEOGRAPH-40BC..CJK UNIFIED IDEOGRAPH-40DE +40E0..4136 ; Uncommon_Use # 3.0 [87] CJK UNIFIED IDEOGRAPH-40E0..CJK UNIFIED IDEOGRAPH-4136 +4138..415E ; Uncommon_Use # 3.0 [39] CJK UNIFIED IDEOGRAPH-4138..CJK UNIFIED IDEOGRAPH-415E +4160..4336 ; Uncommon_Use # 3.0 [471] CJK UNIFIED IDEOGRAPH-4160..CJK UNIFIED IDEOGRAPH-4336 +4338..43AB ; Uncommon_Use # 3.0 [116] CJK UNIFIED IDEOGRAPH-4338..CJK UNIFIED IDEOGRAPH-43AB +43AD..43B0 ; Uncommon_Use # 3.0 [4] CJK UNIFIED IDEOGRAPH-43AD..CJK UNIFIED IDEOGRAPH-43B0 +43B2..43D2 ; Uncommon_Use # 3.0 [33] CJK UNIFIED IDEOGRAPH-43B2..CJK UNIFIED IDEOGRAPH-43D2 +43D4..43DC ; Uncommon_Use # 3.0 [9] CJK UNIFIED IDEOGRAPH-43D4..CJK UNIFIED IDEOGRAPH-43DC +43DE..4442 ; Uncommon_Use # 3.0 [101] CJK UNIFIED IDEOGRAPH-43DE..CJK UNIFIED IDEOGRAPH-4442 +4444..44D5 ; Uncommon_Use # 3.0 [146] CJK UNIFIED IDEOGRAPH-4444..CJK UNIFIED IDEOGRAPH-44D5 +44D7..44E9 ; Uncommon_Use # 3.0 [19] CJK UNIFIED IDEOGRAPH-44D7..CJK UNIFIED IDEOGRAPH-44E9 +44EB..4605 ; Uncommon_Use # 3.0 [283] CJK UNIFIED IDEOGRAPH-44EB..CJK UNIFIED IDEOGRAPH-4605 +4607..464B ; Uncommon_Use # 3.0 [69] CJK UNIFIED IDEOGRAPH-4607..CJK UNIFIED IDEOGRAPH-464B +464D..4660 ; Uncommon_Use # 3.0 [20] CJK UNIFIED IDEOGRAPH-464D..CJK UNIFIED IDEOGRAPH-4660 +4662..4722 ; Uncommon_Use # 3.0 [193] CJK UNIFIED IDEOGRAPH-4662..CJK UNIFIED IDEOGRAPH-4722 +4724..4728 ; Uncommon_Use # 3.0 [5] CJK UNIFIED IDEOGRAPH-4724..CJK UNIFIED IDEOGRAPH-4728 +472A..477B ; Uncommon_Use # 3.0 [82] CJK UNIFIED IDEOGRAPH-472A..CJK UNIFIED IDEOGRAPH-477B +477D..478C ; Uncommon_Use # 3.0 [16] CJK UNIFIED IDEOGRAPH-477D..CJK UNIFIED IDEOGRAPH-478C +478E..47F3 ; Uncommon_Use # 3.0 [102] CJK UNIFIED IDEOGRAPH-478E..CJK UNIFIED IDEOGRAPH-47F3 +47F5..4881 ; Uncommon_Use # 3.0 [141] CJK UNIFIED IDEOGRAPH-47F5..CJK UNIFIED IDEOGRAPH-4881 +4883..4946 ; Uncommon_Use # 3.0 [196] CJK UNIFIED IDEOGRAPH-4883..CJK UNIFIED IDEOGRAPH-4946 +4948..4979 ; Uncommon_Use # 3.0 [50] CJK UNIFIED IDEOGRAPH-4948..CJK UNIFIED IDEOGRAPH-4979 +497B..497C ; Uncommon_Use # 3.0 [2] CJK UNIFIED IDEOGRAPH-497B..CJK UNIFIED IDEOGRAPH-497C +497E..4981 ; Uncommon_Use # 3.0 [4] CJK UNIFIED IDEOGRAPH-497E..CJK UNIFIED IDEOGRAPH-4981 +4984 ; Uncommon_Use # 3.0 CJK UNIFIED IDEOGRAPH-4984 +4987..499A ; Uncommon_Use # 3.0 [20] CJK UNIFIED IDEOGRAPH-4987..CJK UNIFIED IDEOGRAPH-499A +499C..499E ; Uncommon_Use # 3.0 [3] CJK UNIFIED IDEOGRAPH-499C..CJK UNIFIED IDEOGRAPH-499E +49A0..49B5 ; Uncommon_Use # 3.0 [22] CJK UNIFIED IDEOGRAPH-49A0..CJK UNIFIED IDEOGRAPH-49B5 +49B8..4A11 ; Uncommon_Use # 3.0 [90] CJK UNIFIED IDEOGRAPH-49B8..CJK UNIFIED IDEOGRAPH-4A11 +4A13..4AB7 ; Uncommon_Use # 3.0 [165] CJK UNIFIED IDEOGRAPH-4A13..CJK UNIFIED IDEOGRAPH-4AB7 +4AB9..4C76 ; Uncommon_Use # 3.0 [446] CJK UNIFIED IDEOGRAPH-4AB9..CJK UNIFIED IDEOGRAPH-4C76 +4C78..4C7C ; Uncommon_Use # 3.0 [5] CJK UNIFIED IDEOGRAPH-4C78..CJK UNIFIED IDEOGRAPH-4C7C +4C7E..4C80 ; Uncommon_Use # 3.0 [3] CJK UNIFIED IDEOGRAPH-4C7E..CJK UNIFIED IDEOGRAPH-4C80 +4C82..4C84 ; Uncommon_Use # 3.0 [3] CJK UNIFIED IDEOGRAPH-4C82..CJK UNIFIED IDEOGRAPH-4C84 +4C86..4C9C ; Uncommon_Use # 3.0 [23] CJK UNIFIED IDEOGRAPH-4C86..CJK UNIFIED IDEOGRAPH-4C9C +4CA4..4D12 ; Uncommon_Use # 3.0 [111] CJK UNIFIED IDEOGRAPH-4CA4..CJK UNIFIED IDEOGRAPH-4D12 +4D1A..4DAD ; Uncommon_Use # 3.0 [148] CJK UNIFIED IDEOGRAPH-4D1A..CJK UNIFIED IDEOGRAPH-4DAD +4DAF..4DB5 ; Uncommon_Use # 3.0 [7] CJK UNIFIED IDEOGRAPH-4DAF..CJK UNIFIED IDEOGRAPH-4DB5 +4DB6..4DBF ; Uncommon_Use # 13.0 [10] CJK UNIFIED IDEOGRAPH-4DB6..CJK UNIFIED IDEOGRAPH-4DBF +4E12 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4E12 +4E29 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4E29 +4E68 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4E68 +4E79 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4E79 +4E96 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4E96 +4EA3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4EA3 +4EBC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4EBC +4ECC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4ECC +4EE7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4EE7 +4EF8..4EFA ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-4EF8..CJK UNIFIED IDEOGRAPH-4EFA +4EFC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4EFC +4EFE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4EFE +4F07 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F07 +4F16 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F16 +4F28 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F28 +4F31 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F31 +4F35 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F35 +4F37 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F37 +4F40 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F40 +4F44 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F44 +4F71 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F71 +4F8C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F8C +4F8E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4F8E +4FA2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FA2 +4FBD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FBD +4FC6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FC6 +4FC8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FC8 +4FCC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FCC +4FE2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-4FE2 +4FFC..4FFD ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-4FFC..CJK UNIFIED IDEOGRAPH-4FFD +5010 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5010 +5034 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5034 +5038 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5038 +503D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-503D +5042 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5042 +5052 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5052 +5058 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5058 +507C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-507C +5081 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5081 +5093 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5093 +5097 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5097 +509F..50A1 ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-509F..CJK UNIFIED IDEOGRAPH-50A1 +50B9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50B9 +50C3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50C3 +50D8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50D8 +50DF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50DF +50E1..50E2 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-50E1..CJK UNIFIED IDEOGRAPH-50E2 +50EB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50EB +50F4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50F4 +50F7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-50F7 +511B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-511B +5128 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5128 +512B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-512B +5142 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5142 +514A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-514A +514F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-514F +5153 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5153 +5158 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5158 +5160 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5160 +5164 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5164 +5172 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5172 +517E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-517E +5183..5184 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5183..CJK UNIFIED IDEOGRAPH-5184 +518E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-518E +51A1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51A1 +51A3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51A3 +51AD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51AD +51B8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51B8 +51BA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51BA +51C2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51C2 +51D2..51D3 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-51D2..CJK UNIFIED IDEOGRAPH-51D3 +51DF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51DF +51EC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51EC +51EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51EE +51F2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-51F2 +5253 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5253 +5266 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5266 +5279 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5279 +5285 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5285 +528E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-528E +52C4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52C4 +52C8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52C8 +52CC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52CC +52CE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52CE +52D1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52D1 +52D4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52D4 +52E1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52E1 +52E5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52E5 +52EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-52EE +5303..5304 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5303..CJK UNIFIED IDEOGRAPH-5304 +5318 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5318 +531B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-531B +531E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-531E +5327 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5327 +5329 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5329 +5332 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5332 +5335..5336 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5335..CJK UNIFIED IDEOGRAPH-5336 +5342 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5342 +535B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-535B +535D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-535D +536A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-536A +536D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-536D +5380 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5380 +53A1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53A1 +53AA..53AB ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-53AA..CJK UNIFIED IDEOGRAPH-53AB +53AF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53AF +53BA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53BA +53C5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53C5 +53CF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53CF +53DD..53DE ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-53DD..CJK UNIFIED IDEOGRAPH-53DE +53E7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-53E7 +53FF..5400 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-53FF..CJK UNIFIED IDEOGRAPH-5400 +541A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-541A +5422 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5422 +544C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-544C +545D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-545D +5469 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5469 +548A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-548A +54B5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-54B5 +54F6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-54F6 +5515 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5515 +5518..5519 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5518..CJK UNIFIED IDEOGRAPH-5519 +5547 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5547 +5560 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5560 +557A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-557A +55E0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-55E0 +55F8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-55F8 +560A..560B ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-560A..CJK UNIFIED IDEOGRAPH-560B +5620 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5620 +562B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-562B +5637 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5637 +563C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-563C +5644 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5644 +564B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-564B +5651 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5651 +5656 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5656 +565F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-565F +5661 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5661 +5675 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5675 +567D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-567D +5688 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5688 +568B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-568B +5696 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5696 +569E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-569E +56BA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56BA +56CF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56CF +56D9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56D9 +56E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56E6 +56F6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56F6 +56F8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-56F8 +56FB..56FC ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-56FB..CJK UNIFIED IDEOGRAPH-56FC +5705 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5705 +5711 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5711 +5717 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5717 +5721 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5721 +5724 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5724 +573D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-573D +5743 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5743 +5748 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5748 +5755..5756 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5755..CJK UNIFIED IDEOGRAPH-5756 +5758 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5758 +5763 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5763 +5778 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5778 +5781 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5781 +5787 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5787 +5796 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5796 +57A8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-57A8 +57CA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-57CA +57D1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-57D1 +57DB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-57DB +5817..5818 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5817..CJK UNIFIED IDEOGRAPH-5818 +5850 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5850 +5856 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5856 +5860 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5860 +5866..5867 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5866..CJK UNIFIED IDEOGRAPH-5867 +5877 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5877 +5895 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5895 +58AA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58AA +58B6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58B6 +58C0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58C0 +58C3..58C4 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-58C3..CJK UNIFIED IDEOGRAPH-58C4 +58CD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58CD +58D0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58D0 +58E1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58E1 +58E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58E6 +58F5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-58F5 +5901 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5901 +5905 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5905 +5908 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5908 +5911 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5911 +5913 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5913 +5923 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5923 +5933 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5933 +5936 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5936 +5959 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5959 +595B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-595B +59B7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-59B7 +59E7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-59E7 +5A24 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A24 +5A26 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A26 +5A2C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A2C +5A30 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A30 +5A54 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A54 +5A59 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A59 +5A6F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A6F +5A71 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A71 +5A87 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A87 +5A8D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5A8D +5AAB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5AAB +5AD3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5AD3 +5AEF..5AF0 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5AEF..CJK UNIFIED IDEOGRAPH-5AF0 +5B0A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B0A +5B0D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B0D +5B39 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B39 +5B46 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B46 +5B4F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B4F +5B52 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B52 +5B60..5B61 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5B60..CJK UNIFIED IDEOGRAPH-5B61 +5B6F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B6F +5B79 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B79 +5B7E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B7E +5B86 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B86 +5B90 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5B90 +5BA9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BA9 +5BB2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BB2 +5BB7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BB7 +5BBC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BBC +5BC8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BC8 +5BDA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5BDA +5C00 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C00 +5C1B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C1B +5C23 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C23 +5C26 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C26 +5C29 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C29 +5C36 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C36 +5C5A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C5A +5C85 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5C85 +5CB4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5CB4 +5CB9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5CB9 +5CD5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5CD5 +5CDD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5CDD +5CF5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5CF5 +5D2B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D2B +5D2F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D2F +5D3B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D3B +5D53 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D53 +5D57 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D57 +5D60 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D60 +5D83 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D83 +5D96 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5D96 +5DA3..5DA4 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5DA3..CJK UNIFIED IDEOGRAPH-5DA4 +5DAB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DAB +5DB3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DB3 +5DB9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DB9 +5DC4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DC4 +5DD7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DD7 +5DDA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DDA +5DDC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DDC +5DF6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5DF6 +5E12 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5E12 +5E48 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5E48 +5E51 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5E51 +5E92 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5E92 +5EBA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5EBA +5EC0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5EC0 +5EEB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5EEB +5EF9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5EF9 +5F0E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5F0E +5F3B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5F3B +5F3D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5F3D +5F8F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5F8F +5F9A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5F9A +5FA3..5FA4 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-5FA3..CJK UNIFIED IDEOGRAPH-5FA4 +5FB0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FB0 +5FC2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FC2 +5FCE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FCE +5FDB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FDB +5FE2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FE2 +5FEC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FEC +5FFC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-5FFC +6023 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6023 +6056 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6056 +6061 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6061 +6071 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6071 +6074 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6074 +6091 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6091 +6093 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6093 +60A5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60A5 +60D2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60D2 +60D6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60D6 +60DE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60DE +60E5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60E5 +60FD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-60FD +6102 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6102 +6107 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6107 +6111 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6111 +611E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-611E +6131 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6131 +6133 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6133 +6135 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6135 +6138..6139 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6138..CJK UNIFIED IDEOGRAPH-6139 +6160 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6160 +617B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-617B +617F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-617F +6186 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6186 +6197 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6197 +619C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-619C +61B9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61B9 +61BB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61BB +61D3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61D3 +61D5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61D5 +61EC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61EC +61EF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-61EF +6205 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6205 +6235 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6235 +6239 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6239 +6257 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6257 +628D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-628D +629D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-629D +62DE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-62DE +62EA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-62EA +630A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-630A +6317 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6317 +6331 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6331 +6337 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6337 +635B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-635B +638B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-638B +6393 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6393 +63D1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-63D1 +643B..643C ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-643B..CJK UNIFIED IDEOGRAPH-643C +6449 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6449 +645A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-645A +647E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-647E +6486 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6486 +64A1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64A1 +64AF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64AF +64B6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64B6 +64C8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64C8 +64D5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64D5 +64EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64EE +64F5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64F5 +64F9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-64F9 +6502 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6502 +650A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-650A +651F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-651F +6528 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6528 +6540 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6540 +6542 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6542 +655A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-655A +655F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-655F +657D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-657D +658A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-658A +659A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-659A +65B5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65B5 +65BE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65BE +65C8..65C9 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-65C8..CJK UNIFIED IDEOGRAPH-65C9 +65D1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65D1 +65D8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65D8 +65DC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65DC +65E4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65E4 +65EA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65EA +65F9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65F9 +65FE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-65FE +6617 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6617 +662C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-662C +6637..6638 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6637..CJK UNIFIED IDEOGRAPH-6638 +6648 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6648 +664D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-664D +6660 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6660 +6663 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6663 +6692 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6692 +669C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-669C +669E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-669E +66AC..66AD ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-66AC..CJK UNIFIED IDEOGRAPH-66AD +66D0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-66D0 +66D3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-66D3 +66D7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-66D7 +66DF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-66DF +66EF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-66EF +6702 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6702 +6707 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6707 +6719 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6719 +6724 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6724 +6729 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6729 +6767 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6767 +6788 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6788 +6796 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6796 +67BD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-67BD +67BF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-67BF +67D5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-67D5 +67D7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-67D7 +67F9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-67F9 +6801 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6801 +6815 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6815 +6827 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6827 +6830 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6830 +6858 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6858 +685A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-685A +685E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-685E +687A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-687A +6895 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6895 +6899 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6899 +68A5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68A5 +68B8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68B8 +68C3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68C3 +68D9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68D9 +68E2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68E2 +68E5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-68E5 +6909 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6909 +693E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-693E +694D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-694D +699F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-699F +69A2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-69A2 +69C0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-69C0 +69D1..69D2 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-69D1..CJK UNIFIED IDEOGRAPH-69D2 +69D5..69D7 ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-69D5..CJK UNIFIED IDEOGRAPH-69D7 +6A03 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A03 +6A1C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A1C +6A24 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A24 +6A37 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A37 +6A4A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A4A +6A5C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A5C +6A6E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A6E +6A70 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A70 +6A86 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A86 +6A8A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A8A +6A8F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A8F +6A99 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A99 +6A9D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6A9D +6AB1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AB1 +6ABE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6ABE +6AC0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AC0 +6AC4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AC4 +6AC9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AC9 +6AD8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AD8 +6AE9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6AE9 +6B0E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B0E +6B1B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B1B +6B2E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B2E +6B35 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B35 +6B40 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B40 +6B57..6B58 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6B57..CJK UNIFIED IDEOGRAPH-6B58 +6B5D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B5D +6B68 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B68 +6B6C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B6C +6B6E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B6E +6B71 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B71 +6B75 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B75 +6B7D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6B7D +6BB8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6BB8 +6BE9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6BE9 +6BF1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6BF1 +6BF4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6BF4 +6BFA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6BFA +6C0A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C0A +6C1C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C1C +6C2D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C2D +6C3C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C3C +6C45 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C45 +6C6C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C6C +6C6E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6C6E +6CA0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6CA0 +6CD8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6CD8 +6CF4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6CF4 +6D02 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D02 +6D1C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D1C +6D24 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D24 +6D71 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D71 +6D81 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D81 +6D96 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6D96 +6DB0..6DB1 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6DB0..CJK UNIFIED IDEOGRAPH-6DB1 +6DB6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6DB6 +6DFE..6DFF ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6DFE..CJK UNIFIED IDEOGRAPH-6DFF +6E01..6E02 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-6E01..CJK UNIFIED IDEOGRAPH-6E02 +6E06 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E06 +6E12 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E12 +6E18 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E18 +6E2A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E2A +6E4C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E4C +6E6C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E6C +6E7B..6E7D ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-6E7B..CJK UNIFIED IDEOGRAPH-6E7D +6E8B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E8B +6E95 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6E95 +6EDB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6EDB +6EE3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6EE3 +6F04 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F04 +6F0B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F0B +6F42 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F42 +6F48 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F48 +6F4A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F4A +6F79 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F79 +6F98 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F98 +6F9A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F9A +6F9F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6F9F +6FB7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FB7 +6FC5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FC5 +6FD0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FD0 +6FD3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FD3 +6FF5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FF5 +6FFD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-6FFD +7010 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7010 +7013 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7013 +7047 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7047 +704B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-704B +704E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-704E +7072..7073 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7072..CJK UNIFIED IDEOGRAPH-7073 +707B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-707B +7081 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7081 +708D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-708D +7097 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7097 +709B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-709B +70AA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-70AA +70B2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-70B2 +70B6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-70B6 +70D5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-70D5 +70FE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-70FE +7108 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7108 +7124 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7124 +7133..7134 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7133..CJK UNIFIED IDEOGRAPH-7134 +7157 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7157 +716B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-716B +716D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-716D +718D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-718D +7196 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7196 +71A6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71A6 +71AB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71AB +71B6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71B6 +71CC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71CC +71D3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71D3 +71F3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71F3 +71FA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-71FA +720B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-720B +7211 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7211 +7215 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7215 +7217 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7217 +7220 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7220 +7224..7225 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7224..CJK UNIFIED IDEOGRAPH-7225 +722F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-722F +7234 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7234 +7245 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7245 +724E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-724E +7250 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7250 +7255 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7255 +72AB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-72AB +72BE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-72BE +7302 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7302 +7310 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7310 +7328 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7328 +7353 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7353 +739C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-739C +73C1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-73C1 +73F3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-73F3 +73FB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-73FB +7418 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7418 +7439 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7439 +743E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-743E +7447 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7447 +7449 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7449 +7458 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7458 +747B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-747B +7484 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7484 +7496 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7496 +749D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-749D +74C7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-74C7 +74C9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-74C9 +74CC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-74CC +74EB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-74EB +7520 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7520 +7541 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7541 +7552 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7552 +7555 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7555 +755E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-755E +7561 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7561 +7571 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7571 +757B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-757B +7585 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7585 +75A9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-75A9 +75B7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-75B7 +75DC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-75DC +75EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-75EE +762C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-762C +7644..7645 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7644..CJK UNIFIED IDEOGRAPH-7645 +7651 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7651 +7655 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7655 +7673 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7673 +768D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-768D +76A1..76A2 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-76A1..CJK UNIFIED IDEOGRAPH-76A2 +76A5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76A5 +76A8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76A8 +76B3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76B3 +76B6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76B6 +76C1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76C1 +76CB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76CB +76D9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76D9 +76EB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-76EB +7700 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7700 +7702 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7702 +770E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-770E +7721 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7721 +772B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-772B +773F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-773F +7742 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7742 +7764 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7764 +7796 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7796 +77A4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77A4 +77BE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77BE +77C1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77C1 +77D2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77D2 +77DD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77DD +77E4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77E4 +77E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-77E6 +77F4..77F5 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-77F4..CJK UNIFIED IDEOGRAPH-77F5 +7824 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7824 +7836 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7836 +7842 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7842 +7846 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7846 +784B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-784B +7876 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7876 +7888 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7888 +78C2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-78C2 +78C7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-78C7 +78D2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-78D2 +78F0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-78F0 +78F8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-78F8 +7900 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7900 +7908 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7908 +790D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-790D +7915 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7915 +791F..7920 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-791F..CJK UNIFIED IDEOGRAPH-7920 +7932 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7932 +7936 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7936 +7959 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7959 +796C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-796C +796E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-796E +7975..7976 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7975..CJK UNIFIED IDEOGRAPH-7976 +7986..7987 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7986..CJK UNIFIED IDEOGRAPH-7987 +799E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-799E +79A9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79A9 +79BC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79BC +79C4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79C4 +79C7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79C7 +79CC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79CC +79D4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79D4 +79D7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-79D7 +7A01 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A01 +7A07 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A07 +7A09 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A09 +7A2C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A2C +7A38 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A38 +7A3A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A3A +7A64 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A64 +7A6A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A6A +7A6F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A6F +7A82 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7A82 +7A9A..7A9B ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7A9A..CJK UNIFIED IDEOGRAPH-7A9B +7AB9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AB9 +7ABB..7ABD ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-7ABB..CJK UNIFIED IDEOGRAPH-7ABD +7AC2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AC2 +7AC6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AC6 +7AE9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AE9 +7AF5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AF5 +7AFC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7AFC +7B07 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B07 +7B1F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B1F +7B27 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B27 +7B29 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B29 +7B42 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B42 +7B53 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7B53 +7BA3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7BA3 +7BA5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7BA5 +7BB0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7BB0 +7BB2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7BB2 +7BFA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7BFA +7C1B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C1B +7C2E..7C2F ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7C2E..CJK UNIFIED IDEOGRAPH-7C2F +7C52 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C52 +7C55 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C55 +7C5D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C5D +7C76 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C76 +7C87 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C87 +7C93 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C93 +7C9A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7C9A +7CAC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CAC +7CD3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CD3 +7CDA..7CDB ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7CDA..CJK UNIFIED IDEOGRAPH-7CDB +7CE1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CE1 +7CE3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CE3 +7CE5..7CE6 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7CE5..CJK UNIFIED IDEOGRAPH-7CE6 +7CFC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CFC +7CFF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7CFF +7D23 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D23 +7D2A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D2A +7D2D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D2D +7D48 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D48 +7D4D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D4D +7D5A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D5A +7D64 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D64 +7D78 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D78 +7D82 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D82 +7D95 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D95 +7D98 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7D98 +7DA4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DA4 +7DA8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DA8 +7DCD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DCD +7DD3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DD3 +7DE5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DE5 +7DEB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7DEB +7DFD..7DFF ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-7DFD..CJK UNIFIED IDEOGRAPH-7DFF +7E18 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7E18 +7E5B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7E5B +7E64 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7E64 +7E9D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7E9D +7F3B..7F3C ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-7F3B..CJK UNIFIED IDEOGRAPH-7F3C +7F41 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F41 +7F46 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F46 +7F59 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F59 +7F84 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F84 +7F90 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F90 +7F97 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F97 +7F99 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7F99 +7FB4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7FB4 +7FD6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7FD6 +7FDD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7FDD +7FE4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-7FE4 +800A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-800A +802F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-802F +803C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-803C +8040 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8040 +8066 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8066 +8088 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8088 +808E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-808E +8094 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8094 +80A6..80A8 ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-80A6..CJK UNIFIED IDEOGRAPH-80A8 +80B3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-80B3 +80B9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-80B9 +80DF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-80DF +8103..8104 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8103..CJK UNIFIED IDEOGRAPH-8104 +8134..8135 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8134..CJK UNIFIED IDEOGRAPH-8135 +8184 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8184 +8190 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8190 +8196 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8196 +81CB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-81CB +81E4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-81E4 +81EF..81F0 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-81EF..CJK UNIFIED IDEOGRAPH-81F0 +8213 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8213 +8224 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8224 +8241 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8241 +8265 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8265 +828C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-828C +82B2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-82B2 +82E2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-82E2 +82FC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-82FC +830A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-830A +8310 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8310 +8330 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8330 +8355 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8355 +83BE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-83BE +83E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-83E6 +83ED ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-83ED +8414 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8414 +8416..8417 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8416..CJK UNIFIED IDEOGRAPH-8417 +841F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-841F +8458 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8458 +8483 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8483 +8495 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8495 +84B7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-84B7 +84C3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-84C3 +84ED ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-84ED +8505 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8505 +8510 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8510 +8532..8533 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8532..CJK UNIFIED IDEOGRAPH-8533 +854C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-854C +8550 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8550 +857F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-857F +8593 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8593 +85B2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85B2 +85BB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85BB +85CC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85CC +85EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85EE +85F3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85F3 +85FC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-85FC +8603 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8603 +860D..860E ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-860D..CJK UNIFIED IDEOGRAPH-860E +8610 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8610 +8615 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8615 +861D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-861D +8637 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8637 +8657 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8657 +8675 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8675 +8689 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8689 +8692 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8692 +86A0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86A0 +86A6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86A6 +86D5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86D5 +86E0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86E0 +86E7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86E7 +86FD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-86FD +871D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-871D +872F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-872F +873D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-873D +8745 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8745 +8771 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8771 +878E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-878E +8799 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8799 +87DA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-87DA +87F0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-87F0 +8807 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8807 +8812 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8812 +882D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-882D +883A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-883A +8847 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8847 +8858 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8858 +885C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-885C +885F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-885F +887A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-887A +88E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-88E6 +88E9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-88E9 +88ED ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-88ED +8903 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8903 +890F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-890F +8924 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8924 +8965 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8965 +8975 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8975 +897D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-897D +898D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-898D +8990 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8990 +8994 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8994 +8999 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8999 +89B0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89B0 +89B4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89B4 +89BB..89BC ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-89BB..CJK UNIFIED IDEOGRAPH-89BC +89EE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89EE +89F5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89F5 +89F9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89F9 +89FD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-89FD +8A05..8A06 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8A05..CJK UNIFIED IDEOGRAPH-8A06 +8A14 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A14 +8A19 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A19 +8A20..8A21 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8A20..CJK UNIFIED IDEOGRAPH-8A21 +8A2B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A2B +8A3D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A3D +8A4B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A4B +8A64 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A64 +8A78 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A78 +8A7D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A7D +8A88 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A88 +8A9F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8A9F +8AAF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8AAF +8AB7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8AB7 +8AD0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8AD0 +8AEC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8AEC +8B29 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B29 +8B32 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B32 +8B38 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B38 +8B3F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B3F +8B61..8B62 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8B61..CJK UNIFIED IDEOGRAPH-8B62 +8B69 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B69 +8B75 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B75 +8B7C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B7C +8B81 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B81 +8B87 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B87 +8B8D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B8D +8B8F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B8F +8B9B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8B9B +8C38 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C38 +8C40 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C40 +8C44 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C44 +8C51..8C53 ; Uncommon_Use # 1.1 [3] CJK UNIFIED IDEOGRAPH-8C51..CJK UNIFIED IDEOGRAPH-8C53 +8C58 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C58 +8C74 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C74 +8C7F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C7F +8C83 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C83 +8C87 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C87 +8C8B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C8B +8C9B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8C9B +8CA6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CA6 +8CCB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CCB +8CD6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CD6 +8CD8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CD8 +8CE9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CE9 +8CF7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8CF7 +8D01 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8D01 +8D11..8D12 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8D11..CJK UNIFIED IDEOGRAPH-8D12 +8D7C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8D7C +8DA6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8DA6 +8DC0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8DC0 +8DE5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8DE5 +8E01 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E01 +8E0B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E0B +8E32 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E32 +8E46 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E46 +8E4F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E4F +8E6E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E6E +8E75 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E75 +8E77 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E77 +8E79 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E79 +8E9B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8E9B +8EA2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8EA2 +8EB3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8EB3 +8EB6..8EB7 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-8EB6..CJK UNIFIED IDEOGRAPH-8EB7 +8EC1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8EC1 +8EC4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8EC4 +8ED9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8ED9 +8EF0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8EF0 +8F0F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8F0F +8F2D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8F2D +8F3A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8F3A +8F41 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8F41 +8F9D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8F9D +8FA4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8FA4 +8FB3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8FB3 +8FC3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8FC3 +8FCA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8FCA +8FE7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-8FE7 +902A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-902A +902C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-902C +9037 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9037 +9040 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9040 +9046 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9046 +90AB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-90AB +90CC..90CD ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-90CC..CJK UNIFIED IDEOGRAPH-90CD +90D2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-90D2 +90F6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-90F6 +910A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-910A +913C..913D ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-913C..CJK UNIFIED IDEOGRAPH-913D +9159 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9159 +917B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-917B +9195 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9195 +9198 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9198 +91A9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-91A9 +91BF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-91BF +91C4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-91C4 +91E0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-91E0 +91EF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-91EF +9213 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9213 +921F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-921F +9222 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9222 +9243 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9243 +9269..926A ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9269..CJK UNIFIED IDEOGRAPH-926A +9281 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9281 +9284 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9284 +929E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-929E +92BD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-92BD +92D4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-92D4 +92DB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-92DB +92E2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-92E2 +931C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-931C +9330..9331 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9330..CJK UNIFIED IDEOGRAPH-9331 +9362 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9362 +9368 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9368 +936B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-936B +936F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-936F +9373 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9373 +9378 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9378 +937F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-937F +9381 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9381 +938B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-938B +939C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-939C +93A0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-93A0 +93AB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-93AB +93BB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-93BB +93E0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-93E0 +93F3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-93F3 +9402 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9402 +9417 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9417 +941C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-941C +941E..941F ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-941E..CJK UNIFIED IDEOGRAPH-941F +9424 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9424 +9443 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9443 +944E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-944E +946C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-946C +947B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-947B +9578..9579 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9578..CJK UNIFIED IDEOGRAPH-9579 +957E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-957E +9585 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9585 +9597 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9597 +95B3..95B4 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-95B3..CJK UNIFIED IDEOGRAPH-95B4 +95B8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-95B8 +95C1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-95C1 +95D9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-95D9 +95DD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-95DD +9625..9626 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9625..CJK UNIFIED IDEOGRAPH-9626 +9629 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9629 +963E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-963E +9656..9657 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9656..CJK UNIFIED IDEOGRAPH-9657 +9679 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9679 +967B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-967B +967F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-967F +9681..9682 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9681..CJK UNIFIED IDEOGRAPH-9682 +968C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-968C +9696 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9696 +969A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-969A +969D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-969D +969F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-969F +96AB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-96AB +96AF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-96AF +96B5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-96B5 +96E4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-96E4 +96E6..96E7 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-96E6..CJK UNIFIED IDEOGRAPH-96E7 +96FC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-96FC +9714 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9714 +9717 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9717 +971A..971B ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-971A..CJK UNIFIED IDEOGRAPH-971B +9733..9734 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9733..CJK UNIFIED IDEOGRAPH-9734 +9737 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9737 +9740..9741 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9740..CJK UNIFIED IDEOGRAPH-9741 +974D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-974D +9757 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9757 +9763 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9763 +9775 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9775 +9787 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9787 +9789 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9789 +979B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-979B +97A9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97A9 +97B0..97B1 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-97B0..CJK UNIFIED IDEOGRAPH-97B1 +97B5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97B5 +97BE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97BE +97C0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97C0 +97D2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97D2 +97FC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-97FC +981F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-981F +9825 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9825 +982A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-982A +9833 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9833 +983A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-983A +983E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-983E +9842 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9842 +9847 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9847 +9856 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9856 +9866 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9866 +9868 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9868 +98B7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98B7 +98CA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98CA +98E4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98E4 +98EC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98EC +98F1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98F1 +98F8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98F8 +98FB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-98FB +9919 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9919 +993B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-993B +9944 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9944 +995A ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-995A +995D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-995D +99BF ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-99BF +99E0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-99E0 +99E6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-99E6 +99EB ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-99EB +99F5 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-99F5 +9A10 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A10 +9A17..9A18 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9A17..CJK UNIFIED IDEOGRAPH-9A18 +9A3B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A3B +9A51 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A51 +9A58 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A58 +9A5D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A5D +9A63 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9A63 +9AA9 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AA9 +9ABD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9ABD +9AC8 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AC8 +9AD7 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AD7 +9AE0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AE0 +9AE4 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AE4 +9AE8..9AE9 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9AE8..CJK UNIFIED IDEOGRAPH-9AE9 +9AF0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9AF0 +9B00 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B00 +9B02 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B02 +9B09 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B09 +9B14 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B14 +9B1B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B1B +9B34 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B34 +9B3D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B3D +9B40 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B40 +9B50 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B50 +9B57 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B57 +9B62 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B62 +9B72 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B72 +9B89 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B89 +9B8C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B8C +9B99 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9B99 +9BC2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9BC2 +9BF6 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9BF6 +9C00..9C01 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9C00..CJK UNIFIED IDEOGRAPH-9C01 +9C03 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C03 +9C42 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C42 +9C4F ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C4F +9C51 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C51 +9C61 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C61 +9C64 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C64 +9C7B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9C7B +9D0C..9D0D ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9D0C..CJK UNIFIED IDEOGRAPH-9D0D +9D11 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D11 +9D27 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D27 +9D35 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D35 +9D3C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D3C +9D6D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D6D +9D95 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9D95 +9DAE ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9DAE +9DBD ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9DBD +9DC0 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9DC0 +9DEA ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9DEA +9DFC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9DFC +9E0E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E0E +9E16 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E16 +9E1C ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E1C +9E7B ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E7B +9E8F..9E90 ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9E8F..CJK UNIFIED IDEOGRAPH-9E90 +9E98 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E98 +9E9E ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9E9E +9EA2 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9EA2 +9EAB..9EAC ; Uncommon_Use # 1.1 [2] CJK UNIFIED IDEOGRAPH-9EAB..CJK UNIFIED IDEOGRAPH-9EAC +9EB1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9EB1 +9EEC ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9EEC +9EF1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9EF1 +9F03 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F03 +9F11 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F11 +9F14 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F14 +9F26 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F26 +9F45 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F45 +9F53 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F53 +9F6D ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9F6D +9FA1 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9FA1 +9FA3 ; Uncommon_Use # 1.1 CJK UNIFIED IDEOGRAPH-9FA3 +9FA6..9FBB ; Uncommon_Use # 4.1 [22] CJK UNIFIED IDEOGRAPH-9FA6..CJK UNIFIED IDEOGRAPH-9FBB +9FBC..9FC3 ; Uncommon_Use # 5.1 [8] CJK UNIFIED IDEOGRAPH-9FBC..CJK UNIFIED IDEOGRAPH-9FC3 +9FC4..9FCB ; Uncommon_Use # 5.2 [8] CJK UNIFIED IDEOGRAPH-9FC4..CJK UNIFIED IDEOGRAPH-9FCB +9FCC ; Uncommon_Use # 6.1 CJK UNIFIED IDEOGRAPH-9FCC +9FCD..9FD5 ; Uncommon_Use # 8.0 [9] CJK UNIFIED IDEOGRAPH-9FCD..CJK UNIFIED IDEOGRAPH-9FD5 +9FD6..9FEA ; Uncommon_Use # 10.0 [21] CJK UNIFIED IDEOGRAPH-9FD6..CJK UNIFIED IDEOGRAPH-9FEA +9FEB..9FEF ; Uncommon_Use # 11.0 [5] CJK UNIFIED IDEOGRAPH-9FEB..CJK UNIFIED IDEOGRAPH-9FEF +9FF0..9FFC ; Uncommon_Use # 13.0 [13] CJK UNIFIED IDEOGRAPH-9FF0..CJK UNIFIED IDEOGRAPH-9FFC +9FFD..9FFF ; Uncommon_Use # 14.0 [3] CJK UNIFIED IDEOGRAPH-9FFD..CJK UNIFIED IDEOGRAPH-9FFF A66F ; Uncommon_Use # 5.1 COMBINING CYRILLIC VZMET A67C..A67D ; Uncommon_Use # 5.1 [2] COMBINING CYRILLIC KAVYKA..COMBINING CYRILLIC PAYEROK A78B..A78C ; Uncommon_Use # 5.1 [2] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER SALTILLO A78F ; Uncommon_Use # 8.0 LATIN LETTER SINOLOGICAL DOT +A792..A793 ; Uncommon_Use # 6.1 [2] LATIN CAPITAL LETTER C WITH BAR..LATIN SMALL LETTER C WITH BAR A7B2..A7B7 ; Uncommon_Use # 8.0 [6] LATIN CAPITAL LETTER J WITH CROSSED-TAIL..LATIN SMALL LETTER OMEGA A7B8..A7B9 ; Uncommon_Use # 11.0 [2] LATIN CAPITAL LETTER U WITH STROKE..LATIN SMALL LETTER U WITH STROKE +A7C2..A7C3 ; Uncommon_Use # 12.0 [2] LATIN CAPITAL LETTER ANGLICANA W..LATIN SMALL LETTER ANGLICANA W A7CB..A7CD ; Uncommon_Use # 16.0 [3] LATIN CAPITAL LETTER RAMS HORN..LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CE..A7CF ; Uncommon_Use # 17.0 [2] LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7DA..A7DC ; Uncommon_Use # 16.0 [3] LATIN CAPITAL LETTER LAMBDA..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A9E7..A9FE ; Uncommon_Use # 7.0 [24] MYANMAR LETTER TAI LAING NYA..MYANMAR LETTER TAI LAING BHA +AA60..AA76 ; Uncommon_Use # 5.2 [23] MYANMAR LETTER KHAMTI GA..MYANMAR LOGOGRAM KHAMTI HM +AA7A ; Uncommon_Use # 5.2 MYANMAR LETTER AITON RA +AA7C..AA7F ; Uncommon_Use # 7.0 [4] MYANMAR SIGN TAI LAING TONE-2..MYANMAR LETTER SHWE PALAUNG SHA +AB01..AB06 ; Uncommon_Use # 6.0 [6] ETHIOPIC SYLLABLE TTHU..ETHIOPIC SYLLABLE TTHO +AB09..AB0E ; Uncommon_Use # 6.0 [6] ETHIOPIC SYLLABLE DDHU..ETHIOPIC SYLLABLE DDHO +AB11..AB16 ; Uncommon_Use # 6.0 [6] ETHIOPIC SYLLABLE DZU..ETHIOPIC SYLLABLE DZO +AB20..AB26 ; Uncommon_Use # 6.0 [7] ETHIOPIC SYLLABLE CCHHA..ETHIOPIC SYLLABLE CCHHO +AB28..AB2E ; Uncommon_Use # 6.0 [7] ETHIOPIC SYLLABLE BBA..ETHIOPIC SYLLABLE BBO AB60..AB63 ; Uncommon_Use # 8.0 [4] LATIN SMALL LETTER SAKHA YAT..LATIN SMALL LETTER UO +AB66..AB67 ; Uncommon_Use # 12.0 [2] LATIN SMALL LETTER DZ DIGRAPH WITH RETROFLEX HOOK..LATIN SMALL LETTER TS DIGRAPH WITH RETROFLEX HOOK +FA0E..FA0F ; Uncommon_Use # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA0E..CJK COMPATIBILITY IDEOGRAPH-FA0F +FA11 ; Uncommon_Use # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA11 +FA13..FA14 ; Uncommon_Use # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA13..CJK COMPATIBILITY IDEOGRAPH-FA14 +FA1F ; Uncommon_Use # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA1F +FA21 ; Uncommon_Use # 1.1 CJK COMPATIBILITY IDEOGRAPH-FA21 +FA23..FA24 ; Uncommon_Use # 1.1 [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24 +FA27..FA29 ; Uncommon_Use # 1.1 [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29 10780 ; Uncommon_Use # 14.0 MODIFIER LETTER SMALL CAPITAL AA 10EC2..10EC4 ; Uncommon_Use # 16.0 [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC7 ; Uncommon_Use # 17.0 ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA ; Uncommon_Use # 17.0 ARABIC DOUBLE VERTICAL BAR BELOW 10EFC ; Uncommon_Use # 16.0 ARABIC COMBINING ALEF OVERLAY 10EFD..10EFF ; Uncommon_Use # 15.0 [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA +1133B ; Uncommon_Use # 11.0 COMBINING BINDU BELOW 116D0..116E3 ; Uncommon_Use # 16.0 [20] MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE 1AFF0..1AFF3 ; Uncommon_Use # 14.0 [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; Uncommon_Use # 14.0 [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; Uncommon_Use # 14.0 [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 +20000..2070D ; Uncommon_Use # 3.1 [1806] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2070D +2070F..20730 ; Uncommon_Use # 3.1 [34] CJK UNIFIED IDEOGRAPH-2070F..CJK UNIFIED IDEOGRAPH-20730 +20732..20778 ; Uncommon_Use # 3.1 [71] CJK UNIFIED IDEOGRAPH-20732..CJK UNIFIED IDEOGRAPH-20778 +2077A..20C52 ; Uncommon_Use # 3.1 [1241] CJK UNIFIED IDEOGRAPH-2077A..CJK UNIFIED IDEOGRAPH-20C52 +20C54..20C77 ; Uncommon_Use # 3.1 [36] CJK UNIFIED IDEOGRAPH-20C54..CJK UNIFIED IDEOGRAPH-20C77 +20C79..20C95 ; Uncommon_Use # 3.1 [29] CJK UNIFIED IDEOGRAPH-20C79..CJK UNIFIED IDEOGRAPH-20C95 +20C97..20CCE ; Uncommon_Use # 3.1 [56] CJK UNIFIED IDEOGRAPH-20C97..CJK UNIFIED IDEOGRAPH-20CCE +20CD0..20CD4 ; Uncommon_Use # 3.1 [5] CJK UNIFIED IDEOGRAPH-20CD0..CJK UNIFIED IDEOGRAPH-20CD4 +20CD6..20D14 ; Uncommon_Use # 3.1 [63] CJK UNIFIED IDEOGRAPH-20CD6..CJK UNIFIED IDEOGRAPH-20D14 +20D16..20D7B ; Uncommon_Use # 3.1 [102] CJK UNIFIED IDEOGRAPH-20D16..CJK UNIFIED IDEOGRAPH-20D7B +20D7D..20D7E ; Uncommon_Use # 3.1 [2] CJK UNIFIED IDEOGRAPH-20D7D..CJK UNIFIED IDEOGRAPH-20D7E +20D80..20E0D ; Uncommon_Use # 3.1 [142] CJK UNIFIED IDEOGRAPH-20D80..CJK UNIFIED IDEOGRAPH-20E0D +20E10..20E76 ; Uncommon_Use # 3.1 [103] CJK UNIFIED IDEOGRAPH-20E10..CJK UNIFIED IDEOGRAPH-20E76 +20E78..20E9C ; Uncommon_Use # 3.1 [37] CJK UNIFIED IDEOGRAPH-20E78..CJK UNIFIED IDEOGRAPH-20E9C +20E9E..20EA1 ; Uncommon_Use # 3.1 [4] CJK UNIFIED IDEOGRAPH-20E9E..CJK UNIFIED IDEOGRAPH-20EA1 +20EA3..20ED6 ; Uncommon_Use # 3.1 [52] CJK UNIFIED IDEOGRAPH-20EA3..CJK UNIFIED IDEOGRAPH-20ED6 +20ED8..20EF8 ; Uncommon_Use # 3.1 [33] CJK UNIFIED IDEOGRAPH-20ED8..CJK UNIFIED IDEOGRAPH-20EF8 +20EFB..20F2C ; Uncommon_Use # 3.1 [50] CJK UNIFIED IDEOGRAPH-20EFB..CJK UNIFIED IDEOGRAPH-20F2C +20F2F..20F4B ; Uncommon_Use # 3.1 [29] CJK UNIFIED IDEOGRAPH-20F2F..CJK UNIFIED IDEOGRAPH-20F4B +20F4D..20FB3 ; Uncommon_Use # 3.1 [103] CJK UNIFIED IDEOGRAPH-20F4D..CJK UNIFIED IDEOGRAPH-20FB3 +20FB5..20FBB ; Uncommon_Use # 3.1 [7] CJK UNIFIED IDEOGRAPH-20FB5..CJK UNIFIED IDEOGRAPH-20FBB +20FBD..20FE9 ; Uncommon_Use # 3.1 [45] CJK UNIFIED IDEOGRAPH-20FBD..CJK UNIFIED IDEOGRAPH-20FE9 +20FEB..2105B ; Uncommon_Use # 3.1 [113] CJK UNIFIED IDEOGRAPH-20FEB..CJK UNIFIED IDEOGRAPH-2105B +2105D..2106E ; Uncommon_Use # 3.1 [18] CJK UNIFIED IDEOGRAPH-2105D..CJK UNIFIED IDEOGRAPH-2106E +21070..21074 ; Uncommon_Use # 3.1 [5] CJK UNIFIED IDEOGRAPH-21070..CJK UNIFIED IDEOGRAPH-21074 +21077..2107A ; Uncommon_Use # 3.1 [4] CJK UNIFIED IDEOGRAPH-21077..CJK UNIFIED IDEOGRAPH-2107A +2107C..210C0 ; Uncommon_Use # 3.1 [69] CJK UNIFIED IDEOGRAPH-2107C..CJK UNIFIED IDEOGRAPH-210C0 +210C2..210C8 ; Uncommon_Use # 3.1 [7] CJK UNIFIED IDEOGRAPH-210C2..CJK UNIFIED IDEOGRAPH-210C8 +210CA..211D8 ; Uncommon_Use # 3.1 [271] CJK UNIFIED IDEOGRAPH-210CA..CJK UNIFIED IDEOGRAPH-211D8 +211DA..220C6 ; Uncommon_Use # 3.1 [3821] CJK UNIFIED IDEOGRAPH-211DA..CJK UNIFIED IDEOGRAPH-220C6 +220C8..227B4 ; Uncommon_Use # 3.1 [1773] CJK UNIFIED IDEOGRAPH-220C8..CJK UNIFIED IDEOGRAPH-227B4 +227B6..22AD4 ; Uncommon_Use # 3.1 [799] CJK UNIFIED IDEOGRAPH-227B6..CJK UNIFIED IDEOGRAPH-22AD4 +22AD6..22B42 ; Uncommon_Use # 3.1 [109] CJK UNIFIED IDEOGRAPH-22AD6..CJK UNIFIED IDEOGRAPH-22B42 +22B44..22BC9 ; Uncommon_Use # 3.1 [134] CJK UNIFIED IDEOGRAPH-22B44..CJK UNIFIED IDEOGRAPH-22BC9 +22BCB..22C50 ; Uncommon_Use # 3.1 [134] CJK UNIFIED IDEOGRAPH-22BCB..CJK UNIFIED IDEOGRAPH-22C50 +22C52..22C54 ; Uncommon_Use # 3.1 [3] CJK UNIFIED IDEOGRAPH-22C52..CJK UNIFIED IDEOGRAPH-22C54 +22C56..22CC1 ; Uncommon_Use # 3.1 [108] CJK UNIFIED IDEOGRAPH-22C56..CJK UNIFIED IDEOGRAPH-22CC1 +22CC3..22D07 ; Uncommon_Use # 3.1 [69] CJK UNIFIED IDEOGRAPH-22CC3..CJK UNIFIED IDEOGRAPH-22D07 +22D09..22D4B ; Uncommon_Use # 3.1 [67] CJK UNIFIED IDEOGRAPH-22D09..CJK UNIFIED IDEOGRAPH-22D4B +22D4D..22D66 ; Uncommon_Use # 3.1 [26] CJK UNIFIED IDEOGRAPH-22D4D..CJK UNIFIED IDEOGRAPH-22D66 +22D68..22EB2 ; Uncommon_Use # 3.1 [331] CJK UNIFIED IDEOGRAPH-22D68..CJK UNIFIED IDEOGRAPH-22EB2 +22EB4..23CB6 ; Uncommon_Use # 3.1 [3587] CJK UNIFIED IDEOGRAPH-22EB4..CJK UNIFIED IDEOGRAPH-23CB6 +23CB8..244D2 ; Uncommon_Use # 3.1 [2075] CJK UNIFIED IDEOGRAPH-23CB8..CJK UNIFIED IDEOGRAPH-244D2 +244D4..24DB7 ; Uncommon_Use # 3.1 [2276] CJK UNIFIED IDEOGRAPH-244D4..CJK UNIFIED IDEOGRAPH-24DB7 +24DB9..24DE9 ; Uncommon_Use # 3.1 [49] CJK UNIFIED IDEOGRAPH-24DB9..CJK UNIFIED IDEOGRAPH-24DE9 +24DEB..2512A ; Uncommon_Use # 3.1 [832] CJK UNIFIED IDEOGRAPH-24DEB..CJK UNIFIED IDEOGRAPH-2512A +2512C..26257 ; Uncommon_Use # 3.1 [4396] CJK UNIFIED IDEOGRAPH-2512C..CJK UNIFIED IDEOGRAPH-26257 +26259..267CB ; Uncommon_Use # 3.1 [1395] CJK UNIFIED IDEOGRAPH-26259..CJK UNIFIED IDEOGRAPH-267CB +267CD..269F1 ; Uncommon_Use # 3.1 [549] CJK UNIFIED IDEOGRAPH-267CD..CJK UNIFIED IDEOGRAPH-269F1 +269F3..269F9 ; Uncommon_Use # 3.1 [7] CJK UNIFIED IDEOGRAPH-269F3..CJK UNIFIED IDEOGRAPH-269F9 +269FB..27A3D ; Uncommon_Use # 3.1 [4163] CJK UNIFIED IDEOGRAPH-269FB..CJK UNIFIED IDEOGRAPH-27A3D +27A3F..2815C ; Uncommon_Use # 3.1 [1822] CJK UNIFIED IDEOGRAPH-27A3F..CJK UNIFIED IDEOGRAPH-2815C +2815E..28206 ; Uncommon_Use # 3.1 [169] CJK UNIFIED IDEOGRAPH-2815E..CJK UNIFIED IDEOGRAPH-28206 +28208..282E1 ; Uncommon_Use # 3.1 [218] CJK UNIFIED IDEOGRAPH-28208..CJK UNIFIED IDEOGRAPH-282E1 +282E3..28CC9 ; Uncommon_Use # 3.1 [2535] CJK UNIFIED IDEOGRAPH-282E3..CJK UNIFIED IDEOGRAPH-28CC9 +28CCB..28CCC ; Uncommon_Use # 3.1 [2] CJK UNIFIED IDEOGRAPH-28CCB..CJK UNIFIED IDEOGRAPH-28CCC +28CCE..28CD1 ; Uncommon_Use # 3.1 [4] CJK UNIFIED IDEOGRAPH-28CCE..CJK UNIFIED IDEOGRAPH-28CD1 +28CD3..29D97 ; Uncommon_Use # 3.1 [4293] CJK UNIFIED IDEOGRAPH-28CD3..CJK UNIFIED IDEOGRAPH-29D97 +29D99..2A6D6 ; Uncommon_Use # 3.1 [2366] CJK UNIFIED IDEOGRAPH-29D99..CJK UNIFIED IDEOGRAPH-2A6D6 +2A6D7..2A6DD ; Uncommon_Use # 13.0 [7] CJK UNIFIED IDEOGRAPH-2A6D7..CJK UNIFIED IDEOGRAPH-2A6DD +2A6DE..2A6DF ; Uncommon_Use # 14.0 [2] CJK UNIFIED IDEOGRAPH-2A6DE..CJK UNIFIED IDEOGRAPH-2A6DF +2A700..2B734 ; Uncommon_Use # 5.2 [4149] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B734 +2B735..2B738 ; Uncommon_Use # 14.0 [4] CJK UNIFIED IDEOGRAPH-2B735..CJK UNIFIED IDEOGRAPH-2B738 +2B739 ; Uncommon_Use # 15.0 CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F ; Uncommon_Use # 17.0 [6] CJK UNIFIED IDEOGRAPH-2B73A..CJK UNIFIED IDEOGRAPH-2B73F +2B740..2B81D ; Uncommon_Use # 6.0 [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEA1 ; Uncommon_Use # 8.0 [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2CEA2..2CEAD ; Uncommon_Use # 17.0 [12] CJK UNIFIED IDEOGRAPH-2CEA2..CJK UNIFIED IDEOGRAPH-2CEAD +2CEB0..2EBE0 ; Uncommon_Use # 10.0 [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 +2EBF0..2EE5D ; Uncommon_Use # 15.1 [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D +30000..3134A ; Uncommon_Use # 13.0 [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A +31350..323AF ; Uncommon_Use # 15.0 [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..33479 ; Uncommon_Use # 17.0 [4298] CJK UNIFIED IDEOGRAPH-323B0..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 346 +# Total code points: 83130 # Identifier_Type: Uncommon_Use Technical -0253..0254 ; Uncommon_Use Technical # 1.1 [2] LATIN SMALL LETTER B WITH HOOK..LATIN SMALL LETTER OPEN O -0256..0257 ; Uncommon_Use Technical # 1.1 [2] LATIN SMALL LETTER D WITH TAIL..LATIN SMALL LETTER D WITH HOOK -025B ; Uncommon_Use Technical # 1.1 LATIN SMALL LETTER OPEN E -0263 ; Uncommon_Use Technical # 1.1 LATIN SMALL LETTER GAMMA -0268..0269 ; Uncommon_Use Technical # 1.1 [2] LATIN SMALL LETTER I WITH STROKE..LATIN SMALL LETTER IOTA -0272 ; Uncommon_Use Technical # 1.1 LATIN SMALL LETTER N WITH LEFT HOOK -0289 ; Uncommon_Use Technical # 1.1 LATIN SMALL LETTER U BAR -0292 ; Uncommon_Use Technical # 1.1 LATIN SMALL LETTER EZH 05C7 ; Uncommon_Use Technical # 4.1 HEBREW POINT QAMATS QATAN +0653 ; Uncommon_Use Technical # 3.0 ARABIC MADDAH ABOVE 0D8F..0D90 ; Uncommon_Use Technical # 3.0 [2] SINHALA LETTER ILUYANNA..SINHALA LETTER ILUUYANNA -0DA6 ; Uncommon_Use Technical # 3.0 SINHALA LETTER SANYAKA JAYANNA 0DDF ; Uncommon_Use Technical # 3.0 SINHALA VOWEL SIGN GAYANUKITTA 0DF3 ; Uncommon_Use Technical # 3.0 SINHALA VOWEL SIGN DIGA GAYANUKITTA +0FC6 ; Uncommon_Use Technical # 3.0 TIBETAN SYMBOL PADMA GDAN +10F9..10FA ; Uncommon_Use Technical # 4.1 [2] GEORGIAN LETTER TURNED GAN..GEORGIAN LETTER AIN FB1E ; Uncommon_Use Technical # 1.1 HEBREW POINT JUDEO-SPANISH VARIKA FE2E..FE2F ; Uncommon_Use Technical # 8.0 [2] COMBINING CYRILLIC TITLO LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF -# Total code points: 20 +# Total code points: 12 # Identifier_Type: Uncommon_Use Technical Not_XID @@ -916,10 +3473,13 @@ FE2E..FE2F ; Uncommon_Use Technical # 8.0 [2] COMBINING CYRILLIC T 05A2 ; Uncommon_Use Obsolete # 4.1 HEBREW ACCENT ATNAH HAFUKH 05C5 ; Uncommon_Use Obsolete # 4.1 HEBREW MARK LOWER DOT +0F6A ; Uncommon_Use Obsolete # 3.0 TIBETAN LETTER FIXED-FORM RA +0F82..0F83 ; Uncommon_Use Obsolete # 2.0 [2] TIBETAN SIGN NYI ZLA NAA DA..TIBETAN SIGN SNA LDAN +1050..1059 ; Uncommon_Use Obsolete # 3.0 [10] MYANMAR LETTER SHA..MYANMAR VOWEL SIGN VOCALIC LL A69E ; Uncommon_Use Obsolete # 8.0 COMBINING CYRILLIC LETTER EF A8FD ; Uncommon_Use Obsolete # 8.0 DEVANAGARI JAIN OM -# Total code points: 4 +# Total code points: 17 # Identifier_Type: Uncommon_Use Obsolete Not_XID @@ -939,6 +3499,7 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 0180 ; Technical # 1.1 LATIN SMALL LETTER B WITH STROKE 01C0..01C3 ; Technical # 1.1 [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK +0200..0217 ; Technical # 1.1 [24] LATIN CAPITAL LETTER A WITH DOUBLE GRAVE..LATIN SMALL LETTER U WITH INVERTED BREVE 0234..0236 ; Technical # 4.0 [3] LATIN SMALL LETTER L WITH CURL..LATIN SMALL LETTER T WITH CURL 0250..0252 ; Technical # 1.1 [3] LATIN SMALL LETTER TURNED A..LATIN SMALL LETTER TURNED ALPHA 0255 ; Technical # 1.1 LATIN SMALL LETTER C WITH CURL @@ -950,7 +3511,8 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 0273..0276 ; Technical # 1.1 [4] LATIN SMALL LETTER N WITH RETROFLEX HOOK..LATIN LETTER SMALL CAPITAL OE 0278..027B ; Technical # 1.1 [4] LATIN SMALL LETTER PHI..LATIN SMALL LETTER TURNED R WITH HOOK 027D..0288 ; Technical # 1.1 [12] LATIN SMALL LETTER R WITH TAIL..LATIN SMALL LETTER T WITH RETROFLEX HOOK -028A..0291 ; Technical # 1.1 [8] LATIN SMALL LETTER UPSILON..LATIN SMALL LETTER Z WITH CURL +028A ; Technical # 1.1 LATIN SMALL LETTER UPSILON +028C..0291 ; Technical # 1.1 [6] LATIN SMALL LETTER TURNED V..LATIN SMALL LETTER Z WITH CURL 0293..029D ; Technical # 1.1 [11] LATIN SMALL LETTER EZH WITH CURL..LATIN SMALL LETTER J WITH CROSSED-TAIL 029F..02A8 ; Technical # 1.1 [10] LATIN LETTER SMALL CAPITAL L..LATIN SMALL LETTER TC DIGRAPH WITH CURL 02A9..02AD ; Technical # 3.0 [5] LATIN SMALL LETTER FENG DIGRAPH..LATIN LETTER BIDENTAL PERCUSSIVE @@ -958,17 +3520,17 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 02B9..02BA ; Technical # 1.1 [2] MODIFIER LETTER PRIME..MODIFIER LETTER DOUBLE PRIME 02BD..02C1 ; Technical # 1.1 [5] MODIFIER LETTER REVERSED COMMA..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; Technical # 1.1 [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON +02EC ; Technical # 3.0 MODIFIER LETTER VOICING 02EE ; Technical # 3.0 MODIFIER LETTER DOUBLE APOSTROPHE -030E ; Technical # 1.1 COMBINING DOUBLE VERTICAL LINE ABOVE -0312 ; Technical # 1.1 COMBINING TURNED COMMA ABOVE -0315 ; Technical # 1.1 COMBINING COMMA ABOVE RIGHT +030E..0315 ; Technical # 1.1 [8] COMBINING DOUBLE VERTICAL LINE ABOVE..COMBINING COMMA ABOVE RIGHT 0317..031A ; Technical # 1.1 [4] COMBINING ACUTE ACCENT BELOW..COMBINING LEFT ANGLE ABOVE 031C..0320 ; Technical # 1.1 [5] COMBINING LEFT HALF RING BELOW..COMBINING MINUS SIGN BELOW -0329..032C ; Technical # 1.1 [4] COMBINING VERTICAL LINE BELOW..COMBINING CARON BELOW -032F ; Technical # 1.1 COMBINING INVERTED BREVE BELOW +0324..0325 ; Technical # 1.1 [2] COMBINING DIAERESIS BELOW..COMBINING RING BELOW +0329..0330 ; Technical # 1.1 [8] COMBINING VERTICAL LINE BELOW..COMBINING TILDE BELOW 0333 ; Technical # 1.1 COMBINING DOUBLE LOW LINE -0337 ; Technical # 1.1 COMBINING SHORT SOLIDUS OVERLAY -033A..033F ; Technical # 1.1 [6] COMBINING INVERTED BRIDGE BELOW..COMBINING DOUBLE OVERLINE +0335 ; Technical # 1.1 COMBINING SHORT STROKE OVERLAY +0337..033F ; Technical # 1.1 [9] COMBINING SHORT SOLIDUS OVERLAY..COMBINING DOUBLE OVERLINE +0342 ; Technical # 1.1 COMBINING GREEK PERISPOMENI 0346..034E ; Technical # 3.0 [9] COMBINING BRIDGE ABOVE..COMBINING UPWARDS ARROW BELOW 0350..0357 ; Technical # 4.0 [8] COMBINING RIGHT ARROWHEAD ABOVE..COMBINING RIGHT HALF RING ABOVE 0359..035C ; Technical # 4.1 [4] COMBINING ASTERISK BELOW..COMBINING DOUBLE BREVE BELOW @@ -977,13 +3539,31 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 0362 ; Technical # 3.0 COMBINING DOUBLE RIGHTWARDS ARROW BELOW 03CF ; Technical # 5.1 GREEK CAPITAL KAI SYMBOL 03D7 ; Technical # 3.0 GREEK KAI SYMBOL +0559 ; Technical # 1.1 ARMENIAN MODIFIER LETTER LEFT HALF RING 0560 ; Technical # 11.0 ARMENIAN SMALL LETTER TURNED AYB 0588 ; Technical # 11.0 ARMENIAN SMALL LETTER YI WITH STROKE +0671 ; Technical # 1.1 ARABIC LETTER ALEF WASLA +06E5..06E6 ; Technical # 1.1 [2] ARABIC SMALL WAW..ARABIC SMALL YEH +0870..0887 ; Technical # 14.0 [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT +08C9 ; Technical # 14.0 ARABIC SMALL FARSI YEH +0950 ; Technical # 1.1 DEVANAGARI OM 0953..0954 ; Technical # 1.1 [2] DEVANAGARI GRAVE ACCENT..DEVANAGARI ACUTE ACCENT +097D ; Technical # 4.1 DEVANAGARI LETTER GLOTTAL STOP +0A74 ; Technical # 1.1 GURMUKHI EK ONKAR +0AD0 ; Technical # 1.1 GUJARATI OM +0B82 ; Technical # 1.1 TAMIL SIGN ANUSVARA +0BD0 ; Technical # 5.1 TAMIL OM 0D81 ; Technical # 13.0 SINHALA SIGN CANDRABINDU +0EAF ; Technical # 1.1 LAO ELLIPSIS +0F00 ; Technical # 2.0 TIBETAN SYLLABLE OM 0F18..0F19 ; Technical # 2.0 [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS +0F35 ; Technical # 2.0 TIBETAN MARK NGAS BZUNG NYI ZLA +0F37 ; Technical # 2.0 TIBETAN MARK NGAS BZUNG SGOR RTAGS +0F3E..0F3F ; Technical # 2.0 [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES 17CE..17CF ; Technical # 3.0 [2] KHMER SIGN KAKABAT..KHMER SIGN AHSDA 1ABF..1AC0 ; Technical # 13.0 [2] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER TURNED W BELOW +1ACF..1ADD ; Technical # 17.0 [15] COMBINING DOUBLE CARON..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Technical # 17.0 [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1D00..1D2B ; Technical # 4.0 [44] LATIN LETTER SMALL CAPITAL A..CYRILLIC LETTER SMALL CAPITAL EL 1D2F ; Technical # 4.0 MODIFIER LETTER CAPITAL BARRED B 1D3B ; Technical # 4.0 MODIFIER LETTER CAPITAL REVERSED N @@ -1000,6 +3580,10 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 1DFC ; Technical # 6.0 COMBINING DOUBLE INVERTED BREVE BELOW 1DFD ; Technical # 5.2 COMBINING ALMOST EQUAL TO BELOW 1DFE..1DFF ; Technical # 5.0 [2] COMBINING LEFT ARROWHEAD ABOVE..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW +1E00..1E01 ; Technical # 1.1 [2] LATIN CAPITAL LETTER A WITH RING BELOW..LATIN SMALL LETTER A WITH RING BELOW +1E18..1E1B ; Technical # 1.1 [4] LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW..LATIN SMALL LETTER E WITH TILDE BELOW +1E2A..1E2D ; Technical # 1.1 [4] LATIN CAPITAL LETTER H WITH BREVE BELOW..LATIN SMALL LETTER I WITH TILDE BELOW +1E72..1E77 ; Technical # 1.1 [6] LATIN CAPITAL LETTER U WITH DIAERESIS BELOW..LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW 1E9C..1E9D ; Technical # 5.1 [2] LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE..LATIN SMALL LETTER LONG S WITH HIGH STROKE 1E9F ; Technical # 5.1 LATIN SMALL LETTER DELTA 1EFA..1EFF ; Technical # 5.1 [6] LATIN CAPITAL LETTER MIDDLE-WELSH LL..LATIN SMALL LETTER Y WITH LOOP @@ -1015,19 +3599,28 @@ A8FC ; Uncommon_Use Obsolete Not_XID # 8.0 DEVANAGARI SIGN SIDD 2C60..2C67 ; Technical # 5.0 [8] LATIN CAPITAL LETTER L WITH DOUBLE BAR..LATIN CAPITAL LETTER H WITH DESCENDER 2C77 ; Technical # 5.0 LATIN SMALL LETTER TAILLESS PHI 2C78..2C7B ; Technical # 5.1 [4] LATIN SMALL LETTER E WITH NOTCH..LATIN LETTER SMALL CAPITAL TURNED E +2D27 ; Technical # 6.1 GEORGIAN SMALL LETTER YN +2D2D ; Technical # 6.1 GEORGIAN SMALL LETTER AEN 3021..302D ; Technical # 1.1 [13] HANGZHOU NUMERAL ONE..IDEOGRAPHIC ENTERING TONE MARK 3031..3035 ; Technical # 1.1 [5] VERTICAL KANA REPEAT MARK..VERTICAL KANA REPEAT MARK LOWER HALF 303B..303C ; Technical # 3.2 [2] VERTICAL IDEOGRAPHIC ITERATION MARK..MASU MARK +A717..A71A ; Technical # 5.0 [4] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER LETTER LOWER RIGHT CORNER ANGLE +A71B..A71F ; Technical # 5.1 [5] MODIFIER LETTER RAISED UP ARROW..MODIFIER LETTER LOW INVERTED EXCLAMATION MARK +A788 ; Technical # 5.1 MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78E ; Technical # 6.0 LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A7AE ; Technical # 9.0 LATIN CAPITAL LETTER SMALL CAPITAL I A7AF ; Technical # 11.0 LATIN LETTER SMALL CAPITAL Q A7BA..A7BF ; Technical # 12.0 [6] LATIN CAPITAL LETTER GLOTTAL A..LATIN SMALL LETTER GLOTTAL U +A7C5..A7C6 ; Technical # 12.0 [2] LATIN CAPITAL LETTER S WITH HOOK..LATIN CAPITAL LETTER Z WITH PALATAL HOOK A7FA ; Technical # 6.0 LATIN LETTER SMALL CAPITAL TURNED M AB68 ; Technical # 13.0 LATIN SMALL LETTER TURNED R WITH MIDDLE TILDE FE20..FE23 ; Technical # 1.1 [4] COMBINING LIGATURE LEFT HALF..COMBINING DOUBLE TILDE RIGHT HALF FE24..FE26 ; Technical # 5.1 [3] COMBINING MACRON LEFT HALF..COMBINING CONJOINING MACRON FE27..FE2D ; Technical # 7.0 [7] COMBINING LIGATURE LEFT HALF BELOW..COMBINING CONJOINING MACRON BELOW FE73 ; Technical # 3.2 ARABIC TAIL FRAGMENT +10EC5..10EC6 ; Technical # 17.0 [2] ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW..ARABIC LETTER THIN NOON +10EFB ; Technical # 17.0 ARABIC SMALL LOW NOON +16FF2..16FF6 ; Technical # 17.0 [5] CHINESE SMALL SIMPLIFIED ER..YANGQIN SIGN SLOW TWO BEATS 1CF00..1CF2D ; Technical # 14.0 [46] ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT..ZNAMENNY COMBINING MARK KRYZH ON LEFT 1CF30..1CF46 ; Technical # 14.0 [23] ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO..ZNAMENNY PRIZNAK MODIFIER ROG 1D165..1D169 ; Technical # 3.1 [5] MUSICAL SYMBOL COMBINING STEM..MUSICAL SYMBOL COMBINING TREMOLO-3 @@ -1035,8 +3628,10 @@ FE73 ; Technical # 3.2 ARABIC TAIL FRAGMENT 1D17B..1D182 ; Technical # 3.1 [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE 1D185..1D18B ; Technical # 3.1 [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE 1D1AA..1D1AD ; Technical # 3.1 [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO +1DF00..1DF1E ; Technical # 14.0 [31] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A ; Technical # 15.0 [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK -# Total code points: 501 +# Total code points: 682 # Identifier_Type: Technical Exclusion @@ -1054,6 +3649,7 @@ FE73 ; Technical # 3.2 ARABIC TAIL FRAGMENT 027C ; Technical Obsolete # 1.1 LATIN SMALL LETTER R WITH LONG LEG 029E ; Technical Obsolete # 1.1 LATIN SMALL LETTER TURNED K 03F3 ; Technical Obsolete # 1.1 GREEK LETTER YOT +03FC ; Technical Obsolete # 4.1 GREEK RHO WITH STROKE SYMBOL 0484..0486 ; Technical Obsolete # 1.1 [3] COMBINING CYRILLIC PALATALIZATION..COMBINING CYRILLIC PSILI PNEUMATA 0487 ; Technical Obsolete # 5.1 COMBINING CYRILLIC POKRYTIE 0D04 ; Technical Obsolete # 13.0 MALAYALAM LETTER VEDIC ANUSVARA @@ -1062,13 +3658,14 @@ FE73 ; Technical # 3.2 ARABIC TAIL FRAGMENT 1DC0..1DC3 ; Technical Obsolete # 4.1 [4] COMBINING DOTTED GRAVE ACCENT..COMBINING SUSPENSION MARK 1DCE ; Technical Obsolete # 5.1 COMBINING OGONEK ABOVE 1DD1..1DE6 ; Technical Obsolete # 5.1 [22] COMBINING UR ABOVE..COMBINING LATIN SMALL LETTER Z +1FB0..1FB1 ; Technical Obsolete # 1.1 [2] GREEK SMALL LETTER ALPHA WITH VRACHY..GREEK SMALL LETTER ALPHA WITH MACRON 2180..2182 ; Technical Obsolete # 1.1 [3] ROMAN NUMERAL ONE THOUSAND C D..ROMAN NUMERAL TEN THOUSAND 2183 ; Technical Obsolete # 3.0 ROMAN NUMERAL REVERSED ONE HUNDRED 302E..302F ; Technical Obsolete # 1.1 [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK A722..A72F ; Technical Obsolete # 5.1 [14] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN SMALL LETTER CUATRILLO WITH COMMA 1D242..1D244 ; Technical Obsolete # 4.1 [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME -# Total code points: 67 +# Total code points: 70 # Identifier_Type: Technical Obsolete Not_XID @@ -1078,6 +3675,8 @@ A722..A72F ; Technical Obsolete # 5.1 [14] LATIN CAPITAL LETTER # Identifier_Type: Technical Not_XID +0375 ; Technical Not_XID # 1.1 GREEK LOWER NUMERAL SIGN +0888 ; Technical Not_XID # 14.0 ARABIC RAISED ROUND DOT 20DD..20E0 ; Technical Not_XID # 1.1 [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH 20E2..20E3 ; Technical Not_XID # 3.0 [2] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING KEYCAP 20E4 ; Technical Not_XID # 3.2 COMBINING ENCLOSING UPWARD POINTING TRIANGLE @@ -1089,8 +3688,11 @@ A722..A72F ; Technical Obsolete # 5.1 [14] LATIN CAPITAL LETTER A708..A716 ; Technical Not_XID # 4.1 [15] MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR..MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR FBB2..FBC1 ; Technical Not_XID # 6.0 [16] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL SMALL TAH BELOW FBC2 ; Technical Not_XID # 14.0 ARABIC SYMBOL WASLA ABOVE +FBC3..FBD2 ; Technical Not_XID # 17.0 [16] ARABIC LIGATURE JALLA WA-ALAA..ARABIC LIGATURE ALAYHI AR-RAHMAH FD3E..FD3F ; Technical Not_XID # 1.1 [2] ORNATE LEFT PARENTHESIS..ORNATE RIGHT PARENTHESIS FD40..FD4F ; Technical Not_XID # 14.0 [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH +FD90..FD91 ; Technical Not_XID # 17.0 [2] ARABIC LIGATURE RAHMATU ALLAAHI ALAYH..ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA +FDC8..FDCE ; Technical Not_XID # 17.0 [7] ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA..ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH FDCF ; Technical Not_XID # 14.0 ARABIC LIGATURE SALAAMUHU ALAYNAA FDFD ; Technical Not_XID # 4.0 ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM FDFE..FDFF ; Technical Not_XID # 14.0 [2] ARABIC LIGATURE SUBHAANAHU WA TAAALAA..ARABIC LIGATURE AZZA WA JALL @@ -1108,7 +3710,7 @@ FE45..FE46 ; Technical Not_XID # 3.2 [2] SESAME DOT..WHITE SE 1D1E9..1D1EA ; Technical Not_XID # 14.0 [2] MUSICAL SYMBOL SORI..MUSICAL SYMBOL KORON 1D300..1D356 ; Technical Not_XID # 4.0 [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING -# Total code points: 1025 +# Total code points: 1052 # Identifier_Type: Exclusion @@ -1194,6 +3796,7 @@ A930..A953 ; Exclusion # 5.1 [36] REJANG LETTER KA..RE 108F4..108F5 ; Exclusion # 8.0 [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; Exclusion # 5.0 [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; Exclusion # 5.1 [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; Exclusion # 17.0 [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; Exclusion # 6.1 [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; Exclusion # 6.1 [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00..10A03 ; Exclusion # 4.1 [4] KHAROSHTHI LETTER A..KHAROSHTHI VOWEL SIGN VOCALIC R @@ -1319,6 +3922,7 @@ A930..A953 ; Exclusion # 5.1 [36] REJANG LETTER KA..RE 11A86..11A99 ; Exclusion # 10.0 [20] SOYOMBO CLUSTER-INITIAL LETTER RA..SOYOMBO SUBJOINER 11A9D ; Exclusion # 11.0 SOYOMBO MARK PLUTA 11AC0..11AF8 ; Exclusion # 7.0 [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B60..11B67 ; Exclusion # 17.0 [8] SHARADA VOWEL SIGN OE..SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; Exclusion # 16.0 [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11BF0..11BF9 ; Exclusion # 16.0 [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE 11C00..11C08 ; Exclusion # 9.0 [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L @@ -1335,6 +3939,14 @@ A930..A953 ; Exclusion # 5.1 [36] REJANG LETTER KA..RE 11D3C..11D3D ; Exclusion # 10.0 [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O 11D3F..11D47 ; Exclusion # 10.0 [9] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI RA-KARA 11D50..11D59 ; Exclusion # 10.0 [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE +11D60..11D65 ; Exclusion # 11.0 [6] GUNJALA GONDI LETTER A..GUNJALA GONDI LETTER UU +11D67..11D68 ; Exclusion # 11.0 [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI +11D6A..11D8E ; Exclusion # 11.0 [37] GUNJALA GONDI LETTER OO..GUNJALA GONDI VOWEL SIGN UU +11D90..11D91 ; Exclusion # 11.0 [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI +11D93..11D98 ; Exclusion # 11.0 [6] GUNJALA GONDI VOWEL SIGN OO..GUNJALA GONDI OM +11DA0..11DA9 ; Exclusion # 11.0 [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DB0..11DDB ; Exclusion # 17.0 [44] TOLONG SIKI LETTER I..TOLONG SIKI UNGGA +11DE0..11DE9 ; Exclusion # 17.0 [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11EE0..11EF6 ; Exclusion # 11.0 [23] MAKASAR LETTER KA..MAKASAR VOWEL SIGN O 11F00..11F10 ; Exclusion # 15.0 [17] KAWI SIGN CANDRABINDU..KAWI LETTER O 11F12..11F3A ; Exclusion # 15.0 [41] KAWI LETTER KA..KAWI VOWEL SIGN VOCALIC R @@ -1366,16 +3978,21 @@ A930..A953 ; Exclusion # 5.1 [36] REJANG LETTER KA..RE 16D40..16D6C ; Exclusion # 16.0 [45] KIRAT RAI SIGN ANUSVARA..KIRAT RAI SIGN SAAT 16D70..16D79 ; Exclusion # 16.0 [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE 16E40..16E7F ; Exclusion # 11.0 [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Exclusion # 17.0 [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Exclusion # 17.0 [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16FE0 ; Exclusion # 9.0 TANGUT ITERATION MARK 16FE1 ; Exclusion # 10.0 NUSHU ITERATION MARK 16FE4 ; Exclusion # 13.0 KHITAN SMALL SCRIPT FILLER 17000..187EC ; Exclusion # 9.0 [6125] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187EC 187ED..187F1 ; Exclusion # 11.0 [5] TANGUT IDEOGRAPH-187ED..TANGUT IDEOGRAPH-187F1 187F2..187F7 ; Exclusion # 12.0 [6] TANGUT IDEOGRAPH-187F2..TANGUT IDEOGRAPH-187F7 +187F8..187FF ; Exclusion # 17.0 [8] TANGUT IDEOGRAPH-187F8..TANGUT IDEOGRAPH-187FF 18800..18AF2 ; Exclusion # 9.0 [755] TANGUT COMPONENT-001..TANGUT COMPONENT-755 18AF3..18CD5 ; Exclusion # 13.0 [483] TANGUT COMPONENT-756..KHITAN SMALL SCRIPT CHARACTER-18CD5 18CFF ; Exclusion # 16.0 KHITAN SMALL SCRIPT CHARACTER-18CFF 18D00..18D08 ; Exclusion # 13.0 [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +18D09..18D1E ; Exclusion # 17.0 [22] TANGUT IDEOGRAPH-18D09..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Exclusion # 17.0 [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1B170..1B2FB ; Exclusion # 10.0 [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB 1BC00..1BC6A ; Exclusion # 7.0 [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M 1BC70..1BC7C ; Exclusion # 7.0 [13] DUPLOYAN AFFIX LEFT HORIZONTAL SECANT..DUPLOYAN AFFIX ATTACHED TANGENT HOOK @@ -1396,10 +4013,13 @@ A930..A953 ; Exclusion # 5.1 [36] REJANG LETTER KA..RE 1E290..1E2AE ; Exclusion # 14.0 [31] TOTO LETTER PA..TOTO SIGN RISING TONE 1E4D0..1E4F9 ; Exclusion # 15.0 [42] NAG MUNDARI LETTER O..NAG MUNDARI DIGIT NINE 1E5D0..1E5FA ; Exclusion # 16.0 [43] OL ONAL LETTER O..OL ONAL DIGIT NINE +1E6C0..1E6DE ; Exclusion # 17.0 [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6F5 ; Exclusion # 17.0 [22] TAI YO LETTER AA..TAI YO SIGN OM +1E6FE..1E6FF ; Exclusion # 17.0 [2] TAI YO SYMBOL MUEANG..TAI YO XAM LAI 1E800..1E8C4 ; Exclusion # 7.0 [197] MENDE KIKAKUI SYLLABLE M001 KI..MENDE KIKAKUI SYLLABLE M060 NYON 1E8D0..1E8D6 ; Exclusion # 7.0 [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS -# Total code points: 20461 +# Total code points: 20862 # Identifier_Type: Exclusion Not_XID @@ -1515,13 +4135,16 @@ A95F ; Exclusion Not_XID # 5.1 REJANG SECTION MARK # Identifier_Type: Obsolete +0138 ; Obsolete # 1.1 LATIN SMALL LETTER KRA 01B9 ; Obsolete # 1.1 LATIN SMALL LETTER EZH REVERSED 01BF ; Obsolete # 1.1 LATIN LETTER WYNN 01F6..01F7 ; Obsolete # 3.0 [2] LATIN CAPITAL LETTER HWAIR..LATIN CAPITAL LETTER WYNN 021C..021D ; Obsolete # 3.0 [2] LATIN CAPITAL LETTER YOGH..LATIN SMALL LETTER YOGH +0345 ; Obsolete # 1.1 COMBINING GREEK YPOGEGRAMMENI 0363..036F ; Obsolete # 3.2 [13] COMBINING LATIN SMALL LETTER A..COMBINING LATIN SMALL LETTER X 0370..0373 ; Obsolete # 5.1 [4] GREEK CAPITAL LETTER HETA..GREEK SMALL LETTER ARCHAIC SAMPI 0376..0377 ; Obsolete # 5.1 [2] GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA..GREEK SMALL LETTER PAMPHYLIAN DIGAMMA +037B..037D ; Obsolete # 5.0 [3] GREEK SMALL REVERSED LUNATE SIGMA SYMBOL..GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL 037F ; Obsolete # 7.0 GREEK CAPITAL LETTER YOT 03D8..03D9 ; Obsolete # 3.2 [2] GREEK LETTER ARCHAIC KOPPA..GREEK SMALL LETTER ARCHAIC KOPPA 03DA ; Obsolete # 1.1 GREEK LETTER STIGMA @@ -1534,29 +4157,68 @@ A95F ; Exclusion Not_XID # 5.1 REJANG SECTION MARK 03E1 ; Obsolete # 3.0 GREEK SMALL LETTER SAMPI 03F7..03F8 ; Obsolete # 4.0 [2] GREEK CAPITAL LETTER SHO..GREEK SMALL LETTER SHO 03FA..03FB ; Obsolete # 4.0 [2] GREEK CAPITAL LETTER SAN..GREEK SMALL LETTER SAN +03FD..03FF ; Obsolete # 4.1 [3] GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL..GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL 0460..0481 ; Obsolete # 1.1 [34] CYRILLIC CAPITAL LETTER OMEGA..CYRILLIC SMALL LETTER KOPPA 0483 ; Obsolete # 1.1 COMBINING CYRILLIC TITLO +049C..049D ; Obsolete # 1.1 [2] CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE..CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE +04A6..04A7 ; Obsolete # 1.1 [2] CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK..CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK +04B8..04B9 ; Obsolete # 1.1 [2] CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE..CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE 0500..050F ; Obsolete # 3.2 [16] CYRILLIC CAPITAL LETTER KOMI DE..CYRILLIC SMALL LETTER KOMI TJE -052A..052D ; Obsolete # 7.0 [4] CYRILLIC CAPITAL LETTER DZZHE..CYRILLIC SMALL LETTER DCHE +0514..0523 ; Obsolete # 5.1 [16] CYRILLIC CAPITAL LETTER LHA..CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK +0526..0527 ; Obsolete # 6.0 [2] CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER..CYRILLIC SMALL LETTER SHHA WITH DESCENDER +0528..052F ; Obsolete # 7.0 [8] CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK..CYRILLIC SMALL LETTER EL WITH DESCENDER +063B..063C ; Obsolete # 5.1 [2] ARABIC LETTER KEHEH WITH TWO DOTS ABOVE..ARABIC LETTER KEHEH WITH THREE DOTS BELOW +063E..063F ; Obsolete # 5.1 [2] ARABIC LETTER FARSI YEH WITH TWO DOTS ABOVE..ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE 0640 ; Obsolete # 1.1 ARABIC TATWEEL 066E..066F ; Obsolete # 3.2 [2] ARABIC LETTER DOTLESS BEH..ARABIC LETTER DOTLESS QAF -068E ; Obsolete # 1.1 ARABIC LETTER DUL -06A1 ; Obsolete # 1.1 ARABIC LETTER DOTLESS FEH +0690 ; Obsolete # 1.1 ARABIC LETTER DAL WITH FOUR DOTS ABOVE +06AC ; Obsolete # 1.1 ARABIC LETTER KAF WITH DOT ABOVE +077E..077F ; Obsolete # 5.1 [2] ARABIC LETTER SEEN WITH INVERTED V..ARABIC LETTER KAF WITH TWO DOTS ABOVE +088E ; Obsolete # 14.0 ARABIC VERTICAL TAIL 08AD..08B1 ; Obsolete # 7.0 [5] ARABIC LETTER LOW ALEF..ARABIC LETTER STRAIGHT WAW +08B5 ; Obsolete # 14.0 ARABIC LETTER QAF WITH DOT BELOW AND NO DOTS ABOVE +090C ; Obsolete # 1.1 DEVANAGARI LETTER VOCALIC L +093D ; Obsolete # 1.1 DEVANAGARI SIGN AVAGRAHA 094E ; Obsolete # 5.2 DEVANAGARI VOWEL SIGN PRISHTHAMATRA E 0951..0952 ; Obsolete # 1.1 [2] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI STRESS SIGN ANUDATTA +0960..0963 ; Obsolete # 1.1 [4] DEVANAGARI LETTER VOCALIC RR..DEVANAGARI VOWEL SIGN VOCALIC LL +0971 ; Obsolete # 5.1 DEVANAGARI SIGN HIGH SPACING DOT 0978 ; Obsolete # 7.0 DEVANAGARI LETTER MARWARI DDA 0980 ; Obsolete # 7.0 BENGALI ANJI +09BD ; Obsolete # 4.0 BENGALI SIGN AVAGRAHA +09E0..09E3 ; Obsolete # 1.1 [4] BENGALI LETTER VOCALIC RR..BENGALI VOWEL SIGN VOCALIC LL 09FC ; Obsolete # 10.0 BENGALI LETTER VEDIC ANUSVARA +0ABD ; Obsolete # 1.1 GUJARATI SIGN AVAGRAHA +0AE0 ; Obsolete # 1.1 GUJARATI LETTER VOCALIC RR +0AE1..0AE3 ; Obsolete # 4.0 [3] GUJARATI LETTER VOCALIC LL..GUJARATI VOWEL SIGN VOCALIC LL +0B3D ; Obsolete # 1.1 ORIYA SIGN AVAGRAHA +0B60..0B61 ; Obsolete # 1.1 [2] ORIYA LETTER VOCALIC RR..ORIYA LETTER VOCALIC LL 0C00 ; Obsolete # 7.0 TELUGU SIGN COMBINING CANDRABINDU ABOVE 0C34 ; Obsolete # 7.0 TELUGU LETTER LLLA +0C3D ; Obsolete # 5.1 TELUGU SIGN AVAGRAHA 0C58..0C59 ; Obsolete # 5.1 [2] TELUGU LETTER TSA..TELUGU LETTER DZA +0C5C ; Obsolete # 17.0 TELUGU ARCHAIC SHRII +0C60..0C61 ; Obsolete # 1.1 [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C81 ; Obsolete # 7.0 KANNADA SIGN CANDRABINDU +0C8C ; Obsolete # 1.1 KANNADA LETTER VOCALIC L +0CB1 ; Obsolete # 1.1 KANNADA LETTER RRA +0CBD ; Obsolete # 4.0 KANNADA SIGN AVAGRAHA +0CDC ; Obsolete # 17.0 KANNADA ARCHAIC SHRII 0CDE ; Obsolete # 1.1 KANNADA LETTER FA +0CE0..0CE1 ; Obsolete # 1.1 [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL +0CE2..0CE3 ; Obsolete # 5.0 [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL +0CF1..0CF2 ; Obsolete # 5.0 [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D01 ; Obsolete # 7.0 MALAYALAM SIGN CANDRABINDU +0D3A ; Obsolete # 6.0 MALAYALAM LETTER TTTA 0D3B..0D3C ; Obsolete # 10.0 [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA +0D3D ; Obsolete # 5.1 MALAYALAM SIGN AVAGRAHA +0D4C ; Obsolete # 1.1 MALAYALAM VOWEL SIGN AU +0D4E ; Obsolete # 6.0 MALAYALAM LETTER DOT REPH 0D5F ; Obsolete # 8.0 MALAYALAM LETTER ARCHAIC II -0DE6..0DEF ; Obsolete # 7.0 [10] SINHALA LITH DIGIT ZERO..SINHALA LITH DIGIT NINE +0D60..0D61 ; Obsolete # 1.1 [2] MALAYALAM LETTER VOCALIC RR..MALAYALAM LETTER VOCALIC LL +0D9E ; Obsolete # 3.0 SINHALA LETTER KANTAJA NAASIKYAYA +0F86..0F8B ; Obsolete # 2.0 [6] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN GRU MED RGYINGS +0F8C..0F8F ; Obsolete # 6.0 [4] TIBETAN SIGN INVERTED MCHU CAN..TIBETAN SUBJOINED SIGN INVERTED MCHU CAN 10A0..10C5 ; Obsolete # 1.1 [38] GEORGIAN CAPITAL LETTER AN..GEORGIAN CAPITAL LETTER HOE 10F1..10F6 ; Obsolete # 1.1 [6] GEORGIAN LETTER HE..GEORGIAN LETTER FI 1100..1159 ; Obsolete # 1.1 [90] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG YEORINHIEUH @@ -1568,6 +4230,7 @@ A95F ; Exclusion Not_XID # 5.1 REJANG SECTION MARK 1369..1371 ; Obsolete # 3.0 [9] ETHIOPIC DIGIT ONE..ETHIOPIC DIGIT NINE 17A8 ; Obsolete # 3.0 KHMER INDEPENDENT VOWEL QUK 17D3 ; Obsolete # 3.0 KHMER SIGN BATHAMASAT +17DC ; Obsolete # 3.0 KHMER SIGN AVAKRAHASANYA 1AB0..1ABD ; Obsolete # 7.0 [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1C80..1C88 ; Obsolete # 9.0 [9] CYRILLIC SMALL LETTER ROUNDED VE..CYRILLIC SMALL LETTER UNBLENDED UK 1CD0..1CD2 ; Obsolete # 5.2 [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA @@ -1575,6 +4238,36 @@ A95F ; Exclusion Not_XID # 5.1 REJANG SECTION MARK 1CF3..1CF6 ; Obsolete # 6.1 [4] VEDIC SIGN ROTATED ARDHAVISARGA..VEDIC SIGN UPADHMANIYA 1CF7 ; Obsolete # 10.0 VEDIC SIGN ATIKRAMA 1CF8..1CF9 ; Obsolete # 7.0 [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE +1F00..1F15 ; Obsolete # 1.1 [22] GREEK SMALL LETTER ALPHA WITH PSILI..GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA +1F18..1F1D ; Obsolete # 1.1 [6] GREEK CAPITAL LETTER EPSILON WITH PSILI..GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA +1F20..1F45 ; Obsolete # 1.1 [38] GREEK SMALL LETTER ETA WITH PSILI..GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA +1F48..1F4D ; Obsolete # 1.1 [6] GREEK CAPITAL LETTER OMICRON WITH PSILI..GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA +1F50..1F57 ; Obsolete # 1.1 [8] GREEK SMALL LETTER UPSILON WITH PSILI..GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI +1F59 ; Obsolete # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA +1F5B ; Obsolete # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA +1F5D ; Obsolete # 1.1 GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA +1F5F..1F70 ; Obsolete # 1.1 [18] GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI..GREEK SMALL LETTER ALPHA WITH VARIA +1F72 ; Obsolete # 1.1 GREEK SMALL LETTER EPSILON WITH VARIA +1F74 ; Obsolete # 1.1 GREEK SMALL LETTER ETA WITH VARIA +1F76 ; Obsolete # 1.1 GREEK SMALL LETTER IOTA WITH VARIA +1F78 ; Obsolete # 1.1 GREEK SMALL LETTER OMICRON WITH VARIA +1F7A ; Obsolete # 1.1 GREEK SMALL LETTER UPSILON WITH VARIA +1F7C ; Obsolete # 1.1 GREEK SMALL LETTER OMEGA WITH VARIA +1F80..1F9F ; Obsolete # 1.1 [32] GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI..GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI +1FB6..1FBA ; Obsolete # 1.1 [5] GREEK SMALL LETTER ALPHA WITH PERISPOMENI..GREEK CAPITAL LETTER ALPHA WITH VARIA +1FBC ; Obsolete # 1.1 GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI +1FC2..1FC4 ; Obsolete # 1.1 [3] GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI +1FC6..1FC8 ; Obsolete # 1.1 [3] GREEK SMALL LETTER ETA WITH PERISPOMENI..GREEK CAPITAL LETTER EPSILON WITH VARIA +1FCA ; Obsolete # 1.1 GREEK CAPITAL LETTER ETA WITH VARIA +1FCC ; Obsolete # 1.1 GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI +1FD0..1FD2 ; Obsolete # 1.1 [3] GREEK SMALL LETTER IOTA WITH VRACHY..GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA +1FD6..1FDA ; Obsolete # 1.1 [5] GREEK SMALL LETTER IOTA WITH PERISPOMENI..GREEK CAPITAL LETTER IOTA WITH VARIA +1FE0..1FE2 ; Obsolete # 1.1 [3] GREEK SMALL LETTER UPSILON WITH VRACHY..GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA +1FE4..1FEA ; Obsolete # 1.1 [7] GREEK SMALL LETTER RHO WITH PSILI..GREEK CAPITAL LETTER UPSILON WITH VARIA +1FF2..1FF4 ; Obsolete # 1.1 [3] GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI..GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI +1FF6..1FF8 ; Obsolete # 1.1 [3] GREEK SMALL LETTER OMEGA WITH PERISPOMENI..GREEK CAPITAL LETTER OMICRON WITH VARIA +1FFA ; Obsolete # 1.1 GREEK CAPITAL LETTER OMEGA WITH VARIA +1FFC ; Obsolete # 1.1 GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI 2132 ; Obsolete # 1.1 TURNED CAPITAL F 214E ; Obsolete # 5.0 TURNED SMALL F 2184 ; Obsolete # 5.0 LATIN SMALL LETTER REVERSED C @@ -1586,13 +4279,12 @@ A95F ; Exclusion Not_XID # 5.1 REJANG SECTION MARK 2C7E..2C7F ; Obsolete # 5.2 [2] LATIN CAPITAL LETTER S WITH SWASH TAIL..LATIN CAPITAL LETTER Z WITH SWASH TAIL 2D00..2D25 ; Obsolete # 4.1 [38] GEORGIAN SMALL LETTER AN..GEORGIAN SMALL LETTER HOE 2DE0..2DFF ; Obsolete # 5.1 [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS -312E ; Obsolete # 10.0 BOPOMOFO LETTER O WITH DOT ABOVE 31F0..31FF ; Obsolete # 3.2 [16] KATAKANA LETTER SMALL KU..KATAKANA LETTER SMALL RO A640..A65F ; Obsolete # 5.1 [32] CYRILLIC CAPITAL LETTER ZEMLYA..CYRILLIC SMALL LETTER YN A660..A661 ; Obsolete # 6.0 [2] CYRILLIC CAPITAL LETTER REVERSED TSE..CYRILLIC SMALL LETTER REVERSED TSE A662..A66E ; Obsolete # 5.1 [13] CYRILLIC CAPITAL LETTER SOFT DE..CYRILLIC LETTER MULTIOCULAR O A674..A67B ; Obsolete # 6.1 [8] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC LETTER OMEGA -A680..A697 ; Obsolete # 5.1 [24] CYRILLIC CAPITAL LETTER DWE..CYRILLIC SMALL LETTER SHWE +A67F..A697 ; Obsolete # 5.1 [25] CYRILLIC PAYEROK..CYRILLIC SMALL LETTER SHWE A698..A69B ; Obsolete # 7.0 [4] CYRILLIC CAPITAL LETTER DOUBLE O..CYRILLIC SMALL LETTER CROSSED O A69F ; Obsolete # 6.1 COMBINING CYRILLIC LETTER IOTIFIED E A730..A76F ; Obsolete # 5.1 [64] LATIN LETTER SMALL CAPITAL F..LATIN SMALL LETTER CON @@ -1602,6 +4294,14 @@ A794..A79F ; Obsolete # 7.0 [12] LATIN SMALL LETTER C A7A0..A7A9 ; Obsolete # 6.0 [10] LATIN CAPITAL LETTER G WITH OBLIQUE STROKE..LATIN SMALL LETTER S WITH OBLIQUE STROKE A7AB..A7AD ; Obsolete # 7.0 [3] LATIN CAPITAL LETTER REVERSED OPEN E..LATIN CAPITAL LETTER L WITH BELT A7B0..A7B1 ; Obsolete # 7.0 [2] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER TURNED T +A7C0..A7C1 ; Obsolete # 14.0 [2] LATIN CAPITAL LETTER OLD POLISH O..LATIN SMALL LETTER OLD POLISH O +A7C4 ; Obsolete # 12.0 LATIN CAPITAL LETTER C WITH PALATAL HOOK +A7C7..A7CA ; Obsolete # 13.0 [4] LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY..LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY +A7D0..A7D1 ; Obsolete # 14.0 [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G +A7D2 ; Obsolete # 17.0 LATIN CAPITAL LETTER DOUBLE THORN +A7D3 ; Obsolete # 14.0 LATIN SMALL LETTER DOUBLE THORN +A7D4 ; Obsolete # 17.0 LATIN CAPITAL LETTER DOUBLE WYNN +A7D5..A7D9 ; Obsolete # 14.0 [5] LATIN SMALL LETTER DOUBLE WYNN..LATIN SMALL LETTER SIGMOID S A7F5..A7F6 ; Obsolete # 13.0 [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Obsolete # 7.0 LATIN EPIGRAPHIC LETTER SIDEWAYS I A7FB..A7FF ; Obsolete # 5.1 [5] LATIN EPIGRAPHIC LETTER REVERSED F..LATIN EPIGRAPHIC LETTER ARCHAIC M @@ -1618,10 +4318,17 @@ D7CB..D7FB ; Obsolete # 5.2 [49] HANGUL JONGSEONG NIE 101FD ; Obsolete # 5.1 PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE 102E0 ; Obsolete # 7.0 COPTIC EPACT THOUSANDS MARK 16FE3 ; Obsolete # 12.0 OLD CHINESE ITERATION MARK +16FF0..16FF1 ; Obsolete # 13.0 [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY 1B000..1B001 ; Obsolete # 6.0 [2] KATAKANA LETTER ARCHAIC E..HIRAGANA LETTER ARCHAIC YE 1B002..1B11E ; Obsolete # 10.0 [285] HENTAIGANA LETTER A-1..HENTAIGANA LETTER N-MU-MO-2 +1B11F..1B122 ; Obsolete # 14.0 [4] HIRAGANA LETTER ARCHAIC WU..KATAKANA LETTER ARCHAIC WU +1B132 ; Obsolete # 15.0 HIRAGANA LETTER SMALL KO +1B150..1B152 ; Obsolete # 12.0 [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155 ; Obsolete # 15.0 KATAKANA LETTER SMALL KO +1B164..1B167 ; Obsolete # 12.0 [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N +1E08F ; Obsolete # 15.0 COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -# Total code points: 1341 +# Total code points: 1639 # Identifier_Type: Obsolete Not_XID @@ -1677,7 +4384,6 @@ A8F8..A8FA ; Obsolete Not_XID # 5.2 [3] DEVANAGARI SIGN PUSH 02DE ; Not_XID # 1.1 MODIFIER LETTER RHOTIC HOOK 02DF ; Not_XID # 3.0 MODIFIER LETTER CROSS ACCENT 02E5..02E9 ; Not_XID # 1.1 [5] MODIFIER LETTER EXTRA-HIGH TONE BAR..MODIFIER LETTER EXTRA-LOW TONE BAR -02EA..02EB ; Not_XID # 3.0 [2] MODIFIER LETTER YIN DEPARTING TONE MARK..MODIFIER LETTER YANG DEPARTING TONE MARK 02ED ; Not_XID # 3.0 MODIFIER LETTER UNASPIRATED 02EF..02FF ; Not_XID # 4.0 [17] MODIFIER LETTER LOW DOWN ARROWHEAD..MODIFIER LETTER LOW LEFT ARROW 03F6 ; Not_XID # 3.2 GREEK REVERSED LUNATE EPSILON SYMBOL @@ -1704,7 +4410,6 @@ A8F8..A8FA ; Obsolete Not_XID # 5.2 [3] DEVANAGARI SIGN PUSH 06DD ; Not_XID # 1.1 ARABIC END OF AYAH 06DE ; Not_XID # 1.1 ARABIC START OF RUB EL HIZB 06E9 ; Not_XID # 1.1 ARABIC PLACE OF SAJDAH -0888 ; Not_XID # 14.0 ARABIC RAISED ROUND DOT 0890..0891 ; Not_XID # 14.0 [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE 08E2 ; Not_XID # 9.0 ARABIC DISPUTED END OF AYAH 0964..0965 ; Not_XID # 1.1 [2] DEVANAGARI DANDA..DEVANAGARI DOUBLE DANDA @@ -1786,6 +4491,7 @@ A8F8..A8FA ; Obsolete Not_XID # 5.2 [3] DEVANAGARI SIGN PUSH 20BE ; Not_XID # 8.0 LARI SIGN 20BF ; Not_XID # 10.0 BITCOIN SIGN 20C0 ; Not_XID # 14.0 SOM SIGN +20C1 ; Not_XID # 17.0 SAUDI RIYAL SIGN 2104 ; Not_XID # 1.1 CENTRE LINE SYMBOL 2108 ; Not_XID # 1.1 SCRUPLE 2114 ; Not_XID # 1.1 L B BAR SYMBOL @@ -1907,6 +4613,7 @@ A8F8..A8FA ; Obsolete Not_XID # 5.2 [3] DEVANAGARI SIGN PUSH 2B55..2B59 ; Not_XID # 5.2 [5] HEAVY LARGE CIRCLE..HEAVY CIRCLED SALTIRE 2B5A..2B73 ; Not_XID # 7.0 [26] SLANTED NORTH ARROW WITH HOOKED HEAD..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR 2B76..2B95 ; Not_XID # 7.0 [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW +2B96 ; Not_XID # 17.0 EQUALS SIGN WITH INFINITY ABOVE 2B97 ; Not_XID # 13.0 SYMBOL FOR TYPE A ELECTRONICS 2B98..2BB9 ; Not_XID # 7.0 [34] THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD..UP ARROWHEAD IN A RECTANGLE BOX 2BBA..2BBC ; Not_XID # 11.0 [3] OVERLAPPING WHITE SQUARES..OVERLAPPING BLACK SQUARES @@ -1968,13 +4675,17 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1019C ; Not_XID # 13.0 ASCIA SYMBOL 101A0 ; Not_XID # 7.0 GREEK SYMBOL TAU RHO 10E60..10E7E ; Not_XID # 5.2 [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS +10ED0..10ED8 ; Not_XID # 17.0 [9] ARABIC BIBLICAL END OF VERSE..ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH 111E1..111F4 ; Not_XID # 7.0 [20] SINHALA ARCHAIC DIGIT ONE..SINHALA ARCHAIC NUMBER ONE THOUSAND 11B00..11B09 ; Not_XID # 15.0 [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU 11FC0..11FF1 ; Not_XID # 12.0 [50] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL SIGN VAKAIYARAA 11FFF ; Not_XID # 12.0 TAMIL PUNCTUATION END OF TEXT 16FE2 ; Not_XID # 12.0 OLD CHINESE HOOK MARK 1CC00..1CCD5 ; Not_XID # 16.0 [214] UP-POINTING GO-KART..LOWER RIGHT QUADRANT STANDING KNIGHT +1CCFA..1CCFC ; Not_XID # 17.0 [3] SNAKE SYMBOL..NOSE SYMBOL 1CD00..1CEB3 ; Not_XID # 16.0 [436] BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET +1CEBA..1CED0 ; Not_XID # 17.0 [23] FRAGILE SYMBOL..LEUKOTHEA +1CEE0..1CEF0 ; Not_XID # 17.0 [17] GEOMANTIC FIGURE POPULUS..MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1D2C0..1D2D3 ; Not_XID # 15.0 [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN 1D2E0..1D2F3 ; Not_XID # 11.0 [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN 1D360..1D371 ; Not_XID # 5.0 [18] COUNTING ROD UNIT DIGIT ONE..COUNTING ROD TENS DIGIT NINE @@ -2086,6 +4797,7 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1F6D3..1F6D4 ; Not_XID # 10.0 [2] STUPA..PAGODA 1F6D5 ; Not_XID # 12.0 HINDU TEMPLE 1F6D6..1F6D7 ; Not_XID # 13.0 [2] HUT..ELEVATOR +1F6D8 ; Not_XID # 17.0 LANDSLIDE 1F6DC ; Not_XID # 15.0 WIRELESS 1F6DD..1F6DF ; Not_XID # 14.0 [3] PLAYGROUND SLIDE..RING BUOY 1F6E0..1F6EC ; Not_XID # 7.0 [13] HAMMER AND WRENCH..AIRPLANE ARRIVING @@ -2097,6 +4809,7 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1F6FB..1F6FC ; Not_XID # 13.0 [2] PICKUP TRUCK..ROLLER SKATE 1F700..1F773 ; Not_XID # 6.0 [116] ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE 1F774..1F776 ; Not_XID # 15.0 [3] LOT OF FORTUNE..LUNAR ECLIPSE +1F777..1F77A ; Not_XID # 17.0 [4] VESTA FORM TWO..PARTHENOPE FORM TWO 1F77B..1F77F ; Not_XID # 15.0 [5] HAUMEA..ORCUS 1F780..1F7D4 ; Not_XID # 7.0 [85] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..HEAVY TWELVE POINTED PINWHEEL STAR 1F7D5..1F7D8 ; Not_XID # 11.0 [4] CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE @@ -2111,6 +4824,7 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1F8B0..1F8B1 ; Not_XID # 13.0 [2] ARROW POINTING UPWARDS THEN NORTH WEST..ARROW POINTING RIGHTWARDS THEN CURVING SOUTH WEST 1F8B2..1F8BB ; Not_XID # 16.0 [10] RIGHTWARDS ARROW WITH LOWER HOOK..SOUTH WEST ARROW FROM BAR 1F8C0..1F8C1 ; Not_XID # 16.0 [2] LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW +1F8D0..1F8D8 ; Not_XID # 17.0 [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE 1F900..1F90B ; Not_XID # 10.0 [12] CIRCLED CROSS FORMEE WITH FOUR DOTS..DOWNWARD FACING NOTCHED HOOK WITH DOT 1F90C ; Not_XID # 13.0 PINCHED FINGERS 1F90D..1F90F ; Not_XID # 12.0 [3] WHITE HEART..PINCHING HAND @@ -2156,6 +4870,7 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1F9D0..1F9E6 ; Not_XID # 10.0 [23] FACE WITH MONOCLE..SOCKS 1F9E7..1F9FF ; Not_XID # 11.0 [25] RED GIFT ENVELOPE..NAZAR AMULET 1FA00..1FA53 ; Not_XID # 12.0 [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP +1FA54..1FA57 ; Not_XID # 17.0 [4] WHITE CHESS FERZ..BLACK CHESS ALFIL 1FA60..1FA6D ; Not_XID # 11.0 [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER 1FA70..1FA73 ; Not_XID # 12.0 [4] BALLET SHOES..SHORTS 1FA74 ; Not_XID # 13.0 THONG SANDAL @@ -2166,6 +4881,8 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1FA83..1FA86 ; Not_XID # 13.0 [4] BOOMERANG..NESTING DOLLS 1FA87..1FA88 ; Not_XID # 15.0 [2] MARACAS..FLUTE 1FA89 ; Not_XID # 16.0 HARP +1FA8A ; Not_XID # 17.0 TROMBONE +1FA8E ; Not_XID # 17.0 TREASURE CHEST 1FA8F ; Not_XID # 16.0 SHOVEL 1FA90..1FA95 ; Not_XID # 12.0 [6] RINGED PLANET..BANJO 1FA96..1FAA8 ; Not_XID # 13.0 [19] MILITARY HELMET..ROCK @@ -2179,6 +4896,8 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1FAC0..1FAC2 ; Not_XID # 13.0 [3] ANATOMICAL HEART..PEOPLE HUGGING 1FAC3..1FAC5 ; Not_XID # 14.0 [3] PREGNANT MAN..PERSON WITH CROWN 1FAC6 ; Not_XID # 16.0 FINGERPRINT +1FAC8 ; Not_XID # 17.0 HAIRY CREATURE +1FACD ; Not_XID # 17.0 ORCA 1FACE..1FACF ; Not_XID # 15.0 [2] MOOSE..DONKEY 1FAD0..1FAD6 ; Not_XID # 13.0 [7] BLUEBERRIES..TEAPOT 1FAD7..1FAD9 ; Not_XID # 14.0 [3] POURING LIQUID..JAR @@ -2188,13 +4907,16 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 1FAE0..1FAE7 ; Not_XID # 14.0 [8] MELTING FACE..BUBBLES 1FAE8 ; Not_XID # 15.0 SHAKING FACE 1FAE9 ; Not_XID # 16.0 FACE WITH BAGS UNDER EYES +1FAEA ; Not_XID # 17.0 DISTORTED FACE +1FAEF ; Not_XID # 17.0 FIGHT CLOUD 1FAF0..1FAF6 ; Not_XID # 14.0 [7] HAND WITH INDEX FINGER AND THUMB CROSSED..HEART HANDS 1FAF7..1FAF8 ; Not_XID # 15.0 [2] LEFTWARDS PUSHING HAND..RIGHTWARDS PUSHING HAND 1FB00..1FB92 ; Not_XID # 13.0 [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK 1FB94..1FBCA ; Not_XID # 13.0 [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON 1FBCB..1FBEF ; Not_XID # 16.0 [37] WHITE CROSS MARK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE +1FBFA ; Not_XID # 17.0 ALARM BELL SYMBOL -# Total code points: 6411 +# Total code points: 6487 # Identifier_Type: Not_NFKC @@ -2366,6 +5088,7 @@ FFFD ; Not_XID # 1.1 REPLACEMENT CHARACTE 33FF ; Not_NFKC # 4.0 SQUARE GAL A69C..A69D ; Not_NFKC # 7.0 [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN A770 ; Not_NFKC # 5.1 MODIFIER LETTER US +A7F1 ; Not_NFKC # 17.0 MODIFIER LETTER CAPITAL S A7F2..A7F4 ; Not_NFKC # 14.0 [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q A7F8..A7F9 ; Not_NFKC # 6.1 [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE AB5C..AB5F ; Not_NFKC # 7.0 [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK @@ -2508,7 +5231,7 @@ FFE8..FFEE ; Not_NFKC # 1.1 [7] HALFWIDTH FORMS LIGH 1FBF0..1FBF9 ; Not_NFKC # 13.0 [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 2F800..2FA1D ; Not_NFKC # 3.1 [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D -# Total code points: 4957 +# Total code points: 4958 # Identifier_Type: Default_Ignorable diff --git a/lib/elixir/unicode/PropList.txt b/lib/elixir/unicode/PropList.txt index fae2831e7a5..e64b4224d72 100644 --- a/lib/elixir/unicode/PropList.txt +++ b/lib/elixir/unicode/PropList.txt @@ -1,6 +1,6 @@ -# PropList-16.0.0.txt -# Date: 2024-05-31, 18:09:48 GMT -# © 2024 Unicode®, Inc. +# PropList-17.0.0.txt +# Date: 2025-06-30, 06:19:01 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -702,7 +702,7 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA 10D24..10D27 ; Other_Alphabetic # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D69 ; Other_Alphabetic # Mn GARAY VOWEL SIGN E 10EAB..10EAC ; Other_Alphabetic # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC ; Other_Alphabetic # Mn ARABIC COMBINING ALEF OVERLAY +10EFA..10EFC ; Other_Alphabetic # Mn [3] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC COMBINING ALEF OVERLAY 11000 ; Other_Alphabetic # Mc BRAHMI SIGN CANDRABINDU 11001 ; Other_Alphabetic # Mn BRAHMI SIGN ANUSVARA 11002 ; Other_Alphabetic # Mc BRAHMI SIGN VISARGA @@ -809,6 +809,12 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA 11A59..11A5B ; Other_Alphabetic # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK 11A8A..11A96 ; Other_Alphabetic # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A97 ; Other_Alphabetic # Mc SOYOMBO SIGN VISARGA +11B60 ; Other_Alphabetic # Mn SHARADA VOWEL SIGN OE +11B61 ; Other_Alphabetic # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Other_Alphabetic # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Other_Alphabetic # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Other_Alphabetic # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Other_Alphabetic # Mc SHARADA VOWEL SIGN CANDRA O 11C2F ; Other_Alphabetic # Mc BHAIKSUKI VOWEL SIGN AA 11C30..11C36 ; Other_Alphabetic # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Other_Alphabetic # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA @@ -853,12 +859,16 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA 1E023..1E024 ; Other_Alphabetic # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS 1E026..1E02A ; Other_Alphabetic # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA 1E08F ; Other_Alphabetic # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +1E6E3 ; Other_Alphabetic # Mn TAI YO SIGN UE +1E6E6 ; Other_Alphabetic # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Other_Alphabetic # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Other_Alphabetic # Mn TAI YO SIGN OM 1E947 ; Other_Alphabetic # Mn ADLAM HAMZA 1F130..1F149 ; Other_Alphabetic # So [26] SQUARED LATIN CAPITAL LETTER A..SQUARED LATIN CAPITAL LETTER Z 1F150..1F169 ; Other_Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Other_Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 1495 +# Total code points: 1510 # ================================================ @@ -871,21 +881,22 @@ FB1E ; Other_Alphabetic # Mn HEBREW POINT JUDEO-SPANISH VARIKA F900..FA6D ; Ideographic # Lo [366] CJK COMPATIBILITY IDEOGRAPH-F900..CJK COMPATIBILITY IDEOGRAPH-FA6D FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILITY IDEOGRAPH-FAD9 16FE4 ; Ideographic # Mn KHITAN SMALL SCRIPT FILLER -17000..187F7 ; Ideographic # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; Ideographic # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; Ideographic # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; Ideographic # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Ideographic # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; Ideographic # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; Ideographic # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Ideographic # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1B170..1B2FB ; Ideographic # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB 20000..2A6DF ; Ideographic # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Ideographic # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Ideographic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Ideographic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Ideographic # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Ideographic # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Ideographic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Ideographic # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Ideographic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Ideographic # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Ideographic # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Ideographic # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 106477 +# Total code points: 110943 # ================================================ @@ -915,11 +926,11 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM 0384..0385 ; Diacritic # Sk [2] GREEK TONOS..GREEK DIALYTIKA TONOS 0483..0487 ; Diacritic # Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE 0559 ; Diacritic # Lm ARMENIAN MODIFIER LETTER LEFT HALF RING -0591..05A1 ; Diacritic # Mn [17] HEBREW ACCENT ETNAHTA..HEBREW ACCENT PAZER -05A3..05BD ; Diacritic # Mn [27] HEBREW ACCENT MUNAH..HEBREW POINT METEG +0591..05BD ; Diacritic # Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG 05BF ; Diacritic # Mn HEBREW POINT RAFE 05C1..05C2 ; Diacritic # Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT -05C4 ; Diacritic # Mn HEBREW MARK UPPER DOT +05C4..05C5 ; Diacritic # Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT +05C7 ; Diacritic # Mn HEBREW POINT QAMATS QATAN 064B..0652 ; Diacritic # Mn [8] ARABIC FATHATAN..ARABIC SUKUN 0657..0658 ; Diacritic # Mn [2] ARABIC INVERTED DAMMA..ARABIC MARK NOON GHUNNA 06DF..06E0 ; Diacritic # Mn [2] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO @@ -990,6 +1001,8 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM 1AB0..1ABD ; Diacritic # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Diacritic # Me COMBINING PARENTHESES OVERLAY 1AC1..1ACB ; Diacritic # Mn [11] COMBINING LEFT PARENTHESIS ABOVE LEFT..COMBINING TRIPLE ACUTE ACCENT +1ACF..1ADD ; Diacritic # Mn [15] COMBINING DOUBLE CARON..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Diacritic # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B34 ; Diacritic # Mn BALINESE SIGN REREKAN 1B44 ; Diacritic # Mc BALINESE ADEG ADEG 1B6B..1B73 ; Diacritic # Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG @@ -1009,6 +1022,7 @@ FA70..FAD9 ; Ideographic # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COM 1CF7 ; Diacritic # Mc VEDIC SIGN ATIKRAMA 1CF8..1CF9 ; Diacritic # Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE 1D2C..1D6A ; Diacritic # Lm [63] MODIFIER LETTER CAPITAL A..GREEK SUBSCRIPT SMALL LETTER CHI +1D9B..1DBE ; Diacritic # Lm [36] MODIFIER LETTER SMALL TURNED ALPHA..MODIFIER LETTER SMALL EZH 1DC4..1DCF ; Diacritic # Mn [12] COMBINING MACRON-ACUTE..COMBINING ZIGZAG BELOW 1DF5..1DFF ; Diacritic # Mn [11] COMBINING UP TACK ABOVE..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW 1FBD ; Diacritic # Sk GREEK KORONIS @@ -1034,6 +1048,7 @@ A717..A71F ; Diacritic # Lm [9] MODIFIER LETTER DOT VERTICAL BAR..MODIFIER A720..A721 ; Diacritic # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE..MODIFIER LETTER STRESS AND LOW TONE A788 ; Diacritic # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A789..A78A ; Diacritic # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN +A7F1 ; Diacritic # Lm MODIFIER LETTER CAPITAL S A7F8..A7F9 ; Diacritic # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A806 ; Diacritic # Mn SYLOTI NAGRI SIGN HASANTA A82C ; Diacritic # Mn SYLOTI NAGRI SIGN ALTERNATE HASANTA @@ -1077,6 +1092,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON 10D24..10D27 ; Diacritic # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D4E ; Diacritic # Lm GARAY VOWEL LENGTH MARK 10D69..10D6D ; Diacritic # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK +10EFA ; Diacritic # Mn ARABIC DOUBLE VERTICAL BAR BELOW 10EFD..10EFF ; Diacritic # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Diacritic # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Diacritic # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW @@ -1120,6 +1136,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON 11D42 ; Diacritic # Mn MASARAM GONDI SIGN NUKTA 11D44..11D45 ; Diacritic # Mn [2] MASARAM GONDI SIGN HALANTA..MASARAM GONDI VIRAMA 11D97 ; Diacritic # Mn GUNJALA GONDI VIRAMA +11DD9 ; Diacritic # Lm TOLONG SIKI SIGN SELA 11F41 ; Diacritic # Mc KAWI SIGN KILLER 11F42 ; Diacritic # Mn KAWI CONJOINER 11F5A ; Diacritic # Mn KAWI SIGN NUKTA @@ -1150,7 +1167,7 @@ FFE3 ; Diacritic # Sk FULLWIDTH MACRON 1E944..1E946 ; Diacritic # Mn [3] ADLAM ALIF LENGTHENER..ADLAM GEMINATION MARK 1E948..1E94A ; Diacritic # Mn [3] ADLAM CONSONANT MODIFIER..ADLAM NUKTA -# Total code points: 1178 +# Total code points: 1247 # ================================================ @@ -1190,14 +1207,16 @@ FF70 ; Extender # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND 113D3 ; Extender # Lo TULU-TIGALARI SIGN PLUTA 115C6..115C8 ; Extender # Po [3] SIDDHAM REPETITION MARK-1..SIDDHAM REPETITION MARK-3 11A98 ; Extender # Mn SOYOMBO GEMINATION MARK +11DD9 ; Extender # Lm TOLONG SIKI SIGN SELA 16B42..16B43 ; Extender # Lm [2] PAHAWH HMONG SIGN VOS NRUA..PAHAWH HMONG SIGN IB YAM 16FE0..16FE1 ; Extender # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; Extender # Lm OLD CHINESE ITERATION MARK +16FF2..16FF3 ; Extender # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER 1E13C..1E13D ; Extender # Lm [2] NYIAKENG PUACHUE HMONG SIGN XW XW..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER 1E5EF ; Extender # Mn OL ONAL SIGN IKIR 1E944..1E946 ; Extender # Mn [3] ADLAM ALIF LENGTHENER..ADLAM GEMINATION MARK -# Total code points: 59 +# Total code points: 62 # ================================================ @@ -1220,7 +1239,7 @@ FF70 ; Extender # Lm HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND 2C7C..2C7D ; Other_Lowercase # Lm [2] LATIN SUBSCRIPT SMALL LETTER J..MODIFIER LETTER CAPITAL V A69C..A69D ; Other_Lowercase # Lm [2] MODIFIER LETTER CYRILLIC HARD SIGN..MODIFIER LETTER CYRILLIC SOFT SIGN A770 ; Other_Lowercase # Lm MODIFIER LETTER US -A7F2..A7F4 ; Other_Lowercase # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F1..A7F4 ; Other_Lowercase # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F8..A7F9 ; Other_Lowercase # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE AB5C..AB5F ; Other_Lowercase # Lm [4] MODIFIER LETTER SMALL HENG..MODIFIER LETTER SMALL U WITH LEFT HOOK AB69 ; Other_Lowercase # Lm MODIFIER LETTER SMALL TURNED W @@ -1230,7 +1249,7 @@ AB69 ; Other_Lowercase # Lm MODIFIER LETTER SMALL TURNED W 107B2..107BA ; Other_Lowercase # Lm [9] MODIFIER LETTER SMALL CAPITAL Y..MODIFIER LETTER SMALL S WITH CURL 1E030..1E06D ; Other_Lowercase # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE -# Total code points: 311 +# Total code points: 312 # ================================================ @@ -1359,15 +1378,14 @@ FA21 ; Unified_Ideograph # Lo CJK COMPATIBILITY IDEOGRAPH-FA21 FA23..FA24 ; Unified_Ideograph # Lo [2] CJK COMPATIBILITY IDEOGRAPH-FA23..CJK COMPATIBILITY IDEOGRAPH-FA24 FA27..FA29 ; Unified_Ideograph # Lo [3] CJK COMPATIBILITY IDEOGRAPH-FA27..CJK COMPATIBILITY IDEOGRAPH-FA29 20000..2A6DF ; Unified_Ideograph # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Unified_Ideograph # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Unified_Ideograph # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Unified_Ideograph # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Unified_Ideograph # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Unified_Ideograph # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Unified_Ideograph # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Unified_Ideograph # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 30000..3134A ; Unified_Ideograph # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Unified_Ideograph # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Unified_Ideograph # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 97680 +# Total code points: 101996 # ================================================ @@ -1809,9 +1827,7 @@ E0100..E01EF ; Variation_Selector # Mn [240] VARIATION SELECTOR-17..VARIATION S 2B47..2B4C ; Pattern_Syntax # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR 2B4D..2B73 ; Pattern_Syntax # So [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR 2B74..2B75 ; Pattern_Syntax # Cn [2] .. -2B76..2B95 ; Pattern_Syntax # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B96 ; Pattern_Syntax # Cn -2B97..2BFF ; Pattern_Syntax # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Pattern_Syntax # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Pattern_Syntax # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Pattern_Syntax # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Pattern_Syntax # Pf RIGHT SUBSTITUTION BRACKET diff --git a/lib/elixir/unicode/PropertyValueAliases.txt b/lib/elixir/unicode/PropertyValueAliases.txt index 01c6f659a4f..b92662eda28 100644 --- a/lib/elixir/unicode/PropertyValueAliases.txt +++ b/lib/elixir/unicode/PropertyValueAliases.txt @@ -1,6 +1,6 @@ -# PropertyValueAliases-16.0.0.txt -# Date: 2024-07-30, 19:59:00 GMT -# © 2024 Unicode®, Inc. +# PropertyValueAliases-17.0.0.txt +# Date: 2025-06-30, 06:16:21 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -93,6 +93,7 @@ age; 14.0 ; V14_0 age; 15.0 ; V15_0 age; 15.1 ; V15_1 age; 16.0 ; V16_0 +age; 17.0 ; V17_0 age; NA ; Unassigned # Alphabetic (Alpha) @@ -179,6 +180,7 @@ blk; Bamum_Sup ; Bamum_Supplement blk; Bassa_Vah ; Bassa_Vah blk; Batak ; Batak blk; Bengali ; Bengali +blk; Beria_Erfe ; Beria_Erfe blk; Bhaiksuki ; Bhaiksuki blk; Block_Elements ; Block_Elements blk; Bopomofo ; Bopomofo @@ -211,6 +213,7 @@ blk; CJK_Ext_F ; CJK_Unified_Ideographs_Extension_F blk; CJK_Ext_G ; CJK_Unified_Ideographs_Extension_G blk; CJK_Ext_H ; CJK_Unified_Ideographs_Extension_H blk; CJK_Ext_I ; CJK_Unified_Ideographs_Extension_I +blk; CJK_Ext_J ; CJK_Unified_Ideographs_Extension_J blk; CJK_Radicals_Sup ; CJK_Radicals_Supplement blk; CJK_Strokes ; CJK_Strokes blk; CJK_Symbols ; CJK_Symbols_And_Punctuation @@ -360,6 +363,7 @@ blk; Misc_Math_Symbols_A ; Miscellaneous_Mathematical_Symbols_A blk; Misc_Math_Symbols_B ; Miscellaneous_Mathematical_Symbols_B blk; Misc_Pictographs ; Miscellaneous_Symbols_And_Pictographs blk; Misc_Symbols ; Miscellaneous_Symbols +blk; Misc_Symbols_Sup ; Miscellaneous_Symbols_Supplement blk; Misc_Technical ; Miscellaneous_Technical blk; Modi ; Modi blk; Modifier_Letters ; Spacing_Modifier_Letters @@ -419,9 +423,11 @@ blk; Runic ; Runic blk; Samaritan ; Samaritan blk; Saurashtra ; Saurashtra blk; Sharada ; Sharada +blk; Sharada_Sup ; Sharada_Supplement blk; Shavian ; Shavian blk; Shorthand_Format_Controls ; Shorthand_Format_Controls blk; Siddham ; Siddham +blk; Sidetic ; Sidetic blk; Sinhala ; Sinhala blk; Sinhala_Archaic_Numbers ; Sinhala_Archaic_Numbers blk; Small_Forms ; Small_Form_Variants @@ -456,12 +462,14 @@ blk; Tai_Le ; Tai_Le blk; Tai_Tham ; Tai_Tham blk; Tai_Viet ; Tai_Viet blk; Tai_Xuan_Jing ; Tai_Xuan_Jing_Symbols +blk; Tai_Yo ; Tai_Yo blk; Takri ; Takri blk; Tamil ; Tamil blk; Tamil_Sup ; Tamil_Supplement blk; Tangsa ; Tangsa blk; Tangut ; Tangut blk; Tangut_Components ; Tangut_Components +blk; Tangut_Components_Sup ; Tangut_Components_Supplement blk; Tangut_Sup ; Tangut_Supplement blk; Telugu ; Telugu blk; Thaana ; Thaana @@ -470,6 +478,7 @@ blk; Tibetan ; Tibetan blk; Tifinagh ; Tifinagh blk; Tirhuta ; Tirhuta blk; Todhri ; Todhri +blk; Tolong_Siki ; Tolong_Siki blk; Toto ; Toto blk; Transport_And_Map ; Transport_And_Map_Symbols blk; Tulu_Tigalari ; Tulu_Tigalari @@ -878,7 +887,7 @@ InPC; Bottom_And_Left ; Bottom_And_Left InPC; Bottom_And_Right ; Bottom_And_Right InPC; Left ; Left InPC; Left_And_Right ; Left_And_Right -InPC; NA ; NA +InPC; NA ; Not_Applicable InPC; Overstruck ; Overstruck InPC; Right ; Right InPC; Top ; Top @@ -1088,6 +1097,7 @@ jg ; Taw ; Taw jg ; Teh_Marbuta ; Teh_Marbuta jg ; Teh_Marbuta_Goal ; Teh_Marbuta_Goal ; Hamza_On_Heh_Goal jg ; Teth ; Teth +jg ; Thin_Noon ; Thin_Noon jg ; Thin_Yeh ; Thin_Yeh jg ; Vertical_Tail ; Vertical_Tail jg ; Waw ; Waw @@ -1131,6 +1141,7 @@ lb ; EX ; Exclamation lb ; GL ; Glue lb ; H2 ; H2 lb ; H3 ; H3 +lb ; HH ; Unambiguous_Hyphen lb ; HL ; Hebrew_Letter lb ; HY ; Hyphen lb ; ID ; Ideographic @@ -1319,6 +1330,7 @@ sc ; Bamu ; Bamum sc ; Bass ; Bassa_Vah sc ; Batk ; Batak sc ; Beng ; Bengali +sc ; Berf ; Beria_Erfe sc ; Bhks ; Bhaiksuki sc ; Bopo ; Bopomofo sc ; Brah ; Brahmi @@ -1438,6 +1450,7 @@ sc ; Sgnw ; SignWriting sc ; Shaw ; Shavian sc ; Shrd ; Sharada sc ; Sidd ; Siddham +sc ; Sidt ; Sidetic sc ; Sind ; Khudawadi sc ; Sinh ; Sinhala sc ; Sogd ; Sogdian @@ -1455,6 +1468,7 @@ sc ; Talu ; New_Tai_Lue sc ; Taml ; Tamil sc ; Tang ; Tangut sc ; Tavt ; Tai_Viet +sc ; Tayo ; Tai_Yo sc ; Telu ; Telugu sc ; Tfng ; Tifinagh sc ; Tglg ; Tagalog @@ -1464,6 +1478,7 @@ sc ; Tibt ; Tibetan sc ; Tirh ; Tirhuta sc ; Tnsa ; Tangsa sc ; Todr ; Todhri +sc ; Tols ; Tolong_Siki sc ; Toto ; Toto sc ; Tutg ; Tulu_Tigalari sc ; Ugar ; Ugaritic @@ -1705,4 +1720,16 @@ kEH_NoMirror; Y ; Yes ; T kEH_NoRotate; N ; No ; F ; False kEH_NoRotate; Y ; Yes ; T ; True +# kMandarin (cjkMandarin) + +# @missing: 0000..10FFFF; kMandarin; + +# kTotalStrokes (cjkTotalStrokes) + +# @missing: 0000..10FFFF; kTotalStrokes; + +# kUnihanCore2020 (cjkUnihanCore2020) + +# @missing: 0000..10FFFF; kUnihanCore2020; + # EOF diff --git a/lib/elixir/unicode/ScriptExtensions.txt b/lib/elixir/unicode/ScriptExtensions.txt index 140901a872c..98b8d0fb06e 100644 --- a/lib/elixir/unicode/ScriptExtensions.txt +++ b/lib/elixir/unicode/ScriptExtensions.txt @@ -1,6 +1,6 @@ -# ScriptExtensions-16.0.0.txt -# Date: 2024-07-30, 19:38:00 GMT -# © 2024 Unicode®, Inc. +# ScriptExtensions-17.0.0.txt +# Date: 2025-08-01, 21:42:00 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -38,9 +38,9 @@ 0303 ; Glag Latn Sunu Syrc Thai # Mn COMBINING TILDE 0304 ; Aghb Cher Copt Cyrl Goth Grek Latn Osge Syrc Tfng Todr #Mn COMBINING MACRON 0305 ; Copt Elba Glag Goth Kana Latn # Mn COMBINING OVERLINE -0306 ; Cyrl Grek Latn Perm # Mn COMBINING BREVE +0306 ; Cyrl Grek Latn Perm Tfng # Mn COMBINING BREVE 0307 ; Copt Dupl Hebr Latn Perm Syrc Tale Tfng Todr #Mn COMBINING DOT ABOVE -0308 ; Armn Cyrl Dupl Goth Grek Hebr Latn Perm Syrc Tale #Mn COMBINING DIAERESIS +0308 ; Armn Cyrl Dupl Goth Grek Hebr Latn Perm Syrc Tale Tfng #Mn COMBINING DIAERESIS 0309 ; Latn Tfng # Mn COMBINING HOOK ABOVE 030A ; Dupl Latn Syrc # Mn COMBINING RING ABOVE 030B ; Cher Cyrl Latn Osge # Mn COMBINING DOUBLE ACUTE ACCENT @@ -50,14 +50,13 @@ 0310 ; Latn Sunu # Mn COMBINING CANDRABINDU 0311 ; Cyrl Latn Todr # Mn COMBINING INVERTED BREVE 0313 ; Grek Latn Perm Todr # Mn COMBINING COMMA ABOVE -0320 ; Latn Syrc # Mn COMBINING MINUS SIGN BELOW -0323 ; Cher Dupl Kana Latn Syrc # Mn COMBINING DOT BELOW +0323 ; Cher Dupl Kana Latn Syrc Tfng # Mn COMBINING DOT BELOW 0324 ; Cher Dupl Latn Syrc # Mn COMBINING DIAERESIS BELOW 0325 ; Latn Syrc # Mn COMBINING RING BELOW 032D ; Latn Sunu Syrc # Mn COMBINING CIRCUMFLEX ACCENT BELOW 032E ; Latn Syrc # Mn COMBINING BREVE BELOW 0330 ; Cher Latn Syrc # Mn COMBINING TILDE BELOW -0331 ; Aghb Cher Goth Latn Sunu Thai # Mn COMBINING MACRON BELOW +0331 ; Aghb Cher Goth Latn Sunu Syrc Thai #Mn COMBINING MACRON BELOW 0342 ; Grek # Mn COMBINING GREEK PERISPOMENI 0345 ; Grek # Mn COMBINING GREEK YPOGEGRAMMENI 0358 ; Latn Osge # Mn COMBINING DOT ABOVE RIGHT @@ -79,8 +78,8 @@ 0660..0669 ; Arab Thaa Yezi # Nd [10] ARABIC-INDIC DIGIT ZERO..ARABIC-INDIC DIGIT NINE 0670 ; Arab Syrc # Mn ARABIC LETTER SUPERSCRIPT ALEF 06D4 ; Arab Rohg # Po ARABIC FULL STOP -0951 ; Beng Deva Gran Gujr Guru Knda Latn Mlym Orya Shrd Taml Telu Tirh #Mn DEVANAGARI STRESS SIGN UDATTA -0952 ; Beng Deva Gran Gujr Guru Knda Latn Mlym Orya Taml Telu Tirh #Mn DEVANAGARI STRESS SIGN ANUDATTA +0951 ; Beng Deva Gran Gujr Guru Knda Latn Mlym Nand Newa Orya Shrd Taml Telu Tirh #Mn DEVANAGARI STRESS SIGN UDATTA +0952 ; Beng Deva Gran Gujr Guru Knda Latn Mlym Newa Orya Taml Telu Tirh #Mn DEVANAGARI STRESS SIGN ANUDATTA 0964 ; Beng Deva Dogr Gong Gonm Gran Gujr Guru Knda Mahj Mlym Nand Onao Orya Sind Sinh Sylo Takr Taml Telu Tirh #Po DEVANAGARI DANDA 0965 ; Beng Deva Dogr Gong Gonm Gran Gujr Gukh Guru Knda Limb Mahj Mlym Nand Onao Orya Sind Sinh Sylo Takr Taml Telu Tirh #Po DEVANAGARI DOUBLE DANDA 0966..096F ; Deva Dogr Kthi Mahj # Nd [10] DEVANAGARI DIGIT ZERO..DEVANAGARI DIGIT NINE @@ -102,9 +101,10 @@ 1CD2 ; Beng Deva Gran Knda # Mn VEDIC TONE PRENKHA 1CD3 ; Deva Gran Knda # Po VEDIC SIGN NIHSHVASA 1CD4 ; Deva # Mn VEDIC SIGN YAJURVEDIC MIDLINE SVARITA -1CD5..1CD6 ; Beng Deva # Mn [2] VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA..VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA -1CD7 ; Deva Shrd # Mn VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA -1CD8 ; Beng Deva # Mn VEDIC TONE CANDRA BELOW +1CD5 ; Beng Deva Newa Telu Tirh # Mn VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA +1CD6 ; Beng Deva Telu # Mn VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA +1CD7 ; Deva Newa Shrd # Mn VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA +1CD8 ; Beng Deva Newa Telu # Mn VEDIC TONE CANDRA BELOW 1CD9 ; Deva Shrd # Mn VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA SCHROEDER 1CDA ; Deva Knda Mlym Orya Taml Telu # Mn VEDIC TONE DOUBLE SVARITA 1CDB ; Deva # Mn VEDIC TONE TRIPLE SVARITA @@ -112,11 +112,13 @@ 1CDE..1CDF ; Deva # Mn [2] VEDIC TONE TWO DOTS BELOW..VEDIC TONE THREE DOTS BELOW 1CE0 ; Deva Shrd # Mn VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA 1CE1 ; Beng Deva # Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA -1CE2..1CE8 ; Deva # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL -1CE9 ; Deva Nand # Lo VEDIC SIGN ANUSVARA ANTARGOMUKHA -1CEA ; Beng Deva # Lo VEDIC SIGN ANUSVARA BAHIRGOMUKHA -1CEB..1CEC ; Deva # Lo [2] VEDIC SIGN ANUSVARA VAMAGOMUKHA..VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL -1CED ; Beng Deva # Mn VEDIC SIGN TIRYAK +1CE2 ; Deva Newa Tirh # Mn VEDIC SIGN VISARGA SVARITA +1CE3..1CE8 ; Deva # Mn [6] VEDIC SIGN VISARGA UDATTA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL +1CE9 ; Deva Nand Newa # Lo VEDIC SIGN ANUSVARA ANTARGOMUKHA +1CEA ; Beng Deva Shrd # Lo VEDIC SIGN ANUSVARA BAHIRGOMUKHA +1CEB ; Deva Newa # Lo VEDIC SIGN ANUSVARA VAMAGOMUKHA +1CEC ; Deva # Lo VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL +1CED ; Beng Deva Newa Shrd # Mn VEDIC SIGN TIRYAK 1CEE..1CF1 ; Deva # Lo [4] VEDIC SIGN HEXIFORM LONG ANUSVARA..VEDIC SIGN ANUSVARA UBHAYATO MUKHA 1CF2 ; Beng Deva Gran Knda Mlym Nand Orya Sinh Telu Tirh Tutg #Lo VEDIC SIGN ARDHAVISARGA 1CF3 ; Deva Gran # Lo VEDIC SIGN ROTATED ARDHAVISARGA diff --git a/lib/elixir/unicode/Scripts.txt b/lib/elixir/unicode/Scripts.txt index 443a6d2dd61..5574fdd6ae3 100644 --- a/lib/elixir/unicode/Scripts.txt +++ b/lib/elixir/unicode/Scripts.txt @@ -1,6 +1,6 @@ -# Scripts-16.0.0.txt -# Date: 2024-04-30, 21:48:40 GMT -# © 2024 Unicode®, Inc. +# Scripts-17.0.0.txt +# Date: 2025-07-24, 13:28:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -154,7 +154,7 @@ 208A..208C ; Common # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN 208D ; Common # Ps SUBSCRIPT LEFT PARENTHESIS 208E ; Common # Pe SUBSCRIPT RIGHT PARENTHESIS -20A0..20C0 ; Common # Sc [33] EURO-CURRENCY SIGN..SOM SIGN +20A0..20C1 ; Common # Sc [34] EURO-CURRENCY SIGN..SAUDI RIYAL SIGN 2100..2101 ; Common # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT 2102 ; Common # L& DOUBLE-STRUCK CAPITAL C 2103..2106 ; Common # So [4] DEGREE CELSIUS..CADA UNA @@ -306,8 +306,7 @@ 2B45..2B46 ; Common # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW 2B47..2B4C ; Common # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR 2B4D..2B73 ; Common # So [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR -2B76..2B95 ; Common # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B97..2BFF ; Common # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Common # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Common # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Common # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Common # Pf RIGHT SUBSTITUTION BRACKET @@ -524,7 +523,11 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1BCA0..1BCA3 ; Common # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP 1CC00..1CCEF ; Common # So [240] UP-POINTING GO-KART..OUTLINED LATIN CAPITAL LETTER Z 1CCF0..1CCF9 ; Common # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE +1CCFA..1CCFC ; Common # So [3] SNAKE SYMBOL..NOSE SYMBOL 1CD00..1CEB3 ; Common # So [436] BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET +1CEBA..1CED0 ; Common # So [23] FRAGILE SYMBOL..LEUKOTHEA +1CEE0..1CEEF ; Common # So [16] GEOMANTIC FIGURE POPULUS..GEOMANTIC FIGURE VIA +1CEF0 ; Common # Sm MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1CF50..1CFC3 ; Common # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK 1D000..1D0F5 ; Common # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO 1D100..1D126 ; Common # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 @@ -605,11 +608,10 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F260..1F265 ; Common # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI 1F300..1F3FA ; Common # So [251] CYCLONE..AMPHORA 1F3FB..1F3FF ; Common # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 -1F400..1F6D7 ; Common # So [728] RAT..ELEVATOR +1F400..1F6D8 ; Common # So [729] RAT..LANDSLIDE 1F6DC..1F6EC ; Common # So [17] WIRELESS..AIRPLANE ARRIVING 1F6F0..1F6FC ; Common # So [13] SATELLITE..ROLLER SKATE -1F700..1F776 ; Common # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE -1F77B..1F7D9 ; Common # So [95] HAUMEA..NINE POINTED WHITE STAR +1F700..1F7D9 ; Common # So [218] ALCHEMICAL SYMBOL FOR QUINTESSENCE..NINE POINTED WHITE STAR 1F7E0..1F7EB ; Common # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE 1F7F0 ; Common # So HEAVY EQUALS SIGN 1F800..1F80B ; Common # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD @@ -619,21 +621,24 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F890..1F8AD ; Common # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS 1F8B0..1F8BB ; Common # So [12] ARROW POINTING UPWARDS THEN NORTH WEST..SOUTH WEST ARROW FROM BAR 1F8C0..1F8C1 ; Common # So [2] LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW -1F900..1FA53 ; Common # So [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP +1F8D0..1F8D8 ; Common # Sm [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE +1F900..1FA57 ; Common # So [344] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS ALFIL 1FA60..1FA6D ; Common # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER 1FA70..1FA7C ; Common # So [13] BALLET SHOES..CRUTCH -1FA80..1FA89 ; Common # So [10] YO-YO..HARP -1FA8F..1FAC6 ; Common # So [56] SHOVEL..FINGERPRINT -1FACE..1FADC ; Common # So [15] MOOSE..ROOT VEGETABLE -1FADF..1FAE9 ; Common # So [11] SPLATTER..FACE WITH BAGS UNDER EYES -1FAF0..1FAF8 ; Common # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FA80..1FA8A ; Common # So [11] YO-YO..TROMBONE +1FA8E..1FAC6 ; Common # So [57] TREASURE CHEST..FINGERPRINT +1FAC8 ; Common # So HAIRY CREATURE +1FACD..1FADC ; Common # So [16] ORCA..ROOT VEGETABLE +1FADF..1FAEA ; Common # So [12] SPLATTER..DISTORTED FACE +1FAEF..1FAF8 ; Common # So [10] FIGHT CLOUD..RIGHTWARDS PUSHING HAND 1FB00..1FB92 ; Common # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK 1FB94..1FBEF ; Common # So [92] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE 1FBF0..1FBF9 ; Common # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +1FBFA ; Common # So ALARM BELL SYMBOL E0001 ; Common # Cf LANGUAGE TAG E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG -# Total code points: 9053 +# Total code points: 9123 # ================================================ @@ -648,8 +653,8 @@ E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG 01BC..01BF ; Latin # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; Latin # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; Latin # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; Latin # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; Latin # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; Latin # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; Latin # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Latin # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02E0..02E4 ; Latin # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP 1D00..1D25 ; Latin # L& [38] LATIN LETTER SMALL CAPITAL A..LATIN LETTER AIN @@ -676,11 +681,8 @@ A770 ; Latin # Lm MODIFIER LETTER US A771..A787 ; Latin # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T A78B..A78E ; Latin # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; Latin # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; Latin # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Latin # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Latin # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Latin # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Latin # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Latin # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Latin # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Latin # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Latin # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; Latin # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -702,7 +704,7 @@ FF41..FF5A ; Latin # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1DF0B..1DF1E ; Latin # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL 1DF25..1DF2A ; Latin # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK -# Total code points: 1487 +# Total code points: 1492 # ================================================ @@ -869,7 +871,7 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 0750..077F ; Arabic # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE 0870..0887 ; Arabic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT 0888 ; Arabic # Sk ARABIC RAISED ROUND DOT -0889..088E ; Arabic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; Arabic # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0890..0891 ; Arabic # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE 0897..089F ; Arabic # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; Arabic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF @@ -878,11 +880,13 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 08E3..08FF ; Arabic # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA FB50..FBB1 ; Arabic # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM FBB2..FBC2 ; Arabic # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBC3..FBD2 ; Arabic # So [16] ARABIC LIGATURE JALLA WA-ALAA..ARABIC LIGATURE ALAYHI AR-RAHMAH FBD3..FD3D ; Arabic # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM FD40..FD4F ; Arabic # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH FD50..FD8F ; Arabic # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD90..FD91 ; Arabic # So [2] ARABIC LIGATURE RAHMATU ALLAAHI ALAYH..ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA FD92..FDC7 ; Arabic # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM -FDCF ; Arabic # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDC8..FDCF ; Arabic # So [8] ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA..ARABIC LIGATURE SALAAMUHU ALAYNAA FDF0..FDFB ; Arabic # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU FDFC ; Arabic # Sc RIAL SIGN FDFD..FDFF ; Arabic # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL @@ -890,7 +894,11 @@ FE70..FE74 ; Arabic # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM 10E60..10E7E ; Arabic # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS 10EC2..10EC4 ; Arabic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; Arabic # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; Arabic # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; Arabic # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10ED0 ; Arabic # Po ARABIC BIBLICAL END OF VERSE +10ED1..10ED8 ; Arabic # So [8] ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM..ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH +10EFA..10EFF ; Arabic # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 1EE00..1EE03 ; Arabic # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL 1EE05..1EE1F ; Arabic # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF 1EE21..1EE22 ; Arabic # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM @@ -926,7 +934,7 @@ FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LA 1EEAB..1EEBB ; Arabic # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1EEF0..1EEF1 ; Arabic # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL -# Total code points: 1373 +# Total code points: 1413 # ================================================ @@ -1155,7 +1163,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C4A..0C4D ; Telugu # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; Telugu # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; Telugu # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; Telugu # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; Telugu # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; Telugu # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; Telugu # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; Telugu # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -1163,7 +1171,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C78..0C7E ; Telugu # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR 0C7F ; Telugu # So TELUGU SIGN TUUMU -# Total code points: 100 +# Total code points: 101 # ================================================ @@ -1186,14 +1194,14 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0CCA..0CCB ; Kannada # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; Kannada # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; Kannada # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; Kannada # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; Kannada # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; Kannada # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; Kannada # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; Kannada # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE 0CF1..0CF2 ; Kannada # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0CF3 ; Kannada # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT -# Total code points: 91 +# Total code points: 92 # ================================================ @@ -1594,17 +1602,18 @@ FA70..FAD9 ; Han # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILI 16FE2 ; Han # Po OLD CHINESE HOOK MARK 16FE3 ; Han # Lm OLD CHINESE ITERATION MARK 16FF0..16FF1 ; Han # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +16FF2..16FF3 ; Han # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Han # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS 20000..2A6DF ; Han # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Han # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Han # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Han # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Han # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Han # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Han # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Han # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Han # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Han # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Han # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Han # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 99030 +# Total code points: 103351 # ================================================ @@ -1647,7 +1656,8 @@ A490..A4C6 ; Yi # So [55] YI RADICAL QOT..YI RADICAL KE 0951..0954 ; Inherited # Mn [4] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI ACUTE ACCENT 1AB0..1ABD ; Inherited # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Inherited # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Inherited # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Inherited # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Inherited # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1CD0..1CD2 ; Inherited # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA 1CD4..1CE0 ; Inherited # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA 1CE2..1CE8 ; Inherited # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL @@ -1676,7 +1686,7 @@ FE20..FE2D ; Inherited # Mn [14] COMBINING LIGATURE LEFT HALF..COMBINING CON 1D1AA..1D1AD ; Inherited # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO E0100..E01EF ; Inherited # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 657 +# Total code points: 684 # ================================================ @@ -2347,8 +2357,14 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI 111DB ; Sharada # Po SHARADA SIGN SIDDHAM 111DC ; Sharada # Lo SHARADA HEADSTROKE 111DD..111DF ; Sharada # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +11B60 ; Sharada # Mn SHARADA VOWEL SIGN OE +11B61 ; Sharada # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Sharada # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Sharada # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Sharada # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Sharada # Mc SHARADA VOWEL SIGN CANDRA O -# Total code points: 96 +# Total code points: 104 # ================================================ @@ -2756,11 +2772,11 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # ================================================ 16FE0 ; Tangut # Lm TANGUT ITERATION MARK -17000..187F7 ; Tangut # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18AFF ; Tangut # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 -18D00..18D08 ; Tangut # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +17000..18AFF ; Tangut # Lo [6912] TANGUT IDEOGRAPH-17000..TANGUT COMPONENT-768 +18D00..18D1E ; Tangut # Lo [31] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Tangut # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 -# Total code points: 6914 +# Total code points: 7059 # ================================================ @@ -3125,4 +3141,42 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # Total code points: 80 +# ================================================ + +10940..10959 ; Sidetic # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 + +# Total code points: 26 + +# ================================================ + +1E6C0..1E6DE ; Tai_Yo # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; Tai_Yo # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; Tai_Yo # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; Tai_Yo # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; Tai_Yo # Mn TAI YO SIGN AU +1E6E7..1E6ED ; Tai_Yo # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; Tai_Yo # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; Tai_Yo # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; Tai_Yo # Mn TAI YO SIGN OM +1E6FE ; Tai_Yo # Lo TAI YO SYMBOL MUEANG +1E6FF ; Tai_Yo # Lm TAI YO XAM LAI + +# Total code points: 55 + +# ================================================ + +11DB0..11DD8 ; Tolong_Siki # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; Tolong_Siki # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; Tolong_Siki # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; Tolong_Siki # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE + +# Total code points: 54 + +# ================================================ + +16EA0..16EB8 ; Beria_Erfe # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Beria_Erfe # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY + +# Total code points: 50 + # EOF diff --git a/lib/elixir/unicode/SpecialCasing.txt b/lib/elixir/unicode/SpecialCasing.txt index e83aba432f4..1013344a6f2 100644 --- a/lib/elixir/unicode/SpecialCasing.txt +++ b/lib/elixir/unicode/SpecialCasing.txt @@ -1,6 +1,6 @@ -# SpecialCasing-16.0.0.txt -# Date: 2024-05-10, 22:49:00 GMT -# © 2024 Unicode®, Inc. +# SpecialCasing-17.0.0.txt +# Date: 2025-07-31, 22:11:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -47,8 +47,8 @@ # # A language ID is defined by BCP 47, with '-' and '_' treated equivalently. # -# A casing context for a character is defined by Section 3.13 Default Case Algorithms -# of The Unicode Standard. +# A casing context for a character is defined in the +# "Conformance" / "Default Case Algorithms" section of the core specification. # # Parsers of this file must be prepared to deal with future additions to this format: # * Additional contexts @@ -57,6 +57,10 @@ # ================================================================================ # Unconditional mappings +# The mappings in this section are not language-sensitive nor context-sensitive. +# +# Note that comments provide additional information but +# do not modify the case mapping algorithms in the core specification, chapter 3. # ================================================================================ # The German es-zed is special--the normal mapping is to SS. diff --git a/lib/elixir/unicode/UnicodeData.txt b/lib/elixir/unicode/UnicodeData.txt index 64258a37395..fca68e3e154 100644 --- a/lib/elixir/unicode/UnicodeData.txt +++ b/lib/elixir/unicode/UnicodeData.txt @@ -659,7 +659,7 @@ 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; -0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; +0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Lo;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; @@ -2121,6 +2121,7 @@ 088C;ARABIC LETTER TAH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 088D;ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 088E;ARABIC VERTICAL TAIL;Lo;0;AL;;;;;N;;;;; +088F;ARABIC LETTER NOON WITH RING ABOVE;Lo;0;AL;;;;;N;;;;; 0890;ARABIC POUND MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0891;ARABIC PIASTRE MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0897;ARABIC PEPET;Mn;230;NSM;;;;;N;;;;; @@ -2862,6 +2863,7 @@ 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; +0C5C;TELUGU ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0C5D;TELUGU LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; @@ -2958,6 +2960,7 @@ 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; +0CDC;KANNADA ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0CDD;KANNADA LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; @@ -6137,6 +6140,33 @@ 1ACC;COMBINING LATIN SMALL LETTER INSULAR G;Mn;230;NSM;;;;;N;;;;; 1ACD;COMBINING LATIN SMALL LETTER INSULAR R;Mn;230;NSM;;;;;N;;;;; 1ACE;COMBINING LATIN SMALL LETTER INSULAR T;Mn;230;NSM;;;;;N;;;;; +1ACF;COMBINING DOUBLE CARON;Mn;230;NSM;;;;;N;;;;; +1AD0;COMBINING VERTICAL-LINE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD1;COMBINING GRAVE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD2;COMBINING VERTICAL-LINE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD3;COMBINING ACUTE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD4;COMBINING VERTICAL-LINE-MACRON;Mn;230;NSM;;;;;N;;;;; +1AD5;COMBINING MACRON-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD6;COMBINING VERTICAL-LINE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD7;COMBINING VERTICAL-LINE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD8;COMBINING MACRON-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD9;COMBINING SHARP SIGN;Mn;230;NSM;;;;;N;;;;; +1ADA;COMBINING FLAT SIGN;Mn;230;NSM;;;;;N;;;;; +1ADB;COMBINING DOWN TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1ADC;COMBINING DIAERESIS WITH RAISED LEFT DOT;Mn;230;NSM;;;;;N;;;;; +1ADD;COMBINING DOT-AND-RING BELOW;Mn;220;NSM;;;;;N;;;;; +1AE0;COMBINING LEFT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE1;COMBINING RIGHT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE2;COMBINING MINUS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE3;COMBINING INVERTED BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE4;COMBINING SQUARE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE5;COMBINING SEAGULL ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE6;COMBINING DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;;;;; +1AE7;COMBINING DOUBLE ARCH ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE8;COMBINING EQUALS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE9;COMBINING LEFT ANGLE CENTRED ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEA;COMBINING UPWARDS ARROW ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEB;COMBINING DOUBLE RIGHTWARDS ARROW ABOVE;Mn;234;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -7545,6 +7575,7 @@ 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20BF;BITCOIN SIGN;Sc;0;ET;;;;;N;;;;; 20C0;SOM SIGN;Sc;0;ET;;;;;N;;;;; +20C1;SAUDI RIYAL SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; @@ -10239,6 +10270,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B96;EQUALS SIGN WITH INFINITY ABOVE;So;0;ON;;;;;N;;;;; 2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -14274,10 +14306,14 @@ A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 A7CB;LATIN CAPITAL LETTER RAMS HORN;Lu;0;L;;;;;N;;;;0264; A7CC;LATIN CAPITAL LETTER S WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A7CD; A7CD;LATIN SMALL LETTER S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A7CC;;A7CC +A7CE;LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE;Lu;0;L;;;;;N;;;;A7CF; +A7CF;LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;;;A7CE;;A7CE A7D0;LATIN CAPITAL LETTER CLOSED INSULAR G;Lu;0;L;;;;;N;;;;A7D1; A7D1;LATIN SMALL LETTER CLOSED INSULAR G;Ll;0;L;;;;;N;;;A7D0;;A7D0 -A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;;; -A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;;; +A7D2;LATIN CAPITAL LETTER DOUBLE THORN;Lu;0;L;;;;;N;;;;A7D3; +A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;A7D2;;A7D2 +A7D4;LATIN CAPITAL LETTER DOUBLE WYNN;Lu;0;L;;;;;N;;;;A7D5; +A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;A7D4;;A7D4 A7D6;LATIN CAPITAL LETTER MIDDLE SCOTS S;Lu;0;L;;;;;N;;;;A7D7; A7D7;LATIN SMALL LETTER MIDDLE SCOTS S;Ll;0;L;;;;;N;;;A7D6;;A7D6 A7D8;LATIN CAPITAL LETTER SIGMOID S;Lu;0;L;;;;;N;;;;A7D9; @@ -14285,6 +14321,7 @@ A7D9;LATIN SMALL LETTER SIGMOID S;Ll;0;L;;;;;N;;;A7D8;;A7D8 A7DA;LATIN CAPITAL LETTER LAMBDA;Lu;0;L;;;;;N;;;;A7DB; A7DB;LATIN SMALL LETTER LAMBDA;Ll;0;L;;;;;N;;;A7DA;;A7DA A7DC;LATIN CAPITAL LETTER LAMBDA WITH STROKE;Lu;0;L;;;;;N;;;;019B; +A7F1;MODIFIER LETTER CAPITAL S;Lm;0;L; 0053;;;;N;;;;; A7F2;MODIFIER LETTER CAPITAL C;Lm;0;L; 0043;;;;N;;;;; A7F3;MODIFIER LETTER CAPITAL F;Lm;0;L; 0046;;;;N;;;;; A7F4;MODIFIER LETTER CAPITAL Q;Lm;0;L; 0051;;;;N;;;;; @@ -15925,6 +15962,22 @@ FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBC2;ARABIC SYMBOL WASLA ABOVE;Sk;0;AL;;;;;N;;;;; +FBC3;ARABIC LIGATURE JALLA WA-ALAA;So;0;ON;;;;;N;;;;; +FBC4;ARABIC LIGATURE DAAMAT BARAKAATUHUM;So;0;ON;;;;;N;;;;; +FBC5;ARABIC LIGATURE RAHMATU ALLAAHI TAAALAA ALAYH;So;0;ON;;;;;N;;;;; +FBC6;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIM;So;0;ON;;;;;N;;;;; +FBC7;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIMAA;So;0;ON;;;;;N;;;;; +FBC8;ARABIC LIGATURE RAHIMAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBC9;ARABIC LIGATURE RAHIMAHUMAA ALLAAH;So;0;ON;;;;;N;;;;; +FBCA;ARABIC LIGATURE RAHIMAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUM;So;0;ON;;;;;N;;;;; +FBCC;ARABIC LIGATURE HAFIZAHU ALLAAH;So;0;ON;;;;;N;;;;; +FBCD;ARABIC LIGATURE HAFIZAHU ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCE;ARABIC LIGATURE HAFIZAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCF;ARABIC LIGATURE HAFIZAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBD0;ARABIC LIGATURE SALLALLAAHU TAAALAA ALAYHI WA-SALLAM;So;0;ON;;;;;N;;;;; +FBD1;ARABIC LIGATURE AJJAL ALLAAHU FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FBD2;ARABIC LIGATURE ALAYHI AR-RAHMAH;So;0;ON;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; @@ -16370,6 +16423,8 @@ FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; +FD90;ARABIC LIGATURE RAHMATU ALLAAHI ALAYH;So;0;ON;;;;;N;;;;; +FD91;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA;So;0;ON;;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; @@ -16424,6 +16479,13 @@ FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; +FDC8;ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA;So;0;ON;;;;;N;;;;; +FDC9;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANH;So;0;ON;;;;;N;;;;; +FDCA;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHAA;So;0;ON;;;;;N;;;;; +FDCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUMAA;So;0;ON;;;;;N;;;;; +FDCC;ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM;So;0;ON;;;;;N;;;;; +FDCD;ARABIC LIGATURE AJJAL ALLAAHU TAAALAA FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FDCE;ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH;So;0;ON;;;;;N;;;;; FDCF;ARABIC LIGATURE SALAAMUHU ALAYNAA;So;0;ON;;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; @@ -18708,6 +18770,32 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; +10940;SIDETIC LETTER N01;Lo;0;R;;;;;N;;;;; +10941;SIDETIC LETTER N02;Lo;0;R;;;;;N;;;;; +10942;SIDETIC LETTER N03;Lo;0;R;;;;;N;;;;; +10943;SIDETIC LETTER N04;Lo;0;R;;;;;N;;;;; +10944;SIDETIC LETTER N05;Lo;0;R;;;;;N;;;;; +10945;SIDETIC LETTER N06;Lo;0;R;;;;;N;;;;; +10946;SIDETIC LETTER N07;Lo;0;R;;;;;N;;;;; +10947;SIDETIC LETTER N08;Lo;0;R;;;;;N;;;;; +10948;SIDETIC LETTER N09;Lo;0;R;;;;;N;;;;; +10949;SIDETIC LETTER N10;Lo;0;R;;;;;N;;;;; +1094A;SIDETIC LETTER N11;Lo;0;R;;;;;N;;;;; +1094B;SIDETIC LETTER N12;Lo;0;R;;;;;N;;;;; +1094C;SIDETIC LETTER N13;Lo;0;R;;;;;N;;;;; +1094D;SIDETIC LETTER N14;Lo;0;R;;;;;N;;;;; +1094E;SIDETIC LETTER N15;Lo;0;R;;;;;N;;;;; +1094F;SIDETIC LETTER N16;Lo;0;R;;;;;N;;;;; +10950;SIDETIC LETTER N17;Lo;0;R;;;;;N;;;;; +10951;SIDETIC LETTER N18;Lo;0;R;;;;;N;;;;; +10952;SIDETIC LETTER N19;Lo;0;R;;;;;N;;;;; +10953;SIDETIC LETTER N20;Lo;0;R;;;;;N;;;;; +10954;SIDETIC LETTER N21;Lo;0;R;;;;;N;;;;; +10955;SIDETIC LETTER N22;Lo;0;R;;;;;N;;;;; +10956;SIDETIC LETTER N23;Lo;0;R;;;;;N;;;;; +10957;SIDETIC LETTER N24;Lo;0;R;;;;;N;;;;; +10958;SIDETIC LETTER N25;Lo;0;R;;;;;N;;;;; +10959;SIDETIC LETTER N26;Lo;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; @@ -19541,6 +19629,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EC2;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC3;ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC4;ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; +10EC5;ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW;Lm;0;AL;;;;;N;;;;; +10EC6;ARABIC LETTER THIN NOON;Lo;0;AL;;;;;N;;;;; +10EC7;ARABIC LETTER YEH WITH FOUR DOTS BELOW;Lo;0;AL;;;;;N;;;;; +10ED0;ARABIC BIBLICAL END OF VERSE;Po;0;ON;;;;;N;;;;; +10ED1;ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED2;ARABIC LIGATURE ALAYHIM AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED3;ARABIC LIGATURE ALAYHIMAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED4;ARABIC LIGATURE QADDASA ALLAAHU SIRRAH;So;0;ON;;;;;N;;;;; +10ED5;ARABIC LIGATURE QUDDISA SIRRUHUM;So;0;ON;;;;;N;;;;; +10ED6;ARABIC LIGATURE QUDDISA SIRRUHUMAA;So;0;ON;;;;;N;;;;; +10ED7;ARABIC LIGATURE QUDDISAT ASRAARUHUM;So;0;ON;;;;;N;;;;; +10ED8;ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH;So;0;ON;;;;;N;;;;; +10EFA;ARABIC DOUBLE VERTICAL BAR BELOW;Mn;220;NSM;;;;;N;;;;; +10EFB;ARABIC SMALL LOW NOON;Mn;220;NSM;;;;;N;;;;; 10EFC;ARABIC COMBINING ALEF OVERLAY;Mn;0;NSM;;;;;N;;;;; 10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; 10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; @@ -21521,6 +21623,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; +11B60;SHARADA VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; +11B61;SHARADA VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; +11B62;SHARADA VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; +11B63;SHARADA VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; +11B64;SHARADA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; +11B65;SHARADA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; +11B66;SHARADA VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; +11B67;SHARADA VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 11BC0;SUNUWAR LETTER DEVI;Lo;0;L;;;;;N;;;;; 11BC1;SUNUWAR LETTER TASLA;Lo;0;L;;;;;N;;;;; 11BC2;SUNUWAR LETTER EKO;Lo;0;L;;;;;N;;;;; @@ -21868,6 +21978,60 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +11DB0;TOLONG SIKI LETTER I;Lo;0;L;;;;;N;;;;; +11DB1;TOLONG SIKI LETTER E;Lo;0;L;;;;;N;;;;; +11DB2;TOLONG SIKI LETTER U;Lo;0;L;;;;;N;;;;; +11DB3;TOLONG SIKI LETTER O;Lo;0;L;;;;;N;;;;; +11DB4;TOLONG SIKI LETTER A;Lo;0;L;;;;;N;;;;; +11DB5;TOLONG SIKI LETTER AA;Lo;0;L;;;;;N;;;;; +11DB6;TOLONG SIKI LETTER P;Lo;0;L;;;;;N;;;;; +11DB7;TOLONG SIKI LETTER PH;Lo;0;L;;;;;N;;;;; +11DB8;TOLONG SIKI LETTER B;Lo;0;L;;;;;N;;;;; +11DB9;TOLONG SIKI LETTER BH;Lo;0;L;;;;;N;;;;; +11DBA;TOLONG SIKI LETTER M;Lo;0;L;;;;;N;;;;; +11DBB;TOLONG SIKI LETTER T;Lo;0;L;;;;;N;;;;; +11DBC;TOLONG SIKI LETTER TH;Lo;0;L;;;;;N;;;;; +11DBD;TOLONG SIKI LETTER D;Lo;0;L;;;;;N;;;;; +11DBE;TOLONG SIKI LETTER DH;Lo;0;L;;;;;N;;;;; +11DBF;TOLONG SIKI LETTER N;Lo;0;L;;;;;N;;;;; +11DC0;TOLONG SIKI LETTER TT;Lo;0;L;;;;;N;;;;; +11DC1;TOLONG SIKI LETTER TTH;Lo;0;L;;;;;N;;;;; +11DC2;TOLONG SIKI LETTER DD;Lo;0;L;;;;;N;;;;; +11DC3;TOLONG SIKI LETTER DDH;Lo;0;L;;;;;N;;;;; +11DC4;TOLONG SIKI LETTER NN;Lo;0;L;;;;;N;;;;; +11DC5;TOLONG SIKI LETTER C;Lo;0;L;;;;;N;;;;; +11DC6;TOLONG SIKI LETTER CH;Lo;0;L;;;;;N;;;;; +11DC7;TOLONG SIKI LETTER J;Lo;0;L;;;;;N;;;;; +11DC8;TOLONG SIKI LETTER JH;Lo;0;L;;;;;N;;;;; +11DC9;TOLONG SIKI LETTER NY;Lo;0;L;;;;;N;;;;; +11DCA;TOLONG SIKI LETTER K;Lo;0;L;;;;;N;;;;; +11DCB;TOLONG SIKI LETTER KH;Lo;0;L;;;;;N;;;;; +11DCC;TOLONG SIKI LETTER G;Lo;0;L;;;;;N;;;;; +11DCD;TOLONG SIKI LETTER GH;Lo;0;L;;;;;N;;;;; +11DCE;TOLONG SIKI LETTER NG;Lo;0;L;;;;;N;;;;; +11DCF;TOLONG SIKI LETTER Y;Lo;0;L;;;;;N;;;;; +11DD0;TOLONG SIKI LETTER R;Lo;0;L;;;;;N;;;;; +11DD1;TOLONG SIKI LETTER L;Lo;0;L;;;;;N;;;;; +11DD2;TOLONG SIKI LETTER V;Lo;0;L;;;;;N;;;;; +11DD3;TOLONG SIKI LETTER NNY;Lo;0;L;;;;;N;;;;; +11DD4;TOLONG SIKI LETTER S;Lo;0;L;;;;;N;;;;; +11DD5;TOLONG SIKI LETTER H;Lo;0;L;;;;;N;;;;; +11DD6;TOLONG SIKI LETTER X;Lo;0;L;;;;;N;;;;; +11DD7;TOLONG SIKI LETTER RR;Lo;0;L;;;;;N;;;;; +11DD8;TOLONG SIKI LETTER RRH;Lo;0;L;;;;;N;;;;; +11DD9;TOLONG SIKI SIGN SELA;Lm;0;L;;;;;N;;;;; +11DDA;TOLONG SIKI SIGN HECAKA;Lo;0;L;;;;;N;;;;; +11DDB;TOLONG SIKI UNGGA;Lo;0;L;;;;;N;;;;; +11DE0;TOLONG SIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11DE1;TOLONG SIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11DE2;TOLONG SIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11DE3;TOLONG SIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11DE4;TOLONG SIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11DE5;TOLONG SIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11DE6;TOLONG SIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11DE7;TOLONG SIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11DE8;TOLONG SIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11DE9;TOLONG SIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;; 11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;; 11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;; @@ -22088,8 +22252,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; -12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; -12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; +12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;1;N;;;;; +12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;1;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; @@ -22153,7 +22317,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; -12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; +12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;1;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; @@ -22582,12 +22746,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; -12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; +12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;1/2;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; -1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; +1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;2;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; @@ -22811,9 +22975,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; -1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; +1230B;CUNEIFORM SIGN U;Lo;0;L;;;;1;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; -1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; +1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;3;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; @@ -22953,7 +23117,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; -12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; +12399;CUNEIFORM SIGN U U;Lo;0;L;;;;2;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; @@ -30124,6 +30288,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;; 16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;; 16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;; +16EA0;BERIA ERFE CAPITAL LETTER ARKAB;Lu;0;L;;;;;N;;;;16EBB; +16EA1;BERIA ERFE CAPITAL LETTER BASIGNA;Lu;0;L;;;;;N;;;;16EBC; +16EA2;BERIA ERFE CAPITAL LETTER DARBAI;Lu;0;L;;;;;N;;;;16EBD; +16EA3;BERIA ERFE CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;16EBE; +16EA4;BERIA ERFE CAPITAL LETTER FITKO;Lu;0;L;;;;;N;;;;16EBF; +16EA5;BERIA ERFE CAPITAL LETTER GOWAY;Lu;0;L;;;;;N;;;;16EC0; +16EA6;BERIA ERFE CAPITAL LETTER HIRDEABO;Lu;0;L;;;;;N;;;;16EC1; +16EA7;BERIA ERFE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16EC2; +16EA8;BERIA ERFE CAPITAL LETTER DJAI;Lu;0;L;;;;;N;;;;16EC3; +16EA9;BERIA ERFE CAPITAL LETTER KOBO;Lu;0;L;;;;;N;;;;16EC4; +16EAA;BERIA ERFE CAPITAL LETTER LAKKO;Lu;0;L;;;;;N;;;;16EC5; +16EAB;BERIA ERFE CAPITAL LETTER MERI;Lu;0;L;;;;;N;;;;16EC6; +16EAC;BERIA ERFE CAPITAL LETTER NINI;Lu;0;L;;;;;N;;;;16EC7; +16EAD;BERIA ERFE CAPITAL LETTER GNA;Lu;0;L;;;;;N;;;;16EC8; +16EAE;BERIA ERFE CAPITAL LETTER NGAY;Lu;0;L;;;;;N;;;;16EC9; +16EAF;BERIA ERFE CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;16ECA; +16EB0;BERIA ERFE CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;16ECB; +16EB1;BERIA ERFE CAPITAL LETTER ERIGO;Lu;0;L;;;;;N;;;;16ECC; +16EB2;BERIA ERFE CAPITAL LETTER ERIGO TAMURA;Lu;0;L;;;;;N;;;;16ECD; +16EB3;BERIA ERFE CAPITAL LETTER SERI;Lu;0;L;;;;;N;;;;16ECE; +16EB4;BERIA ERFE CAPITAL LETTER SHEP;Lu;0;L;;;;;N;;;;16ECF; +16EB5;BERIA ERFE CAPITAL LETTER TATASOUE;Lu;0;L;;;;;N;;;;16ED0; +16EB6;BERIA ERFE CAPITAL LETTER UI;Lu;0;L;;;;;N;;;;16ED1; +16EB7;BERIA ERFE CAPITAL LETTER WASSE;Lu;0;L;;;;;N;;;;16ED2; +16EB8;BERIA ERFE CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;16ED3; +16EBB;BERIA ERFE SMALL LETTER ARKAB;Ll;0;L;;;;;N;;;16EA0;;16EA0 +16EBC;BERIA ERFE SMALL LETTER BASIGNA;Ll;0;L;;;;;N;;;16EA1;;16EA1 +16EBD;BERIA ERFE SMALL LETTER DARBAI;Ll;0;L;;;;;N;;;16EA2;;16EA2 +16EBE;BERIA ERFE SMALL LETTER EH;Ll;0;L;;;;;N;;;16EA3;;16EA3 +16EBF;BERIA ERFE SMALL LETTER FITKO;Ll;0;L;;;;;N;;;16EA4;;16EA4 +16EC0;BERIA ERFE SMALL LETTER GOWAY;Ll;0;L;;;;;N;;;16EA5;;16EA5 +16EC1;BERIA ERFE SMALL LETTER HIRDEABO;Ll;0;L;;;;;N;;;16EA6;;16EA6 +16EC2;BERIA ERFE SMALL LETTER I;Ll;0;L;;;;;N;;;16EA7;;16EA7 +16EC3;BERIA ERFE SMALL LETTER DJAI;Ll;0;L;;;;;N;;;16EA8;;16EA8 +16EC4;BERIA ERFE SMALL LETTER KOBO;Ll;0;L;;;;;N;;;16EA9;;16EA9 +16EC5;BERIA ERFE SMALL LETTER LAKKO;Ll;0;L;;;;;N;;;16EAA;;16EAA +16EC6;BERIA ERFE SMALL LETTER MERI;Ll;0;L;;;;;N;;;16EAB;;16EAB +16EC7;BERIA ERFE SMALL LETTER NINI;Ll;0;L;;;;;N;;;16EAC;;16EAC +16EC8;BERIA ERFE SMALL LETTER GNA;Ll;0;L;;;;;N;;;16EAD;;16EAD +16EC9;BERIA ERFE SMALL LETTER NGAY;Ll;0;L;;;;;N;;;16EAE;;16EAE +16ECA;BERIA ERFE SMALL LETTER OI;Ll;0;L;;;;;N;;;16EAF;;16EAF +16ECB;BERIA ERFE SMALL LETTER PI;Ll;0;L;;;;;N;;;16EB0;;16EB0 +16ECC;BERIA ERFE SMALL LETTER ERIGO;Ll;0;L;;;;;N;;;16EB1;;16EB1 +16ECD;BERIA ERFE SMALL LETTER ERIGO TAMURA;Ll;0;L;;;;;N;;;16EB2;;16EB2 +16ECE;BERIA ERFE SMALL LETTER SERI;Ll;0;L;;;;;N;;;16EB3;;16EB3 +16ECF;BERIA ERFE SMALL LETTER SHEP;Ll;0;L;;;;;N;;;16EB4;;16EB4 +16ED0;BERIA ERFE SMALL LETTER TATASOUE;Ll;0;L;;;;;N;;;16EB5;;16EB5 +16ED1;BERIA ERFE SMALL LETTER UI;Ll;0;L;;;;;N;;;16EB6;;16EB6 +16ED2;BERIA ERFE SMALL LETTER WASSE;Ll;0;L;;;;;N;;;16EB7;;16EB7 +16ED3;BERIA ERFE SMALL LETTER AY;Ll;0;L;;;;;N;;;16EB8;;16EB8 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; @@ -30280,8 +30494,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; 16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; 16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; +16FF2;CHINESE SMALL SIMPLIFIED ER;Lm;0;L;;;;;N;;;;; +16FF3;CHINESE SMALL TRADITIONAL ER;Lm;0;L;;;;;N;;;;; +16FF4;YANGQIN SIGN SLOW ONE BEAT;Nl;0;L;;;;1;N;;;;; +16FF5;YANGQIN SIGN SLOW THREE HALF BEATS;Nl;0;L;;;;3/2;N;;;;; +16FF6;YANGQIN SIGN SLOW TWO BEATS;Nl;0;L;;;;2;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; -187F7;;Lo;0;L;;;;;N;;;;; +187FF;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; @@ -31522,7 +31741,122 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; 18CFF;KHITAN SMALL SCRIPT CHARACTER-18CFF;Lo;0;L;;;;;N;;;;; 18D00;;Lo;0;L;;;;;N;;;;; -18D08;;Lo;0;L;;;;;N;;;;; +18D1E;;Lo;0;L;;;;;N;;;;; +18D80;TANGUT COMPONENT-769;Lo;0;L;;;;;N;;;;; +18D81;TANGUT COMPONENT-770;Lo;0;L;;;;;N;;;;; +18D82;TANGUT COMPONENT-771;Lo;0;L;;;;;N;;;;; +18D83;TANGUT COMPONENT-772;Lo;0;L;;;;;N;;;;; +18D84;TANGUT COMPONENT-773;Lo;0;L;;;;;N;;;;; +18D85;TANGUT COMPONENT-774;Lo;0;L;;;;;N;;;;; +18D86;TANGUT COMPONENT-775;Lo;0;L;;;;;N;;;;; +18D87;TANGUT COMPONENT-776;Lo;0;L;;;;;N;;;;; +18D88;TANGUT COMPONENT-777;Lo;0;L;;;;;N;;;;; +18D89;TANGUT COMPONENT-778;Lo;0;L;;;;;N;;;;; +18D8A;TANGUT COMPONENT-779;Lo;0;L;;;;;N;;;;; +18D8B;TANGUT COMPONENT-780;Lo;0;L;;;;;N;;;;; +18D8C;TANGUT COMPONENT-781;Lo;0;L;;;;;N;;;;; +18D8D;TANGUT COMPONENT-782;Lo;0;L;;;;;N;;;;; +18D8E;TANGUT COMPONENT-783;Lo;0;L;;;;;N;;;;; +18D8F;TANGUT COMPONENT-784;Lo;0;L;;;;;N;;;;; +18D90;TANGUT COMPONENT-785;Lo;0;L;;;;;N;;;;; +18D91;TANGUT COMPONENT-786;Lo;0;L;;;;;N;;;;; +18D92;TANGUT COMPONENT-787;Lo;0;L;;;;;N;;;;; +18D93;TANGUT COMPONENT-788;Lo;0;L;;;;;N;;;;; +18D94;TANGUT COMPONENT-789;Lo;0;L;;;;;N;;;;; +18D95;TANGUT COMPONENT-790;Lo;0;L;;;;;N;;;;; +18D96;TANGUT COMPONENT-791;Lo;0;L;;;;;N;;;;; +18D97;TANGUT COMPONENT-792;Lo;0;L;;;;;N;;;;; +18D98;TANGUT COMPONENT-793;Lo;0;L;;;;;N;;;;; +18D99;TANGUT COMPONENT-794;Lo;0;L;;;;;N;;;;; +18D9A;TANGUT COMPONENT-795;Lo;0;L;;;;;N;;;;; +18D9B;TANGUT COMPONENT-796;Lo;0;L;;;;;N;;;;; +18D9C;TANGUT COMPONENT-797;Lo;0;L;;;;;N;;;;; +18D9D;TANGUT COMPONENT-798;Lo;0;L;;;;;N;;;;; +18D9E;TANGUT COMPONENT-799;Lo;0;L;;;;;N;;;;; +18D9F;TANGUT COMPONENT-800;Lo;0;L;;;;;N;;;;; +18DA0;TANGUT COMPONENT-801;Lo;0;L;;;;;N;;;;; +18DA1;TANGUT COMPONENT-802;Lo;0;L;;;;;N;;;;; +18DA2;TANGUT COMPONENT-803;Lo;0;L;;;;;N;;;;; +18DA3;TANGUT COMPONENT-804;Lo;0;L;;;;;N;;;;; +18DA4;TANGUT COMPONENT-805;Lo;0;L;;;;;N;;;;; +18DA5;TANGUT COMPONENT-806;Lo;0;L;;;;;N;;;;; +18DA6;TANGUT COMPONENT-807;Lo;0;L;;;;;N;;;;; +18DA7;TANGUT COMPONENT-808;Lo;0;L;;;;;N;;;;; +18DA8;TANGUT COMPONENT-809;Lo;0;L;;;;;N;;;;; +18DA9;TANGUT COMPONENT-810;Lo;0;L;;;;;N;;;;; +18DAA;TANGUT COMPONENT-811;Lo;0;L;;;;;N;;;;; +18DAB;TANGUT COMPONENT-812;Lo;0;L;;;;;N;;;;; +18DAC;TANGUT COMPONENT-813;Lo;0;L;;;;;N;;;;; +18DAD;TANGUT COMPONENT-814;Lo;0;L;;;;;N;;;;; +18DAE;TANGUT COMPONENT-815;Lo;0;L;;;;;N;;;;; +18DAF;TANGUT COMPONENT-816;Lo;0;L;;;;;N;;;;; +18DB0;TANGUT COMPONENT-817;Lo;0;L;;;;;N;;;;; +18DB1;TANGUT COMPONENT-818;Lo;0;L;;;;;N;;;;; +18DB2;TANGUT COMPONENT-819;Lo;0;L;;;;;N;;;;; +18DB3;TANGUT COMPONENT-820;Lo;0;L;;;;;N;;;;; +18DB4;TANGUT COMPONENT-821;Lo;0;L;;;;;N;;;;; +18DB5;TANGUT COMPONENT-822;Lo;0;L;;;;;N;;;;; +18DB6;TANGUT COMPONENT-823;Lo;0;L;;;;;N;;;;; +18DB7;TANGUT COMPONENT-824;Lo;0;L;;;;;N;;;;; +18DB8;TANGUT COMPONENT-825;Lo;0;L;;;;;N;;;;; +18DB9;TANGUT COMPONENT-826;Lo;0;L;;;;;N;;;;; +18DBA;TANGUT COMPONENT-827;Lo;0;L;;;;;N;;;;; +18DBB;TANGUT COMPONENT-828;Lo;0;L;;;;;N;;;;; +18DBC;TANGUT COMPONENT-829;Lo;0;L;;;;;N;;;;; +18DBD;TANGUT COMPONENT-830;Lo;0;L;;;;;N;;;;; +18DBE;TANGUT COMPONENT-831;Lo;0;L;;;;;N;;;;; +18DBF;TANGUT COMPONENT-832;Lo;0;L;;;;;N;;;;; +18DC0;TANGUT COMPONENT-833;Lo;0;L;;;;;N;;;;; +18DC1;TANGUT COMPONENT-834;Lo;0;L;;;;;N;;;;; +18DC2;TANGUT COMPONENT-835;Lo;0;L;;;;;N;;;;; +18DC3;TANGUT COMPONENT-836;Lo;0;L;;;;;N;;;;; +18DC4;TANGUT COMPONENT-837;Lo;0;L;;;;;N;;;;; +18DC5;TANGUT COMPONENT-838;Lo;0;L;;;;;N;;;;; +18DC6;TANGUT COMPONENT-839;Lo;0;L;;;;;N;;;;; +18DC7;TANGUT COMPONENT-840;Lo;0;L;;;;;N;;;;; +18DC8;TANGUT COMPONENT-841;Lo;0;L;;;;;N;;;;; +18DC9;TANGUT COMPONENT-842;Lo;0;L;;;;;N;;;;; +18DCA;TANGUT COMPONENT-843;Lo;0;L;;;;;N;;;;; +18DCB;TANGUT COMPONENT-844;Lo;0;L;;;;;N;;;;; +18DCC;TANGUT COMPONENT-845;Lo;0;L;;;;;N;;;;; +18DCD;TANGUT COMPONENT-846;Lo;0;L;;;;;N;;;;; +18DCE;TANGUT COMPONENT-847;Lo;0;L;;;;;N;;;;; +18DCF;TANGUT COMPONENT-848;Lo;0;L;;;;;N;;;;; +18DD0;TANGUT COMPONENT-849;Lo;0;L;;;;;N;;;;; +18DD1;TANGUT COMPONENT-850;Lo;0;L;;;;;N;;;;; +18DD2;TANGUT COMPONENT-851;Lo;0;L;;;;;N;;;;; +18DD3;TANGUT COMPONENT-852;Lo;0;L;;;;;N;;;;; +18DD4;TANGUT COMPONENT-853;Lo;0;L;;;;;N;;;;; +18DD5;TANGUT COMPONENT-854;Lo;0;L;;;;;N;;;;; +18DD6;TANGUT COMPONENT-855;Lo;0;L;;;;;N;;;;; +18DD7;TANGUT COMPONENT-856;Lo;0;L;;;;;N;;;;; +18DD8;TANGUT COMPONENT-857;Lo;0;L;;;;;N;;;;; +18DD9;TANGUT COMPONENT-858;Lo;0;L;;;;;N;;;;; +18DDA;TANGUT COMPONENT-859;Lo;0;L;;;;;N;;;;; +18DDB;TANGUT COMPONENT-860;Lo;0;L;;;;;N;;;;; +18DDC;TANGUT COMPONENT-861;Lo;0;L;;;;;N;;;;; +18DDD;TANGUT COMPONENT-862;Lo;0;L;;;;;N;;;;; +18DDE;TANGUT COMPONENT-863;Lo;0;L;;;;;N;;;;; +18DDF;TANGUT COMPONENT-864;Lo;0;L;;;;;N;;;;; +18DE0;TANGUT COMPONENT-865;Lo;0;L;;;;;N;;;;; +18DE1;TANGUT COMPONENT-866;Lo;0;L;;;;;N;;;;; +18DE2;TANGUT COMPONENT-867;Lo;0;L;;;;;N;;;;; +18DE3;TANGUT COMPONENT-868;Lo;0;L;;;;;N;;;;; +18DE4;TANGUT COMPONENT-869;Lo;0;L;;;;;N;;;;; +18DE5;TANGUT COMPONENT-870;Lo;0;L;;;;;N;;;;; +18DE6;TANGUT COMPONENT-871;Lo;0;L;;;;;N;;;;; +18DE7;TANGUT COMPONENT-872;Lo;0;L;;;;;N;;;;; +18DE8;TANGUT COMPONENT-873;Lo;0;L;;;;;N;;;;; +18DE9;TANGUT COMPONENT-874;Lo;0;L;;;;;N;;;;; +18DEA;TANGUT COMPONENT-875;Lo;0;L;;;;;N;;;;; +18DEB;TANGUT COMPONENT-876;Lo;0;L;;;;;N;;;;; +18DEC;TANGUT COMPONENT-877;Lo;0;L;;;;;N;;;;; +18DED;TANGUT COMPONENT-878;Lo;0;L;;;;;N;;;;; +18DEE;TANGUT COMPONENT-879;Lo;0;L;;;;;N;;;;; +18DEF;TANGUT COMPONENT-880;Lo;0;L;;;;;N;;;;; +18DF0;TANGUT COMPONENT-881;Lo;0;L;;;;;N;;;;; +18DF1;TANGUT COMPONENT-882;Lo;0;L;;;;;N;;;;; +18DF2;TANGUT COMPONENT-883;Lo;0;L;;;;;N;;;;; 1AFF0;KATAKANA LETTER MINNAN TONE-2;Lm;0;L;;;;;N;;;;; 1AFF1;KATAKANA LETTER MINNAN TONE-3;Lm;0;L;;;;;N;;;;; 1AFF2;KATAKANA LETTER MINNAN TONE-4;Lm;0;L;;;;;N;;;;; @@ -32629,6 +32963,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CCF7;OUTLINED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1CCF8;OUTLINED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1CCF9;OUTLINED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1CCFA;SNAKE SYMBOL;So;0;ON;;;;;N;;;;; +1CCFB;FLYING SAUCER SYMBOL;So;0;ON;;;;;N;;;;; +1CCFC;NOSE SYMBOL;So;0;ON;;;;;N;;;;; 1CD00;BLOCK OCTANT-3;So;0;ON;;;;;N;;;;; 1CD01;BLOCK OCTANT-23;So;0;ON;;;;;N;;;;; 1CD02;BLOCK OCTANT-123;So;0;ON;;;;;N;;;;; @@ -33065,6 +33402,46 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CEB1;KEYHOLE;So;0;ON;;;;;N;;;;; 1CEB2;OLD PERSONAL COMPUTER WITH MONITOR IN PORTRAIT ORIENTATION;So;0;ON;;;;;N;;;;; 1CEB3;BLACK RIGHT TRIANGLE CARET;So;0;ON;;;;;N;;;;; +1CEBA;FRAGILE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBB;OFFICE BUILDING SYMBOL;So;0;ON;;;;;N;;;;; +1CEBC;TREE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBD;APPLE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBE;CHERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEBF;STRAWBERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEC0;HEBE;So;0;ON;;;;;N;;;;; +1CEC1;IRIS;So;0;ON;;;;;N;;;;; +1CEC2;FLORA;So;0;ON;;;;;N;;;;; +1CEC3;METIS;So;0;ON;;;;;N;;;;; +1CEC4;PARTHENOPE;So;0;ON;;;;;N;;;;; +1CEC5;VICTORIA;So;0;ON;;;;;N;;;;; +1CEC6;EGERIA;So;0;ON;;;;;N;;;;; +1CEC7;IRENE;So;0;ON;;;;;N;;;;; +1CEC8;EUNOMIA;So;0;ON;;;;;N;;;;; +1CEC9;PSYCHE;So;0;ON;;;;;N;;;;; +1CECA;THETIS;So;0;ON;;;;;N;;;;; +1CECB;MELPOMENE;So;0;ON;;;;;N;;;;; +1CECC;FORTUNA;So;0;ON;;;;;N;;;;; +1CECD;ASTRONOMICAL SYMBOL FOR ASTEROID PROSERPINA;So;0;ON;;;;;N;;;;; +1CECE;BELLONA;So;0;ON;;;;;N;;;;; +1CECF;AMPHITRITE;So;0;ON;;;;;N;;;;; +1CED0;LEUKOTHEA;So;0;ON;;;;;N;;;;; +1CEE0;GEOMANTIC FIGURE POPULUS;So;0;ON;;;;;N;;;;; +1CEE1;GEOMANTIC FIGURE TRISTITIA;So;0;ON;;;;;N;;;;; +1CEE2;GEOMANTIC FIGURE ALBUS;So;0;ON;;;;;N;;;;; +1CEE3;GEOMANTIC FIGURE FORTUNA MAJOR;So;0;ON;;;;;N;;;;; +1CEE4;GEOMANTIC FIGURE RUBEUS;So;0;ON;;;;;N;;;;; +1CEE5;GEOMANTIC FIGURE ACQUISITIO;So;0;ON;;;;;N;;;;; +1CEE6;GEOMANTIC FIGURE CONJUNCTIO;So;0;ON;;;;;N;;;;; +1CEE7;GEOMANTIC FIGURE CAPUT DRACONIS;So;0;ON;;;;;N;;;;; +1CEE8;GEOMANTIC FIGURE LAETITIA;So;0;ON;;;;;N;;;;; +1CEE9;GEOMANTIC FIGURE CARCER;So;0;ON;;;;;N;;;;; +1CEEA;GEOMANTIC FIGURE AMISSIO;So;0;ON;;;;;N;;;;; +1CEEB;GEOMANTIC FIGURE PUELLA;So;0;ON;;;;;N;;;;; +1CEEC;GEOMANTIC FIGURE FORTUNA MINOR;So;0;ON;;;;;N;;;;; +1CEED;GEOMANTIC FIGURE PUER;So;0;ON;;;;;N;;;;; +1CEEE;GEOMANTIC FIGURE CAUDA DRACONIS;So;0;ON;;;;;N;;;;; +1CEEF;GEOMANTIC FIGURE VIA;So;0;ON;;;;;N;;;;; +1CEF0;MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 1CF00;ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF01;ZNAMENNY COMBINING MARK NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF02;ZNAMENNY COMBINING MARK TSATA ON LEFT;Mn;0;NSM;;;;;N;;;;; @@ -36004,6 +36381,61 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E5F9;OL ONAL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E5FA;OL ONAL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E5FF;OL ONAL ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; +1E6C0;TAI YO LETTER LOW KO;Lo;0;L;;;;;N;;;;; +1E6C1;TAI YO LETTER HIGH KO;Lo;0;L;;;;;N;;;;; +1E6C2;TAI YO LETTER LOW KHO;Lo;0;L;;;;;N;;;;; +1E6C3;TAI YO LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; +1E6C4;TAI YO LETTER GO;Lo;0;L;;;;;N;;;;; +1E6C5;TAI YO LETTER NGO;Lo;0;L;;;;;N;;;;; +1E6C6;TAI YO LETTER CO;Lo;0;L;;;;;N;;;;; +1E6C7;TAI YO LETTER LOW XO;Lo;0;L;;;;;N;;;;; +1E6C8;TAI YO LETTER HIGH XO;Lo;0;L;;;;;N;;;;; +1E6C9;TAI YO LETTER LOW NYO;Lo;0;L;;;;;N;;;;; +1E6CA;TAI YO LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; +1E6CB;TAI YO LETTER DO;Lo;0;L;;;;;N;;;;; +1E6CC;TAI YO LETTER LOW TO;Lo;0;L;;;;;N;;;;; +1E6CD;TAI YO LETTER HIGH TO;Lo;0;L;;;;;N;;;;; +1E6CE;TAI YO LETTER THO;Lo;0;L;;;;;N;;;;; +1E6CF;TAI YO LETTER NO;Lo;0;L;;;;;N;;;;; +1E6D0;TAI YO LETTER BO;Lo;0;L;;;;;N;;;;; +1E6D1;TAI YO LETTER LOW PO;Lo;0;L;;;;;N;;;;; +1E6D2;TAI YO LETTER HIGH PO;Lo;0;L;;;;;N;;;;; +1E6D3;TAI YO LETTER PHO;Lo;0;L;;;;;N;;;;; +1E6D4;TAI YO LETTER LOW FO;Lo;0;L;;;;;N;;;;; +1E6D5;TAI YO LETTER HIGH FO;Lo;0;L;;;;;N;;;;; +1E6D6;TAI YO LETTER MO;Lo;0;L;;;;;N;;;;; +1E6D7;TAI YO LETTER YO;Lo;0;L;;;;;N;;;;; +1E6D8;TAI YO LETTER LO;Lo;0;L;;;;;N;;;;; +1E6D9;TAI YO LETTER VO;Lo;0;L;;;;;N;;;;; +1E6DA;TAI YO LETTER LOW HO;Lo;0;L;;;;;N;;;;; +1E6DB;TAI YO LETTER HIGH HO;Lo;0;L;;;;;N;;;;; +1E6DC;TAI YO LETTER QO;Lo;0;L;;;;;N;;;;; +1E6DD;TAI YO LETTER LOW KVO;Lo;0;L;;;;;N;;;;; +1E6DE;TAI YO LETTER HIGH KVO;Lo;0;L;;;;;N;;;;; +1E6E0;TAI YO LETTER AA;Lo;0;L;;;;;N;;;;; +1E6E1;TAI YO LETTER I;Lo;0;L;;;;;N;;;;; +1E6E2;TAI YO LETTER UE;Lo;0;L;;;;;N;;;;; +1E6E3;TAI YO SIGN UE;Mn;230;NSM;;;;;N;;;;; +1E6E4;TAI YO LETTER U;Lo;0;L;;;;;N;;;;; +1E6E5;TAI YO LETTER AE;Lo;0;L;;;;;N;;;;; +1E6E6;TAI YO SIGN AU;Mn;230;NSM;;;;;N;;;;; +1E6E7;TAI YO LETTER O;Lo;0;L;;;;;N;;;;; +1E6E8;TAI YO LETTER E;Lo;0;L;;;;;N;;;;; +1E6E9;TAI YO LETTER IA;Lo;0;L;;;;;N;;;;; +1E6EA;TAI YO LETTER UEA;Lo;0;L;;;;;N;;;;; +1E6EB;TAI YO LETTER UA;Lo;0;L;;;;;N;;;;; +1E6EC;TAI YO LETTER OO;Lo;0;L;;;;;N;;;;; +1E6ED;TAI YO LETTER AUE;Lo;0;L;;;;;N;;;;; +1E6EE;TAI YO SIGN AY;Mn;230;NSM;;;;;N;;;;; +1E6EF;TAI YO SIGN ANG;Mn;230;NSM;;;;;N;;;;; +1E6F0;TAI YO LETTER AN;Lo;0;L;;;;;N;;;;; +1E6F1;TAI YO LETTER AM;Lo;0;L;;;;;N;;;;; +1E6F2;TAI YO LETTER AK;Lo;0;L;;;;;N;;;;; +1E6F3;TAI YO LETTER AT;Lo;0;L;;;;;N;;;;; +1E6F4;TAI YO LETTER AP;Lo;0;L;;;;;N;;;;; +1E6F5;TAI YO SIGN OM;Mn;230;NSM;;;;;N;;;;; +1E6FE;TAI YO SYMBOL MUEANG;Lo;0;L;;;;;N;;;;; +1E6FF;TAI YO XAM LAI;Lm;0;L;;;;;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -38079,6 +38511,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6D8;LANDSLIDE;So;0;ON;;;;;N;;;;; 1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; @@ -38228,6 +38661,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; 1F775;OCCULTATION;So;0;ON;;;;;N;;;;; 1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F777;VESTA FORM TWO;So;0;ON;;;;;N;;;;; +1F778;ASTRAEA FORM TWO;So;0;ON;;;;;N;;;;; +1F779;HYGIEA FORM TWO;So;0;ON;;;;;N;;;;; +1F77A;PARTHENOPE FORM TWO;So;0;ON;;;;;N;;;;; 1F77B;HAUMEA;So;0;ON;;;;;N;;;;; 1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; 1F77D;GONGGONG;So;0;ON;;;;;N;;;;; @@ -38498,6 +38935,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8BB;SOUTH WEST ARROW FROM BAR;So;0;ON;;;;;N;;;;; 1F8C0;LEFTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 1F8C1;RIGHTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; +1F8D0;LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; +1F8D1;LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D2;LONG RIGHTWARDS HARPOON ABOVE SHORT LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D3;SHORT RIGHTWARDS HARPOON ABOVE LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D4;LONG LEFTWARDS HARPOON ABOVE SHORT RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D5;SHORT LEFTWARDS HARPOON ABOVE LONG RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D6;LONG RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; +1F8D7;LONG RIGHTWARDS ARROW WITH DOUBLE SLASH;Sm;0;ON;;;;;N;;;;; +1F8D8;LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE;Sm;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -38838,6 +39284,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;; 1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;; 1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;; +1FA54;WHITE CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA55;WHITE CHESS ALFIL;So;0;ON;;;;;N;;;;; +1FA56;BLACK CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA57;BLACK CHESS ALFIL;So;0;ON;;;;;N;;;;; 1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;; 1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;; 1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;; @@ -38875,6 +39325,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA87;MARACAS;So;0;ON;;;;;N;;;;; 1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA89;HARP;So;0;ON;;;;;N;;;;; +1FA8A;TROMBONE;So;0;ON;;;;;N;;;;; +1FA8E;TREASURE CHEST;So;0;ON;;;;;N;;;;; 1FA8F;SHOVEL;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; @@ -38931,6 +39383,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; 1FAC6;FINGERPRINT;So;0;ON;;;;;N;;;;; +1FAC8;HAIRY CREATURE;So;0;ON;;;;;N;;;;; +1FACD;ORCA;So;0;ON;;;;;N;;;;; 1FACE;MOOSE;So;0;ON;;;;;N;;;;; 1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; @@ -38957,6 +39411,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; 1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAE9;FACE WITH BAGS UNDER EYES;So;0;ON;;;;;N;;;;; +1FAEA;DISTORTED FACE;So;0;ON;;;;;N;;;;; +1FAEF;FIGHT CLOUD;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -39215,14 +39671,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1FBFA;ALARM BELL SYMBOL;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6DF;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; -2B739;;Lo;0;L;;;;;N;;;;; +2B73F;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; -2CEA1;;Lo;0;L;;;;;N;;;;; +2CEAD;;Lo;0;L;;;;;N;;;;; 2CEB0;;Lo;0;L;;;;;N;;;;; 2EBE0;;Lo;0;L;;;;;N;;;;; 2EBF0;;Lo;0;L;;;;;N;;;;; @@ -39773,6 +40230,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 3134A;;Lo;0;L;;;;;N;;;;; 31350;;Lo;0;L;;;;;N;;;;; 323AF;;Lo;0;L;;;;;N;;;;; +323B0;;Lo;0;L;;;;;N;;;;; +33479;;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/lib/elixir/unicode/confusables.txt b/lib/elixir/unicode/confusables.txt index f88841b7ff0..d52b6278a42 100644 --- a/lib/elixir/unicode/confusables.txt +++ b/lib/elixir/unicode/confusables.txt @@ -1,11 +1,11 @@ # confusables.txt -# Date: 2024-08-14, 23:39:57 GMT -# © 2024 Unicode®, Inc. +# Date: 2025-07-22, 05:49:37 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Unicode Security Mechanisms for UTS #39 -# Version: 16.0.0 +# Version: 17.0.0 # # For documentation and usage, see https://www.unicode.org/reports/tr39 # @@ -44,6 +44,8 @@ A67C ; 0306 ; MA # ( ꙼ → ̆ ) COMBINING CYRILLIC KAVYKA → COMBINING BREVE 0658 ; 0306 ; MA # ( ٘ → ̆ ) ARABIC MARK NOON GHUNNA → COMBINING BREVE # 065A ; 0306 ; MA # ( ٚ → ̆ ) ARABIC VOWEL SIGN SMALL V ABOVE → COMBINING BREVE # →̌→ 036E ; 0306 ; MA # ( ͮ → ̆ ) COMBINING LATIN SMALL LETTER V → COMBINING BREVE # →̌→ +0945 ; 0306 ; MA # ( ॅ → ̆ ) DEVANAGARI VOWEL SIGN CANDRA E → COMBINING BREVE # +11B66 ; 0306 ; MA # ( 𑭦 → ̆ ) SHARADA VOWEL SIGN CANDRA E → COMBINING BREVE # →ॅ→ 06E8 ; 0306 0307 ; MA # ( ۨ → ̆̇ ) ARABIC SMALL HIGH NOON → COMBINING BREVE, COMBINING DOT ABOVE # →̐→ 0310 ; 0306 0307 ; MA # ( ̐ → ̆̇ ) COMBINING CANDRABINDU → COMBINING BREVE, COMBINING DOT ABOVE # @@ -112,6 +114,9 @@ A6F0 ; 0302 ; MA # ( ꛰ → ̂ ) BAMUM COMBINING MARK KOQNDON → COMBINING CIR 0659 ; 0304 ; MA # ( ٙ → ̄ ) ARABIC ZWARAKAY → COMBINING MACRON # 07EB ; 0304 ; MA # ( ߫ → ̄ ) NKO COMBINING SHORT HIGH TONE → COMBINING MACRON # A6F1 ; 0304 ; MA # ( ꛱ → ̄ ) BAMUM COMBINING MARK TUKWENTIS → COMBINING MACRON # +1AE2 ; 0304 ; MA # ( ᫢ → ̄ ) COMBINING MINUS SIGN ABOVE → COMBINING MACRON # + +1AE8 ; 0304 0304 ; MA # ( ᫨ → ̄̄ ) COMBINING EQUALS SIGN ABOVE → COMBINING MACRON, COMBINING MACRON # 1CDA ; 030E ; MA # ( ᳚ → ̎ ) VEDIC TONE DOUBLE SVARITA → COMBINING DOUBLE VERTICAL LINE ABOVE # @@ -123,6 +128,10 @@ A6F1 ; 0304 ; MA # ( ꛱ → ̄ ) BAMUM COMBINING MARK TUKWENTIS → COMBINING M 0900 ; 0352 ; MA # ( ऀ → ͒ ) DEVANAGARI SIGN INVERTED CANDRABINDU → COMBINING FERMATA # +1AD9 ; 1AC6 ; MA # ( ᫙ → ᫆ ) COMBINING SHARP SIGN → COMBINING NUMBER SIGN ABOVE # + +1E6EE ; 1AC8 ; MA # ( 𞛮 → ᫈ ) TAI YO SIGN AY → COMBINING PLUS SIGN ABOVE # + 1CED ; 0316 ; MA # ( ᳭ → ̖ ) VEDIC SIGN TIRYAK → COMBINING GRAVE ACCENT BELOW # 1CDC ; 0329 ; MA # ( ᳜ → ̩ ) VEDIC TONE KATHAKA ANUDATTA → COMBINING VERTICAL LINE BELOW # @@ -260,6 +269,7 @@ FE58 ; 002D ; MA #* ( ﹘ → - ) SMALL EM DASH → HYPHEN-MINUS # 02D7 ; 002D ; MA #* ( ˗ → - ) MODIFIER LETTER MINUS SIGN → HYPHEN-MINUS # 2212 ; 002D ; MA #* ( − → - ) MINUS SIGN → HYPHEN-MINUS # 2796 ; 002D ; MA #* ( ➖ → - ) HEAVY MINUS SIGN → HYPHEN-MINUS # →−→ +2CBB ; 002D ; MA # ( ⲻ → - ) COPTIC SMALL LETTER DIALECT-P NI → HYPHEN-MINUS # →−→ 2CBA ; 002D ; MA # ( Ⲻ → - ) COPTIC CAPITAL LETTER DIALECT-P NI → HYPHEN-MINUS # →‒→ 2A29 ; 002D 0313 ; MA #* ( ⨩ → -̓ ) MINUS SIGN WITH COMMA ABOVE → HYPHEN-MINUS, COMBINING COMMA ABOVE # →−̓→ @@ -268,6 +278,8 @@ FE58 ; 002D ; MA #* ( ﹘ → - ) SMALL EM DASH → HYPHEN-MINUS # FB29 ; 002D 0307 ; MA #* ( ﬩ → -̇ ) HEBREW LETTER ALTERNATIVE PLUS SIGN → HYPHEN-MINUS, COMBINING DOT ABOVE # →∸→→−̇→ 2238 ; 002D 0307 ; MA #* ( ∸ → -̇ ) DOT MINUS → HYPHEN-MINUS, COMBINING DOT ABOVE # →−̇→ +2CB3 ; 002D 0307 ; MA # ( ⲳ → -̇ ) COPTIC SMALL LETTER DIALECT-P ALEF → HYPHEN-MINUS, COMBINING DOT ABOVE # →﬩→→∸→→−̇→ +2CB2 ; 002D 0307 ; MA # ( Ⲳ → -̇ ) COPTIC CAPITAL LETTER DIALECT-P ALEF → HYPHEN-MINUS, COMBINING DOT ABOVE # →﬩→→∸→→−̇→ 2A2A ; 002D 0323 ; MA #* ( ⨪ → -̣ ) MINUS SIGN WITH DOT BELOW → HYPHEN-MINUS, COMBINING DOT BELOW # →−̣→ @@ -305,6 +317,7 @@ A789 ; 003A ; MA #* ( ꞉ → : ) MODIFIER LETTER COLON → COLON # 2236 ; 003A ; MA #* ( ∶ → : ) RATIO → COLON # 02D0 ; 003A ; MA # ( ː → : ) MODIFIER LETTER TRIANGULAR COLON → COLON # A4FD ; 003A ; MA # ( ꓽ → : ) LISU LETTER TONE MYA JEU → COLON # +11DD9 ; 003A ; MA # ( 𑷙 → : ) TOLONG SIKI SIGN SELA → COLON # 2A74 ; 003A 003A 003D ; MA #* ( ⩴ → ::= ) DOUBLE COLON EQUAL → COLON, COLON, EQUALS SIGN # @@ -593,7 +606,7 @@ FF40 ; 0027 ; MA #* ( ` → ' ) FULLWIDTH GRAVE ACCENT → APOSTROPHE # →‘ 02B9 ; 0027 ; MA # ( ʹ → ' ) MODIFIER LETTER PRIME → APOSTROPHE # 0374 ; 0027 ; MA # ( ʹ → ' ) GREEK NUMERAL SIGN → APOSTROPHE # →′→ 02C8 ; 0027 ; MA # ( ˈ → ' ) MODIFIER LETTER VERTICAL LINE → APOSTROPHE # -02CA ; 0027 ; MA # ( ˊ → ' ) MODIFIER LETTER ACUTE ACCENT → APOSTROPHE # →ʹ→→′→ +02CA ; 0027 ; MA # ( ˊ → ' ) MODIFIER LETTER ACUTE ACCENT → APOSTROPHE # →΄→→ʹ→ 02CB ; 0027 ; MA # ( ˋ → ' ) MODIFIER LETTER GRAVE ACCENT → APOSTROPHE # →`→→‘→ 02F4 ; 0027 ; MA #* ( ˴ → ' ) MODIFIER LETTER MIDDLE GRAVE ACCENT → APOSTROPHE # →ˋ→→`→→‘→ 02BB ; 0027 ; MA # ( ʻ → ' ) MODIFIER LETTER TURNED COMMA → APOSTROPHE # →‘→ @@ -990,6 +1003,7 @@ FF3E ; FE3F ; MA #* ( ^ → ︿ ) FULLWIDTH CIRCUMFLEX ACCENT → PRESENTATION 1D23A ; 002F ; MA #* ( 𝈺 → / ) GREEK INSTRUMENTAL NOTATION SYMBOL-47 → SOLIDUS # 31D3 ; 002F ; MA #* ( ㇓ → / ) CJK STROKE SP → SOLIDUS # →⼃→ 3033 ; 002F ; MA # ( 〳 → / ) VERTICAL KANA REPEAT MARK UPPER HALF → SOLIDUS # +2CC7 ; 002F ; MA # ( ⳇ → / ) COPTIC SMALL LETTER OLD COPTIC ESH → SOLIDUS # 2CC6 ; 002F ; MA # ( Ⳇ → / ) COPTIC CAPITAL LETTER OLD COPTIC ESH → SOLIDUS # 30CE ; 002F ; MA # ( ノ → / ) KATAKANA LETTER NO → SOLIDUS # →⼃→ 4E3F ; 002F ; MA # ( 丿 → / ) CJK UNIFIED IDEOGRAPH-4E3F → SOLIDUS # →⼃→ @@ -1072,6 +1086,7 @@ A714 ; 02EB ; MA #* ( ꜔ → ˫ ) MODIFIER LETTER MID LEFT-STEM TONE BAR → MO 25E6 ; 00B0 ; MA #* ( ◦ → ° ) WHITE BULLET → DEGREE SIGN # →∘→ 235C ; 00B0 0332 ; MA #* ( ⍜ → °̲ ) APL FUNCTIONAL SYMBOL CIRCLE UNDERBAR → DEGREE SIGN, COMBINING LOW LINE # →○̲→ +10ED0 ; 00B0 0332 ; MA #* ( 𐻐 → °̲ ) ARABIC BIBLICAL END OF VERSE → DEGREE SIGN, COMBINING LOW LINE # →⍜→→○̲→ 2364 ; 00B0 0308 ; MA #* ( ⍤ → °̈ ) APL FUNCTIONAL SYMBOL JOT DIAERESIS → DEGREE SIGN, COMBINING DIAERESIS # →◦̈→→∘̈→ @@ -1142,6 +1157,7 @@ A714 ; 02EB ; MA #* ( ꜔ → ˫ ) MODIFIER LETTER MID LEFT-STEM TONE BAR → MO 16ED ; 002B ; MA #* ( ᛭ → + ) RUNIC CROSS PUNCTUATION → PLUS SIGN # 2795 ; 002B ; MA #* ( ➕ → + ) HEAVY PLUS SIGN → PLUS SIGN # 1029B ; 002B ; MA # ( 𐊛 → + ) LYCIAN LETTER H → PLUS SIGN # +1E6E9 ; 002B ; MA # ( 𞛩 → + ) TAI YO LETTER IA → PLUS SIGN # 2A23 ; 002B 0302 ; MA #* ( ⨣ → +̂ ) PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE → PLUS SIGN, COMBINING CIRCUMFLEX ACCENT # @@ -1167,6 +1183,7 @@ A714 ; 02EB ; MA #* ( ꜔ → ˫ ) MODIFIER LETTER MID LEFT-STEM TONE BAR → MO 16B2 ; 003C ; MA # ( ᚲ → < ) RUNIC LETTER KAUNA → LESS-THAN SIGN # 22D6 ; 003C 00B7 ; MA #* ( ⋖ → <· ) LESS-THAN WITH DOT → LESS-THAN SIGN, MIDDLE DOT # →ᑅ→→ᐸᐧ→ +2CB5 ; 003C 00B7 ; MA # ( ⲵ → <· ) COPTIC SMALL LETTER OLD COPTIC AIN → LESS-THAN SIGN, MIDDLE DOT # →⋖→→ᑅ→→ᐸᐧ→ 2CB4 ; 003C 00B7 ; MA # ( Ⲵ → <· ) COPTIC CAPITAL LETTER OLD COPTIC AIN → LESS-THAN SIGN, MIDDLE DOT # →ᑅ→→ᐸᐧ→ 1445 ; 003C 00B7 ; MA # ( ᑅ → <· ) CANADIAN SYLLABICS WEST-CREE PWA → LESS-THAN SIGN, MIDDLE DOT # →ᐸᐧ→ @@ -1189,6 +1206,8 @@ A4FF ; 003D ; MA #* ( ꓿ → = ) LISU PUNCTUATION FULL STOP → EQUALS SIGN # 2251 ; 003D 0307 0323 ; MA #* ( ≑ → =̣̇ ) GEOMETRICALLY EQUAL TO → EQUALS SIGN, COMBINING DOT ABOVE, COMBINING DOT BELOW # →≐̣→ +2B96 ; 003D 1AB2 ; MA #* ( ⮖ → =᪲ ) EQUALS SIGN WITH INFINITY ABOVE → EQUALS SIGN, COMBINING INFINITY # + 2A6E ; 003D 20F0 ; MA #* ( ⩮ → =⃰ ) EQUALS WITH ASTERISK → EQUALS SIGN, COMBINING ASTERISK ABOVE # 2A75 ; 003D 003D ; MA #* ( ⩵ → == ) TWO CONSECUTIVE EQUALS SIGNS → EQUALS SIGN, EQUALS SIGN # @@ -1245,6 +1264,7 @@ A4FF ; 003D ; MA #* ( ꓿ → = ) LISU PUNCTUATION FULL STOP → EQUALS SIGN # 1F75E ; 224F ; MA #* ( 🝞 → ≏ ) ALCHEMICAL SYMBOL FOR SUBLIMATION → DIFFERENCE BETWEEN # →♎→ 2263 ; 2261 ; MA #* ( ≣ → ≡ ) STRICTLY EQUIVALENT TO → IDENTICAL TO # +2CB7 ; 2261 ; MA # ( ⲷ → ≡ ) COPTIC SMALL LETTER CRYPTOGRAMMIC EIE → IDENTICAL TO # 2A03 ; 228D ; MA #* ( ⨃ → ⊍ ) N-ARY UNION OPERATOR WITH DOT → MULTISET MULTIPLICATION # @@ -1352,8 +1372,6 @@ FFED ; 25AA ; MA #* ( ■ → ▪ ) HALFWIDTH BLACK SQUARE → BLACK SMALL SQUAR 2A3E ; 2A1F ; MA #* ( ⨾ → ⨟ ) Z NOTATION RELATIONAL COMPOSITION → Z NOTATION SCHEMA COMPOSITION # -101A0 ; 2CE8 ; MA #* ( 𐆠 → ⳨ ) GREEK SYMBOL TAU RHO → COPTIC SYMBOL TAU RO # - 2669 ; 1D158 1D165 ; MA #* ( ♩ → 𝅘𝅥 ) QUARTER NOTE → MUSICAL SYMBOL NOTEHEAD BLACK, MUSICAL SYMBOL COMBINING STEM # 266A ; 1D158 1D165 1D16E ; MA #* ( ♪ → 𝅘𝅥𝅮 ) EIGHTH NOTE → MUSICAL SYMBOL NOTEHEAD BLACK, MUSICAL SYMBOL COMBINING STEM, MUSICAL SYMBOL COMBINING FLAG-1 # @@ -1362,6 +1380,8 @@ FFED ; 25AA ; MA #* ( ■ → ▪ ) HALFWIDTH BLACK SQUARE → BLACK SMALL SQUAR 21BA ; 1F10E ; MA #* ( ↺ → 🄎 ) ANTICLOCKWISE OPEN CIRCLE ARROW → CIRCLED ANTICLOCKWISE ARROW # +1CCFB ; 1F6F8 ; MA #* ( 𜳻 → 🛸 ) FLYING SAUCER SYMBOL → FLYING SAUCER # + 02D9 ; 0971 ; MA #* ( ˙ → ॱ ) DOT ABOVE → DEVANAGARI SIGN HIGH SPACING DOT # 0D4E ; 0971 ; MA # ( ൎ → ॱ ) MALAYALAM LETTER DOT REPH → DEVANAGARI SIGN HIGH SPACING DOT # →˙→ @@ -1435,6 +1455,7 @@ A9CF ; 0662 ; MA # ( ꧏ → ‎٢‎ ) JAVANESE PANGRANGKEP → ARABIC-INDIC DI 06F2 ; 0662 ; MA # ( ۲ → ‎٢‎ ) EXTENDED ARABIC-INDIC DIGIT TWO → ARABIC-INDIC DIGIT TWO # 0AE8 ; 0968 ; MA # ( ૨ → २ ) GUJARATI DIGIT TWO → DEVANAGARI DIGIT TWO # +0AB0 ; 0968 ; MA # ( ર → २ ) GUJARATI LETTER RA → DEVANAGARI DIGIT TWO # →૨→ 114D2 ; 09E8 ; MA # ( 𑓒 → ২ ) TIRHUTA DIGIT TWO → BENGALI DIGIT TWO # @@ -1491,6 +1512,8 @@ A9CF ; 0662 ; MA # ( ꧏ → ‎٢‎ ) JAVANESE PANGRANGKEP → ARABIC-INDIC DI 335A ; 0032 70B9 ; MA #* ( ㍚ → 2点 ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWO → DIGIT TWO, CJK UNIFIED IDEOGRAPH-70B9 # 1D206 ; 0033 ; MA #* ( 𝈆 → 3 ) GREEK VOCAL NOTATION SYMBOL-7 → DIGIT THREE # +0969 ; 0033 ; MA # ( ३ → 3 ) DEVANAGARI DIGIT THREE → DIGIT THREE # →૩→ +0AE9 ; 0033 ; MA # ( ૩ → 3 ) GUJARATI DIGIT THREE → DIGIT THREE # 1CCF3 ; 0033 ; MA # ( 𜳳 → 3 ) OUTLINED DIGIT THREE → DIGIT THREE # 1D7D1 ; 0033 ; MA # ( 𝟑 → 3 ) MATHEMATICAL BOLD DIGIT THREE → DIGIT THREE # 1D7DB ; 0033 ; MA # ( 𝟛 → 3 ) MATHEMATICAL DOUBLE-STRUCK DIGIT THREE → DIGIT THREE # @@ -1502,6 +1525,8 @@ A7AB ; 0033 ; MA # ( Ɜ → 3 ) LATIN CAPITAL LETTER REVERSED OPEN E → DIGIT 021C ; 0033 ; MA # ( Ȝ → 3 ) LATIN CAPITAL LETTER YOGH → DIGIT THREE # →Ʒ→ 01B7 ; 0033 ; MA # ( Ʒ → 3 ) LATIN CAPITAL LETTER EZH → DIGIT THREE # A76A ; 0033 ; MA # ( Ꝫ → 3 ) LATIN CAPITAL LETTER ET → DIGIT THREE # +2C9C ; 0033 ; MA # ( Ⲝ → 3 ) COPTIC CAPITAL LETTER KSI → DIGIT THREE # →Ʒ→ +2CC4 ; 0033 ; MA # ( Ⳅ → 3 ) COPTIC CAPITAL LETTER OLD COPTIC SHEI → DIGIT THREE # →Ʒ→ 2CCC ; 0033 ; MA # ( Ⳍ → 3 ) COPTIC CAPITAL LETTER OLD COPTIC HORI → DIGIT THREE # →Ȝ→→Ʒ→ 0417 ; 0033 ; MA # ( З → 3 ) CYRILLIC CAPITAL LETTER ZE → DIGIT THREE # 04E0 ; 0033 ; MA # ( Ӡ → 3 ) CYRILLIC CAPITAL LETTER ABKHASIAN DZE → DIGIT THREE # →Ʒ→ @@ -1511,8 +1536,6 @@ A76A ; 0033 ; MA # ( Ꝫ → 3 ) LATIN CAPITAL LETTER ET → DIGIT THREE # 06F3 ; 0663 ; MA # ( ۳ → ‎٣‎ ) EXTENDED ARABIC-INDIC DIGIT THREE → ARABIC-INDIC DIGIT THREE # 1E8C9 ; 0663 ; MA #* ( ‎𞣉‎ → ‎٣‎ ) MENDE KIKAKUI DIGIT THREE → ARABIC-INDIC DIGIT THREE # -0AE9 ; 0969 ; MA # ( ૩ → ३ ) GUJARATI DIGIT THREE → DEVANAGARI DIGIT THREE # - 2462 ; 2782 ; MA #* ( ③ → ➂ ) CIRCLED DIGIT THREE → DINGBAT CIRCLED SANS-SERIF DIGIT THREE # 0498 ; 0033 0326 ; MA # ( Ҙ → 3̦ ) CYRILLIC CAPITAL LETTER ZE WITH DESCENDER → DIGIT THREE, COMBINING COMMA BELOW # →З̧→ @@ -1588,7 +1611,10 @@ A76A ; 0033 ; MA # ( Ꝫ → 3 ) LATIN CAPITAL LETTER ET → DIGIT THREE # 1D7F2 ; 0036 ; MA # ( 𝟲 → 6 ) MATHEMATICAL SANS-SERIF BOLD DIGIT SIX → DIGIT SIX # 1D7FC ; 0036 ; MA # ( 𝟼 → 6 ) MATHEMATICAL MONOSPACE DIGIT SIX → DIGIT SIX # 1FBF6 ; 0036 ; MA # ( 🯶 → 6 ) SEGMENTED DIGIT SIX → DIGIT SIX # +2CD3 ; 0036 ; MA # ( ⳓ → 6 ) COPTIC SMALL LETTER OLD COPTIC HEI → DIGIT SIX # 2CD2 ; 0036 ; MA # ( Ⳓ → 6 ) COPTIC CAPITAL LETTER OLD COPTIC HEI → DIGIT SIX # +03EC ; 0036 ; MA # ( Ϭ → 6 ) COPTIC CAPITAL LETTER SHIMA → DIGIT SIX # +2CDC ; 0036 ; MA # ( Ⳝ → 6 ) COPTIC CAPITAL LETTER OLD NUBIAN SHIMA → DIGIT SIX # →Ϭ→ 0431 ; 0036 ; MA # ( б → 6 ) CYRILLIC SMALL LETTER BE → DIGIT SIX # 13EE ; 0036 ; MA # ( Ꮾ → 6 ) CHEROKEE LETTER WV → DIGIT SIX # 118D5 ; 0036 ; MA # ( 𑣕 → 6 ) WARANG CITI SMALL LETTER AT → DIGIT SIX # @@ -1673,6 +1699,7 @@ A76A ; 0033 ; MA # ( Ꝫ → 3 ) LATIN CAPITAL LETTER ET → DIGIT THREE # 1D7FF ; 0039 ; MA # ( 𝟿 → 9 ) MATHEMATICAL MONOSPACE DIGIT NINE → DIGIT NINE # 1FBF9 ; 0039 ; MA # ( 🯹 → 9 ) SEGMENTED DIGIT NINE → DIGIT NINE # A76E ; 0039 ; MA # ( Ꝯ → 9 ) LATIN CAPITAL LETTER CON → DIGIT NINE # +2CCB ; 0039 ; MA # ( ⳋ → 9 ) COPTIC SMALL LETTER DIALECT-P HORI → DIGIT NINE # 2CCA ; 0039 ; MA # ( Ⳋ → 9 ) COPTIC CAPITAL LETTER DIALECT-P HORI → DIGIT NINE # 118CC ; 0039 ; MA # ( 𑣌 → 9 ) WARANG CITI SMALL LETTER KO → DIGIT NINE # 118AC ; 0039 ; MA # ( 𑢬 → 9 ) WARANG CITI CAPITAL LETTER KO → DIGIT NINE # @@ -1823,6 +1850,7 @@ A4EF ; 2C6F ; MA # ( ꓯ → Ɐ ) LISU LETTER AE → LATIN CAPITAL LETTER TURNE 13CF ; 0062 ; MA # ( Ꮟ → b ) CHEROKEE LETTER SI → LATIN SMALL LETTER B # 1472 ; 0062 ; MA # ( ᑲ → b ) CANADIAN SYLLABICS KA → LATIN SMALL LETTER B # 15AF ; 0062 ; MA # ( ᖯ → b ) CANADIAN SYLLABICS AIVILIK B → LATIN SMALL LETTER B # +16EB6 ; 0062 ; MA # ( 𖺶 → b ) BERIA ERFE CAPITAL LETTER UI → LATIN SMALL LETTER B # →Ь→→Ƅ→ FF22 ; 0042 ; MA # ( B → B ) FULLWIDTH LATIN CAPITAL LETTER B → LATIN CAPITAL LETTER B # →Β→ 212C ; 0042 ; MA # ( ℬ → B ) SCRIPT CAPITAL B → LATIN CAPITAL LETTER B # @@ -1846,6 +1874,7 @@ A7B4 ; 0042 ; MA # ( Ꞵ → B ) LATIN CAPITAL LETTER BETA → LATIN CAPITAL LET 1D71D ; 0042 ; MA # ( 𝜝 → B ) MATHEMATICAL BOLD ITALIC CAPITAL BETA → LATIN CAPITAL LETTER B # →Β→ 1D757 ; 0042 ; MA # ( 𝝗 → B ) MATHEMATICAL SANS-SERIF BOLD CAPITAL BETA → LATIN CAPITAL LETTER B # →Β→ 1D791 ; 0042 ; MA # ( 𝞑 → B ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL BETA → LATIN CAPITAL LETTER B # →Β→ +2C82 ; 0042 ; MA # ( Ⲃ → B ) COPTIC CAPITAL LETTER VIDA → LATIN CAPITAL LETTER B # 0412 ; 0042 ; MA # ( В → B ) CYRILLIC CAPITAL LETTER VE → LATIN CAPITAL LETTER B # 13F4 ; 0042 ; MA # ( Ᏼ → B ) CHEROKEE LETTER YV → LATIN CAPITAL LETTER B # 15F7 ; 0042 ; MA # ( ᗷ → B ) CANADIAN SYLLABICS CARRIER KHE → LATIN CAPITAL LETTER B # @@ -1876,6 +1905,7 @@ A4D0 ; 0042 ; MA # ( ꓐ → B ) LISU LETTER BA → LATIN CAPITAL LETTER B # 042B ; 0062 006C ; MA # ( Ы → bl ) CYRILLIC CAPITAL LETTER YERU → LATIN SMALL LETTER B, LATIN SMALL LETTER L # →ЬІ→→Ь1→ +2C83 ; 0299 ; MA # ( ⲃ → ʙ ) COPTIC SMALL LETTER VIDA → LATIN LETTER SMALL CAPITAL B # →в→ 0432 ; 0299 ; MA # ( в → ʙ ) CYRILLIC SMALL LETTER VE → LATIN LETTER SMALL CAPITAL B # 13FC ; 0299 ; MA # ( ᏼ → ʙ ) CHEROKEE SMALL LETTER YV → LATIN LETTER SMALL CAPITAL B # @@ -1898,6 +1928,8 @@ FF43 ; 0063 ; MA # ( c → c ) FULLWIDTH LATIN SMALL LETTER C → LATIN SMALL 03F2 ; 0063 ; MA # ( ϲ → c ) GREEK LUNATE SIGMA SYMBOL → LATIN SMALL LETTER C # 2CA5 ; 0063 ; MA # ( ⲥ → c ) COPTIC SMALL LETTER SIMA → LATIN SMALL LETTER C # →ϲ→ 0441 ; 0063 ; MA # ( с → c ) CYRILLIC SMALL LETTER ES → LATIN SMALL LETTER C # +1004 ; 0063 ; MA # ( င → c ) MYANMAR LETTER NGA → LATIN SMALL LETTER C # +105A ; 0063 ; MA # ( ၚ → c ) MYANMAR LETTER MON NGA → LATIN SMALL LETTER C # →င→ ABAF ; 0063 ; MA # ( ꮯ → c ) CHEROKEE SMALL LETTER TLI → LATIN SMALL LETTER C # →ᴄ→ 1043D ; 0063 ; MA # ( 𐐽 → c ) DESERET SMALL LETTER CHEE → LATIN SMALL LETTER C # @@ -2071,6 +2103,7 @@ AB70 ; 1D05 ; MA # ( ꭰ → ᴅ ) CHEROKEE SMALL LETTER A → LATIN LETTER SMAL 1D739 ; 1E9F ; MA # ( 𝜹 → ẟ ) MATHEMATICAL BOLD ITALIC SMALL DELTA → LATIN SMALL LETTER DELTA # →δ→ 1D773 ; 1E9F ; MA # ( 𝝳 → ẟ ) MATHEMATICAL SANS-SERIF BOLD SMALL DELTA → LATIN SMALL LETTER DELTA # →δ→ 1D7AD ; 1E9F ; MA # ( 𝞭 → ẟ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL DELTA → LATIN SMALL LETTER DELTA # →δ→ +2CDD ; 1E9F ; MA # ( ⳝ → ẟ ) COPTIC SMALL LETTER OLD NUBIAN SHIMA → LATIN SMALL LETTER DELTA # →δ→ 056E ; 1E9F ; MA # ( ծ → ẟ ) ARMENIAN SMALL LETTER CA → LATIN SMALL LETTER DELTA # →δ→ 1577 ; 1E9F ; MA # ( ᕷ → ẟ ) CANADIAN SYLLABICS NUNAVIK HO → LATIN SMALL LETTER DELTA # →δ→ @@ -2189,6 +2222,7 @@ A79D ; 029A ; MA # ( ꞝ → ʚ ) LATIN SMALL LETTER VOLAPUK OE → LATIN SMALL 1D68F ; 0066 ; MA # ( 𝚏 → f ) MATHEMATICAL MONOSPACE SMALL F → LATIN SMALL LETTER F # AB35 ; 0066 ; MA # ( ꬵ → f ) LATIN SMALL LETTER LENIS F → LATIN SMALL LETTER F # A799 ; 0066 ; MA # ( ꞙ → f ) LATIN SMALL LETTER F WITH STROKE → LATIN SMALL LETTER F # +0192 ; 0066 ; MA # ( ƒ → f ) LATIN SMALL LETTER F WITH HOOK → LATIN SMALL LETTER F # 017F ; 0066 ; MA # ( ſ → f ) LATIN SMALL LETTER LONG S → LATIN SMALL LETTER F # 1E9D ; 0066 ; MA # ( ẝ → f ) LATIN SMALL LETTER LONG S WITH HIGH STROKE → LATIN SMALL LETTER F # 0584 ; 0066 ; MA # ( ք → f ) ARMENIAN SMALL LETTER KEH → LATIN SMALL LETTER F # @@ -2219,8 +2253,6 @@ A4DD ; 0046 ; MA # ( ꓝ → F ) LISU LETTER TSA → LATIN CAPITAL LETTER F # 102A5 ; 0046 ; MA # ( 𐊥 → F ) CARIAN LETTER R → LATIN CAPITAL LETTER F # 10525 ; 0046 ; MA # ( 𐔥 → F ) ELBASAN LETTER GHE → LATIN CAPITAL LETTER F # -0192 ; 0066 0326 ; MA # ( ƒ → f̦ ) LATIN SMALL LETTER F WITH HOOK → LATIN SMALL LETTER F, COMBINING COMMA BELOW # →f̡→ - 0191 ; 0046 0326 ; MA # ( Ƒ → F̦ ) LATIN CAPITAL LETTER F WITH HOOK → LATIN CAPITAL LETTER F, COMBINING COMMA BELOW # →F̡→ 1D6E ; 0066 0334 ; MA # ( ᵮ → f̴ ) LATIN SMALL LETTER F WITH MIDDLE TILDE → LATIN SMALL LETTER F, COMBINING TILDE OVERLAY # @@ -2237,7 +2269,7 @@ FB01 ; 0066 0069 ; MA # ( fi → fi ) LATIN SMALL LIGATURE FI → LATIN SMALL L FB02 ; 0066 006C ; MA # ( fl → fl ) LATIN SMALL LIGATURE FL → LATIN SMALL LETTER F, LATIN SMALL LETTER L # -02A9 ; 0066 014B ; MA # ( ʩ → fŋ ) LATIN SMALL LETTER FENG DIGRAPH → LATIN SMALL LETTER F, LATIN SMALL LETTER ENG # +02A9 ; 0066 006E 0329 ; MA # ( ʩ → fn̩ ) LATIN SMALL LETTER FENG DIGRAPH → LATIN SMALL LETTER F, LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →fŋ→ 15B5 ; 2132 ; MA # ( ᖵ → Ⅎ ) CANADIAN SYLLABICS BLACKFOOT WI → TURNED CAPITAL F # A4DE ; 2132 ; MA # ( ꓞ → Ⅎ ) LISU LETTER TSHA → TURNED CAPITAL F # @@ -2367,6 +2399,7 @@ A695 ; 0068 0314 ; MA # ( ꚕ → h̔ ) CYRILLIC SMALL LETTER HWE → LATIN SMAL 04C9 ; 0048 0326 ; MA # ( Ӊ → H̦ ) CYRILLIC CAPITAL LETTER EN WITH TAIL → LATIN CAPITAL LETTER H, COMBINING COMMA BELOW # →Н̡→ 04C7 ; 0048 0326 ; MA # ( Ӈ → H̦ ) CYRILLIC CAPITAL LETTER EN WITH HOOK → LATIN CAPITAL LETTER H, COMBINING COMMA BELOW # →Н̡→ +2C8F ; 029C ; MA # ( ⲏ → ʜ ) COPTIC SMALL LETTER HATE → LATIN LETTER SMALL CAPITAL H # →н→ 043D ; 029C ; MA # ( н → ʜ ) CYRILLIC SMALL LETTER EN → LATIN LETTER SMALL CAPITAL H # AB8B ; 029C ; MA # ( ꮋ → ʜ ) CHEROKEE SMALL LETTER MI → LATIN LETTER SMALL CAPITAL H # @@ -2417,9 +2450,10 @@ FF49 ; 0069 ; MA # ( i → i ) FULLWIDTH LATIN SMALL LETTER I → LATIN SMALL 1D73E ; 0069 ; MA # ( 𝜾 → i ) MATHEMATICAL BOLD ITALIC SMALL IOTA → LATIN SMALL LETTER I # →ι→ 1D778 ; 0069 ; MA # ( 𝝸 → i ) MATHEMATICAL SANS-SERIF BOLD SMALL IOTA → LATIN SMALL LETTER I # →ι→ 1D7B2 ; 0069 ; MA # ( 𝞲 → i ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL IOTA → LATIN SMALL LETTER I # →ι→ +2C93 ; 0069 ; MA # ( ⲓ → i ) COPTIC SMALL LETTER IAUDA → LATIN SMALL LETTER I # →ı→ 0456 ; 0069 ; MA # ( і → i ) CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I → LATIN SMALL LETTER I # A647 ; 0069 ; MA # ( ꙇ → i ) CYRILLIC SMALL LETTER IOTA → LATIN SMALL LETTER I # →ι→ -04CF ; 0069 ; MA # ( ӏ → i ) CYRILLIC SMALL LETTER PALOCHKA → LATIN SMALL LETTER I # →ı→ +0582 ; 0069 ; MA # ( ւ → i ) ARMENIAN SMALL LETTER YIWN → LATIN SMALL LETTER I # →ı→ AB75 ; 0069 ; MA # ( ꭵ → i ) CHEROKEE SMALL LETTER V → LATIN SMALL LETTER I # 13A5 ; 0069 ; MA # ( Ꭵ → i ) CHEROKEE LETTER V → LATIN SMALL LETTER I # 118C3 ; 0069 ; MA # ( 𑣃 → i ) WARANG CITI SMALL LETTER YU → LATIN SMALL LETTER I # →ι→ @@ -2611,6 +2645,7 @@ FF4C ; 006C ; MA # ( l → l ) FULLWIDTH LATIN SMALL LETTER L → LATIN SMALL 1D798 ; 006C ; MA # ( 𝞘 → l ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL IOTA → LATIN SMALL LETTER L # →Ι→ 2C92 ; 006C ; MA # ( Ⲓ → l ) COPTIC CAPITAL LETTER IAUDA → LATIN SMALL LETTER L # →Ӏ→ 0406 ; 006C ; MA # ( І → l ) CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I → LATIN SMALL LETTER L # +04CF ; 006C ; MA # ( ӏ → l ) CYRILLIC SMALL LETTER PALOCHKA → LATIN SMALL LETTER L # →I→ 04C0 ; 006C ; MA # ( Ӏ → l ) CYRILLIC LETTER PALOCHKA → LATIN SMALL LETTER L # 05D5 ; 006C ; MA # ( ‎ו‎ → l ) HEBREW LETTER VAV → LATIN SMALL LETTER L # 05DF ; 006C ; MA # ( ‎ן‎ → l ) HEBREW LETTER FINAL NUN → LATIN SMALL LETTER L # @@ -2626,6 +2661,9 @@ A4F2 ; 006C ; MA # ( ꓲ → l ) LISU LETTER I → LATIN SMALL LETTER L # →I 16F28 ; 006C ; MA # ( 𖼨 → l ) MIAO LETTER GHA → LATIN SMALL LETTER L # →I→ 1028A ; 006C ; MA # ( 𐊊 → l ) LYCIAN LETTER J → LATIN SMALL LETTER L # →I→ 10309 ; 006C ; MA # ( 𐌉 → l ) OLD ITALIC LETTER I → LATIN SMALL LETTER L # →I→ +11DDA ; 006C ; MA # ( 𑷚 → l ) TOLONG SIKI SIGN HECAKA → LATIN SMALL LETTER L # →|→ +11DE1 ; 006C ; MA # ( 𑷡 → l ) TOLONG SIKI DIGIT ONE → LATIN SMALL LETTER L # →|→ +16EAA ; 006C ; MA # ( 𖺪 → l ) BERIA ERFE CAPITAL LETTER LAKKO → LATIN SMALL LETTER L # →I→ 1D22A ; 004C ; MA #* ( 𝈪 → L ) GREEK INSTRUMENTAL NOTATION SYMBOL-23 → LATIN CAPITAL LETTER L # 216C ; 004C ; MA # ( Ⅼ → L ) ROMAN NUMERAL FIFTY → LATIN CAPITAL LETTER L # @@ -2886,12 +2924,14 @@ A4E0 ; 004E ; MA # ( ꓠ → N ) LISU LETTER NA → LATIN CAPITAL LETTER N # 0273 ; 006E 0328 ; MA # ( ɳ → n̨ ) LATIN SMALL LETTER N WITH RETROFLEX HOOK → LATIN SMALL LETTER N, COMBINING OGONEK # →n̢→ 019E ; 006E 0329 ; MA # ( ƞ → n̩ ) LATIN SMALL LETTER N WITH LONG RIGHT LEG → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # +014B ; 006E 0329 ; MA # ( ŋ → n̩ ) LATIN SMALL LETTER ENG → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 03B7 ; 006E 0329 ; MA # ( η → n̩ ) GREEK SMALL LETTER ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →ƞ→ 1D6C8 ; 006E 0329 ; MA # ( 𝛈 → n̩ ) MATHEMATICAL BOLD SMALL ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 1D702 ; 006E 0329 ; MA # ( 𝜂 → n̩ ) MATHEMATICAL ITALIC SMALL ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 1D73C ; 006E 0329 ; MA # ( 𝜼 → n̩ ) MATHEMATICAL BOLD ITALIC SMALL ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 1D776 ; 006E 0329 ; MA # ( 𝝶 → n̩ ) MATHEMATICAL SANS-SERIF BOLD SMALL ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 1D7B0 ; 006E 0329 ; MA # ( 𝞰 → n̩ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ETA → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ +0572 ; 006E 0329 ; MA # ( ղ → n̩ ) ARMENIAN SMALL LETTER GHAD → LATIN SMALL LETTER N, COMBINING VERTICAL LINE BELOW # →η→→ƞ→ 019D ; 004E 0326 ; MA # ( Ɲ → N̦ ) LATIN CAPITAL LETTER N WITH LEFT HOOK → LATIN CAPITAL LETTER N, COMBINING COMMA BELOW # →N̡→ @@ -2905,6 +2945,8 @@ A4E0 ; 004E ; MA # ( ꓠ → N ) LISU LETTER NA → LATIN CAPITAL LETTER N # 2116 ; 004E 006F ; MA #* ( № → No ) NUMERO SIGN → LATIN CAPITAL LETTER N, LATIN SMALL LETTER O # +2C9B ; 0274 ; MA # ( ⲛ → ɴ ) COPTIC SMALL LETTER NI → LATIN LETTER SMALL CAPITAL N # + 0377 ; 1D0E ; MA # ( ͷ → ᴎ ) GREEK SMALL LETTER PAMPHYLIAN DIGAMMA → LATIN LETTER SMALL CAPITAL REVERSED N # →и→ 0438 ; 1D0E ; MA # ( и → ᴎ ) CYRILLIC SMALL LETTER I → LATIN LETTER SMALL CAPITAL REVERSED N # 1044D ; 1D0E ; MA # ( 𐑍 → ᴎ ) DESERET SMALL LETTER ENG → LATIN LETTER SMALL CAPITAL REVERSED N # →и→ @@ -2916,15 +2958,18 @@ A4E0 ; 004E ; MA # ( ꓠ → N ) LISU LETTER NA → LATIN CAPITAL LETTER N # 0D02 ; 006F ; MA # ( ം → o ) MALAYALAM SIGN ANUSVARA → LATIN SMALL LETTER O # 0D82 ; 006F ; MA # ( ං → o ) SINHALA SIGN ANUSVARAYA → LATIN SMALL LETTER O # 0966 ; 006F ; MA # ( ० → o ) DEVANAGARI DIGIT ZERO → LATIN SMALL LETTER O # +09E6 ; 006F ; MA # ( ০ → o ) BENGALI DIGIT ZERO → LATIN SMALL LETTER O # 0A66 ; 006F ; MA # ( ੦ → o ) GURMUKHI DIGIT ZERO → LATIN SMALL LETTER O # 0AE6 ; 006F ; MA # ( ૦ → o ) GUJARATI DIGIT ZERO → LATIN SMALL LETTER O # +0B66 ; 006F ; MA # ( ୦ → o ) ORIYA DIGIT ZERO → LATIN SMALL LETTER O # 0BE6 ; 006F ; MA # ( ௦ → o ) TAMIL DIGIT ZERO → LATIN SMALL LETTER O # 0C66 ; 006F ; MA # ( ౦ → o ) TELUGU DIGIT ZERO → LATIN SMALL LETTER O # -0CE6 ; 006F ; MA # ( ೦ → o ) KANNADA DIGIT ZERO → LATIN SMALL LETTER O # →౦→ 0D66 ; 006F ; MA # ( ൦ → o ) MALAYALAM DIGIT ZERO → LATIN SMALL LETTER O # 0E50 ; 006F ; MA # ( ๐ → o ) THAI DIGIT ZERO → LATIN SMALL LETTER O # 0ED0 ; 006F ; MA # ( ໐ → o ) LAO DIGIT ZERO → LATIN SMALL LETTER O # 1040 ; 006F ; MA # ( ၀ → o ) MYANMAR DIGIT ZERO → LATIN SMALL LETTER O # +17E0 ; 006F ; MA # ( ០ → o ) KHMER DIGIT ZERO → LATIN SMALL LETTER O # +114D0 ; 006F ; MA # ( 𑓐 → o ) TIRHUTA DIGIT ZERO → LATIN SMALL LETTER O # →০→ 0665 ; 006F ; MA # ( ‎٥‎ → o ) ARABIC-INDIC DIGIT FIVE → LATIN SMALL LETTER O # 06F5 ; 006F ; MA # ( ۵ → o ) EXTENDED ARABIC-INDIC DIGIT FIVE → LATIN SMALL LETTER O # →‎٥‎→ FF4F ; 006F ; MA # ( o → o ) FULLWIDTH LATIN SMALL LETTER O → LATIN SMALL LETTER O # →о→ @@ -2957,6 +3002,7 @@ AB3D ; 006F ; MA # ( ꬽ → o ) LATIN SMALL LETTER BLACKLETTER O → LATIN SMAL 1D782 ; 006F ; MA # ( 𝞂 → o ) MATHEMATICAL SANS-SERIF BOLD SMALL SIGMA → LATIN SMALL LETTER O # →σ→ 1D7BC ; 006F ; MA # ( 𝞼 → o ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL SIGMA → LATIN SMALL LETTER O # →σ→ 2C9F ; 006F ; MA # ( ⲟ → o ) COPTIC SMALL LETTER O → LATIN SMALL LETTER O # +03ED ; 006F ; MA # ( ϭ → o ) COPTIC SMALL LETTER SHIMA → LATIN SMALL LETTER O # →σ→ 043E ; 006F ; MA # ( о → o ) CYRILLIC SMALL LETTER O → LATIN SMALL LETTER O # 10FF ; 006F ; MA # ( ჿ → o ) GEORGIAN LETTER LABIAL SIGN → LATIN SMALL LETTER O # 0585 ; 006F ; MA # ( օ → o ) ARMENIAN SMALL LETTER OH → LATIN SMALL LETTER O # @@ -2989,10 +3035,8 @@ FBA6 ; 006F ; MA # ( ‎ﮦ‎ → o ) ARABIC LETTER HEH GOAL ISOLATED FORM → 0030 ; 004F ; MA # ( 0 → O ) DIGIT ZERO → LATIN CAPITAL LETTER O # 07C0 ; 004F ; MA # ( ‎߀‎ → O ) NKO DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ -09E6 ; 004F ; MA # ( ০ → O ) BENGALI DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ -0B66 ; 004F ; MA # ( ୦ → O ) ORIYA DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ +0CE6 ; 004F ; MA # ( ೦ → O ) KANNADA DIGIT ZERO → LATIN CAPITAL LETTER O # 3007 ; 004F ; MA # ( 〇 → O ) IDEOGRAPHIC NUMBER ZERO → LATIN CAPITAL LETTER O # -114D0 ; 004F ; MA # ( 𑓐 → O ) TIRHUTA DIGIT ZERO → LATIN CAPITAL LETTER O # →০→→0→ 118E0 ; 004F ; MA # ( 𑣠 → O ) WARANG CITI DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ 1CCF0 ; 004F ; MA # ( 𜳰 → O ) OUTLINED DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ 1D7CE ; 004F ; MA # ( 𝟎 → O ) MATHEMATICAL BOLD DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ @@ -3027,7 +3071,7 @@ FF2F ; 004F ; MA # ( O → O ) FULLWIDTH LATIN CAPITAL LETTER O → LATIN CAPI 0555 ; 004F ; MA # ( Օ → O ) ARMENIAN CAPITAL LETTER OH → LATIN CAPITAL LETTER O # 2D54 ; 004F ; MA # ( ⵔ → O ) TIFINAGH LETTER YAR → LATIN CAPITAL LETTER O # 12D0 ; 004F ; MA # ( ዐ → O ) ETHIOPIC SYLLABLE PHARYNGEAL A → LATIN CAPITAL LETTER O # →Օ→ -0B20 ; 004F ; MA # ( ଠ → O ) ORIYA LETTER TTHA → LATIN CAPITAL LETTER O # →୦→→0→ +0B20 ; 004F ; MA # ( ଠ → O ) ORIYA LETTER TTHA → LATIN CAPITAL LETTER O # 104C2 ; 004F ; MA # ( 𐓂 → O ) OSAGE CAPITAL LETTER O → LATIN CAPITAL LETTER O # A4F3 ; 004F ; MA # ( ꓳ → O ) LISU LETTER O → LATIN CAPITAL LETTER O # 118B5 ; 004F ; MA # ( 𑢵 → O ) WARANG CITI CAPITAL LETTER AT → LATIN CAPITAL LETTER O # @@ -3035,6 +3079,7 @@ A4F3 ; 004F ; MA # ( ꓳ → O ) LISU LETTER O → LATIN CAPITAL LETTER O # 102AB ; 004F ; MA # ( 𐊫 → O ) CARIAN LETTER O → LATIN CAPITAL LETTER O # 10404 ; 004F ; MA # ( 𐐄 → O ) DESERET CAPITAL LETTER LONG O → LATIN CAPITAL LETTER O # 10516 ; 004F ; MA # ( 𐔖 → O ) ELBASAN LETTER O → LATIN CAPITAL LETTER O # +11DE0 ; 004F ; MA # ( 𑷠 → O ) TOLONG SIKI DIGIT ZERO → LATIN CAPITAL LETTER O # →0→ 2070 ; 00BA ; MA #* ( ⁰ → º ) SUPERSCRIPT ZERO → MASCULINE ORDINAL INDICATOR # 1D52 ; 00BA ; MA # ( ᵒ → º ) MODIFIER LETTER SMALL O → MASCULINE ORDINAL INDICATOR # →⁰→ @@ -3057,6 +3102,7 @@ AB3E ; 006F 0338 ; MA # ( ꬾ → o̸ ) LATIN SMALL LETTER BLACKLETTER O WITH ST 0275 ; 006F 0335 ; MA # ( ɵ → o̵ ) LATIN SMALL LETTER BARRED O → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # A74B ; 006F 0335 ; MA # ( ꝋ → o̵ ) LATIN SMALL LETTER O WITH LONG STROKE OVERLAY → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # →o̶→ +2C91 ; 006F 0335 ; MA # ( ⲑ → o̵ ) COPTIC SMALL LETTER THETHE → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # →ɵ→ 04E9 ; 006F 0335 ; MA # ( ө → o̵ ) CYRILLIC SMALL LETTER BARRED O → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # →ѳ→ 0473 ; 006F 0335 ; MA # ( ѳ → o̵ ) CYRILLIC SMALL LETTER FITA → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # AB8E ; 006F 0335 ; MA # ( ꮎ → o̵ ) CHEROKEE SMALL LETTER NA → LATIN SMALL LETTER O, COMBINING SHORT STROKE OVERLAY # →ɵ→ @@ -3093,6 +3139,7 @@ A74A ; 004F 0335 ; MA # ( Ꝋ → O̵ ) LATIN CAPITAL LETTER O WITH LONG STROKE 1D767 ; 004F 0335 ; MA # ( 𝝧 → O̵ ) MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA SYMBOL → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →Θ→→Ꮎ→ 1D797 ; 004F 0335 ; MA # ( 𝞗 → O̵ ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →Θ→→Ꮎ→ 1D7A1 ; 004F 0335 ; MA # ( 𝞡 → O̵ ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA SYMBOL → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →Θ→→Ꮎ→ +2C90 ; 004F 0335 ; MA # ( Ⲑ → O̵ ) COPTIC CAPITAL LETTER THETHE → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →ϴ→→Ѳ→→О̵→ 04E8 ; 004F 0335 ; MA # ( Ө → O̵ ) CYRILLIC CAPITAL LETTER BARRED O → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →Ѳ→→О̵→ 0472 ; 004F 0335 ; MA # ( Ѳ → O̵ ) CYRILLIC CAPITAL LETTER FITA → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →О̵→ 2D31 ; 004F 0335 ; MA # ( ⴱ → O̵ ) TIFINAGH LETTER YAB → LATIN CAPITAL LETTER O, COMBINING SHORT STROKE OVERLAY # →Ɵ→→O̶→ @@ -3150,6 +3197,7 @@ FC54 ; 006F 0649 ; MA # ( ‎ﱔ‎ → ‎oى‎ ) ARABIC LIGATURE HEH WITH YEH 0D5F ; 006F 0D30 006F ; MA # ( ൟ → oരo ) MALAYALAM LETTER ARCHAIC II → LATIN SMALL LETTER O, MALAYALAM LETTER RA, LATIN SMALL LETTER O # →ംരം→ +10D7 ; 006F 102C ; MA # ( თ → oာ ) GEORGIAN LETTER TAN → LATIN SMALL LETTER O, MYANMAR VOWEL SIGN AA # →တ→→ဝာ→ 1010 ; 006F 102C ; MA # ( တ → oာ ) MYANMAR LETTER TA → LATIN SMALL LETTER O, MYANMAR VOWEL SIGN AA # →ဝာ→ 3358 ; 004F 70B9 ; MA #* ( ㍘ → O点 ) IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ZERO → LATIN CAPITAL LETTER O, CJK UNIFIED IDEOGRAPH-70B9 # →0点→ @@ -3185,6 +3233,8 @@ FF50 ; 0070 ; MA # ( p → p ) FULLWIDTH LATIN SMALL LETTER P → LATIN SMALL 1D631 ; 0070 ; MA # ( 𝘱 → p ) MATHEMATICAL SANS-SERIF ITALIC SMALL P → LATIN SMALL LETTER P # 1D665 ; 0070 ; MA # ( 𝙥 → p ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P → LATIN SMALL LETTER P # 1D699 ; 0070 ; MA # ( 𝚙 → p ) MATHEMATICAL MONOSPACE SMALL P → LATIN SMALL LETTER P # +00FE ; 0070 ; MA # ( þ → p ) LATIN SMALL LETTER THORN → LATIN SMALL LETTER P # →ƿ→ +01BF ; 0070 ; MA # ( ƿ → p ) LATIN LETTER WYNN → LATIN SMALL LETTER P # 03C1 ; 0070 ; MA # ( ρ → p ) GREEK SMALL LETTER RHO → LATIN SMALL LETTER P # 03F1 ; 0070 ; MA # ( ϱ → p ) GREEK RHO SYMBOL → LATIN SMALL LETTER P # →ρ→ 1D6D2 ; 0070 ; MA # ( 𝛒 → p ) MATHEMATICAL BOLD SMALL RHO → LATIN SMALL LETTER P # →ρ→ @@ -3197,7 +3247,9 @@ FF50 ; 0070 ; MA # ( p → p ) FULLWIDTH LATIN SMALL LETTER P → LATIN SMALL 1D78E ; 0070 ; MA # ( 𝞎 → p ) MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL → LATIN SMALL LETTER P # →ρ→ 1D7BA ; 0070 ; MA # ( 𝞺 → p ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL RHO → LATIN SMALL LETTER P # →ρ→ 1D7C8 ; 0070 ; MA # ( 𝟈 → p ) MATHEMATICAL SANS-SERIF BOLD ITALIC RHO SYMBOL → LATIN SMALL LETTER P # →ρ→ +03F8 ; 0070 ; MA # ( ϸ → p ) GREEK SMALL LETTER SHO → LATIN SMALL LETTER P # →þ→→ƿ→ 2CA3 ; 0070 ; MA # ( ⲣ → p ) COPTIC SMALL LETTER RO → LATIN SMALL LETTER P # →ρ→ +2CCF ; 0070 ; MA # ( ⳏ → p ) COPTIC SMALL LETTER OLD COPTIC HA → LATIN SMALL LETTER P # 0440 ; 0070 ; MA # ( р → p ) CYRILLIC SMALL LETTER ER → LATIN SMALL LETTER P # FF30 ; 0050 ; MA # ( P → P ) FULLWIDTH LATIN CAPITAL LETTER P → LATIN CAPITAL LETTER P # →Р→ @@ -3222,6 +3274,7 @@ FF30 ; 0050 ; MA # ( P → P ) FULLWIDTH LATIN CAPITAL LETTER P → LATIN CAPI 1D766 ; 0050 ; MA # ( 𝝦 → P ) MATHEMATICAL SANS-SERIF BOLD CAPITAL RHO → LATIN CAPITAL LETTER P # →Ρ→ 1D7A0 ; 0050 ; MA # ( 𝞠 → P ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL RHO → LATIN CAPITAL LETTER P # →Ρ→ 2CA2 ; 0050 ; MA # ( Ⲣ → P ) COPTIC CAPITAL LETTER RO → LATIN CAPITAL LETTER P # +2CCE ; 0050 ; MA # ( Ⳏ → P ) COPTIC CAPITAL LETTER OLD COPTIC HA → LATIN CAPITAL LETTER P # 0420 ; 0050 ; MA # ( Р → P ) CYRILLIC CAPITAL LETTER ER → LATIN CAPITAL LETTER P # 13E2 ; 0050 ; MA # ( Ꮲ → P ) CHEROKEE LETTER TLV → LATIN CAPITAL LETTER P # 146D ; 0050 ; MA # ( ᑭ → P ) CANADIAN SYLLABICS KI → LATIN CAPITAL LETTER P # @@ -3252,6 +3305,8 @@ ABB2 ; 1D18 ; MA # ( ꮲ → ᴘ ) CHEROKEE SMALL LETTER TLV → LATIN LETTER SM 1D7BF ; 0278 ; MA # ( 𝞿 → ɸ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PHI → LATIN SMALL LETTER PHI # →φ→ 1D7C7 ; 0278 ; MA # ( 𝟇 → ɸ ) MATHEMATICAL SANS-SERIF BOLD ITALIC PHI SYMBOL → LATIN SMALL LETTER PHI # →φ→ 2CAB ; 0278 ; MA # ( ⲫ → ɸ ) COPTIC SMALL LETTER FI → LATIN SMALL LETTER PHI # →ϕ→ +2CE1 ; 0278 ; MA # ( ⳡ → ɸ ) COPTIC SMALL LETTER OLD NUBIAN NYI → LATIN SMALL LETTER PHI # →φ→ +2CE0 ; 0278 ; MA # ( Ⳡ → ɸ ) COPTIC CAPITAL LETTER OLD NUBIAN NYI → LATIN SMALL LETTER PHI # →φ→ 0444 ; 0278 ; MA # ( ф → ɸ ) CYRILLIC SMALL LETTER EF → LATIN SMALL LETTER PHI # 1D42A ; 0071 ; MA # ( 𝐪 → q ) MATHEMATICAL BOLD SMALL Q → LATIN SMALL LETTER Q # @@ -3420,6 +3475,7 @@ FF53 ; 0073 ; MA # ( s → s ) FULLWIDTH LATIN SMALL LETTER S → LATIN SMALL A731 ; 0073 ; MA # ( ꜱ → s ) LATIN LETTER SMALL CAPITAL S → LATIN SMALL LETTER S # 01BD ; 0073 ; MA # ( ƽ → s ) LATIN SMALL LETTER TONE FIVE → LATIN SMALL LETTER S # 0455 ; 0073 ; MA # ( ѕ → s ) CYRILLIC SMALL LETTER DZE → LATIN SMALL LETTER S # +0D1F ; 0073 ; MA # ( ട → s ) MALAYALAM LETTER TTA → LATIN SMALL LETTER S # ABAA ; 0073 ; MA # ( ꮪ → s ) CHEROKEE SMALL LETTER DU → LATIN SMALL LETTER S # →ꜱ→ 118C1 ; 0073 ; MA # ( 𑣁 → s ) WARANG CITI SMALL LETTER A → LATIN SMALL LETTER S # 10448 ; 0073 ; MA # ( 𐑈 → s ) DESERET SMALL LETTER ZHEE → LATIN SMALL LETTER S # @@ -3577,6 +3633,7 @@ A729 ; 0074 021D ; MA # ( ꜩ → tȝ ) LATIN SMALL LETTER TZ → LATIN SMALL LE 1D749 ; 1D1B ; MA # ( 𝝉 → ᴛ ) MATHEMATICAL BOLD ITALIC SMALL TAU → LATIN LETTER SMALL CAPITAL T # 1D783 ; 1D1B ; MA # ( 𝞃 → ᴛ ) MATHEMATICAL SANS-SERIF BOLD SMALL TAU → LATIN LETTER SMALL CAPITAL T # 1D7BD ; 1D1B ; MA # ( 𝞽 → ᴛ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL TAU → LATIN LETTER SMALL CAPITAL T # +2CA7 ; 1D1B ; MA # ( ⲧ → ᴛ ) COPTIC SMALL LETTER TAU → LATIN LETTER SMALL CAPITAL T # →т→ 0442 ; 1D1B ; MA # ( т → ᴛ ) CYRILLIC SMALL LETTER TE → LATIN LETTER SMALL CAPITAL T # AB72 ; 1D1B ; MA # ( ꭲ → ᴛ ) CHEROKEE SMALL LETTER I → LATIN LETTER SMALL CAPITAL T # @@ -3642,6 +3699,8 @@ A4F4 ; 0055 ; MA # ( ꓴ → U ) LISU LETTER U → LATIN CAPITAL LETTER U # 01D3 ; 016C ; MA # ( Ǔ → Ŭ ) LATIN CAPITAL LETTER U WITH CARON → LATIN CAPITAL LETTER U WITH BREVE # +045F ; 0075 0329 ; MA # ( џ → u̩ ) CYRILLIC SMALL LETTER DZHE → LATIN SMALL LETTER U, COMBINING VERTICAL LINE BELOW # + 1D7E ; 0075 0335 ; MA # ( ᵾ → u̵ ) LATIN SMALL CAPITAL LETTER U WITH STROKE → LATIN SMALL LETTER U, COMBINING SHORT STROKE OVERLAY # →ᴜ̵→ AB9C ; 0075 0335 ; MA # ( ꮜ → u̵ ) CHEROKEE SMALL LETTER SA → LATIN SMALL LETTER U, COMBINING SHORT STROKE OVERLAY # →ᴜ̵→ @@ -3746,6 +3805,7 @@ A4E6 ; 0056 ; MA # ( ꓦ → V ) LISU LETTER HA → LATIN CAPITAL LETTER V # 1F708 ; 0056 1DE4 ; MA #* ( 🜈 → Vᷤ ) ALCHEMICAL SYMBOL FOR AQUA VITAE → LATIN CAPITAL LETTER V, COMBINING LATIN SMALL LETTER S # 1D27 ; 028C ; MA # ( ᴧ → ʌ ) GREEK LETTER SMALL CAPITAL LAMDA → LATIN SMALL LETTER TURNED V # +2C97 ; 028C ; MA # ( ⲗ → ʌ ) COPTIC SMALL LETTER LAULA → LATIN SMALL LETTER TURNED V # 104D8 ; 028C ; MA # ( 𐓘 → ʌ ) OSAGE SMALL LETTER A → LATIN SMALL LETTER TURNED V # 0668 ; 0245 ; MA # ( ‎٨‎ → Ʌ ) ARABIC-INDIC DIGIT EIGHT → LATIN CAPITAL LETTER TURNED V # →Λ→ @@ -3787,7 +3847,9 @@ A7DC ; 0245 0338 ; MA # ( Ƛ → Ʌ̸ ) LATIN CAPITAL LETTER LAMBDA WITH STROKE 1D66C ; 0077 ; MA # ( 𝙬 → w ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W → LATIN SMALL LETTER W # 1D6A0 ; 0077 ; MA # ( 𝚠 → w ) MATHEMATICAL MONOSPACE SMALL W → LATIN SMALL LETTER W # 1D21 ; 0077 ; MA # ( ᴡ → w ) LATIN LETTER SMALL CAPITAL W → LATIN SMALL LETTER W # +2CBD ; 0077 ; MA # ( ⲽ → w ) COPTIC SMALL LETTER CRYPTOGRAMMIC NI → LATIN SMALL LETTER W # →ш→ 0461 ; 0077 ; MA # ( ѡ → w ) CYRILLIC SMALL LETTER OMEGA → LATIN SMALL LETTER W # +0448 ; 0077 ; MA # ( ш → w ) CYRILLIC SMALL LETTER SHA → LATIN SMALL LETTER W # 051D ; 0077 ; MA # ( ԝ → w ) CYRILLIC SMALL LETTER WE → LATIN SMALL LETTER W # 0561 ; 0077 ; MA # ( ա → w ) ARMENIAN SMALL LETTER AYB → LATIN SMALL LETTER W # →ɯ→ 1170A ; 0077 ; MA # ( 𑜊 → w ) AHOM LETTER JA → LATIN SMALL LETTER W # @@ -3825,6 +3887,7 @@ A4EA ; 0057 ; MA # ( ꓪ → W ) LISU LETTER WA → LATIN CAPITAL LETTER W # A761 ; 0077 0326 ; MA # ( ꝡ → w̦ ) LATIN SMALL LETTER VY → LATIN SMALL LETTER W, COMBINING COMMA BELOW # →w̡→ 1D0D ; 028D ; MA # ( ᴍ → ʍ ) LATIN LETTER SMALL CAPITAL M → LATIN SMALL LETTER TURNED W # →м→ +2C99 ; 028D ; MA # ( ⲙ → ʍ ) COPTIC SMALL LETTER MI → LATIN SMALL LETTER TURNED W # →ᴍ→→м→ 043C ; 028D ; MA # ( м → ʍ ) CYRILLIC SMALL LETTER EM → LATIN SMALL LETTER TURNED W # AB87 ; 028D ; MA # ( ꮇ → ʍ ) CHEROKEE SMALL LETTER LU → LATIN SMALL LETTER TURNED W # →ᴍ→→м→ @@ -3933,6 +3996,7 @@ AB5A ; 0079 ; MA # ( ꭚ → y ) LATIN SMALL LETTER Y WITH SHORT RIGHT LEG → L 1D738 ; 0079 ; MA # ( 𝜸 → y ) MATHEMATICAL BOLD ITALIC SMALL GAMMA → LATIN SMALL LETTER Y # →γ→ 1D772 ; 0079 ; MA # ( 𝝲 → y ) MATHEMATICAL SANS-SERIF BOLD SMALL GAMMA → LATIN SMALL LETTER Y # →γ→ 1D7AC ; 0079 ; MA # ( 𝞬 → y ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL GAMMA → LATIN SMALL LETTER Y # →γ→ +2CA9 ; 0079 ; MA # ( ⲩ → y ) COPTIC SMALL LETTER UA → LATIN SMALL LETTER Y # →γ→ 0443 ; 0079 ; MA # ( у → y ) CYRILLIC SMALL LETTER U → LATIN SMALL LETTER Y # 04AF ; 0079 ; MA # ( ү → y ) CYRILLIC SMALL LETTER STRAIGHT U → LATIN SMALL LETTER Y # →γ→ 10E7 ; 0079 ; MA # ( ყ → y ) GEORGIAN LETTER QAR → LATIN SMALL LETTER Y # @@ -3981,6 +4045,7 @@ A4EC ; 0059 ; MA # ( ꓬ → Y ) LISU LETTER YA → LATIN CAPITAL LETTER Y # 0292 ; 021D ; MA # ( ʒ → ȝ ) LATIN SMALL LETTER EZH → LATIN SMALL LETTER YOGH # A76B ; 021D ; MA # ( ꝫ → ȝ ) LATIN SMALL LETTER ET → LATIN SMALL LETTER YOGH # +2CC5 ; 021D ; MA # ( ⳅ → ȝ ) COPTIC SMALL LETTER OLD COPTIC SHEI → LATIN SMALL LETTER YOGH # →ʒ→ 2CCD ; 021D ; MA # ( ⳍ → ȝ ) COPTIC SMALL LETTER OLD COPTIC HORI → LATIN SMALL LETTER YOGH # 04E1 ; 021D ; MA # ( ӡ → ȝ ) CYRILLIC SMALL LETTER ABKHASIAN DZE → LATIN SMALL LETTER YOGH # →ʒ→ 10F3 ; 021D ; MA # ( ჳ → ȝ ) GEORGIAN LETTER WE → LATIN SMALL LETTER YOGH # →ʒ→ @@ -4041,12 +4106,19 @@ A4DC ; 005A ; MA # ( ꓜ → Z ) LISU LETTER DZA → LATIN CAPITAL LETTER Z # 1D76 ; 007A 0334 ; MA # ( ᵶ → z̴ ) LATIN SMALL LETTER Z WITH MIDDLE TILDE → LATIN SMALL LETTER Z, COMBINING TILDE OVERLAY # -01BF ; 00FE ; MA # ( ƿ → þ ) LATIN LETTER WYNN → LATIN SMALL LETTER THORN # -03F8 ; 00FE ; MA # ( ϸ → þ ) GREEK SMALL LETTER SHO → LATIN SMALL LETTER THORN # +2C8D ; 2C6C ; MA # ( ⲍ → ⱬ ) COPTIC SMALL LETTER ZATA → LATIN SMALL LETTER Z WITH DESCENDER # + +2C8C ; 2C6B ; MA # ( Ⲍ → Ⱬ ) COPTIC CAPITAL LETTER ZATA → LATIN CAPITAL LETTER Z WITH DESCENDER # + +2C9D ; 0293 ; MA # ( ⲝ → ʓ ) COPTIC SMALL LETTER KSI → LATIN SMALL LETTER EZH WITH CURL # 03F7 ; 00DE ; MA # ( Ϸ → Þ ) GREEK CAPITAL LETTER SHO → LATIN CAPITAL LETTER THORN # 104C4 ; 00DE ; MA # ( 𐓄 → Þ ) OSAGE CAPITAL LETTER PA → LATIN CAPITAL LETTER THORN # +A7D2 ; A7D3 ; MA # ( ꟒ → ꟓ ) LATIN CAPITAL LETTER DOUBLE THORN → LATIN SMALL LETTER DOUBLE THORN # + +A7D4 ; A7D5 ; MA # ( ꟔ → ꟕ ) LATIN CAPITAL LETTER DOUBLE WYNN → LATIN SMALL LETTER DOUBLE WYNN # + 2079 ; A770 ; MA #* ( ⁹ → ꝰ ) SUPERSCRIPT NINE → MODIFIER LETTER US # 1D24 ; 01A8 ; MA # ( ᴤ → ƨ ) LATIN LETTER VOICED LARYNGEAL SPIRANT → LATIN SMALL LETTER TONE TWO # @@ -4055,6 +4127,7 @@ A645 ; 01A8 ; MA # ( ꙅ → ƨ ) CYRILLIC SMALL LETTER REVERSED DZE → LATIN S 044C ; 0185 ; MA # ( ь → ƅ ) CYRILLIC SMALL LETTER SOFT SIGN → LATIN SMALL LETTER TONE SIX # AB9F ; 0185 ; MA # ( ꮟ → ƅ ) CHEROKEE SMALL LETTER SI → LATIN SMALL LETTER TONE SIX # →ь→ +16ED1 ; 0185 ; MA # ( 𖻑 → ƅ ) BERIA ERFE SMALL LETTER UI → LATIN SMALL LETTER TONE SIX # →ь→ 044B ; 0185 0069 ; MA # ( ы → ƅi ) CYRILLIC SMALL LETTER YERU → LATIN SMALL LETTER TONE SIX, LATIN SMALL LETTER I # →ьı→ @@ -4064,6 +4137,8 @@ AB7E ; 0242 ; MA # ( ꭾ → ɂ ) CHEROKEE SMALL LETTER HE → LATIN SMALL LETTE A6CD ; 02A1 ; MA # ( ꛍ → ʡ ) BAMUM LETTER LU → LATIN LETTER GLOTTAL STOP WITH STROKE # +256A ; 01C2 ; MA #* ( ╪ → ǂ ) BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE → LATIN LETTER ALVEOLAR CLICK # + 2299 ; 0298 ; MA #* ( ⊙ → ʘ ) CIRCLED DOT OPERATOR → LATIN LETTER BILABIAL CLICK # 2609 ; 0298 ; MA #* ( ☉ → ʘ ) SUN → LATIN LETTER BILABIAL CLICK # →⊙→ 2A00 ; 0298 ; MA #* ( ⨀ → ʘ ) N-ARY CIRCLED DOT OPERATOR → LATIN LETTER BILABIAL CLICK # →⊙→ @@ -4144,11 +4219,13 @@ A7DB ; 03BB ; MA # ( ꟛ → λ ) LATIN SMALL LETTER LAMBDA → GREEK SMALL LETT 1D77D ; 03BE ; MA # ( 𝝽 → ξ ) MATHEMATICAL SANS-SERIF BOLD SMALL XI → GREEK SMALL LETTER XI # 1D7B7 ; 03BE ; MA # ( 𝞷 → ξ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL XI → GREEK SMALL LETTER XI # +2630 ; 039E ; MA #* ( ☰ → Ξ ) TRIGRAM FOR HEAVEN → GREEK CAPITAL LETTER XI # →Ⲷ→ 1D6B5 ; 039E ; MA # ( 𝚵 → Ξ ) MATHEMATICAL BOLD CAPITAL XI → GREEK CAPITAL LETTER XI # 1D6EF ; 039E ; MA # ( 𝛯 → Ξ ) MATHEMATICAL ITALIC CAPITAL XI → GREEK CAPITAL LETTER XI # 1D729 ; 039E ; MA # ( 𝜩 → Ξ ) MATHEMATICAL BOLD ITALIC CAPITAL XI → GREEK CAPITAL LETTER XI # 1D763 ; 039E ; MA # ( 𝝣 → Ξ ) MATHEMATICAL SANS-SERIF BOLD CAPITAL XI → GREEK CAPITAL LETTER XI # 1D79D ; 039E ; MA # ( 𝞝 → Ξ ) MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL XI → GREEK CAPITAL LETTER XI # +2CB6 ; 039E ; MA # ( Ⲷ → Ξ ) COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE → GREEK CAPITAL LETTER XI # 03D6 ; 03C0 ; MA # ( ϖ → π ) GREEK PI SYMBOL → GREEK SMALL LETTER PI # 213C ; 03C0 ; MA # ( ℼ → π ) DOUBLE-STRUCK SMALL PI → GREEK SMALL LETTER PI # @@ -4163,7 +4240,9 @@ A7DB ; 03BB ; MA # ( ꟛ → λ ) LATIN SMALL LETTER LAMBDA → GREEK SMALL LETT 1D7B9 ; 03C0 ; MA # ( 𝞹 → π ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PI → GREEK SMALL LETTER PI # 1D7C9 ; 03C0 ; MA # ( 𝟉 → π ) MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL → GREEK SMALL LETTER PI # 1D28 ; 03C0 ; MA # ( ᴨ → π ) GREEK LETTER SMALL CAPITAL PI → GREEK SMALL LETTER PI # →п→ +2CA1 ; 03C0 ; MA # ( ⲡ → π ) COPTIC SMALL LETTER PI → GREEK SMALL LETTER PI # →п→ 043F ; 03C0 ; MA # ( п → π ) CYRILLIC SMALL LETTER PE → GREEK SMALL LETTER PI # +16EC1 ; 03C0 ; MA # ( 𖻁 → π ) BERIA ERFE SMALL LETTER HIRDEABO → GREEK SMALL LETTER PI # →п→ 220F ; 03A0 ; MA #* ( ∏ → Π ) N-ARY PRODUCT → GREEK CAPITAL LETTER PI # 213F ; 03A0 ; MA # ( ℿ → Π ) DOUBLE-STRUCK CAPITAL PI → GREEK CAPITAL LETTER PI # @@ -4175,16 +4254,20 @@ A7DB ; 03BB ; MA # ( ꟛ → λ ) LATIN SMALL LETTER LAMBDA → GREEK SMALL LETT 2CA0 ; 03A0 ; MA # ( Ⲡ → Π ) COPTIC CAPITAL LETTER PI → GREEK CAPITAL LETTER PI # 041F ; 03A0 ; MA # ( П → Π ) CYRILLIC CAPITAL LETTER PE → GREEK CAPITAL LETTER PI # A6DB ; 03A0 ; MA # ( ꛛ → Π ) BAMUM LETTER NA → GREEK CAPITAL LETTER PI # +16EA6 ; 03A0 ; MA # ( 𖺦 → Π ) BERIA ERFE CAPITAL LETTER HIRDEABO → GREEK CAPITAL LETTER PI # →П→ 102AD ; 03D8 ; MA # ( 𐊭 → Ϙ ) CARIAN LETTER T → GREEK LETTER ARCHAIC KOPPA # 10312 ; 03D8 ; MA # ( 𐌒 → Ϙ ) OLD ITALIC LETTER KU → GREEK LETTER ARCHAIC KOPPA # +2CC1 ; 03FC ; MA # ( ⳁ → ϼ ) COPTIC SMALL LETTER SAMPI → GREEK RHO WITH STROKE SYMBOL # + 03DB ; 03C2 ; MA # ( ϛ → ς ) GREEK SMALL LETTER STIGMA → GREEK SMALL LETTER FINAL SIGMA # 1D6D3 ; 03C2 ; MA # ( 𝛓 → ς ) MATHEMATICAL BOLD SMALL FINAL SIGMA → GREEK SMALL LETTER FINAL SIGMA # 1D70D ; 03C2 ; MA # ( 𝜍 → ς ) MATHEMATICAL ITALIC SMALL FINAL SIGMA → GREEK SMALL LETTER FINAL SIGMA # 1D747 ; 03C2 ; MA # ( 𝝇 → ς ) MATHEMATICAL BOLD ITALIC SMALL FINAL SIGMA → GREEK SMALL LETTER FINAL SIGMA # 1D781 ; 03C2 ; MA # ( 𝞁 → ς ) MATHEMATICAL SANS-SERIF BOLD SMALL FINAL SIGMA → GREEK SMALL LETTER FINAL SIGMA # 1D7BB ; 03C2 ; MA # ( 𝞻 → ς ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL FINAL SIGMA → GREEK SMALL LETTER FINAL SIGMA # +2C8B ; 03C2 ; MA # ( ⲋ → ς ) COPTIC SMALL LETTER SOU → GREEK SMALL LETTER FINAL SIGMA # 1D6BD ; 03A6 ; MA # ( 𝚽 → Φ ) MATHEMATICAL BOLD CAPITAL PHI → GREEK CAPITAL LETTER PHI # 1D6F7 ; 03A6 ; MA # ( 𝛷 → Φ ) MATHEMATICAL ITALIC CAPITAL PHI → GREEK CAPITAL LETTER PHI # @@ -4212,6 +4295,7 @@ AB55 ; 03C7 ; MA # ( ꭕ → χ ) LATIN SMALL LETTER CHI WITH LOW LEFT SERIF → 1D74D ; 03C8 ; MA # ( 𝝍 → ψ ) MATHEMATICAL BOLD ITALIC SMALL PSI → GREEK SMALL LETTER PSI # 1D787 ; 03C8 ; MA # ( 𝞇 → ψ ) MATHEMATICAL SANS-SERIF BOLD SMALL PSI → GREEK SMALL LETTER PSI # 1D7C1 ; 03C8 ; MA # ( 𝟁 → ψ ) MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PSI → GREEK SMALL LETTER PSI # +2CAF ; 03C8 ; MA # ( ⲯ → ψ ) COPTIC SMALL LETTER PSI → GREEK SMALL LETTER PSI # 0471 ; 03C8 ; MA # ( ѱ → ψ ) CYRILLIC SMALL LETTER PSI → GREEK SMALL LETTER PSI # 104F9 ; 03C8 ; MA # ( 𐓹 → ψ ) OSAGE SMALL LETTER GHA → GREEK SMALL LETTER PSI # @@ -4250,10 +4334,6 @@ A64D ; 03C9 ; MA # ( ꙍ → ω ) CYRILLIC SMALL LETTER BROAD OMEGA → GREEK SM 1F7D ; 1FF4 ; MA # ( ώ → ῴ ) GREEK SMALL LETTER OMEGA WITH OXIA → GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI # -2630 ; 2CB6 ; MA #* ( ☰ → Ⲷ ) TRIGRAM FOR HEAVEN → COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE # - -2CDC ; 03EC ; MA # ( Ⳝ → Ϭ ) COPTIC CAPITAL LETTER OLD NUBIAN SHIMA → COPTIC CAPITAL LETTER SHIMA # - 0497 ; 0436 0329 ; MA # ( җ → ж̩ ) CYRILLIC SMALL LETTER ZHE WITH DESCENDER → CYRILLIC SMALL LETTER ZHE, COMBINING VERTICAL LINE BELOW # 0496 ; 0416 0329 ; MA # ( Җ → Ж̩ ) CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER → CYRILLIC CAPITAL LETTER ZHE, COMBINING VERTICAL LINE BELOW # @@ -4296,6 +4376,13 @@ AB60 ; 0459 ; MA # ( ꭠ → љ ) LATIN SMALL LETTER SAKHA YAT → CYRILLIC SMAL 18ED ; 0460 00B7 ; MA # ( ᣭ → Ѡ· ) CANADIAN SYLLABICS CARRIER GWU → CYRILLIC CAPITAL LETTER OMEGA, MIDDLE DOT # →ᗯᐧ→ A7B6 ; A64C ; MA # ( Ꞷ → Ꙍ ) LATIN CAPITAL LETTER OMEGA → CYRILLIC CAPITAL LETTER BROAD OMEGA # +2CB0 ; A64C ; MA # ( Ⲱ → Ꙍ ) COPTIC CAPITAL LETTER OOU → CYRILLIC CAPITAL LETTER BROAD OMEGA # + +0AEB ; 0447 ; MA # ( ૫ → ч ) GUJARATI DIGIT FIVE → CYRILLIC SMALL LETTER CHE # +03E5 ; 0447 ; MA # ( ϥ → ч ) COPTIC SMALL LETTER FEI → CYRILLIC SMALL LETTER CHE # +0AAA ; 0447 ; MA # ( પ → ч ) GUJARATI LETTER PA → CYRILLIC SMALL LETTER CHE # →૫→ + +03E4 ; 0427 ; MA # ( Ϥ → Ч ) COPTIC CAPITAL LETTER FEI → CYRILLIC CAPITAL LETTER CHE # 04CC ; 04B7 ; MA # ( ӌ → ҷ ) CYRILLIC SMALL LETTER KHAKASSIAN CHE → CYRILLIC SMALL LETTER CHE WITH DESCENDER # @@ -4303,8 +4390,6 @@ A7B6 ; A64C ; MA # ( Ꞷ → Ꙍ ) LATIN CAPITAL LETTER OMEGA → CYRILLIC CAPIT 04BE ; 04BC 0328 ; MA # ( Ҿ → Ҽ̨ ) CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER → CYRILLIC CAPITAL LETTER ABKHASIAN CHE, COMBINING OGONEK # -2CBD ; 0448 ; MA # ( ⲽ → ш ) COPTIC SMALL LETTER CRYPTOGRAMMIC NI → CYRILLIC SMALL LETTER SHA # - 2CBC ; 0428 ; MA # ( Ⲽ → Ш ) COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI → CYRILLIC CAPITAL LETTER SHA # A650 ; 042A 006C ; MA # ( Ꙑ → Ъl ) CYRILLIC CAPITAL LETTER YERU WITH BACK YER → CYRILLIC CAPITAL LETTER HARD SIGN, LATIN SMALL LETTER L # →ЪІ→ @@ -4316,7 +4401,7 @@ A650 ; 042A 006C ; MA # ( Ꙑ → Ъl ) CYRILLIC CAPITAL LETTER YERU WITH BACK Y A992 ; 2C3F ; MA # ( ꦒ → ⰿ ) JAVANESE LETTER GA → GLAGOLITIC SMALL LETTER MYSLITE # -0587 ; 0565 0582 ; MA # ( և → եւ ) ARMENIAN SMALL LIGATURE ECH YIWN → ARMENIAN SMALL LETTER ECH, ARMENIAN SMALL LETTER YIWN # +0587 ; 0565 0069 ; MA # ( և → եi ) ARMENIAN SMALL LIGATURE ECH YIWN → ARMENIAN SMALL LETTER ECH, LATIN SMALL LETTER I # →եւ→ 1294 ; 0571 ; MA # ( ኔ → ձ ) ETHIOPIC SYLLABLE NEE → ARMENIAN SMALL LETTER JA # @@ -4341,7 +4426,10 @@ A4F5 ; 0548 ; MA # ( ꓵ → Ո ) LISU LETTER UE → ARMENIAN CAPITAL LETTER VO FB16 ; 057E 0576 ; MA # ( ﬖ → վն ) ARMENIAN SMALL LIGATURE VEW NOW → ARMENIAN SMALL LETTER VEW, ARMENIAN SMALL LETTER NOW # +2CE8 ; 0554 ; MA #* ( ⳨ → Ք ) COPTIC SYMBOL TAU RO → ARMENIAN CAPITAL LETTER KEH # →₽→ +101A0 ; 0554 ; MA #* ( 𐆠 → Ք ) GREEK SYMBOL TAU RHO → ARMENIAN CAPITAL LETTER KEH # →⳨→→₽→ 20BD ; 0554 ; MA #* ( ₽ → Ք ) RUBLE SIGN → ARMENIAN CAPITAL LETTER KEH # +2CC0 ; 0554 ; MA # ( Ⳁ → Ք ) COPTIC CAPITAL LETTER SAMPI → ARMENIAN CAPITAL LETTER KEH # →₽→ 02D3 ; 0559 ; MA #* ( ˓ → ՙ ) MODIFIER LETTER CENTRED LEFT HALF RING → ARMENIAN MODIFIER LETTER LEFT HALF RING # 02BF ; 0559 ; MA # ( ʿ → ՙ ) MODIFIER LETTER LEFT HALF RING → ARMENIAN MODIFIER LETTER LEFT HALF RING # @@ -4384,7 +4472,7 @@ FB28 ; 05EA ; MA # ( ‎ﬨ‎ → ‎ת‎ ) HEBREW LETTER WIDE TAV → HEBREW FE80 ; 0621 ; MA # ( ‎ﺀ‎ → ‎ء‎ ) ARABIC LETTER HAMZA ISOLATED FORM → ARABIC LETTER HAMZA # -06FD ; 0621 0348 ; MA #* ( ‎۽‎ → ‎ء͈‎ ) ARABIC SIGN SINDHI AMPERSAND → ARABIC LETTER HAMZA, COMBINING DOUBLE VERTICAL LINE BELOW # +06FD ; 0621 10EFA ; MA #* ( ‎۽‎ → ‎ء𐻺‎ ) ARABIC SIGN SINDHI AMPERSAND → ARABIC LETTER HAMZA, ARABIC DOUBLE VERTICAL BAR BELOW # FE82 ; 0622 ; MA # ( ‎ﺂ‎ → ‎آ‎ ) ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM → ARABIC LETTER ALEF WITH MADDA ABOVE # FE81 ; 0622 ; MA # ( ‎ﺁ‎ → ‎آ‎ ) ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM → ARABIC LETTER ALEF WITH MADDA ABOVE # @@ -4456,6 +4544,7 @@ FB5C ; 0680 ; MA # ( ‎ﭜ‎ → ‎ڀ‎ ) ARABIC LETTER BEHEH INITIAL FORM FB5D ; 0680 ; MA # ( ‎ﭝ‎ → ‎ڀ‎ ) ARABIC LETTER BEHEH MEDIAL FORM → ARABIC LETTER BEHEH # FB5B ; 0680 ; MA # ( ‎ﭛ‎ → ‎ڀ‎ ) ARABIC LETTER BEHEH FINAL FORM → ARABIC LETTER BEHEH # FB5A ; 0680 ; MA # ( ‎ﭚ‎ → ‎ڀ‎ ) ARABIC LETTER BEHEH ISOLATED FORM → ARABIC LETTER BEHEH # +10EC7 ; 0680 ; MA # ( ‎𐻇‎ → ‎ڀ‎ ) ARABIC LETTER YEH WITH FOUR DOTS BELOW → ARABIC LETTER BEHEH # 08A9 ; 0754 ; MA # ( ‎ࢩ‎ → ‎ݔ‎ ) ARABIC LETTER YEH WITH TWO DOTS BELOW AND DOT ABOVE → ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE # 0767 ; 0754 ; MA # ( ‎ݧ‎ → ‎ݔ‎ ) ARABIC LETTER NOON WITH TWO DOTS BELOW → ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE # @@ -4475,6 +4564,13 @@ FE97 ; 062A ; MA # ( ‎ﺗ‎ → ‎ت‎ ) ARABIC LETTER TEH INITIAL FORM → FE98 ; 062A ; MA # ( ‎ﺘ‎ → ‎ت‎ ) ARABIC LETTER TEH MEDIAL FORM → ARABIC LETTER TEH # FE96 ; 062A ; MA # ( ‎ﺖ‎ → ‎ت‎ ) ARABIC LETTER TEH FINAL FORM → ARABIC LETTER TEH # FE95 ; 062A ; MA # ( ‎ﺕ‎ → ‎ت‎ ) ARABIC LETTER TEH ISOLATED FORM → ARABIC LETTER TEH # +067A ; 062A ; MA # ( ‎ٺ‎ → ‎ت‎ ) ARABIC LETTER TTEHEH → ARABIC LETTER TEH # +FB60 ; 062A ; MA # ( ‎ﭠ‎ → ‎ت‎ ) ARABIC LETTER TTEHEH INITIAL FORM → ARABIC LETTER TEH # →‎ٺ‎→ +FB61 ; 062A ; MA # ( ‎ﭡ‎ → ‎ت‎ ) ARABIC LETTER TTEHEH MEDIAL FORM → ARABIC LETTER TEH # →‎ٺ‎→ +FB5F ; 062A ; MA # ( ‎ﭟ‎ → ‎ت‎ ) ARABIC LETTER TTEHEH FINAL FORM → ARABIC LETTER TEH # →‎ٺ‎→ +FB5E ; 062A ; MA # ( ‎ﭞ‎ → ‎ت‎ ) ARABIC LETTER TTEHEH ISOLATED FORM → ARABIC LETTER TEH # →‎ٺ‎→ + +08BF ; 062A 0306 ; MA # ( ‎ࢿ‎ → ‎ت̆‎ ) ARABIC LETTER TEH WITH SMALL V → ARABIC LETTER TEH, COMBINING BREVE # →‎تٚ‎→ FCA5 ; 062A 006F ; MA # ( ‎ﲥ‎ → ‎تo‎ ) ARABIC LIGATURE TEH WITH HEH INITIAL FORM → ARABIC LETTER TEH, LATIN SMALL LETTER O # →‎ته‎→ FCE4 ; 062A 006F ; MA # ( ‎ﳤ‎ → ‎تo‎ ) ARABIC LIGATURE TEH WITH HEH MEDIAL FORM → ARABIC LETTER TEH, LATIN SMALL LETTER O # →‎ته‎→ @@ -4528,11 +4624,6 @@ FC0F ; 062A 0649 ; MA # ( ‎ﰏ‎ → ‎تى‎ ) ARABIC LIGATURE TEH WITH AL FC75 ; 062A 0649 ; MA # ( ‎ﱵ‎ → ‎تى‎ ) ARABIC LIGATURE TEH WITH YEH FINAL FORM → ARABIC LETTER TEH, ARABIC LETTER ALEF MAKSURA # →‎تي‎→ FC10 ; 062A 0649 ; MA # ( ‎ﰐ‎ → ‎تى‎ ) ARABIC LIGATURE TEH WITH YEH ISOLATED FORM → ARABIC LETTER TEH, ARABIC LETTER ALEF MAKSURA # →‎تي‎→ -FB60 ; 067A ; MA # ( ‎ﭠ‎ → ‎ٺ‎ ) ARABIC LETTER TTEHEH INITIAL FORM → ARABIC LETTER TTEHEH # -FB61 ; 067A ; MA # ( ‎ﭡ‎ → ‎ٺ‎ ) ARABIC LETTER TTEHEH MEDIAL FORM → ARABIC LETTER TTEHEH # -FB5F ; 067A ; MA # ( ‎ﭟ‎ → ‎ٺ‎ ) ARABIC LETTER TTEHEH FINAL FORM → ARABIC LETTER TTEHEH # -FB5E ; 067A ; MA # ( ‎ﭞ‎ → ‎ٺ‎ ) ARABIC LETTER TTEHEH ISOLATED FORM → ARABIC LETTER TTEHEH # - FB64 ; 067F ; MA # ( ‎ﭤ‎ → ‎ٿ‎ ) ARABIC LETTER TEHEH INITIAL FORM → ARABIC LETTER TEHEH # FB65 ; 067F ; MA # ( ‎ﭥ‎ → ‎ٿ‎ ) ARABIC LETTER TEHEH MEDIAL FORM → ARABIC LETTER TEHEH # FB63 ; 067F ; MA # ( ‎ﭣ‎ → ‎ٿ‎ ) ARABIC LETTER TEHEH FINAL FORM → ARABIC LETTER TEHEH # @@ -4586,6 +4677,8 @@ FB7D ; 0686 ; MA # ( ‎ﭽ‎ → ‎چ‎ ) ARABIC LETTER TCHEH MEDIAL FORM FB7B ; 0686 ; MA # ( ‎ﭻ‎ → ‎چ‎ ) ARABIC LETTER TCHEH FINAL FORM → ARABIC LETTER TCHEH # FB7A ; 0686 ; MA # ( ‎ﭺ‎ → ‎چ‎ ) ARABIC LETTER TCHEH ISOLATED FORM → ARABIC LETTER TCHEH # +08C1 ; 0686 0306 ; MA # ( ‎ࣁ‎ → ‎چ̆‎ ) ARABIC LETTER TCHEH WITH SMALL V → ARABIC LETTER TCHEH, COMBINING BREVE # →‎چٚ‎→ + FB80 ; 0687 ; MA # ( ‎ﮀ‎ → ‎ڇ‎ ) ARABIC LETTER TCHEHEH INITIAL FORM → ARABIC LETTER TCHEHEH # FB81 ; 0687 ; MA # ( ‎ﮁ‎ → ‎ڇ‎ ) ARABIC LETTER TCHEHEH MEDIAL FORM → ARABIC LETTER TCHEHEH # FB7F ; 0687 ; MA # ( ‎ﭿ‎ → ‎ڇ‎ ) ARABIC LETTER TCHEHEH FINAL FORM → ARABIC LETTER TCHEHEH # @@ -4661,6 +4754,7 @@ FB88 ; 062F 0615 ; MA # ( ‎ﮈ‎ → ‎دؕ‎ ) ARABIC LETTER DDAL ISOLATED 068E ; 062F 06DB ; MA # ( ‎ڎ‎ → ‎دۛ‎ ) ARABIC LETTER DUL → ARABIC LETTER DAL, ARABIC SMALL HIGH THREE DOTS # FB87 ; 062F 06DB ; MA # ( ‎ﮇ‎ → ‎دۛ‎ ) ARABIC LETTER DUL FINAL FORM → ARABIC LETTER DAL, ARABIC SMALL HIGH THREE DOTS # →‎ڎ‎→ FB86 ; 062F 06DB ; MA # ( ‎ﮆ‎ → ‎دۛ‎ ) ARABIC LETTER DUL ISOLATED FORM → ARABIC LETTER DAL, ARABIC SMALL HIGH THREE DOTS # →‎ڎ‎→ +068F ; 062F 06DB ; MA # ( ‎ڏ‎ → ‎دۛ‎ ) ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS → ARABIC LETTER DAL, ARABIC SMALL HIGH THREE DOTS # →‎ڎ‎→ 06EE ; 062F 0302 ; MA # ( ‎ۮ‎ → ‎د̂‎ ) ARABIC LETTER DAL WITH INVERTED V → ARABIC LETTER DAL, COMBINING CIRCUMFLEX ACCENT # →‎دٛ‎→ @@ -4709,6 +4803,7 @@ FC5C ; 0631 0670 ; MA # ( ‎ﱜ‎ → ‎رٰ‎ ) ARABIC LIGATURE REH WITH SU FDF6 ; 0631 0633 0648 0644 ; MA # ( ‎ﷶ‎ → ‎رسول‎ ) ARABIC LIGATURE RASOUL ISOLATED FORM → ARABIC LETTER REH, ARABIC LETTER SEEN, ARABIC LETTER WAW, ARABIC LETTER LAM # FDFC ; 0631 0649 006C 0644 ; MA #* ( ‎﷼‎ → ‎رىlل‎ ) RIAL SIGN → ARABIC LETTER REH, ARABIC LETTER ALEF MAKSURA, LATIN SMALL LETTER L, ARABIC LETTER LAM # →‎ریال‎→ +20C1 ; 0631 0649 006C 0644 ; MA #* ( ⃁ → ‎رىlل‎ ) SAUDI RIYAL SIGN → ARABIC LETTER REH, ARABIC LETTER ALEF MAKSURA, LATIN SMALL LETTER L, ARABIC LETTER LAM # →‎﷼‎→→‎ریال‎→ 1EE06 ; 0632 ; MA # ( ‎𞸆‎ → ‎ز‎ ) ARABIC MATHEMATICAL ZAIN → ARABIC LETTER ZAIN # 1EE86 ; 0632 ; MA # ( ‎𞺆‎ → ‎ز‎ ) ARABIC MATHEMATICAL LOOPED ZAIN → ARABIC LETTER ZAIN # @@ -5129,6 +5224,8 @@ FBD4 ; 0643 06DB ; MA # ( ‎ﯔ‎ → ‎كۛ‎ ) ARABIC LETTER NG FINAL FORM FBD3 ; 0643 06DB ; MA # ( ‎ﯓ‎ → ‎كۛ‎ ) ARABIC LETTER NG ISOLATED FORM → ARABIC LETTER KAF, ARABIC SMALL HIGH THREE DOTS # →‎ڭ‎→ 0763 ; 0643 06DB ; MA # ( ‎ݣ‎ → ‎كۛ‎ ) ARABIC LETTER KEHEH WITH THREE DOTS ABOVE → ARABIC LETTER KAF, ARABIC SMALL HIGH THREE DOTS # →‎ڭ‎→ +08C2 ; 0643 0306 ; MA # ( ‎ࣂ‎ → ‎ك̆‎ ) ARABIC LETTER KEHEH WITH SMALL V → ARABIC LETTER KAF, COMBINING BREVE # →‎کٚ‎→ + FC80 ; 0643 006C ; MA # ( ‎ﲀ‎ → ‎كl‎ ) ARABIC LIGATURE KAF WITH ALEF FINAL FORM → ARABIC LETTER KAF, LATIN SMALL LETTER L # →‎كا‎→ FC37 ; 0643 006C ; MA # ( ‎ﰷ‎ → ‎كl‎ ) ARABIC LIGATURE KAF WITH ALEF ISOLATED FORM → ARABIC LETTER KAF, LATIN SMALL LETTER L # →‎كا‎→ @@ -5262,8 +5359,6 @@ FEE1 ; 0645 ; MA # ( ‎ﻡ‎ → ‎م‎ ) ARABIC LETTER MEEM ISOLATED FORM 08A7 ; 0645 06DB ; MA # ( ‎ࢧ‎ → ‎مۛ‎ ) ARABIC LETTER MEEM WITH THREE DOTS ABOVE → ARABIC LETTER MEEM, ARABIC SMALL HIGH THREE DOTS # -06FE ; 0645 0348 ; MA #* ( ‎۾‎ → ‎م͈‎ ) ARABIC SIGN SINDHI POSTPOSITION MEN → ARABIC LETTER MEEM, COMBINING DOUBLE VERTICAL LINE BELOW # - FC88 ; 0645 006C ; MA # ( ‎ﲈ‎ → ‎مl‎ ) ARABIC LIGATURE MEEM WITH ALEF FINAL FORM → ARABIC LETTER MEEM, LATIN SMALL LETTER L # →‎ما‎→ FCCE ; 0645 062C ; MA # ( ‎ﳎ‎ → ‎مج‎ ) ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM → ARABIC LETTER MEEM, ARABIC LETTER JEEM # @@ -5306,6 +5401,8 @@ FDB1 ; 0645 0645 0649 ; MA # ( ‎ﶱ‎ → ‎ممى‎ ) ARABIC LIGATURE MEEM FC49 ; 0645 0649 ; MA # ( ‎ﱉ‎ → ‎مى‎ ) ARABIC LIGATURE MEEM WITH ALEF MAKSURA ISOLATED FORM → ARABIC LETTER MEEM, ARABIC LETTER ALEF MAKSURA # FC4A ; 0645 0649 ; MA # ( ‎ﱊ‎ → ‎مى‎ ) ARABIC LIGATURE MEEM WITH YEH ISOLATED FORM → ARABIC LETTER MEEM, ARABIC LETTER ALEF MAKSURA # →‎مي‎→ +06FE ; 0645 10EFA ; MA #* ( ‎۾‎ → ‎م𐻺‎ ) ARABIC SIGN SINDHI POSTPOSITION MEN → ARABIC LETTER MEEM, ARABIC DOUBLE VERTICAL BAR BELOW # + 1EE0D ; 0646 ; MA # ( ‎𞸍‎ → ‎ن‎ ) ARABIC MATHEMATICAL NOON → ARABIC LETTER NOON # 1EE2D ; 0646 ; MA # ( ‎𞸭‎ → ‎ن‎ ) ARABIC MATHEMATICAL INITIAL NOON → ARABIC LETTER NOON # 1EE4D ; 0646 ; MA # ( ‎𞹍‎ → ‎ن‎ ) ARABIC MATHEMATICAL TAILED NOON → ARABIC LETTER NOON # @@ -5316,6 +5413,7 @@ FEE7 ; 0646 ; MA # ( ‎ﻧ‎ → ‎ن‎ ) ARABIC LETTER NOON INITIAL FORM FEE8 ; 0646 ; MA # ( ‎ﻨ‎ → ‎ن‎ ) ARABIC LETTER NOON MEDIAL FORM → ARABIC LETTER NOON # FEE6 ; 0646 ; MA # ( ‎ﻦ‎ → ‎ن‎ ) ARABIC LETTER NOON FINAL FORM → ARABIC LETTER NOON # FEE5 ; 0646 ; MA # ( ‎ﻥ‎ → ‎ن‎ ) ARABIC LETTER NOON ISOLATED FORM → ARABIC LETTER NOON # +10EC6 ; 0646 ; MA # ( ‎𐻆‎ → ‎ن‎ ) ARABIC LETTER THIN NOON → ARABIC LETTER NOON # 0768 ; 0646 0615 ; MA # ( ‎ݨ‎ → ‎نؕ‎ ) ARABIC LETTER NOON WITH SMALL TAH → ARABIC LETTER NOON, ARABIC SMALL HIGH TAH # @@ -5457,6 +5555,7 @@ FB58 ; 0649 06DB ; MA # ( ‎ﭘ‎ → ‎ىۛ‎ ) ARABIC LETTER PEH INITIAL F FB59 ; 0649 06DB ; MA # ( ‎ﭙ‎ → ‎ىۛ‎ ) ARABIC LETTER PEH MEDIAL FORM → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎پ‎→→‎ڽ‎→→‎ںۛ‎→ FB57 ; 0649 06DB ; MA # ( ‎ﭗ‎ → ‎ىۛ‎ ) ARABIC LETTER PEH FINAL FORM → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎پ‎→→‎ڽ‎→→‎ںۛ‎→ FB56 ; 0649 06DB ; MA # ( ‎ﭖ‎ → ‎ىۛ‎ ) ARABIC LETTER PEH ISOLATED FORM → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎پ‎→→‎ڽ‎→→‎ںۛ‎→ +0752 ; 0649 06DB ; MA # ( ‎ݒ‎ → ‎ىۛ‎ ) ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎پ‎→→‎ڽ‎→→‎ںۛ‎→ 062B ; 0649 06DB ; MA # ( ‎ث‎ → ‎ىۛ‎ ) ARABIC LETTER THEH → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎ٮۛ‎→ 1EE16 ; 0649 06DB ; MA # ( ‎𞸖‎ → ‎ىۛ‎ ) ARABIC MATHEMATICAL THEH → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎ث‎→→‎ٮۛ‎→ 1EE36 ; 0649 06DB ; MA # ( ‎𞸶‎ → ‎ىۛ‎ ) ARABIC MATHEMATICAL INITIAL THEH → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS # →‎ث‎→→‎ٮۛ‎→ @@ -5476,10 +5575,16 @@ FE99 ; 0649 06DB ; MA # ( ‎ﺙ‎ → ‎ىۛ‎ ) ARABIC LETTER THEH ISOLATED 0756 ; 0649 0306 ; MA # ( ‎ݖ‎ → ‎ى̆‎ ) ARABIC LETTER BEH WITH SMALL V → ARABIC LETTER ALEF MAKSURA, COMBINING BREVE # →‎ٮٚ‎→ 06CE ; 0649 0306 ; MA # ( ‎ێ‎ → ‎ى̆‎ ) ARABIC LETTER YEH WITH SMALL V → ARABIC LETTER ALEF MAKSURA, COMBINING BREVE # →‎یٚ‎→ +08C0 ; 0649 0615 0306 ; MA # ( ‎ࣀ‎ → ‎ىؕ̆‎ ) ARABIC LETTER TTEH WITH SMALL V → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH TAH, COMBINING BREVE # →‎ٹٚ‎→ + +08BE ; 0649 06DB 0306 ; MA # ( ‎ࢾ‎ → ‎ىۛ̆‎ ) ARABIC LETTER PEH WITH SMALL V → ARABIC LETTER ALEF MAKSURA, ARABIC SMALL HIGH THREE DOTS, COMBINING BREVE # →‎پٚ‎→ + 08BA ; 0649 0306 0307 ; MA # ( ‎ࢺ‎ → ‎ى̆̇‎ ) ARABIC LETTER YEH WITH TWO DOTS BELOW AND SMALL NOON ABOVE → ARABIC LETTER ALEF MAKSURA, COMBINING BREVE, COMBINING DOT ABOVE # →‎يۨ‎→ 063D ; 0649 0302 ; MA # ( ‎ؽ‎ → ‎ى̂‎ ) ARABIC LETTER FARSI YEH WITH INVERTED V → ARABIC LETTER ALEF MAKSURA, COMBINING CIRCUMFLEX ACCENT # →‎یٛ‎→ +088F ; 0649 030A ; MA # ( ‎࢏‎ → ‎ى̊‎ ) ARABIC LETTER NOON WITH RING ABOVE → ARABIC LETTER ALEF MAKSURA, COMBINING RING ABOVE # →‎ں̊‎→ + 08A8 ; 0649 0654 ; MA # ( ‎ࢨ‎ → ‎ىٔ‎ ) ARABIC LETTER YEH WITH TWO DOTS BELOW AND HAMZA ABOVE → ARABIC LETTER ALEF MAKSURA, ARABIC HAMZA ABOVE # →‎ئ‎→ FC90 ; 0649 0670 ; MA # ( ‎ﲐ‎ → ‎ىٰ‎ ) ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF FINAL FORM → ARABIC LETTER ALEF MAKSURA, ARABIC LETTER SUPERSCRIPT ALEF # @@ -5608,6 +5713,7 @@ FBB0 ; 06D3 ; MA # ( ‎ﮰ‎ → ‎ۓ‎ ) ARABIC LETTER YEH BARREE WITH HAMZ 205E ; 2D42 ; MA #* ( ⁞ → ⵂ ) VERTICAL FOUR DOTS → TIFINAGH LETTER TUAREG YAH # 2E3D ; 2D42 ; MA #* ( ⸽ → ⵂ ) VERTICAL SIX DOTS → TIFINAGH LETTER TUAREG YAH # →⁞→ 2999 ; 2D42 ; MA #* ( ⦙ → ⵂ ) DOTTED FENCE → TIFINAGH LETTER TUAREG YAH # →⁞→ +1CEEF ; 2D42 ; MA #* ( 𜻯 → ⵂ ) GEOMANTIC FIGURE VIA → TIFINAGH LETTER TUAREG YAH # →⁞→ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS → TIFINAGH LETTER TUAREG YAGH # →⁝→ 205D ; 2D57 ; MA #* ( ⁝ → ⵗ ) TRICOLON → TIFINAGH LETTER TUAREG YAGH # @@ -5621,31 +5727,72 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 054A ; 1323 ; MA # ( Պ → ጣ ) ARMENIAN CAPITAL LETTER PEH → ETHIOPIC SYLLABLE THAA # +0972 ; 0905 0306 ; MA # ( ॲ → अ̆ ) DEVANAGARI LETTER CANDRA A → DEVANAGARI LETTER A, COMBINING BREVE # →अॅ→ + 0906 ; 0905 093E ; MA # ( आ → अा ) DEVANAGARI LETTER AA → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA # -0912 ; 0905 093E 0946 ; MA # ( ऒ → अाॆ ) DEVANAGARI LETTER SHORT O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN SHORT E # →अॊ→→आॆ→ +0911 ; 0905 093E 0306 ; MA # ( ऑ → अा̆ ) DEVANAGARI LETTER CANDRA O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, COMBINING BREVE # →अॉ→ -0913 ; 0905 093E 0947 ; MA # ( ओ → अाे ) DEVANAGARI LETTER O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN E # →अो→→आे→ +0974 ; 0905 093E 093A ; MA # ( ॴ → अाऺ ) DEVANAGARI LETTER OOE → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN OE # →अऻ→ + +0912 ; 0905 093E 0946 ; MA # ( ऒ → अाॆ ) DEVANAGARI LETTER SHORT O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN SHORT E # →अॊ→→आॆ→ 0914 ; 0905 093E 0948 ; MA # ( औ → अाै ) DEVANAGARI LETTER AU → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN AI # →अौ→→आै→ +0913 ; 0905 093E 11B64 ; MA # ( ओ → अा𑭤 ) DEVANAGARI LETTER O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AA, SHARADA VOWEL SIGN SHORT E # →अो→→आे→ + +0973 ; 0905 093A ; MA # ( ॳ → अऺ ) DEVANAGARI LETTER OE → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN OE # + +0975 ; 0905 094F ; MA # ( ॵ → अॏ ) DEVANAGARI LETTER AW → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN AW # + 0904 ; 0905 0946 ; MA # ( ऄ → अॆ ) DEVANAGARI LETTER SHORT A → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN SHORT E # -0911 ; 0905 0949 ; MA # ( ऑ → अॉ ) DEVANAGARI LETTER CANDRA O → DEVANAGARI LETTER A, DEVANAGARI VOWEL SIGN CANDRA O # +0A24 ; 0909 ; MA # ( ਤ → उ ) GURMUKHI LETTER TA → DEVANAGARI LETTER U # -090D ; 090F 0945 ; MA # ( ऍ → एॅ ) DEVANAGARI LETTER CANDRA E → DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN CANDRA E # +090D ; 090F 0306 ; MA # ( ऍ → ए̆ ) DEVANAGARI LETTER CANDRA E → DEVANAGARI LETTER E, COMBINING BREVE # →एॅ→ 090E ; 090F 0946 ; MA # ( ऎ → एॆ ) DEVANAGARI LETTER SHORT E → DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN SHORT E # -0910 ; 090F 0947 ; MA # ( ऐ → एे ) DEVANAGARI LETTER AI → DEVANAGARI LETTER E, DEVANAGARI VOWEL SIGN E # +0910 ; 090F 11B64 ; MA # ( ऐ → ए𑭤 ) DEVANAGARI LETTER AI → DEVANAGARI LETTER E, SHARADA VOWEL SIGN SHORT E # →एे→ + +0A1F ; 091F ; MA # ( ਟ → ट ) GURMUKHI LETTER TTA → DEVANAGARI LETTER TTA # + +0A20 ; 0920 ; MA # ( ਠ → ठ ) GURMUKHI LETTER TTHA → DEVANAGARI LETTER TTHA # + +0A2B ; 0922 ; MA # ( ਫ → ढ ) GURMUKHI LETTER PHA → DEVANAGARI LETTER DDHA # + +0A1C ; 0924 094D 0924 ; MA # ( ਜ → त्त ) GURMUKHI LETTER JA → DEVANAGARI LETTER TA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER TA # + +0A27 ; 092A ; MA # ( ਧ → प ) GURMUKHI LETTER DHA → DEVANAGARI LETTER PA # + +0A72 ; 092A 094D 091F ; MA # ( ੲ → प्ट ) GURMUKHI IRI → DEVANAGARI LETTER PA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER TTA # + +0A07 ; 092A 094D 091F 09BF ; MA # ( ਇ → प्टি ) GURMUKHI LETTER I → DEVANAGARI LETTER PA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER TTA, BENGALI VOWEL SIGN I # →ੲਿ→ + +0A08 ; 092A 094D 091F 0A40 ; MA # ( ਈ → प्टੀ ) GURMUKHI LETTER II → DEVANAGARI LETTER PA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER TTA, GURMUKHI VOWEL SIGN II # →ੲੀ→ + +0A0F ; 092A 094D 091F 11B64 ; MA # ( ਏ → प्ट𑭤 ) GURMUKHI LETTER EE → DEVANAGARI LETTER PA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER TTA, SHARADA VOWEL SIGN SHORT E # →ੲੇ→ + +0A2E ; 092D ; MA # ( ਮ → भ ) GURMUKHI LETTER MA → DEVANAGARI LETTER BHA # + +0A38 ; 092E ; MA # ( ਸ → म ) GURMUKHI LETTER SA → DEVANAGARI LETTER MA # 0908 ; 0930 094D 0907 ; MA # ( ई → र्इ ) DEVANAGARI LETTER II → DEVANAGARI LETTER RA, DEVANAGARI SIGN VIRAMA, DEVANAGARI LETTER I # +0A15 ; 0935 ; MA # ( ਕ → व ) GURMUKHI LETTER KA → DEVANAGARI LETTER VA # + +0A35 ; 0939 ; MA # ( ਵ → ह ) GURMUKHI LETTER VA → DEVANAGARI LETTER HA # + 0ABD ; 093D ; MA # ( ઽ → ऽ ) GUJARATI SIGN AVAGRAHA → DEVANAGARI SIGN AVAGRAHA # 111DC ; A8FB ; MA # ( 𑇜 → ꣻ ) SHARADA HEADSTROKE → DEVANAGARI HEADSTROKE # +0949 ; 093E 0306 ; MA # ( ॉ → ा̆ ) DEVANAGARI VOWEL SIGN CANDRA O → DEVANAGARI VOWEL SIGN AA, COMBINING BREVE # →ाॅ→ + +093B ; 093E 093A ; MA # ( ऻ → ाऺ ) DEVANAGARI VOWEL SIGN OOE → DEVANAGARI VOWEL SIGN AA, DEVANAGARI VOWEL SIGN OE # + 111CB ; 093A ; MA # ( 𑇋 → ऺ ) SHARADA VOWEL MODIFIER MARK → DEVANAGARI VOWEL SIGN OE # +11B60 ; 093A ; MA # ( 𑭠 → ऺ ) SHARADA VOWEL SIGN OE → DEVANAGARI VOWEL SIGN OE # 0AC1 ; 0941 ; MA # ( ુ → ु ) GUJARATI VOWEL SIGN U → DEVANAGARI VOWEL SIGN U # @@ -5653,6 +5800,8 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0A4B ; 0946 ; MA # ( ੋ → ॆ ) GURMUKHI VOWEL SIGN OO → DEVANAGARI VOWEL SIGN SHORT E # +0A48 ; 0948 ; MA # ( ੈ → ै ) GURMUKHI VOWEL SIGN AI → DEVANAGARI VOWEL SIGN AI # + 0A4D ; 094D ; MA # ( ੍ → ् ) GURMUKHI SIGN VIRAMA → DEVANAGARI SIGN VIRAMA # 0ACD ; 094D ; MA # ( ્ → ् ) GUJARATI SIGN VIRAMA → DEVANAGARI SIGN VIRAMA # @@ -5693,6 +5842,7 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 114A8 ; 09AF ; MA # ( 𑒨 → য ) TIRHUTA LETTER YA → BENGALI LETTER YA # +09F0 ; 09B0 ; MA # ( ৰ → র ) BENGALI LETTER RA WITH MIDDLE DIAGONAL → BENGALI LETTER RA # 114AB ; 09B0 ; MA # ( 𑒫 → র ) TIRHUTA LETTER VA → BENGALI LETTER RA # 1149D ; 09B2 ; MA # ( 𑒝 → ল ) TIRHUTA LETTER NNA → BENGALI LETTER LA # @@ -5705,6 +5855,8 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 114B0 ; 09BE ; MA # ( 𑒰 → া ) TIRHUTA VOWEL SIGN AA → BENGALI VOWEL SIGN AA # +093F ; 09BF ; MA # ( ि → ি ) DEVANAGARI VOWEL SIGN I → BENGALI VOWEL SIGN I # +0A3F ; 09BF ; MA # ( ਿ → ি ) GURMUKHI VOWEL SIGN I → BENGALI VOWEL SIGN I # →ि→ 114B1 ; 09BF ; MA # ( 𑒱 → ি ) TIRHUTA VOWEL SIGN I → BENGALI VOWEL SIGN I # 114B9 ; 09C7 ; MA # ( 𑒹 → ে ) TIRHUTA VOWEL SIGN E → BENGALI VOWEL SIGN E # @@ -5717,22 +5869,16 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 114BD ; 09D7 ; MA # ( 𑒽 → ৗ ) TIRHUTA VOWEL SIGN SHORT O → BENGALI AU LENGTH MARK # -0A09 ; 0A73 0A41 ; MA # ( ਉ → ੳੁ ) GURMUKHI LETTER U → GURMUKHI URA, GURMUKHI VOWEL SIGN U # +0A09 ; 0A73 11B62 ; MA # ( ਉ → ੳ𑭢 ) GURMUKHI LETTER U → GURMUKHI URA, SHARADA VOWEL SIGN UE # →ੳੁ→ -0A0A ; 0A73 0A42 ; MA # ( ਊ → ੳੂ ) GURMUKHI LETTER UU → GURMUKHI URA, GURMUKHI VOWEL SIGN UU # +0A0A ; 0A73 11B63 ; MA # ( ਊ → ੳ𑭣 ) GURMUKHI LETTER UU → GURMUKHI URA, SHARADA VOWEL SIGN UUE # →ੳੂ→ -0A06 ; 0A05 0A3E ; MA # ( ਆ → ਅਾ ) GURMUKHI LETTER AA → GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AA # +0A10 ; 0A05 0948 ; MA # ( ਐ → ਅै ) GURMUKHI LETTER AI → GURMUKHI LETTER A, DEVANAGARI VOWEL SIGN AI # →ਅੈ→ -0A10 ; 0A05 0A48 ; MA # ( ਐ → ਅੈ ) GURMUKHI LETTER AI → GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AI # +0A06 ; 0A05 0A3E ; MA # ( ਆ → ਅਾ ) GURMUKHI LETTER AA → GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AA # 0A14 ; 0A05 0A4C ; MA # ( ਔ → ਅੌ ) GURMUKHI LETTER AU → GURMUKHI LETTER A, GURMUKHI VOWEL SIGN AU # -0A07 ; 0A72 0A3F ; MA # ( ਇ → ੲਿ ) GURMUKHI LETTER I → GURMUKHI IRI, GURMUKHI VOWEL SIGN I # - -0A08 ; 0A72 0A40 ; MA # ( ਈ → ੲੀ ) GURMUKHI LETTER II → GURMUKHI IRI, GURMUKHI VOWEL SIGN II # - -0A0F ; 0A72 0A47 ; MA # ( ਏ → ੲੇ ) GURMUKHI LETTER EE → GURMUKHI IRI, GURMUKHI VOWEL SIGN EE # - 0A86 ; 0A85 0ABE ; MA # ( આ → અા ) GUJARATI LETTER AA → GUJARATI LETTER A, GUJARATI VOWEL SIGN AA # 0A91 ; 0A85 0ABE 0AC5 ; MA # ( ઑ → અાૅ ) GUJARATI VOWEL CANDRA O → GUJARATI LETTER A, GUJARATI VOWEL SIGN AA, GUJARATI VOWEL SIGN CANDRA E # →અૉ→→આૅ→ @@ -5749,6 +5895,8 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0B06 ; 0B05 0B3E ; MA # ( ଆ → ଅା ) ORIYA LETTER AA → ORIYA LETTER A, ORIYA VOWEL SIGN AA # +1031 ; 0B47 ; MA # ( ေ → େ ) MYANMAR VOWEL SIGN E → ORIYA VOWEL SIGN E # + 0BEE ; 0B85 ; MA # ( ௮ → அ ) TAMIL DIGIT EIGHT → TAMIL LETTER A # 0BB0 ; 0B88 ; MA # ( ர → ஈ ) TAMIL LETTER RA → TAMIL LETTER II # →ா→ @@ -5770,6 +5918,8 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0B9C ; 0B90 ; MA # ( ஜ → ஐ ) TAMIL LETTER JA → TAMIL LETTER AI # 0D1C ; 0B90 ; MA # ( ജ → ஐ ) MALAYALAM LETTER JA → TAMIL LETTER AI # →ஜ→ +0B94 ; 0B92 0BB3 ; MA # ( ஔ → ஒள ) TAMIL LETTER AU → TAMIL LETTER O, TAMIL LETTER LLA # + 0BE7 ; 0B95 ; MA # ( ௧ → க ) TAMIL DIGIT ONE → TAMIL LETTER KA # 0BEA ; 0B9A ; MA # ( ௪ → ச ) TAMIL DIGIT FOUR → TAMIL LETTER CA # @@ -5782,18 +5932,25 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0D23 ; 0BA3 ; MA # ( ണ → ண ) MALAYALAM LETTER NNA → TAMIL LETTER NNA # +0D7A ; 0BA3 0D4D ; MA # ( ൺ → ண് ) MALAYALAM LETTER CHILLU NN → TAMIL LETTER NNA, MALAYALAM SIGN VIRAMA # →ണ്→ + 0BFA ; 0BA8 0BC0 ; MA #* ( ௺ → நீ ) TAMIL NUMBER SIGN → TAMIL LETTER NA, TAMIL VOWEL SIGN II # +0D25 ; 0BAE ; MA # ( ഥ → ம ) MALAYALAM LETTER THA → TAMIL LETTER MA # + 0BF4 ; 0BAE 0BC0 ; MA #* ( ௴ → மீ ) TAMIL MONTH SIGN → TAMIL LETTER MA, TAMIL VOWEL SIGN II # 0BF0 ; 0BAF ; MA #* ( ௰ → ய ) TAMIL NUMBER TEN → TAMIL LETTER YA # +0D16 ; 0BB5 ; MA # ( ഖ → வ ) MALAYALAM LETTER KHA → TAMIL LETTER VA # + 0D34 ; 0BB4 ; MA # ( ഴ → ழ ) MALAYALAM LETTER LLLA → TAMIL LETTER LLLA # 0BD7 ; 0BB3 ; MA # ( ௗ → ள ) TAMIL AU LENGTH MARK → TAMIL LETTER LLA # 0BC8 ; 0BA9 ; MA # ( ை → ன ) TAMIL VOWEL SIGN AI → TAMIL LETTER NNNA # +0BB8 ; 0BB6 ; MA # ( ஸ → ஶ ) TAMIL LETTER SA → TAMIL LETTER SHA # 0D36 ; 0BB6 ; MA # ( ശ → ஶ ) MALAYALAM LETTER SHA → TAMIL LETTER SHA # 0BF8 ; 0BB7 ; MA #* ( ௸ → ஷ ) TAMIL AS ABOVE SIGN → TAMIL LETTER SSA # @@ -5801,10 +5958,18 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0D3F ; 0BBF ; MA # ( ി → ி ) MALAYALAM VOWEL SIGN I → TAMIL VOWEL SIGN I # 0D40 ; 0BBF ; MA # ( ീ → ி ) MALAYALAM VOWEL SIGN II → TAMIL VOWEL SIGN I # +0D46 ; 0BC6 ; MA # ( െ → ெ ) MALAYALAM VOWEL SIGN E → TAMIL VOWEL SIGN E # + 0BCA ; 0BC6 0B88 ; MA # ( ொ → ெஈ ) TAMIL VOWEL SIGN O → TAMIL VOWEL SIGN E, TAMIL LETTER II # →ெர→ 0BCC ; 0BC6 0BB3 ; MA # ( ௌ → ெள ) TAMIL VOWEL SIGN AU → TAMIL VOWEL SIGN E, TAMIL LETTER LLA # +0D48 ; 0BC6 0BC6 ; MA # ( ൈ → ெெ ) MALAYALAM VOWEL SIGN AI → TAMIL VOWEL SIGN E, TAMIL VOWEL SIGN E # →െെ→ + +0D10 ; 0BC6 0D0E ; MA # ( ഐ → ெഎ ) MALAYALAM LETTER AI → TAMIL VOWEL SIGN E, MALAYALAM LETTER E # →െഎ→ + +0D47 ; 0BC7 ; MA # ( േ → ே ) MALAYALAM VOWEL SIGN EE → TAMIL VOWEL SIGN EE # + 0BCB ; 0BC7 0B88 ; MA # ( ோ → ேஈ ) TAMIL VOWEL SIGN OO → TAMIL VOWEL SIGN EE, TAMIL LETTER II # →ேர→ 0C85 ; 0C05 ; MA # ( ಅ → అ ) KANNADA LETTER A → TELUGU LETTER A # @@ -5817,6 +5982,8 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0C61 ; 0C0C 0C3E ; MA # ( ౡ → ఌా ) TELUGU LETTER VOCALIC LL → TELUGU LETTER VOCALIC L, TELUGU VOWEL SIGN AA # +0C90 ; 0C10 ; MA # ( ಐ → ఐ ) KANNADA LETTER AI → TELUGU LETTER AI # + 0C92 ; 0C12 ; MA # ( ಒ → ఒ ) KANNADA LETTER O → TELUGU LETTER O # 0C14 ; 0C12 0C4C ; MA # ( ఔ → ఒౌ ) TELUGU LETTER AU → TELUGU LETTER O, TELUGU VOWEL SIGN AU # @@ -5825,20 +5992,32 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0C13 ; 0C12 0C55 ; MA # ( ఓ → ఒౕ ) TELUGU LETTER OO → TELUGU LETTER O, TELUGU LENGTH MARK # 0C93 ; 0C12 0C55 ; MA # ( ಓ → ఒౕ ) KANNADA LETTER OO → TELUGU LETTER O, TELUGU LENGTH MARK # →ఓ→ +0C97 ; 0C17 ; MA # ( ಗ → గ ) KANNADA LETTER GA → TELUGU LETTER GA # + 0C9C ; 0C1C ; MA # ( ಜ → జ ) KANNADA LETTER JA → TELUGU LETTER JA # +0C9D ; 0C1D ; MA # ( ಝ → ఝ ) KANNADA LETTER JHA → TELUGU LETTER JHA # + 0C9E ; 0C1E ; MA # ( ಞ → ఞ ) KANNADA LETTER NYA → TELUGU LETTER NYA # +0C9F ; 0C1F ; MA # ( ಟ → ట ) KANNADA LETTER TTA → TELUGU LETTER TTA # + 0C22 ; 0C21 0323 ; MA # ( ఢ → డ̣ ) TELUGU LETTER DDHA → TELUGU LETTER DDA, COMBINING DOT BELOW # 0CA3 ; 0C23 ; MA # ( ಣ → ణ ) KANNADA LETTER NNA → TELUGU LETTER NNA # +0CA6 ; 0C26 ; MA # ( ದ → ద ) KANNADA LETTER DA → TELUGU LETTER DA # + 0C25 ; 0C27 05BC ; MA # ( థ → ధּ ) TELUGU LETTER THA → TELUGU LETTER DHA, HEBREW POINT DAGESH OR MAPIQ # +0CA8 ; 0C28 ; MA # ( ನ → న ) KANNADA LETTER NA → TELUGU LETTER NA # + 0C2D ; 0C2C 0323 ; MA # ( భ → బ̣ ) TELUGU LETTER BHA → TELUGU LETTER BA, COMBINING DOT BELOW # 0CAF ; 0C2F ; MA # ( ಯ → య ) KANNADA LETTER YA → TELUGU LETTER YA # +0CB0 ; 0C30 ; MA # ( ರ → ర ) KANNADA LETTER RA → TELUGU LETTER RA # + 0C20 ; 0C30 05BC ; MA # ( ఠ → రּ ) TELUGU LETTER TTHA → TELUGU LETTER RA, HEBREW POINT DAGESH OR MAPIQ # 0CB1 ; 0C31 ; MA # ( ಱ → ఱ ) KANNADA LETTER RRA → TELUGU LETTER RRA # @@ -5851,15 +6030,23 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0C2E ; 0C35 0C41 ; MA # ( మ → వు ) TELUGU LETTER MA → TELUGU LETTER VA, TELUGU VOWEL SIGN U # +0CB3 ; 0C33 ; MA # ( ಳ → ళ ) KANNADA LETTER LLA → TELUGU LETTER LLA # + +0CBF ; 0C3F ; MA # ( ಿ → ి ) KANNADA VOWEL SIGN I → TELUGU VOWEL SIGN I # + +0CC1 ; 0C41 ; MA # ( ು → ు ) KANNADA VOWEL SIGN U → TELUGU VOWEL SIGN U # + 0C42 ; 0C41 0C3E ; MA # ( ూ → ుా ) TELUGU VOWEL SIGN UU → TELUGU VOWEL SIGN U, TELUGU VOWEL SIGN AA # +0CC3 ; 0C43 ; MA # ( ೃ → ృ ) KANNADA VOWEL SIGN VOCALIC R → TELUGU VOWEL SIGN VOCALIC R # + 0C44 ; 0C43 0C3E ; MA # ( ౄ → ృా ) TELUGU VOWEL SIGN VOCALIC RR → TELUGU VOWEL SIGN VOCALIC R, TELUGU VOWEL SIGN AA # 0CE1 ; 0C8C 0CBE ; MA # ( ೡ → ಌಾ ) KANNADA LETTER VOCALIC LL → KANNADA LETTER VOCALIC L, KANNADA VOWEL SIGN AA # -0D08 ; 0D07 0D57 ; MA # ( ഈ → ഇൗ ) MALAYALAM LETTER II → MALAYALAM LETTER I, MALAYALAM AU LENGTH MARK # +0C16 ; 0C96 0323 ; MA # ( ఖ → ಖ̣ ) TELUGU LETTER KHA → KANNADA LETTER KHA, COMBINING DOT BELOW # -0D10 ; 0D0E 0D46 ; MA # ( ഐ → എെ ) MALAYALAM LETTER AI → MALAYALAM LETTER E, MALAYALAM VOWEL SIGN E # +0D08 ; 0D07 0D57 ; MA # ( ഈ → ഇൗ ) MALAYALAM LETTER II → MALAYALAM LETTER I, MALAYALAM AU LENGTH MARK # 0D13 ; 0D12 0D3E ; MA # ( ഓ → ഒാ ) MALAYALAM LETTER OO → MALAYALAM LETTER O, MALAYALAM VOWEL SIGN AA # @@ -5880,24 +6067,50 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0D5A ; 0D28 0D4D 0D2E ; MA #* ( ൚ → ന്മ ) MALAYALAM FRACTION THREE EIGHTIETHS → MALAYALAM LETTER NA, MALAYALAM SIGN VIRAMA, MALAYALAM LETTER MA # +10D8 ; 0D30 ; MA # ( ი → ര ) GEORGIAN LETTER IN → MALAYALAM LETTER RA # →റ→ 0D31 ; 0D30 ; MA # ( റ → ര ) MALAYALAM LETTER RRA → MALAYALAM LETTER RA # +1002 ; 0D30 ; MA # ( ဂ → ര ) MYANMAR LETTER GA → MALAYALAM LETTER RA # →റ→ 0D6A ; 0D30 0D4D ; MA # ( ൪ → ര് ) MALAYALAM DIGIT FOUR → MALAYALAM LETTER RA, MALAYALAM SIGN VIRAMA # 0D7C ; 0D30 0D4D ; MA # ( ർ → ര് ) MALAYALAM LETTER CHILLU RR → MALAYALAM LETTER RA, MALAYALAM SIGN VIRAMA # →൪→ +1081 ; 0D30 103E ; MA # ( ႁ → രှ ) MYANMAR LETTER SHAN HA → MALAYALAM LETTER RA, MYANMAR CONSONANT SIGN MEDIAL HA # →ဂှ→ + +1000 ; 0D30 102C ; MA # ( က → രာ ) MYANMAR LETTER KA → MALAYALAM LETTER RA, MYANMAR VOWEL SIGN AA # →ဂာ→ + +1023 ; 0D30 102C 1039 0D30 102C ; MA # ( ဣ → രာ္രာ ) MYANMAR LETTER I → MALAYALAM LETTER RA, MYANMAR VOWEL SIGN AA, MYANMAR SIGN VIRAMA, MALAYALAM LETTER RA, MYANMAR VOWEL SIGN AA # →က္က→ + +0D7D ; 0D32 0D4D ; MA # ( ൽ → ല് ) MALAYALAM LETTER CHILLU L → MALAYALAM LETTER LA, MALAYALAM SIGN VIRAMA # + 0D6E ; 0D35 0D4D 0D30 ; MA # ( ൮ → വ്ര ) MALAYALAM DIGIT EIGHT → MALAYALAM LETTER VA, MALAYALAM SIGN VIRAMA, MALAYALAM LETTER RA # 0D76 ; 0D39 0D4D 0D2E ; MA #* ( ൶ → ഹ്മ ) MALAYALAM FRACTION ONE SIXTEENTH → MALAYALAM LETTER HA, MALAYALAM SIGN VIRAMA, MALAYALAM LETTER MA # +0D7E ; 0D33 0D4D ; MA # ( ൾ → ള് ) MALAYALAM LETTER CHILLU LL → MALAYALAM LETTER LLA, MALAYALAM SIGN VIRAMA # + 0D42 ; 0D41 ; MA # ( ൂ → ു ) MALAYALAM VOWEL SIGN UU → MALAYALAM VOWEL SIGN U # 0D43 ; 0D41 ; MA # ( ൃ → ു ) MALAYALAM VOWEL SIGN VOCALIC R → MALAYALAM VOWEL SIGN U # →ൂ→ -0D48 ; 0D46 0D46 ; MA # ( ൈ → െെ ) MALAYALAM VOWEL SIGN AI → MALAYALAM VOWEL SIGN E, MALAYALAM VOWEL SIGN E # +0DB5 ; 0D91 ; MA # ( ඵ → එ ) SINHALA LETTER MAHAAPRAANA PAYANNA → SINHALA LETTER EYANNA # + +0D93 ; 0D91 0DD9 ; MA # ( ඓ → එෙ ) SINHALA LETTER AIYANNA → SINHALA LETTER EYANNA, SINHALA VOWEL SIGN KOMBUVA # + +0D92 ; 0D91 0DCA ; MA # ( ඒ → එ් ) SINHALA LETTER EEYANNA → SINHALA LETTER EYANNA, SINHALA SIGN AL-LAKUNA # + +0DB9 ; 0D94 ; MA # ( ඹ → ඔ ) SINHALA LETTER AMBA BAYANNA → SINHALA LETTER OYANNA # + +0DB6 ; 0D9B ; MA # ( බ → ඛ ) SINHALA LETTER ALPAPRAANA BAYANNA → SINHALA LETTER MAHAAPRAANA KAYANNA # + +0DC0 ; 0DA0 ; MA # ( ව → ච ) SINHALA LETTER VAYANNA → SINHALA LETTER ALPAPRAANA CAYANNA # 0DEA ; 0DA2 ; MA # ( ෪ → ජ ) SINHALA LITH DIGIT FOUR → SINHALA LETTER ALPAPRAANA JAYANNA # 0DEB ; 0DAF ; MA # ( ෫ → ද ) SINHALA LITH DIGIT FIVE → SINHALA LETTER ALPAPRAANA DAYANNA # +0DC4 ; 0DB7 ; MA # ( හ → භ ) SINHALA LETTER HAYANNA → SINHALA LETTER MAHAAPRAANA BAYANNA # + +0D8D ; 0DC3 0DD8 ; MA # ( ඍ → සෘ ) SINHALA LETTER IRUYANNA → SINHALA LETTER DANTAJA SAYANNA, SINHALA VOWEL SIGN GAETTA-PILLA # + 11413 ; 11434 11442 11412 ; MA # ( 𑐓 → 𑐴𑑂𑐒 ) NEWA LETTER NGHA → NEWA LETTER HA, NEWA SIGN VIRAMA, NEWA LETTER NGA # 11419 ; 11434 11442 11418 ; MA # ( 𑐙 → 𑐴𑑂𑐘 ) NEWA LETTER NYHA → NEWA LETTER HA, NEWA SIGN VIRAMA, NEWA LETTER NYA # @@ -5988,13 +6201,13 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 0F79 ; 0FB3 0F71 0F80 ; MA # ( ཹ → ླཱྀ ) TIBETAN VOWEL SIGN VOCALIC LL → TIBETAN SUBJOINED LETTER LA, TIBETAN VOWEL SIGN AA, TIBETAN VOWEL SIGN REVERSED I # -11CB2 ; 11CAA ; MA # ( 𑲲 → 𑲪 ) MARCHEN VOWEL SIGN U → MARCHEN SUBJOINED LETTER RA # +0F7B ; 0F7A 0F7A ; MA # ( ཻ → ེེ ) TIBETAN VOWEL SIGN EE → TIBETAN VOWEL SIGN E, TIBETAN VOWEL SIGN E # -1734 ; 1715 ; MA # ( ᜴ → ᜕ ) HANUNOO SIGN PAMUDPOD → TAGALOG SIGN PAMUDPOD # +0F7D ; 0F7C 0F7C ; MA # ( ཽ → ོོ ) TIBETAN VOWEL SIGN OO → TIBETAN VOWEL SIGN O, TIBETAN VOWEL SIGN O # -1081 ; 1002 103E ; MA # ( ႁ → ဂှ ) MYANMAR LETTER SHAN HA → MYANMAR LETTER GA, MYANMAR CONSONANT SIGN MEDIAL HA # +11CB2 ; 11CAA ; MA # ( 𑲲 → 𑲪 ) MARCHEN VOWEL SIGN U → MARCHEN SUBJOINED LETTER RA # -1000 ; 1002 102C ; MA # ( က → ဂာ ) MYANMAR LETTER KA → MYANMAR LETTER GA, MYANMAR VOWEL SIGN AA # +1734 ; 1715 ; MA # ( ᜴ → ᜕ ) HANUNOO SIGN PAMUDPOD → TAGALOG SIGN PAMUDPOD # 1070 ; 1003 103E ; MA # ( ၰ → ဃှ ) MYANMAR LETTER EASTERN PWO KAREN GHWA → MYANMAR LETTER GHA, MYANMAR CONSONANT SIGN MEDIAL HA # @@ -6006,12 +6219,16 @@ FE19 ; 2D57 ; MA #* ( ︙ → ⵗ ) PRESENTATION FORM FOR VERTICAL HORIZONTAL EL 107E ; 107D 103E ; MA # ( ၾ → ၽှ ) MYANMAR LETTER SHAN FA → MYANMAR LETTER SHAN PHA, MYANMAR CONSONANT SIGN MEDIAL HA # +1061 ; 101B 103E ; MA # ( ၡ → ရှ ) MYANMAR LETTER SGAW KAREN SHA → MYANMAR LETTER RA, MYANMAR CONSONANT SIGN MEDIAL HA # + 1029 ; 101E 103C ; MA # ( ဩ → သြ ) MYANMAR LETTER O → MYANMAR LETTER SA, MYANMAR CONSONANT SIGN MEDIAL RA # -102A ; 101E 103C 1031 102C 103A ; MA # ( ဪ → သြော် ) MYANMAR LETTER AU → MYANMAR LETTER SA, MYANMAR CONSONANT SIGN MEDIAL RA, MYANMAR VOWEL SIGN E, MYANMAR VOWEL SIGN AA, MYANMAR SIGN ASAT # →ဩော်→ +102A ; 101E 103C 0B47 102C 103A ; MA # ( ဪ → သြେာ် ) MYANMAR LETTER AU → MYANMAR LETTER SA, MYANMAR CONSONANT SIGN MEDIAL RA, ORIYA VOWEL SIGN E, MYANMAR VOWEL SIGN AA, MYANMAR SIGN ASAT # →ဩော်→ 109E ; 1083 030A ; MA #* ( ႞ → ႃ̊ ) MYANMAR SYMBOL SHAN ONE → MYANMAR VOWEL SIGN SHAN AA, COMBINING RING ABOVE # →ႃံ→ +178F ; 178A ; MA # ( ត → ដ ) KHMER LETTER TA → KHMER LETTER DA # + 17A3 ; 17A2 ; MA # ( ឣ → អ ) KHMER INDEPENDENT VOWEL QAQ → KHMER LETTER QA # 19D0 ; 199E ; MA # ( ᧐ → ᦞ ) NEW TAI LUE DIGIT ZERO → NEW TAI LUE LETTER LOW VA # @@ -6319,6 +6536,7 @@ A4ED ; 1660 ; MA # ( ꓭ → ᙠ ) LISU LETTER GHA → CANADIAN SYLLABICS CARRIE 02E2 ; 18F5 ; MA # ( ˢ → ᣵ ) MODIFIER LETTER SMALL S → CANADIAN SYLLABICS CARRIER DENTAL S # 18DB ; 18F5 ; MA # ( ᣛ → ᣵ ) CANADIAN SYLLABICS OJIBWAY SH → CANADIAN SYLLABICS CARRIER DENTAL S # →ˢ→ +A7F1 ; 18F5 ; MA # ( ꟱ → ᣵ ) MODIFIER LETTER CAPITAL S → CANADIAN SYLLABICS CARRIER DENTAL S # →ˢ→ A6B0 ; 16B9 ; MA # ( ꚰ → ᚹ ) BAMUM LETTER TAA → RUNIC LETTER WUNJO WYNN W # @@ -6426,6 +6644,9 @@ D7CC ; 1102 110E ; MA # ( ퟌ → ᄂᄎ ) HANGUL JONGSEONG NIEUN-CHIEUCH → HA 11C8 ; 1102 1140 ; MA # ( ᇈ → ᄂᅀ ) HANGUL JONGSEONG NIEUN-PANSIOS → HANGUL CHOSEONG NIEUN, HANGUL CHOSEONG PANSIOS # →ᆫᇫ→ 3168 ; 1102 1140 ; MA # ( ㅨ → ᄂᅀ ) HANGUL LETTER NIEUN-PANSIOS → HANGUL CHOSEONG NIEUN, HANGUL CHOSEONG PANSIOS # →ᇈ→→ᆫᇫ→ +723F ; 1102 116E 4E28 ; MA # ( 爿 → 누丨 ) CJK UNIFIED IDEOGRAPH-723F → HANGUL CHOSEONG NIEUN, HANGUL JUNGSEONG U, CJK UNIFIED IDEOGRAPH-4E28 # →뉘→→누ᅵ→ +2F59 ; 1102 116E 4E28 ; MA #* ( ⽙ → 누丨 ) KANGXI RADICAL HALF TREE TRUNK → HANGUL CHOSEONG NIEUN, HANGUL JUNGSEONG U, CJK UNIFIED IDEOGRAPH-4E28 # →爿→→뉘→→누ᅵ→ + 3137 ; 1103 ; MA # ( ㄷ → ᄃ ) HANGUL LETTER TIKEUT → HANGUL CHOSEONG TIKEUT # 11AE ; 1103 ; MA # ( ᆮ → ᄃ ) HANGUL JONGSEONG TIKEUT → HANGUL CHOSEONG TIKEUT # @@ -6594,6 +6815,8 @@ D7E2 ; 1106 110C ; MA # ( ퟢ → ᄆᄌ ) HANGUL JONGSEONG MIEUM-CIEUC → HANG 11DF ; 1106 1140 ; MA # ( ᇟ → ᄆᅀ ) HANGUL JONGSEONG MIEUM-PANSIOS → HANGUL CHOSEONG MIEUM, HANGUL CHOSEONG PANSIOS # →ᆷᇫ→ 3170 ; 1106 1140 ; MA # ( ㅰ → ᄆᅀ ) HANGUL LETTER MIEUM-PANSIOS → HANGUL CHOSEONG MIEUM, HANGUL CHOSEONG PANSIOS # →ᇟ→→ᆷᇫ→ +535F ; 1106 1161 ; MA # ( 卟 → 마 ) CJK UNIFIED IDEOGRAPH-535F → HANGUL CHOSEONG MIEUM, HANGUL JUNGSEONG A # + 3142 ; 1107 ; MA # ( ㅂ → ᄇ ) HANGUL LETTER PIEUP → HANGUL CHOSEONG PIEUP # 11B8 ; 1107 ; MA # ( ᆸ → ᄇ ) HANGUL JONGSEONG PIEUP → HANGUL CHOSEONG PIEUP # @@ -6663,6 +6886,10 @@ A974 ; 1107 1112 ; MA # ( ꥴ → ᄇᄒ ) HANGUL CHOSEONG PIEUP-HIEUH → HANGU 3145 ; 1109 ; MA # ( ㅅ → ᄉ ) HANGUL LETTER SIOS → HANGUL CHOSEONG SIOS # 11BA ; 1109 ; MA # ( ᆺ → ᄉ ) HANGUL JONGSEONG SIOS → HANGUL CHOSEONG SIOS # +4ECA ; 1109 30FC 1100 ; MA # ( 今 → ᄉーᄀ ) CJK UNIFIED IDEOGRAPH-4ECA → HANGUL CHOSEONG SIOS, KATAKANA-HIRAGANA PROLONGED SOUND MARK, HANGUL CHOSEONG KIYEOK # →슥→→스ᄀ→ + +5408 ; 1109 30FC 1106 ; MA # ( 合 → ᄉーᄆ ) CJK UNIFIED IDEOGRAPH-5408 → HANGUL CHOSEONG SIOS, KATAKANA-HIRAGANA PROLONGED SOUND MARK, HANGUL CHOSEONG MIEUM # →슴→→스ᄆ→ + 112D ; 1109 1100 ; MA # ( ᄭ → ᄉᄀ ) HANGUL CHOSEONG SIOS-KIYEOK → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG KIYEOK # 317A ; 1109 1100 ; MA # ( ㅺ → ᄉᄀ ) HANGUL LETTER SIOS-KIYEOK → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG KIYEOK # →ᄭ→ 11E7 ; 1109 1100 ; MA # ( ᇧ → ᄉᄀ ) HANGUL JONGSEONG SIOS-KIYEOK → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG KIYEOK # →ᄭ→ @@ -6692,6 +6919,8 @@ D7EB ; 1109 1107 110B ; MA # ( ퟫ → ᄉᄇᄋ ) HANGUL JONGSEONG SIOS-KAPYEOU 3146 ; 1109 1109 ; MA # ( ㅆ → ᄉᄉ ) HANGUL LETTER SSANGSIOS → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG SIOS # →ᄊ→ 11BB ; 1109 1109 ; MA # ( ᆻ → ᄉᄉ ) HANGUL JONGSEONG SSANGSIOS → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG SIOS # →ᄊ→ +4E1B ; 1109 1109 30FC ; MA # ( 丛 → ᄉᄉー ) CJK UNIFIED IDEOGRAPH-4E1B → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG SIOS, KATAKANA-HIRAGANA PROLONGED SOUND MARK # →쓰→→ᄉ스→ + D7EC ; 1109 1109 1100 ; MA # ( ퟬ → ᄉᄉᄀ ) HANGUL JONGSEONG SSANGSIOS-KIYEOK → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG SIOS, HANGUL CHOSEONG KIYEOK # →ᆺᆺᆨ→ D7ED ; 1109 1109 1103 ; MA # ( ퟭ → ᄉᄉᄃ ) HANGUL JONGSEONG SSANGSIOS-TIKEUT → HANGUL CHOSEONG SIOS, HANGUL CHOSEONG SIOS, HANGUL CHOSEONG TIKEUT # →ᆺᆺᆮ→ @@ -6773,6 +7002,8 @@ D7F9 ; 110C 110C ; MA # ( ퟹ → ᄌᄌ ) HANGUL JONGSEONG SSANGCIEUC → HANGU A978 ; 110C 110C 1112 ; MA # ( ꥸ → ᄌᄌᄒ ) HANGUL CHOSEONG SSANGCIEUC-HIEUH → HANGUL CHOSEONG CIEUC, HANGUL CHOSEONG CIEUC, HANGUL CHOSEONG HIEUH # +4E15 ; 110C 1169 ; MA # ( 丕 → 조 ) CJK UNIFIED IDEOGRAPH-4E15 → HANGUL CHOSEONG CIEUC, HANGUL JUNGSEONG O # + 314A ; 110E ; MA # ( ㅊ → ᄎ ) HANGUL LETTER CHIEUCH → HANGUL CHOSEONG CHIEUCH # 11BE ; 110E ; MA # ( ᆾ → ᄎ ) HANGUL JONGSEONG CHIEUCH → HANGUL CHOSEONG CHIEUCH # @@ -6786,6 +7017,10 @@ A978 ; 110C 110C 1112 ; MA # ( ꥸ → ᄌᄌᄒ ) HANGUL CHOSEONG SSANGCIEUC-HI 314C ; 1110 ; MA # ( ㅌ → ᄐ ) HANGUL LETTER THIEUTH → HANGUL CHOSEONG THIEUTH # 11C0 ; 1110 ; MA # ( ᇀ → ᄐ ) HANGUL JONGSEONG THIEUTH → HANGUL CHOSEONG THIEUTH # +9577 ; 1110 30FC 1102 110C ; MA # ( 長 → ᄐーᄂᄌ ) CJK UNIFIED IDEOGRAPH-9577 → HANGUL CHOSEONG THIEUTH, KATAKANA-HIRAGANA PROLONGED SOUND MARK, HANGUL CHOSEONG NIEUN, HANGUL CHOSEONG CIEUC # →튽→→트ᄂᄌ→ +2ED1 ; 1110 30FC 1102 110C ; MA #* ( ⻑ → ᄐーᄂᄌ ) CJK RADICAL LONG ONE → HANGUL CHOSEONG THIEUTH, KATAKANA-HIRAGANA PROLONGED SOUND MARK, HANGUL CHOSEONG NIEUN, HANGUL CHOSEONG CIEUC # →長→→튽→→트ᄂᄌ→ +2FA7 ; 1110 30FC 1102 110C ; MA #* ( ⾧ → ᄐーᄂᄌ ) KANGXI RADICAL LONG → HANGUL CHOSEONG THIEUTH, KATAKANA-HIRAGANA PROLONGED SOUND MARK, HANGUL CHOSEONG NIEUN, HANGUL CHOSEONG CIEUC # →長→→튽→→트ᄂᄌ→ + A979 ; 1110 1110 ; MA # ( ꥹ → ᄐᄐ ) HANGUL CHOSEONG SSANGTHIEUTH → HANGUL CHOSEONG THIEUTH, HANGUL CHOSEONG THIEUTH # 314D ; 1111 ; MA # ( ㅍ → ᄑ ) HANGUL LETTER PHIEUPH → HANGUL CHOSEONG PHIEUPH # @@ -7010,6 +7245,8 @@ D7C6 ; 119E 1165 4E28 ; MA # ( ퟆ → ᆞᅥ丨 ) HANGUL JUNGSEONG ARAEA-E → 2341 ; 303C ; MA #* ( ⍁ → 〼 ) APL FUNCTIONAL SYMBOL QUAD SLASH → MASU MARK # →⧄→ 29C4 ; 303C ; MA #* ( ⧄ → 〼 ) SQUARED RISING DIAGONAL SLASH → MASU MARK # +4E8E ; 1B122 ; MA # ( 于 → 𛄢 ) CJK UNIFIED IDEOGRAPH-4E8E → KATAKANA LETTER ARCHAIC WU # + A49E ; A04A ; MA #* ( ꒞ → ꁊ ) YI RADICAL PUT → YI SYLLABLE PUT # A4AC ; A050 ; MA #* ( ꒬ → ꁐ ) YI RADICAL PYT → YI SYLLABLE PYT # @@ -7233,6 +7470,7 @@ FA31 ; 50E7 ; MA # ( 僧 → 僧 ) CJK COMPATIBILITY IDEOGRAPH-FA31 → CJK UNIF 3126 ; 513F ; MA # ( ㄦ → 儿 ) BOPOMOFO LETTER ER → CJK UNIFIED IDEOGRAPH-513F # 2F09 ; 513F ; MA #* ( ⼉ → 儿 ) KANGXI RADICAL LEGS → CJK UNIFIED IDEOGRAPH-513F # +16FF2 ; 513F ; MA # ( 𖿲 → 儿 ) CHINESE SMALL SIMPLIFIED ER → CJK UNIFIED IDEOGRAPH-513F # FA0C ; 5140 ; MA # ( 兀 → 兀 ) CJK COMPATIBILITY IDEOGRAPH-FA0C → CJK UNIFIED IDEOGRAPH-5140 # 2E8E ; 5140 ; MA #* ( ⺎ → 兀 ) CJK RADICAL LAME ONE → CJK UNIFIED IDEOGRAPH-5140 # @@ -7312,6 +7550,7 @@ FA15 ; 51DE ; MA # ( 凞 → 凞 ) CJK COMPATIBILITY IDEOGRAPH-FA15 → CJK UNIF 2F81D ; 51F5 ; MA # ( 凵 → 凵 ) CJK COMPATIBILITY IDEOGRAPH-2F81D → CJK UNIFIED IDEOGRAPH-51F5 # 2F10 ; 51F5 ; MA #* ( ⼐ → 凵 ) KANGXI RADICAL OPEN BOX → CJK UNIFIED IDEOGRAPH-51F5 # +20674 ; 51F5 ; MA # ( 𠙴 → 凵 ) CJK UNIFIED IDEOGRAPH-20674 → CJK UNIFIED IDEOGRAPH-51F5 # →凵→ 2F11 ; 5200 ; MA #* ( ⼑ → 刀 ) KANGXI RADICAL KNIFE → CJK UNIFIED IDEOGRAPH-5200 # @@ -7402,6 +7641,7 @@ F9EB ; 533F ; MA # ( 匿 → 匿 ) CJK COMPATIBILITY IDEOGRAPH-F9EB → CJK UNIF FA35 ; 5351 ; MA # ( 卑 → 卑 ) CJK COMPATIBILITY IDEOGRAPH-FA35 → CJK UNIFIED IDEOGRAPH-5351 # 2F82D ; 5351 ; MA # ( 卑 → 卑 ) CJK COMPATIBILITY IDEOGRAPH-2F82D → CJK UNIFIED IDEOGRAPH-5351 # +2D161 ; 5351 ; MA # ( 𭅡 → 卑 ) CJK UNIFIED IDEOGRAPH-2D161 → CJK UNIFIED IDEOGRAPH-5351 # →卑→ 2F82E ; 535A ; MA # ( 博 → 博 ) CJK COMPATIBILITY IDEOGRAPH-2F82E → CJK UNIFIED IDEOGRAPH-535A # @@ -7562,6 +7802,7 @@ F942 ; 58DF ; MA # ( 壟 → 壟 ) CJK COMPATIBILITY IDEOGRAPH-F942 → CJK UNIF 2F85A ; 58F2 ; MA # ( 売 → 売 ) CJK COMPATIBILITY IDEOGRAPH-2F85A → CJK UNIFIED IDEOGRAPH-58F2 # 2F85B ; 58F7 ; MA # ( 壷 → 壷 ) CJK COMPATIBILITY IDEOGRAPH-2F85B → CJK UNIFIED IDEOGRAPH-58F7 # +21533 ; 58F7 ; MA # ( 𡔳 → 壷 ) CJK UNIFIED IDEOGRAPH-21533 → CJK UNIFIED IDEOGRAPH-58F7 # →壷→ 2F21 ; 5902 ; MA #* ( ⼡ → 夂 ) KANGXI RADICAL GO → CJK UNIFIED IDEOGRAPH-5902 # @@ -7573,6 +7814,7 @@ F942 ; 58DF ; MA # ( 壟 → 壟 ) CJK COMPATIBILITY IDEOGRAPH-F942 → CJK UNIF 2F23 ; 5915 ; MA #* ( ⼣ → 夕 ) KANGXI RADICAL EVENING → CJK UNIFIED IDEOGRAPH-5915 # 2F85D ; 591A ; MA # ( 多 → 多 ) CJK COMPATIBILITY IDEOGRAPH-2F85D → CJK UNIFIED IDEOGRAPH-591A # +21587 ; 591A ; MA # ( 𡖇 → 多 ) CJK UNIFIED IDEOGRAPH-21587 → CJK UNIFIED IDEOGRAPH-591A # →多→ 2F85E ; 5922 ; MA # ( 夢 → 夢 ) CJK COMPATIBILITY IDEOGRAPH-2F85E → CJK UNIFIED IDEOGRAPH-5922 # @@ -7591,6 +7833,7 @@ F909 ; 5951 ; MA # ( 契 → 契 ) CJK COMPATIBILITY IDEOGRAPH-F909 → CJK UNIF F981 ; 5973 ; MA # ( 女 → 女 ) CJK COMPATIBILITY IDEOGRAPH-F981 → CJK UNIFIED IDEOGRAPH-5973 # 2F25 ; 5973 ; MA #* ( ⼥ → 女 ) KANGXI RADICAL WOMAN → CJK UNIFIED IDEOGRAPH-5973 # +216A7 ; 216A8 ; MA # ( 𡚧 → 𡚨 ) CJK UNIFIED IDEOGRAPH-216A7 → CJK UNIFIED IDEOGRAPH-216A8 # →𡚨→ 2F860 ; 216A8 ; MA # ( 𡚨 → 𡚨 ) CJK COMPATIBILITY IDEOGRAPH-2F860 → CJK UNIFIED IDEOGRAPH-216A8 # 2F861 ; 216EA ; MA # ( 𡛪 → 𡛪 ) CJK COMPATIBILITY IDEOGRAPH-2F861 → CJK UNIFIED IDEOGRAPH-216EA # @@ -7681,6 +7924,7 @@ FA3C ; 5C6E ; MA # ( 屮 → 屮 ) CJK COMPATIBILITY IDEOGRAPH-FA3C → CJK UNIF 2F2D ; 5C71 ; MA #* ( ⼭ → 山 ) KANGXI RADICAL MOUNTAIN → CJK UNIFIED IDEOGRAPH-5C71 # 2F879 ; 5CC0 ; MA # ( 峀 → 峀 ) CJK COMPATIBILITY IDEOGRAPH-2F879 → CJK UNIFIED IDEOGRAPH-5CC0 # +2B73A ; 5CC0 ; MA # ( 𫜺 → 峀 ) CJK UNIFIED IDEOGRAPH-2B73A → CJK UNIFIED IDEOGRAPH-5CC0 # 2F87A ; 5C8D ; MA # ( 岍 → 岍 ) CJK COMPATIBILITY IDEOGRAPH-2F87A → CJK UNIFIED IDEOGRAPH-5C8D # @@ -7806,6 +8050,7 @@ F9D8 ; 5F8B ; MA # ( 律 → 律 ) CJK COMPATIBILITY IDEOGRAPH-F9D8 → CJK UNIF 2F89B ; 38E3 ; MA # ( 㣣 → 㣣 ) CJK COMPATIBILITY IDEOGRAPH-2F89B → CJK UNIFIED IDEOGRAPH-38E3 # +22505 ; 5F9A ; MA # ( 𢔅 → 徚 ) CJK UNIFIED IDEOGRAPH-22505 → CJK UNIFIED IDEOGRAPH-5F9A # →徚→ 2F89C ; 5F9A ; MA # ( 徚 → 徚 ) CJK COMPATIBILITY IDEOGRAPH-2F89C → CJK UNIFIED IDEOGRAPH-5F9A # F966 ; 5FA9 ; MA # ( 復 → 復 ) CJK COMPATIBILITY IDEOGRAPH-F966 → CJK UNIFIED IDEOGRAPH-5FA9 # @@ -8055,6 +8300,7 @@ F901 ; 66F4 ; MA # ( 更 → 更 ) CJK COMPATIBILITY IDEOGRAPH-F901 → CJK UNIF 2F49 ; 6708 ; MA #* ( ⽉ → 月 ) KANGXI RADICAL MOON → CJK UNIFIED IDEOGRAPH-6708 # 2F980 ; 2335F ; MA # ( 𣍟 → 𣍟 ) CJK COMPATIBILITY IDEOGRAPH-2F980 → CJK UNIFIED IDEOGRAPH-2335F # +2B73E ; 2335F ; MA # ( 𫜾 → 𣍟 ) CJK UNIFIED IDEOGRAPH-2B73E → CJK UNIFIED IDEOGRAPH-2335F # 80A6 ; 670C ; MA # ( 肦 → 朌 ) CJK UNIFIED IDEOGRAPH-80A6 → CJK UNIFIED IDEOGRAPH-670C # @@ -8262,6 +8508,7 @@ FA45 ; 6D77 ; MA # ( 海 → 海 ) CJK COMPATIBILITY IDEOGRAPH-FA45 → CJK UNIF 2F904 ; 6D78 ; MA # ( 浸 → 浸 ) CJK COMPATIBILITY IDEOGRAPH-2F904 → CJK UNIFIED IDEOGRAPH-6D78 # 2F905 ; 6D85 ; MA # ( 涅 → 涅 ) CJK COMPATIBILITY IDEOGRAPH-2F905 → CJK UNIFIED IDEOGRAPH-6D85 # +23D40 ; 6D85 ; MA # ( 𣵀 → 涅 ) CJK UNIFIED IDEOGRAPH-23D40 → CJK UNIFIED IDEOGRAPH-6D85 # →涅→ 2F906 ; 23D1E ; MA # ( 𣴞 → 𣴞 ) CJK COMPATIBILITY IDEOGRAPH-2F906 → CJK UNIFIED IDEOGRAPH-23D1E # @@ -8385,8 +8632,6 @@ FA9E ; 7235 ; MA # ( 爵 → 爵 ) CJK COMPATIBILITY IDEOGRAPH-FA9E → CJK UNIF 2EA6 ; 4E2C ; MA #* ( ⺦ → 丬 ) CJK RADICAL SIMPLIFIED HALF TREE TRUNK → CJK UNIFIED IDEOGRAPH-4E2C # -2F59 ; 723F ; MA #* ( ⽙ → 爿 ) KANGXI RADICAL HALF TREE TRUNK → CJK UNIFIED IDEOGRAPH-723F # - 2F5A ; 7247 ; MA #* ( ⽚ → 片 ) KANGXI RADICAL SLICE → CJK UNIFIED IDEOGRAPH-7247 # 2F922 ; 7250 ; MA # ( 牐 → 牐 ) CJK COMPATIBILITY IDEOGRAPH-2F922 → CJK UNIFIED IDEOGRAPH-7250 # @@ -8418,6 +8663,7 @@ F92B ; 72FC ; MA # ( 狼 → 狼 ) CJK COMPATIBILITY IDEOGRAPH-F92B → CJK UNIF FA16 ; 732A ; MA # ( 猪 → 猪 ) CJK COMPATIBILITY IDEOGRAPH-FA16 → CJK UNIFIED IDEOGRAPH-732A # FAA0 ; 732A ; MA # ( 猪 → 猪 ) CJK COMPATIBILITY IDEOGRAPH-FAA0 → CJK UNIFIED IDEOGRAPH-732A # +2AEC5 ; 24814 ; MA # ( 𪻅 → 𤠔 ) CJK UNIFIED IDEOGRAPH-2AEC5 → CJK UNIFIED IDEOGRAPH-24814 # →𤠔→ 2F927 ; 24814 ; MA # ( 𤠔 → 𤠔 ) CJK COMPATIBILITY IDEOGRAPH-2F927 → CJK UNIFIED IDEOGRAPH-24814 # F9A7 ; 7375 ; MA # ( 獵 → 獵 ) CJK COMPATIBILITY IDEOGRAPH-F9A7 → CJK UNIFIED IDEOGRAPH-7375 # @@ -8436,6 +8682,7 @@ F9DB ; 7387 ; MA # ( 率 → 率 ) CJK COMPATIBILITY IDEOGRAPH-F9DB → CJK UNIF 2F92A ; 3EAC ; MA # ( 㺬 → 㺬 ) CJK COMPATIBILITY IDEOGRAPH-2F92A → CJK UNIFIED IDEOGRAPH-3EAC # 2F92B ; 73A5 ; MA # ( 玥 → 玥 ) CJK COMPATIBILITY IDEOGRAPH-2F92B → CJK UNIFIED IDEOGRAPH-73A5 # +248FD ; 73A5 ; MA # ( 𤣽 → 玥 ) CJK UNIFIED IDEOGRAPH-248FD → CJK UNIFIED IDEOGRAPH-73A5 # →玥→ F9AD ; 73B2 ; MA # ( 玲 → 玲 ) CJK COMPATIBILITY IDEOGRAPH-F9AD → CJK UNIFIED IDEOGRAPH-73B2 # @@ -8555,6 +8802,7 @@ F96D ; 7701 ; MA # ( 省 → 省 ) CJK COMPATIBILITY IDEOGRAPH-F96D → CJK UNIF FAD3 ; 4018 ; MA # ( 䀘 → 䀘 ) CJK COMPATIBILITY IDEOGRAPH-FAD3 → CJK UNIFIED IDEOGRAPH-4018 # 2F943 ; 25119 ; MA # ( 𥄙 → 𥄙 ) CJK COMPATIBILITY IDEOGRAPH-2F943 → CJK UNIFIED IDEOGRAPH-25119 # +2511A ; 25119 ; MA # ( 𥄚 → 𥄙 ) CJK UNIFIED IDEOGRAPH-2511A → CJK UNIFIED IDEOGRAPH-25119 # →𥄙→ 2F945 ; 771E ; MA # ( 眞 → 眞 ) CJK COMPATIBILITY IDEOGRAPH-2F945 → CJK UNIFIED IDEOGRAPH-771E # @@ -8764,6 +9012,7 @@ F93D ; 7DA0 ; MA # ( 綠 → 綠 ) CJK COMPATIBILITY IDEOGRAPH-F93D → CJK UNIF F957 ; 7DBE ; MA # ( 綾 → 綾 ) CJK COMPATIBILITY IDEOGRAPH-F957 → CJK UNIFIED IDEOGRAPH-7DBE # 2F96E ; 7DC7 ; MA # ( 緇 → 緇 ) CJK COMPATIBILITY IDEOGRAPH-2F96E → CJK UNIFIED IDEOGRAPH-7DC7 # +31E7C ; 7DC7 ; MA # ( 𱹼 → 緇 ) CJK UNIFIED IDEOGRAPH-31E7C → CJK UNIFIED IDEOGRAPH-7DC7 # →緇→ F996 ; 7DF4 ; MA # ( 練 → 練 ) CJK COMPATIBILITY IDEOGRAPH-F996 → CJK UNIFIED IDEOGRAPH-7DF4 # FA57 ; 7DF4 ; MA # ( 練 → 練 ) CJK COMPATIBILITY IDEOGRAPH-FA57 → CJK UNIFIED IDEOGRAPH-7DF4 # @@ -8848,6 +9097,7 @@ F9B0 ; 8046 ; MA # ( 聆 → 聆 ) CJK COMPATIBILITY IDEOGRAPH-F9B0 → CJK UNIF 2F97D ; 8060 ; MA # ( 聠 → 聠 ) CJK COMPATIBILITY IDEOGRAPH-2F97D → CJK UNIFIED IDEOGRAPH-8060 # +2659D ; 265A8 ; MA # ( 𦖝 → 𦖨 ) CJK UNIFIED IDEOGRAPH-2659D → CJK UNIFIED IDEOGRAPH-265A8 # →𦖨→ 2F97E ; 265A8 ; MA # ( 𦖨 → 𦖨 ) CJK COMPATIBILITY IDEOGRAPH-2F97E → CJK UNIFIED IDEOGRAPH-265A8 # F997 ; 806F ; MA # ( 聯 → 聯 ) CJK COMPATIBILITY IDEOGRAPH-F997 → CJK UNIFIED IDEOGRAPH-806F # @@ -8982,6 +9232,7 @@ FA5F ; 8457 ; MA # ( 著 → 著 ) CJK COMPATIBILITY IDEOGRAPH-FA5F → CJK UNIF 2F99F ; 8457 ; MA # ( 著 → 著 ) CJK COMPATIBILITY IDEOGRAPH-2F99F → CJK UNIFIED IDEOGRAPH-8457 # 2F9A4 ; 26C36 ; MA # ( 𦰶 → 𦰶 ) CJK COMPATIBILITY IDEOGRAPH-2F9A4 → CJK UNIFIED IDEOGRAPH-26C36 # +26D06 ; 26C36 ; MA # ( 𦴆 → 𦰶 ) CJK UNIFIED IDEOGRAPH-26D06 → CJK UNIFIED IDEOGRAPH-26C36 # →𦰶→ 2F99B ; 83AD ; MA # ( 莭 → 莭 ) CJK COMPATIBILITY IDEOGRAPH-2F99B → CJK UNIFIED IDEOGRAPH-83AD # @@ -9128,14 +9379,17 @@ FAB7 ; 8986 ; MA # ( 覆 → 覆 ) CJK COMPATIBILITY IDEOGRAPH-FAB7 → CJK UNIF FA0A ; 898B ; MA # ( 見 → 見 ) CJK COMPATIBILITY IDEOGRAPH-FA0A → CJK UNIFIED IDEOGRAPH-898B # 2F92 ; 898B ; MA #* ( ⾒ → 見 ) KANGXI RADICAL SEE → CJK UNIFIED IDEOGRAPH-898B # -2F9CB ; 278AE ; MA # ( 𧢮 → 𧢮 ) CJK COMPATIBILITY IDEOGRAPH-2F9CB → CJK UNIFIED IDEOGRAPH-278AE # - 2EC5 ; 89C1 ; MA #* ( ⻅ → 见 ) CJK RADICAL C-SIMPLIFIED SEE → CJK UNIFIED IDEOGRAPH-89C1 # +4695 ; 278AE ; MA # ( 䚕 → 𧢮 ) CJK UNIFIED IDEOGRAPH-4695 → CJK UNIFIED IDEOGRAPH-278AE # →𧢮→ +2F9CB ; 278AE ; MA # ( 𧢮 → 𧢮 ) CJK COMPATIBILITY IDEOGRAPH-2F9CB → CJK UNIFIED IDEOGRAPH-278AE # + 2F93 ; 89D2 ; MA #* ( ⾓ → 角 ) KANGXI RADICAL HORN → CJK UNIFIED IDEOGRAPH-89D2 # 2F94 ; 8A00 ; MA #* ( ⾔ → 言 ) KANGXI RADICAL SPEECH → CJK UNIFIED IDEOGRAPH-8A00 # +2EC8 ; 8BA0 ; MA #* ( ⻈ → 讠 ) CJK RADICAL C-SIMPLIFIED SPEECH → CJK UNIFIED IDEOGRAPH-8BA0 # + 2F9CC ; 27966 ; MA # ( 𧥦 → 𧥦 ) CJK COMPATIBILITY IDEOGRAPH-2F9CC → CJK UNIFIED IDEOGRAPH-27966 # 8A7D ; 8A2E ; MA # ( 詽 → 訮 ) CJK UNIFIED IDEOGRAPH-8A7D → CJK UNIFIED IDEOGRAPH-8A2E # @@ -9183,8 +9437,6 @@ F95A ; 8B80 ; MA # ( 讀 → 讀 ) CJK COMPATIBILITY IDEOGRAPH-F95A → CJK UNIF FAC0 ; 8B8A ; MA # ( 變 → 變 ) CJK COMPATIBILITY IDEOGRAPH-FAC0 → CJK UNIFIED IDEOGRAPH-8B8A # 2F9D1 ; 8B8A ; MA # ( 變 → 變 ) CJK COMPATIBILITY IDEOGRAPH-2F9D1 → CJK UNIFIED IDEOGRAPH-8B8A # -2EC8 ; 8BA0 ; MA #* ( ⻈ → 讠 ) CJK RADICAL C-SIMPLIFIED SPEECH → CJK UNIFIED IDEOGRAPH-8BA0 # - 2F95 ; 8C37 ; MA #* ( ⾕ → 谷 ) KANGXI RADICAL VALLEY → CJK UNIFIED IDEOGRAPH-8C37 # 2F96 ; 8C46 ; MA #* ( ⾖ → 豆 ) KANGXI RADICAL BEAN → CJK UNIFIED IDEOGRAPH-8C46 # @@ -9202,6 +9454,8 @@ F900 ; 8C48 ; MA # ( 豈 → 豈 ) CJK COMPATIBILITY IDEOGRAPH-F900 → CJK UNIF 2F99 ; 8C9D ; MA #* ( ⾙ → 貝 ) KANGXI RADICAL SHELL → CJK UNIFIED IDEOGRAPH-8C9D # +2EC9 ; 8D1D ; MA #* ( ⻉ → 贝 ) CJK RADICAL C-SIMPLIFIED SHELL → CJK UNIFIED IDEOGRAPH-8D1D # + 2F9D4 ; 8CAB ; MA # ( 貫 → 貫 ) CJK COMPATIBILITY IDEOGRAPH-2F9D4 → CJK UNIFIED IDEOGRAPH-8CAB # 2F9D5 ; 8CC1 ; MA # ( 賁 → 賁 ) CJK COMPATIBILITY IDEOGRAPH-2F9D5 → CJK UNIFIED IDEOGRAPH-8CC1 # @@ -9215,10 +9469,9 @@ FA64 ; 8CD3 ; MA # ( 賓 → 賓 ) CJK COMPATIBILITY IDEOGRAPH-FA64 → CJK UNIF FA65 ; 8D08 ; MA # ( 贈 → 贈 ) CJK COMPATIBILITY IDEOGRAPH-FA65 → CJK UNIFIED IDEOGRAPH-8D08 # FAC1 ; 8D08 ; MA # ( 贈 → 贈 ) CJK COMPATIBILITY IDEOGRAPH-FAC1 → CJK UNIFIED IDEOGRAPH-8D08 # +25AD4 ; 8D1B ; MA # ( 𥫔 → 贛 ) CJK UNIFIED IDEOGRAPH-25AD4 → CJK UNIFIED IDEOGRAPH-8D1B # →贛→ 2F9D6 ; 8D1B ; MA # ( 贛 → 贛 ) CJK COMPATIBILITY IDEOGRAPH-2F9D6 → CJK UNIFIED IDEOGRAPH-8D1B # -2EC9 ; 8D1D ; MA #* ( ⻉ → 贝 ) CJK RADICAL C-SIMPLIFIED SHELL → CJK UNIFIED IDEOGRAPH-8D1D # - 2F9A ; 8D64 ; MA #* ( ⾚ → 赤 ) KANGXI RADICAL RED → CJK UNIFIED IDEOGRAPH-8D64 # 2F9B ; 8D70 ; MA #* ( ⾛ → 走 ) KANGXI RADICAL RUN → CJK UNIFIED IDEOGRAPH-8D70 # @@ -9250,6 +9503,8 @@ F937 ; 8DEF ; MA # ( 路 → 路 ) CJK COMPATIBILITY IDEOGRAPH-F937 → CJK UNIF F902 ; 8ECA ; MA # ( 車 → 車 ) CJK COMPATIBILITY IDEOGRAPH-F902 → CJK UNIFIED IDEOGRAPH-8ECA # 2F9E ; 8ECA ; MA #* ( ⾞ → 車 ) KANGXI RADICAL CART → CJK UNIFIED IDEOGRAPH-8ECA # +2ECB ; 8F66 ; MA #* ( ⻋ → 车 ) CJK RADICAL C-SIMPLIFIED CART → CJK UNIFIED IDEOGRAPH-8F66 # + 2F9DE ; 8ED4 ; MA # ( 軔 → 軔 ) CJK COMPATIBILITY IDEOGRAPH-2F9DE → CJK UNIFIED IDEOGRAPH-8ED4 # 8F27 ; 8EFF ; MA # ( 輧 → 軿 ) CJK UNIFIED IDEOGRAPH-8F27 → CJK UNIFIED IDEOGRAPH-8EFF # @@ -9265,8 +9520,6 @@ FA07 ; 8F3B ; MA # ( 輻 → 輻 ) CJK COMPATIBILITY IDEOGRAPH-FA07 → CJK UNIF F98D ; 8F62 ; MA # ( 轢 → 轢 ) CJK COMPATIBILITY IDEOGRAPH-F98D → CJK UNIFIED IDEOGRAPH-8F62 # -2ECB ; 8F66 ; MA #* ( ⻋ → 车 ) CJK RADICAL C-SIMPLIFIED CART → CJK UNIFIED IDEOGRAPH-8F66 # - 2F9F ; 8F9B ; MA #* ( ⾟ → 辛 ) KANGXI RADICAL BITTER → CJK UNIFIED IDEOGRAPH-8F9B # 2F98D ; 8F9E ; MA # ( 辞 → 辞 ) CJK COMPATIBILITY IDEOGRAPH-2F98D → CJK UNIFIED IDEOGRAPH-8F9E # @@ -9333,6 +9586,8 @@ F97E ; 91CF ; MA # ( 量 → 量 ) CJK COMPATIBILITY IDEOGRAPH-F97E → CJK UNIF F90A ; 91D1 ; MA # ( 金 → 金 ) CJK COMPATIBILITY IDEOGRAPH-F90A → CJK UNIFIED IDEOGRAPH-91D1 # 2FA6 ; 91D1 ; MA #* ( ⾦ → 金 ) KANGXI RADICAL GOLD → CJK UNIFIED IDEOGRAPH-91D1 # +2ED0 ; 9485 ; MA #* ( ⻐ → 钅 ) CJK RADICAL C-SIMPLIFIED GOLD → CJK UNIFIED IDEOGRAPH-9485 # + F9B1 ; 9234 ; MA # ( 鈴 → 鈴 ) CJK COMPATIBILITY IDEOGRAPH-F9B1 → CJK UNIFIED IDEOGRAPH-9234 # 2F9E7 ; 9238 ; MA # ( 鈸 → 鈸 ) CJK COMPATIBILITY IDEOGRAPH-2F9E7 → CJK UNIFIED IDEOGRAPH-9238 # @@ -9357,17 +9612,14 @@ F99B ; 934A ; MA # ( 鍊 → 鍊 ) CJK COMPATIBILITY IDEOGRAPH-F99B → CJK UNIF 2F9ED ; 28BFA ; MA # ( 𨯺 → 𨯺 ) CJK COMPATIBILITY IDEOGRAPH-2F9ED → CJK UNIFIED IDEOGRAPH-28BFA # -2ED0 ; 9485 ; MA #* ( ⻐ → 钅 ) CJK RADICAL C-SIMPLIFIED GOLD → CJK UNIFIED IDEOGRAPH-9485 # - -2ED1 ; 9577 ; MA #* ( ⻑ → 長 ) CJK RADICAL LONG ONE → CJK UNIFIED IDEOGRAPH-9577 # -2FA7 ; 9577 ; MA #* ( ⾧ → 長 ) KANGXI RADICAL LONG → CJK UNIFIED IDEOGRAPH-9577 # - 2ED2 ; 9578 ; MA #* ( ⻒ → 镸 ) CJK RADICAL LONG TWO → CJK UNIFIED IDEOGRAPH-9578 # 2ED3 ; 957F ; MA #* ( ⻓ → 长 ) CJK RADICAL C-SIMPLIFIED LONG → CJK UNIFIED IDEOGRAPH-957F # 2FA8 ; 9580 ; MA #* ( ⾨ → 門 ) KANGXI RADICAL GATE → CJK UNIFIED IDEOGRAPH-9580 # +2ED4 ; 95E8 ; MA #* ( ⻔ → 门 ) CJK RADICAL C-SIMPLIFIED GATE → CJK UNIFIED IDEOGRAPH-95E8 # + 2F9EE ; 958B ; MA # ( 開 → 開 ) CJK COMPATIBILITY IDEOGRAPH-2F9EE → CJK UNIFIED IDEOGRAPH-958B # 2F9EF ; 4995 ; MA # ( 䦕 → 䦕 ) CJK COMPATIBILITY IDEOGRAPH-2F9EF → CJK UNIFIED IDEOGRAPH-4995 # @@ -9378,8 +9630,6 @@ F986 ; 95AD ; MA # ( 閭 → 閭 ) CJK COMPATIBILITY IDEOGRAPH-F986 → CJK UNIF 2F9F1 ; 28D77 ; MA # ( 𨵷 → 𨵷 ) CJK COMPATIBILITY IDEOGRAPH-2F9F1 → CJK UNIFIED IDEOGRAPH-28D77 # -2ED4 ; 95E8 ; MA #* ( ⻔ → 门 ) CJK RADICAL C-SIMPLIFIED GATE → CJK UNIFIED IDEOGRAPH-95E8 # - 2FA9 ; 961C ; MA #* ( ⾩ → 阜 ) KANGXI RADICAL MOUND → CJK UNIFIED IDEOGRAPH-961C # 2ECF ; 961D ; MA #* ( ⻏ → 阝 ) CJK RADICAL CITY → CJK UNIFIED IDEOGRAPH-961D # @@ -9455,12 +9705,12 @@ FAC8 ; 9756 ; MA # ( 靖 → 靖 ) CJK COMPATIBILITY IDEOGRAPH-FAC8 → CJK UNIF 2FB1 ; 97CB ; MA #* ( ⾱ → 韋 ) KANGXI RADICAL TANNED LEATHER → CJK UNIFIED IDEOGRAPH-97CB # +2ED9 ; 97E6 ; MA #* ( ⻙ → 韦 ) CJK RADICAL C-SIMPLIFIED TANNED LEATHER → CJK UNIFIED IDEOGRAPH-97E6 # + FAC9 ; 97DB ; MA # ( 韛 → 韛 ) CJK COMPATIBILITY IDEOGRAPH-FAC9 → CJK UNIFIED IDEOGRAPH-97DB # 2F9FA ; 97E0 ; MA # ( 韠 → 韠 ) CJK COMPATIBILITY IDEOGRAPH-2F9FA → CJK UNIFIED IDEOGRAPH-97E0 # -2ED9 ; 97E6 ; MA #* ( ⻙ → 韦 ) CJK RADICAL C-SIMPLIFIED TANNED LEATHER → CJK UNIFIED IDEOGRAPH-97E6 # - 2FB2 ; 97ED ; MA #* ( ⾲ → 韭 ) KANGXI RADICAL LEEK → CJK UNIFIED IDEOGRAPH-97ED # 2F9FB ; 2940A ; MA # ( 𩐊 → 𩐊 ) CJK COMPATIBILITY IDEOGRAPH-2F9FB → CJK UNIFIED IDEOGRAPH-2940A # @@ -9472,6 +9722,8 @@ FACA ; 97FF ; MA # ( 響 → 響 ) CJK COMPATIBILITY IDEOGRAPH-FACA → CJK UNIF 2FB4 ; 9801 ; MA #* ( ⾴ → 頁 ) KANGXI RADICAL LEAF → CJK UNIFIED IDEOGRAPH-9801 # +2EDA ; 9875 ; MA #* ( ⻚ → 页 ) CJK RADICAL C-SIMPLIFIED LEAF → CJK UNIFIED IDEOGRAPH-9875 # + 2F9FC ; 4AB2 ; MA # ( 䪲 → 䪲 ) CJK COMPATIBILITY IDEOGRAPH-2F9FC → CJK UNIFIED IDEOGRAPH-4AB2 # FACB ; 980B ; MA # ( 頋 → 頋 ) CJK COMPATIBILITY IDEOGRAPH-FACB → CJK UNIFIED IDEOGRAPH-980B # @@ -9489,14 +9741,12 @@ FACC ; 983B ; MA # ( 頻 → 頻 ) CJK COMPATIBILITY IDEOGRAPH-FACC → CJK UNIF F9D0 ; 985E ; MA # ( 類 → 類 ) CJK COMPATIBILITY IDEOGRAPH-F9D0 → CJK UNIFIED IDEOGRAPH-985E # -2EDA ; 9875 ; MA #* ( ⻚ → 页 ) CJK RADICAL C-SIMPLIFIED LEAF → CJK UNIFIED IDEOGRAPH-9875 # - 2FB5 ; 98A8 ; MA #* ( ⾵ → 風 ) KANGXI RADICAL WIND → CJK UNIFIED IDEOGRAPH-98A8 # -2FA01 ; 295B6 ; MA # ( 𩖶 → 𩖶 ) CJK COMPATIBILITY IDEOGRAPH-2FA01 → CJK UNIFIED IDEOGRAPH-295B6 # - 2EDB ; 98CE ; MA #* ( ⻛ → 风 ) CJK RADICAL C-SIMPLIFIED WIND → CJK UNIFIED IDEOGRAPH-98CE # +2FA01 ; 295B6 ; MA # ( 𩖶 → 𩖶 ) CJK COMPATIBILITY IDEOGRAPH-2FA01 → CJK UNIFIED IDEOGRAPH-295B6 # + 2FB6 ; 98DB ; MA #* ( ⾶ → 飛 ) KANGXI RADICAL FLY → CJK UNIFIED IDEOGRAPH-98DB # 2EDC ; 98DE ; MA #* ( ⻜ → 飞 ) CJK RADICAL C-SIMPLIFIED FLY → CJK UNIFIED IDEOGRAPH-98DE # @@ -9506,6 +9756,8 @@ F9D0 ; 985E ; MA # ( 類 → 類 ) CJK COMPATIBILITY IDEOGRAPH-F9D0 → CJK UNIF 2EDF ; 98E0 ; MA #* ( ⻟ → 飠 ) CJK RADICAL EAT THREE → CJK UNIFIED IDEOGRAPH-98E0 # +2EE0 ; 9963 ; MA #* ( ⻠ → 饣 ) CJK RADICAL C-SIMPLIFIED EAT → CJK UNIFIED IDEOGRAPH-9963 # + 2FA02 ; 98E2 ; MA # ( 飢 → 飢 ) CJK COMPATIBILITY IDEOGRAPH-2FA02 → CJK UNIFIED IDEOGRAPH-98E2 # FA2A ; 98EF ; MA # ( 飯 → 飯 ) CJK COMPATIBILITY IDEOGRAPH-FA2A → CJK UNIFIED IDEOGRAPH-98EF # @@ -9518,8 +9770,6 @@ FA2C ; 9928 ; MA # ( 館 → 館 ) CJK COMPATIBILITY IDEOGRAPH-FA2C → CJK UNIF 2FA04 ; 9929 ; MA # ( 餩 → 餩 ) CJK COMPATIBILITY IDEOGRAPH-2FA04 → CJK UNIFIED IDEOGRAPH-9929 # -2EE0 ; 9963 ; MA #* ( ⻠ → 饣 ) CJK RADICAL C-SIMPLIFIED EAT → CJK UNIFIED IDEOGRAPH-9963 # - 2FB8 ; 9996 ; MA #* ( ⾸ → 首 ) KANGXI RADICAL HEAD → CJK UNIFIED IDEOGRAPH-9996 # 2FB9 ; 9999 ; MA #* ( ⾹ → 香 ) KANGXI RADICAL FRAGRANT → CJK UNIFIED IDEOGRAPH-9999 # @@ -9528,6 +9778,8 @@ FA2C ; 9928 ; MA # ( 館 → 館 ) CJK COMPATIBILITY IDEOGRAPH-FA2C → CJK UNIF 2FBA ; 99AC ; MA #* ( ⾺ → 馬 ) KANGXI RADICAL HORSE → CJK UNIFIED IDEOGRAPH-99AC # +2EE2 ; 9A6C ; MA #* ( ⻢ → 马 ) CJK RADICAL C-SIMPLIFIED HORSE → CJK UNIFIED IDEOGRAPH-9A6C # + 2FA06 ; 99C2 ; MA # ( 駂 → 駂 ) CJK COMPATIBILITY IDEOGRAPH-2FA06 → CJK UNIFIED IDEOGRAPH-99C2 # F91A ; 99F1 ; MA # ( 駱 → 駱 ) CJK COMPATIBILITY IDEOGRAPH-F91A → CJK UNIFIED IDEOGRAPH-99F1 # @@ -9536,8 +9788,6 @@ F91A ; 99F1 ; MA # ( 駱 → 駱 ) CJK COMPATIBILITY IDEOGRAPH-F91A → CJK UNIF F987 ; 9A6A ; MA # ( 驪 → 驪 ) CJK COMPATIBILITY IDEOGRAPH-F987 → CJK UNIFIED IDEOGRAPH-9A6A # -2EE2 ; 9A6C ; MA #* ( ⻢ → 马 ) CJK RADICAL C-SIMPLIFIED HORSE → CJK UNIFIED IDEOGRAPH-9A6C # - 2FBB ; 9AA8 ; MA #* ( ⾻ → 骨 ) KANGXI RADICAL BONE → CJK UNIFIED IDEOGRAPH-9AA8 # 2FA08 ; 4BCE ; MA # ( 䯎 → 䯎 ) CJK COMPATIBILITY IDEOGRAPH-2FA08 → CJK UNIFIED IDEOGRAPH-4BCE # @@ -9562,20 +9812,22 @@ FACD ; 9B12 ; MA # ( 鬒 → 鬒 ) CJK COMPATIBILITY IDEOGRAPH-FACD → CJK UNIF 2FC2 ; 9B5A ; MA #* ( ⿂ → 魚 ) KANGXI RADICAL FISH → CJK UNIFIED IDEOGRAPH-9B5A # +2EE5 ; 9C7C ; MA #* ( ⻥ → 鱼 ) CJK RADICAL C-SIMPLIFIED FISH → CJK UNIFIED IDEOGRAPH-9C7C # + F939 ; 9B6F ; MA # ( 魯 → 魯 ) CJK COMPATIBILITY IDEOGRAPH-F939 → CJK UNIFIED IDEOGRAPH-9B6F # 2FA0B ; 9C40 ; MA # ( 鱀 → 鱀 ) CJK COMPATIBILITY IDEOGRAPH-2FA0B → CJK UNIFIED IDEOGRAPH-9C40 # F9F2 ; 9C57 ; MA # ( 鱗 → 鱗 ) CJK COMPATIBILITY IDEOGRAPH-F9F2 → CJK UNIFIED IDEOGRAPH-9C57 # -2EE5 ; 9C7C ; MA #* ( ⻥ → 鱼 ) CJK RADICAL C-SIMPLIFIED FISH → CJK UNIFIED IDEOGRAPH-9C7C # - 2FC3 ; 9CE5 ; MA #* ( ⿃ → 鳥 ) KANGXI RADICAL BIRD → CJK UNIFIED IDEOGRAPH-9CE5 # 2FA0C ; 9CFD ; MA # ( 鳽 → 鳽 ) CJK COMPATIBILITY IDEOGRAPH-2FA0C → CJK UNIFIED IDEOGRAPH-9CFD # 2FA0D ; 4CCE ; MA # ( 䳎 → 䳎 ) CJK COMPATIBILITY IDEOGRAPH-2FA0D → CJK UNIFIED IDEOGRAPH-4CCE # +9E43 ; 9E42 ; MA # ( 鹃 → 鹂 ) CJK UNIFIED IDEOGRAPH-9E43 → CJK UNIFIED IDEOGRAPH-9E42 # + 2FA0F ; 9D67 ; MA # ( 鵧 → 鵧 ) CJK COMPATIBILITY IDEOGRAPH-2FA0F → CJK UNIFIED IDEOGRAPH-9D67 # 2FA0E ; 4CED ; MA # ( 䳭 → 䳭 ) CJK COMPATIBILITY IDEOGRAPH-2FA0E → CJK UNIFIED IDEOGRAPH-4CED # @@ -9594,8 +9846,6 @@ F93A ; 9DFA ; MA # ( 鷺 → 鷺 ) CJK COMPATIBILITY IDEOGRAPH-F93A → CJK UNIF F920 ; 9E1E ; MA # ( 鸞 → 鸞 ) CJK COMPATIBILITY IDEOGRAPH-F920 → CJK UNIFIED IDEOGRAPH-9E1E # -9E43 ; 9E42 ; MA # ( 鹃 → 鹂 ) CJK UNIFIED IDEOGRAPH-9E43 → CJK UNIFIED IDEOGRAPH-9E42 # - 2FC4 ; 9E75 ; MA #* ( ⿄ → 鹵 ) KANGXI RADICAL SALT → CJK UNIFIED IDEOGRAPH-9E75 # F940 ; 9E7F ; MA # ( 鹿 → 鹿 ) CJK COMPATIBILITY IDEOGRAPH-F940 → CJK UNIFIED IDEOGRAPH-9E7F # @@ -9636,10 +9886,10 @@ FA3A ; 58A8 ; MA # ( 墨 → 墨 ) CJK COMPATIBILITY IDEOGRAPH-FA3A → CJK UNIF 2FCC ; 9EFD ; MA #* ( ⿌ → 黽 ) KANGXI RADICAL FROG → CJK UNIFIED IDEOGRAPH-9EFD # -2FA19 ; 9F05 ; MA # ( 鼅 → 鼅 ) CJK COMPATIBILITY IDEOGRAPH-2FA19 → CJK UNIFIED IDEOGRAPH-9F05 # - 2FA18 ; 9EFE ; MA # ( 黾 → 黾 ) CJK COMPATIBILITY IDEOGRAPH-2FA18 → CJK UNIFIED IDEOGRAPH-9EFE # +2FA19 ; 9F05 ; MA # ( 鼅 → 鼅 ) CJK COMPATIBILITY IDEOGRAPH-2FA19 → CJK UNIFIED IDEOGRAPH-9F05 # + 2FCD ; 9F0E ; MA #* ( ⿍ → 鼎 ) KANGXI RADICAL TRIPOD → CJK UNIFIED IDEOGRAPH-9F0E # 2FA1A ; 9F0F ; MA # ( 鼏 → 鼏 ) CJK COMPATIBILITY IDEOGRAPH-2FA1A → CJK UNIFIED IDEOGRAPH-9F0F # @@ -9661,17 +9911,17 @@ FAD8 ; 9F43 ; MA # ( 齃 → 齃 ) CJK COMPATIBILITY IDEOGRAPH-FAD8 → CJK UNIF 2FD2 ; 9F52 ; MA #* ( ⿒ → 齒 ) KANGXI RADICAL TOOTH → CJK UNIFIED IDEOGRAPH-9F52 # -2FA1D ; 2A600 ; MA # ( 𪘀 → 𪘀 ) CJK COMPATIBILITY IDEOGRAPH-2FA1D → CJK UNIFIED IDEOGRAPH-2A600 # - 2EEE ; 9F7F ; MA #* ( ⻮ → 齿 ) CJK RADICAL C-SIMPLIFIED TOOTH → CJK UNIFIED IDEOGRAPH-9F7F # +2FA1D ; 2A600 ; MA # ( 𪘀 → 𪘀 ) CJK COMPATIBILITY IDEOGRAPH-2FA1D → CJK UNIFIED IDEOGRAPH-2A600 # + F9C4 ; 9F8D ; MA # ( 龍 → 龍 ) CJK COMPATIBILITY IDEOGRAPH-F9C4 → CJK UNIFIED IDEOGRAPH-9F8D # 2FD3 ; 9F8D ; MA #* ( ⿓ → 龍 ) KANGXI RADICAL DRAGON → CJK UNIFIED IDEOGRAPH-9F8D # -FAD9 ; 9F8E ; MA # ( 龎 → 龎 ) CJK COMPATIBILITY IDEOGRAPH-FAD9 → CJK UNIFIED IDEOGRAPH-9F8E # - 2EF0 ; 9F99 ; MA #* ( ⻰ → 龙 ) CJK RADICAL C-SIMPLIFIED DRAGON → CJK UNIFIED IDEOGRAPH-9F99 # +FAD9 ; 9F8E ; MA # ( 龎 → 龎 ) CJK COMPATIBILITY IDEOGRAPH-FAD9 → CJK UNIFIED IDEOGRAPH-9F8E # + F907 ; 9F9C ; MA # ( 龜 → 龜 ) CJK COMPATIBILITY IDEOGRAPH-F907 → CJK UNIFIED IDEOGRAPH-9F9C # F908 ; 9F9C ; MA # ( 龜 → 龜 ) CJK COMPATIBILITY IDEOGRAPH-F908 → CJK UNIFIED IDEOGRAPH-9F9C # FACE ; 9F9C ; MA # ( 龜 → 龜 ) CJK COMPATIBILITY IDEOGRAPH-FACE → CJK UNIFIED IDEOGRAPH-9F9C # @@ -9681,5 +9931,64 @@ FACE ; 9F9C ; MA # ( 龜 → 龜 ) CJK COMPATIBILITY IDEOGRAPH-FACE → CJK UNIF 2FD5 ; 9FA0 ; MA #* ( ⿕ → 龠 ) KANGXI RADICAL FLUTE → CJK UNIFIED IDEOGRAPH-9FA0 # -# total: 6355 +0CDC ; 0C5C ; MA # ( ೜ → ౜ ) KANNADA ARCHAIC SHRII → TELUGU ARCHAIC SHRII # + +1DE8 ; 1ADA ; MA # ( ᷨ → ᫚ ) COMBINING LATIN SMALL LETTER B → COMBINING FLAT SIGN # + +2DEE ; 1ADB ; MA # ( ⷮ → ᫛ ) COMBINING CYRILLIC LETTER TE → COMBINING DOWN TACK ABOVE # + +1AE7 ; 1AE5 ; MA # ( ᫧ → ᫥ ) COMBINING DOUBLE ARCH ABOVE → COMBINING SEAGULL ABOVE # + +031A ; 1AE9 ; MA # ( ̚ → ᫩ ) COMBINING LEFT ANGLE ABOVE → COMBINING LEFT ANGLE CENTRED ABOVE # + +0295 ; A7CE ; MA # ( ʕ → ꟎ ) LATIN LETTER PHARYNGEAL VOICED FRICATIVE → LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE # +A7CF ; A7CE ; MA # ( ꟏ → ꟎ ) LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE → LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE # →ʕ→ + +0348 ; 10EFA ; MA # ( ͈ → 𐻺 ) COMBINING DOUBLE VERTICAL LINE BELOW → ARABIC DOUBLE VERTICAL BAR BELOW # + +0956 ; 11B62 ; MA # ( ॖ → 𑭢 ) DEVANAGARI VOWEL SIGN UE → SHARADA VOWEL SIGN UE # +0A41 ; 11B62 ; MA # ( ੁ → 𑭢 ) GURMUKHI VOWEL SIGN U → SHARADA VOWEL SIGN UE # →ॖ→ + +0957 ; 11B63 ; MA # ( ॗ → 𑭣 ) DEVANAGARI VOWEL SIGN UUE → SHARADA VOWEL SIGN UUE # +0A42 ; 11B63 ; MA # ( ੂ → 𑭣 ) GURMUKHI VOWEL SIGN UU → SHARADA VOWEL SIGN UUE # →ॗ→ + +0947 ; 11B64 ; MA # ( े → 𑭤 ) DEVANAGARI VOWEL SIGN E → SHARADA VOWEL SIGN SHORT E # +0A47 ; 11B64 ; MA # ( ੇ → 𑭤 ) GURMUKHI VOWEL SIGN EE → SHARADA VOWEL SIGN SHORT E # →े→ + +5152 ; 16FF3 ; MA # ( 兒 → 𖿳 ) CJK UNIFIED IDEOGRAPH-5152 → CHINESE SMALL TRADITIONAL ER # + +1F40D ; 1CCFA ; MA #* ( 🐍 → 𜳺 ) SNAKE → SNAKE SYMBOL # + +1F443 ; 1CCFC ; MA #* ( 👃 → 𜳼 ) NOSE → NOSE SYMBOL # + +1F377 ; 1CEBA ; MA #* ( 🍷 → 𜺺 ) WINE GLASS → FRAGILE SYMBOL # + +1F3E2 ; 1CEBB ; MA #* ( 🏢 → 𜺻 ) OFFICE BUILDING → OFFICE BUILDING SYMBOL # + +1F333 ; 1CEBC ; MA #* ( 🌳 → 𜺼 ) DECIDUOUS TREE → TREE SYMBOL # + +1F34E ; 1CEBD ; MA #* ( 🍎 → 𜺽 ) RED APPLE → APPLE SYMBOL # +1F34F ; 1CEBD ; MA #* ( 🍏 → 𜺽 ) GREEN APPLE → APPLE SYMBOL # + +1F352 ; 1CEBE ; MA #* ( 🍒 → 𜺾 ) CHERRIES → CHERRY SYMBOL # + +1F353 ; 1CEBF ; MA #* ( 🍓 → 𜺿 ) STRAWBERRY → STRAWBERRY SYMBOL # + +28FF ; 1CEE0 ; MA #* ( ⣿ → 𜻠 ) BRAILLE PATTERN DOTS-12345678 → GEOMANTIC FIGURE POPULUS # + +29B5 ; 1CEF0 ; MA #* ( ⦵ → 𜻰 ) CIRCLE WITH HORIZONTAL BAR → MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR # + +21C4 ; 1F8D0 ; MA #* ( ⇄ → 🣐 ) RIGHTWARDS ARROW OVER LEFTWARDS ARROW → LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW # + +21CC ; 1F8D1 ; MA #* ( ⇌ → 🣑 ) RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON → LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON # + +2657 ; 1FA55 ; MA #* ( ♗ → 🩕 ) WHITE CHESS BISHOP → WHITE CHESS ALFIL # + +265D ; 1FA57 ; MA #* ( ♝ → 🩗 ) BLACK CHESS BISHOP → BLACK CHESS ALFIL # + +1F514 ; 1FBFA ; MA #* ( 🔔 → 🯺 ) BELL → ALARM BELL SYMBOL # + +6138 ; 2B73F ; MA # ( 愸 → 𫜿 ) CJK UNIFIED IDEOGRAPH-6138 → CJK UNIFIED IDEOGRAPH-2B73F # + +# total: 6565 diff --git a/lib/elixir/unicode/tokenizer.ex b/lib/elixir/unicode/tokenizer.ex index ba1a37c4690..9e612812346 100644 --- a/lib/elixir/unicode/tokenizer.ex +++ b/lib/elixir/unicode/tokenizer.ex @@ -217,7 +217,11 @@ defmodule String.Tokenizer do end) |> Map.new() - # Some scriptsets must be augmented according to the rules below + # Some scriptsets must be augmented according to the rules below, + # see https://www.unicode.org/reports/tr39/ + # + # Note, however, Bopo(mofo) characters are not actually tokenized, + # as it is not in the list of recommended scriptsets. augmentation_rules = %{ "Han" => ["Han with Bopomofo", "Japanese", "Korean"], "Hiragana" => ["Japanese"], diff --git a/lib/elixir/unicode/unicode.ex b/lib/elixir/unicode/unicode.ex index 7b7b850de87..8aa9ef02a3e 100644 --- a/lib/elixir/unicode/unicode.ex +++ b/lib/elixir/unicode/unicode.ex @@ -154,7 +154,7 @@ case_ignorable_categories = :binary.compile_pattern(["Mn", "Me", "Cf", "Lm", "Sk defmodule String.Unicode do @moduledoc false - def version, do: {16, 0, 0} + def version, do: {17, 0, 0} [unconditional_mappings, _conditional_mappings] = Path.join(__DIR__, "SpecialCasing.txt") From c0e334df2aa62762cbc6555e17344caa049b05ba Mon Sep 17 00:00:00 2001 From: Jean Klingler Date: Wed, 10 Sep 2025 20:13:46 +0900 Subject: [PATCH 108/111] Add missing :generated to Macro.escape_opts/0 type (#14761) --- lib/elixir/lib/macro.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex index 1ce25f6859d..9111a7076df 100644 --- a/lib/elixir/lib/macro.ex +++ b/lib/elixir/lib/macro.ex @@ -199,7 +199,8 @@ defmodule Macro do @type escape_opts :: [ unquote: boolean(), - prune_metadata: boolean() + prune_metadata: boolean(), + generated: boolean() ] @type inspect_atom_opts :: [ From 7477c9326ebc28e24ea378267d51132d83ad9dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 10 Sep 2025 22:15:42 +0200 Subject: [PATCH 109/111] Expand on why we supervise, not how (#14764) Closes #14763. --- .../anti-patterns/process-anti-patterns.md | 75 ++++++------------- 1 file changed, 23 insertions(+), 52 deletions(-) diff --git a/lib/elixir/pages/anti-patterns/process-anti-patterns.md b/lib/elixir/pages/anti-patterns/process-anti-patterns.md index cf2b6cb3709..07e27eb268b 100644 --- a/lib/elixir/pages/anti-patterns/process-anti-patterns.md +++ b/lib/elixir/pages/anti-patterns/process-anti-patterns.md @@ -251,71 +251,55 @@ GenServer.cast(pid, {:report_ip_address, conn.remote_ip}) #### Problem -In Elixir, creating a process outside a supervision tree is not an anti-pattern in itself. However, when you spawn many long-running processes outside of supervision trees, this can make visibility and monitoring of these processes difficult, preventing developers from fully controlling their applications. +In Elixir, creating a process outside a supervision tree is not an anti-pattern in itself. However, when you spawn many long-running processes outside of supervision trees, this can make visibility and monitoring of these processes difficult, preventing developers from fully controlling their lifecycle. #### Example -The following code example seeks to illustrate a library responsible for maintaining a numerical `Counter` through a `GenServer` process *outside a supervision tree*. Multiple counters can be created simultaneously by a client (one process for each counter), making these *unsupervised* processes difficult to manage. This can cause problems with the initialization, restart, and shutdown of a system. +The following code example seeks to illustrate a library responsible for maintaining a numerical `Counter` through a `Agent` process *outside a supervision tree*. ```elixir defmodule Counter do @moduledoc """ - Global counter implemented through a GenServer process. + Global counter implemented as an Agent. """ - use GenServer + use Agent @doc "Starts a counter process." def start_link(opts \\ []) do - initial_value = Keyword.get(opts, :initial_value, 0) + initial_state = Keyword.get(opts, :initial_value, 0) name = Keyword.get(opts, :name, __MODULE__) - GenServer.start(__MODULE__, initial_value, name: name) + Agent.start_link(fn -> initial_state end, name: name) end @doc "Gets the current value of the given counter." - def get(pid_name \\ __MODULE__) do - GenServer.call(pid_name, :get) + def get(name \\ __MODULE__) do + Agent.get(name, fn state -> state end) end @doc "Bumps the value of the given counter." - def bump(pid_name \\ __MODULE__, value) do - GenServer.call(pid_name, {:bump, value}) - end - - @impl true - def init(counter) do - {:ok, counter} - end - - @impl true - def handle_call(:get, _from, counter) do - {:reply, counter, counter} - end - - def handle_call({:bump, value}, _from, counter) do - {:reply, counter, counter + value} + def bump(name \\ __MODULE__, value) do + Agent.get_and_update(fn state -> {state, value + state} end) end end ``` +While it is possible to start the process outside of a supervision tree: + ```elixir iex> Counter.start_link() {:ok, #PID<0.115.0>} -iex> Counter.get() +iex> Counter.bump(13) 0 -iex> Counter.start_link(initial_value: 15, name: :other_counter) -{:ok, #PID<0.120.0>} -iex> Counter.get(:other_counter) -15 -iex> Counter.bump(:other_counter, -3) -12 -iex> Counter.bump(Counter, 7) -7 +iex> Counter.get() +13 ``` +Such processes are harder to observe and control their lifecycle. For example, if you have other processes that depend on the `Counter` above, you will need ad-hoc mechanisms to make sure they are initialized in order. Furthermore, when your application is shutting down, there is no guarantee when they are terminated. + #### Refactoring -To ensure that clients of a library have full control over their systems, regardless of the number of processes used and the lifetime of each one, all processes must be started inside a supervision tree. As shown below, this code uses a `Supervisor` as a supervision tree. When this Elixir application is started, two different counters (`Counter` and `:other_counter`) are also started as child processes of the `Supervisor` named `App.Supervisor`. One is initialized with `0`, the other with `15`. By means of this supervision tree, it is possible to manage the life cycle of all child processes (stopping or restarting each one), improving the visibility of the entire app. +To ensure that clients of a library have full control over their systems, regardless of the number of processes used and the lifetime of each one, all processes must be started inside a supervision tree. As shown below, this code uses a `Supervisor` as a supervision tree. ```elixir defmodule SupervisedProcess.Application do @@ -338,21 +322,8 @@ defmodule SupervisedProcess.Application do end ``` -```elixir -iex> Supervisor.count_children(App.Supervisor) -%{active: 2, specs: 2, supervisors: 0, workers: 2} -iex> Counter.get(Counter) -0 -iex> Counter.get(:other_counter) -15 -iex> Counter.bump(Counter, 7) -7 -iex> Supervisor.terminate_child(App.Supervisor, Counter) -iex> Supervisor.count_children(App.Supervisor) # Only one active child -%{active: 1, specs: 2, supervisors: 0, workers: 2} -iex> Counter.get(Counter) # The process was terminated -** (EXIT) no process: the process is not alive... -iex> Supervisor.restart_child(App.Supervisor, Counter) -iex> Counter.get(Counter) # After the restart, this process can be used again -0 -``` +Besides having a deterministic order in which processes are started, supervision trees also guarantee they are terminated in reverse order, allowing you to perform any necessary clean up during shut down. Furthermore, supervision strategies allows us to configure exactly how process should act in case of unexpected failures. + +Finally, applications and supervision trees can be introspected through applications like the [Phoenix.LiveDashboard](http://github.com/phoenixframework/phoenix_live_dashboard) and [Erlang's built-in observer](https://www.erlang.org/doc/apps/observer/observer_ug): + +Observer GUI screenshot From 1f7ddd7acd67d6ffaa9a91528afc3bc58d9b62cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 13 Sep 2025 20:07:35 +0200 Subject: [PATCH 110/111] Convert verification errors into diagnostics, closes #14768 --- lib/elixir/lib/module/parallel_checker.ex | 16 +++++++++++++- .../elixir/module/types/integration_test.exs | 21 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex index 2fdd5c3d5d2..50f6f92e992 100644 --- a/lib/elixir/lib/module/parallel_checker.ex +++ b/lib/elixir/lib/module/parallel_checker.ex @@ -283,7 +283,21 @@ defmodule Module.ParallelChecker do |> emit_warnings(file, log?) Enum.each(after_verify, fn {verify_mod, verify_fun} -> - apply(verify_mod, verify_fun, [module]) + try do + apply(verify_mod, verify_fun, [module]) + catch + # We need to catch exceptions because files have already been written to disk, + # so we need to convert verification errors into diagnostics by using IO.warn. + kind, reason -> + IO.warn( + "exception happened while verifying module #{inspect(module)}\n\n" <> + Exception.format(kind, reason, __STACKTRACE__), + file: file, + line: line, + module: verify_mod, + function: {verify_fun, 1} + ) + end end) diagnostics diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs index 2377cb75ee1..a3f7b7eb5ba 100644 --- a/lib/elixir/test/elixir/module/types/integration_test.exs +++ b/lib/elixir/test/elixir/module/types/integration_test.exs @@ -1225,6 +1225,27 @@ defmodule Module.Types.IntegrationTest do assert_warnings(files, warning) end + + test "converts errors into diagnostics" do + files = %{ + "a.ex" => """ + defmodule A do + @after_verify __MODULE__ + + def __after_verify__(__MODULE__) do + raise "oops" + end + end + """ + } + + warning = [ + "warning: exception happened while verifying module A", + "** (RuntimeError) oops" + ] + + assert_warnings(files, warning) + end end describe "deprecated" do From cb108d5ce5fff39d0397d4fe203eeac3f7e125e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 13 Sep 2025 21:26:36 +0200 Subject: [PATCH 111/111] Unify mix recompile and external resource handling Imagine the following scenario: 1. the user changes an external resource 2. the user calls mix compile and it fails 3. the user reverts the changes to the external resource We had already fixed this bug for `__mix_recompile__?`. Therefore, this pull request unifies how both are handled by touching the source file after we detect it is stale (which is also what we do on every subsequent compilation cycle). Note that before we stored the result of `__mix_recompile__?` in the checkpoint file. However, that's not needed. The checkpoint is useful to track changes to sources that may not define any module. But since the compilation check and external resources always require a module, touching the file is enough. Closes https://github.com/phoenixframework/phoenix/issues/6476. --- lib/mix/lib/mix/compilers/elixir.ex | 143 +++++++++--------- .../test/mix/tasks/compile.elixir_test.exs | 13 ++ 2 files changed, 84 insertions(+), 72 deletions(-) diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex index 866c8f27916..8999dc0c933 100644 --- a/lib/mix/lib/mix/compilers/elixir.ex +++ b/lib/mix/lib/mix/compilers/elixir.ex @@ -6,7 +6,7 @@ defmodule Mix.Compilers.Elixir do @moduledoc false @manifest_vsn 28 - @checkpoint_vsn 2 + @checkpoint_vsn 3 import Record @@ -152,7 +152,8 @@ defmodule Mix.Compilers.Elixir do removed, Map.merge(stale_modules, removed_modules), Map.merge(stale_exports, removed_modules), - dest + dest, + timestamp ) end @@ -320,8 +321,11 @@ defmodule Mix.Compilers.Elixir do {%{}, %{}, all_paths, sources_stats} end - # If any .beam file is missing, the first one will the first to miss, - # so we always check that. If there are no modules, then we can rely + # If the user does a change, compilation fails, and then they revert + # the change, the mtime will have changed but the .beam files will + # be missing and the digest is the same, so we need to check if .beam + # files are available. Checking the first .beam file is enough, as + # they would be all removed. If there are no modules, then we can rely # purely on digests. defp missing_beam_file?(dest, [mod | _]), do: not File.exists?(beam_path(dest, mod)) defp missing_beam_file?(_dest, []), do: false @@ -334,55 +338,43 @@ defmodule Mix.Compilers.Elixir do removed, stale_modules, stale_exports, - dest + dest, + timestamp ) do - {modules_to_recompile, modules_to_mix_check} = - for {module, module(recompile?: recompile?)} <- all_modules, reduce: {[], []} do - {modules_to_recompile, modules_to_mix_check} -> - cond do - Map.has_key?(stale_modules, module) -> - {[module | modules_to_recompile], modules_to_mix_check} + {checkpoint_stale_modules, checkpoint_stale_exports} = parse_checkpoint(manifest) + stale_modules = Map.merge(checkpoint_stale_modules, stale_modules) + stale_exports = Map.merge(checkpoint_stale_exports, stale_exports) - recompile? -> - {modules_to_recompile, [module | modules_to_mix_check]} + if map_size(stale_modules) != map_size(checkpoint_stale_modules) or + map_size(stale_exports) != map_size(checkpoint_stale_exports) do + write_checkpoint(manifest, stale_modules, stale_exports) + end - true -> - {modules_to_recompile, modules_to_mix_check} - end - end + # We don't need to store those in the checkpoint because + # these changes come from modules and, when they are stale, + # we remove the .beam files and touch sources. + modules_to_mix_check = + for {module, module(recompile?: true)} <- all_modules, + not Map.has_key?(stale_modules, module), + do: module _ = Code.ensure_all_loaded(modules_to_mix_check) modules_to_recompile = - modules_to_recompile ++ - for {:ok, {module, true}} <- - Task.async_stream( - modules_to_mix_check, - fn module -> - {module, - function_exported?(module, :__mix_recompile__?, 0) and - module.__mix_recompile__?()} - end, - ordered: false, - timeout: :infinity - ) do - module - end - - {checkpoint_stale_modules, checkpoint_stale_exports, checkpoint_modules} = - parse_checkpoint(manifest) - - modules_to_recompile = - Map.merge(checkpoint_modules, Map.from_keys(modules_to_recompile, true)) - - stale_modules = Map.merge(checkpoint_stale_modules, stale_modules) - stale_exports = Map.merge(checkpoint_stale_exports, stale_exports) - - if map_size(stale_modules) != map_size(checkpoint_stale_modules) or - map_size(stale_exports) != map_size(checkpoint_stale_exports) or - map_size(modules_to_recompile) != map_size(checkpoint_modules) do - write_checkpoint(manifest, stale_modules, stale_exports, modules_to_recompile) - end + for {:ok, {module, true}} <- + Task.async_stream( + modules_to_mix_check, + fn module -> + {module, + function_exported?(module, :__mix_recompile__?, 0) and + module.__mix_recompile__?()} + end, + ordered: false, + timeout: :infinity + ), + into: %{} do + {module, true} + end sources_stats = for path <- new_paths, @@ -392,20 +384,30 @@ defmodule Mix.Compilers.Elixir do # Sources that have changed on disk or # any modules associated with them need to be recompiled changed = - for {source, - source(external: external, size: size, mtime: mtime, digest: digest, modules: modules)} <- - all_sources, - {last_mtime, last_size} = Map.fetch!(sources_stats, source), - # If the user does a change, compilation fails, and then they revert - # the change, the mtime will have changed but the .beam files will - # be missing and the digest is the same, so we need to check if .beam - # files are available. - size != last_size or - Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or + Enum.flat_map(all_sources, fn + {source, + source(external: external, size: size, mtime: mtime, digest: digest, modules: modules)} -> + {last_mtime, last_size} = Map.fetch!(sources_stats, source) + + cond do Enum.any?(external, &stale_external?(&1, sources_stats)) or - (last_mtime > mtime and - (missing_beam_file?(dest, modules) or digest_changed?(source, digest))), - do: source + has_any_key?(modules_to_recompile, modules) -> + # Mark the source as changed so the combination of a timestamp + # plus removed beam files (which are removed by update_stale_entries) + # causes it to be recompiled + File.touch!(source, timestamp + 1) + [source] + + size != last_size or + has_any_key?(stale_modules, modules) or + (last_mtime > mtime and + (missing_beam_file?(dest, modules) or digest_changed?(source, digest))) -> + [source] + + true -> + [] + end + end) changed = new_paths ++ changed @@ -576,7 +578,7 @@ defmodule Mix.Compilers.Elixir do end defp has_any_key?(map, enumerable) do - Enum.any?(enumerable, &Map.has_key?(map, &1)) + map != %{} and Enum.any?(enumerable, &Map.has_key?(map, &1)) end defp stale_local_deps(local_deps, manifest, stale_modules, modified, deps_exports) do @@ -940,25 +942,20 @@ defmodule Mix.Compilers.Elixir do # # Therefore, it is important for us to checkpoint any state that may # have lead to a compilation and which can now be reverted. - defp parse_checkpoint(manifest) do try do (manifest <> ".checkpoint") |> File.read!() |> :erlang.binary_to_term() rescue - _ -> - {%{}, %{}, %{}} + _ -> {%{}, %{}} else - {@checkpoint_vsn, stale_modules, stale_exports, recompile_modules} -> - {stale_modules, stale_exports, recompile_modules} - - _ -> - {%{}, %{}, %{}} + {@checkpoint_vsn, stale_modules, stale_exports} -> {stale_modules, stale_exports} + _ -> {%{}, %{}} end end - defp write_checkpoint(manifest, stale_modules, stale_exports, recompile_modules) do + defp write_checkpoint(manifest, stale_modules, stale_exports) do File.mkdir_p!(Path.dirname(manifest)) - term = {@checkpoint_vsn, stale_modules, stale_exports, recompile_modules} + term = {@checkpoint_vsn, stale_modules, stale_exports} checkpoint_data = :erlang.term_to_binary(term, [:compressed]) File.write!(manifest <> ".checkpoint", checkpoint_data) end @@ -1117,8 +1114,10 @@ defmodule Mix.Compilers.Elixir do {pending_modules, exports, changed} = update_stale_entries(pending_modules, sources, changed, %{}, stale_exports, compile_path) - # For each changed file, mark it as changed. - # If compilation fails mid-cycle, they will be picked next time around. + # Those files have been changed transitively, so we mark them as changed + # in case compilation fails mid-cycle. The combination of the outdated + # timestamp plus the missing BEAM files (which were removed in + # update_stale_entries above) will cause them to be recompiled next time. for file <- changed do File.touch!(file, timestamp) end diff --git a/lib/mix/test/mix/tasks/compile.elixir_test.exs b/lib/mix/test/mix/tasks/compile.elixir_test.exs index a52a4b00734..e0e26d69307 100644 --- a/lib/mix/test/mix/tasks/compile.elixir_test.exs +++ b/lib/mix/test/mix/tasks/compile.elixir_test.exs @@ -965,6 +965,10 @@ defmodule Mix.Tasks.Compile.ElixirTest do @external_resource "lib/a.eex" @external_resource #{inspect(tmp)} def a, do: :ok + + if System.get_env("RAISE_EXTERNAL_RESOURCE") do + raise "oops" + end end """) @@ -978,7 +982,16 @@ defmodule Mix.Tasks.Compile.ElixirTest do File.touch!("lib/a.eex", {{2038, 1, 1}, {0, 0, 0}}) assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []} + # Force recompilation but fail compilation force_recompilation("lib/a.eex") + System.put_env("RAISE_EXTERNAL_RESOURCE", "1") + + assert capture_io(:stderr, fn -> + assert {:error, [_]} = Mix.Tasks.Compile.Elixir.run(["--verbose"]) + end) =~ "oops" + + # Delete failure reason, it will now compile for good + System.delete_env("RAISE_EXTERNAL_RESOURCE") assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []} assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}