texlive[69889] Master/texmf-dist: l3kernel (14feb24)

commits+karl at tug.org commits+karl at tug.org
Wed Feb 14 23:04:08 CET 2024


Revision: 69889
          https://tug.org/svn/texlive?view=revision&revision=69889
Author:   karl
Date:     2024-02-14 23:04:08 +0100 (Wed, 14 Feb 2024)
Log Message:
-----------
l3kernel (14feb24)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md
    trunk/Master/texmf-dist/doc/latex/l3kernel/README.md
    trunk/Master/texmf-dist/doc/latex/l3kernel/expl3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3doc.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3docstrip.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news01.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news02.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news03.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news04.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news05.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news06.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news07.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news08.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news09.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news10.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news11.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news12.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3obsolete.txt
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/source3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex
    trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty
    trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def
    trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md	2024-02-14 22:04:08 UTC (rev 69889)
@@ -7,6 +7,26 @@
 
 ## [Unreleased]
 
+## [2024-02-13]
+
+### Added
+- Checking missing `\endgroup` at the end of `\DocInclude`
+- Linked storage type for large property lists (issue \#1040, pull \#1059)
+
+### Changed
+- `\meta` now typesets in `\texttt`, along with `\Arg`
+- Improved the performance of `\cs_if_exist:NTF`, `\cs_if_free:NTF`,
+    `\cs_if_exist_use:NTF`, and most notably their `c`-type variants.
+
+### Fixed
+- Inconsistent local/global assignments in `\vcoffin_gset:Nnn` and
+  `\vcoffin_gset:Nnw`
+- Tokenization by `\peek_analysis_map_inline:n` of one additional
+  character after any space or brace
+
+### Deprecated
+- `\msg_gset:nnn(n)`
+
 ## [2024-01-22]
 
 ### Added
@@ -1648,7 +1668,8 @@
 - Step functions have been added for dim variables,
   e.g. `\dim_step_inline:nnnn`
 
-[Unreleased]: https://github.com/latex3/latex3/compare/2024-01-22...HEAD
+[Unreleased]: https://github.com/latex3/latex3/compare/2024-02-13...HEAD
+[2024-02-13]: https://github.com/latex3/latex3/compare/2024-01-22...2024-02-13
 [2024-01-22]: https://github.com/latex3/latex3/compare/2024-01-04...2024-01-22
 [2024-01-04]: https://github.com/latex3/latex3/compare/2023-12-11...2024-01-04
 [2023-12-11]: https://github.com/latex3/latex3/compare/2023-12-08...2023-12-11

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/README.md	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/README.md	2024-02-14 22:04:08 UTC (rev 69889)
@@ -1,7 +1,7 @@
 LaTeX3 Programming Conventions
 ==============================
 
-Release 2024-01-22
+Release 2024-02-13
 
 Overview
 --------

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/expl3.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,12 +42,6 @@
 
 \documentclass[kernel]{l3doc}
 
-% fix for l3doc class
-\ExplSyntaxOn
-\DeclareDocumentCommand \meta { m }
-  { \texttt{ \__codedoc_meta:n {#1} } }
-\ExplSyntaxOff
-
 \newif\ifinterface
 \interfacetrue
 

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3doc.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3docstrip.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news01.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news02.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news03.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news04.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news05.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news06.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news07.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news08.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news09.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news10.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news11.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3news12.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3obsolete.txt
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3obsolete.txt	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3obsolete.txt	2024-02-14 22:04:08 UTC (rev 69889)
@@ -53,6 +53,8 @@
 \l_keys_path_tl                          2020-02-08
 \l_text_accents_tl                       2023-02-07
 \l_text_letterlike_tl                    2023-02-07
+\msg_gset:nnn                            2024-01-11
+\msg_gset:nnnn                           2024-01-11
 \peek_catcode_ignore_spaces:N            2022-01-11
 \peek_catcode_remove_ignore_spaces:N     2022-01-11
 \peek_charcode_ignore_spaces:N           2022-01-11

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv	2024-02-14 22:04:08 UTC (rev 69889)
@@ -12,8 +12,8 @@
 ampersand,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 apfs,apfontspec,Qing Lee,https://github.com/CTeX-org/apfontspec,https://github.com/CTeX-org/apfontspec.git,https://github.com/CTeX-org/apfontspec/issues,2020-05-17,2020-05-17,
 arch,archaeologie,Lukas C. Bossert,http://www.biblatex-archaeologie.de,https://github.com/LukasCBossert/biblatex-archaeologie.git,https://github.com/LukasCBossert/biblatex-archaeologie/issues,2017-03-24,2017-03-24,
+array,hobby,Andrew Stacey,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby/issues,2013-03-16,2020-10-29,
 arsenal,arsenal,Boris Veytsman,https://github.com/borisveytsman/arsenal,https://github.com/borisveytsman/arsenal,https://github.com/borisveytsman/arsenal/issues,2023-09-04,2023-09-04,
-array,hobby,Andrew Stacey,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby,https://github.com/loopspace/hobby/issues,2013-03-16,2020-10-29,
 atsign,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 avm,langsci-avm,Felix Kopecky,https://ctan.org/pkg/langsci-avm,https://github.com/langsci/langsci-avm,https://github.com/langsci/langsci-avm/issues,2020-03-11,2020-03-11,
 babellatin,babel-latin,Keno Wehr,https://ctan.org/pkg/babel-latin,https://github.com/wehro/babel-latin,https://github.com/wehro/babel-latin/issues,2021-08-23,2021-08-23,
@@ -59,6 +59,7 @@
 debug,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2017-07-15,2017-07-15,
 denisbdoc,denisbdoc,Denis Bitouzé,https://github.com/dbitouze/denisbdoc,git@github.com:dbitouze/denisbdoc.git,,2020-05-13,2020-05-13,
 deriv,derivative,Simon Jensen,,,,2019-07-24,2019-07-24,
+didec,didec,Thomas F. Sturm,https://github.com/T-F-S/didec,https://github.com/T-F-S/didec.git,https://github.com/T-F-S/didec/issues,2024-02-02,2024-02-02,
 diffcoeff,diffcoeff,Andrew Parsloe,,,,2019-08-26,2019-08-26,
 dim,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
 document,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2019-06-02,2019-06-02,
@@ -131,7 +132,7 @@
 keyval,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
 kgl,kantlipsum,Enrico Gregorio,,,,2013-03-16,2013-03-16,
 kivitendo,"kiviletter, kivitables",Marei Peischl for Kivitendo,https://www.kivitendo.de/,https://github.com/kivitendo/kivitendo-erp,https://forum.kivitendo.de/,2021-05-28,2021-05-28,
-langsci,langscibook ,Language Science Press,https://langsci-press.org,https://github.com/langsci/langscibook,https://github.com/langsci/langscibook/issues,2021-07-20,2021-07-21,
+langsci,langscibook,Language Science Press,https://langsci-press.org,https://github.com/langsci/langscibook,https://github.com/langsci/langscibook/issues,2021-07-20,2021-07-21,
 left,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 lltxmath,lualatex-math,Philipp Stephani,https://github.com/phst/lualatex-math,https://github.com/phst/lualatex-math.git,https://github.com/phst/lualatex-math/issues,2012-11-07,2012-11-07,
 log,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
@@ -204,7 +205,7 @@
 property,latex2e,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex2e.git,https://github.com/latex3/latex2e/issues,2021-01-20,2021-03-03,
 pseudo,pseudo,Magnus Lie Hetland,https://github.com/mlhetland/pseudo.sty,https://github.com/mlhetland/pseudo.sty.git,https://github.com/mlhetland/pseudo.sty/issues,2019-06-24,2019-06-24,
 ptex,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2015-07-28,2015-07-28,
-ptxcd,ptxcd, Marei Peischl,,,,2020-07-27,2020-07-27,Used for specific corporate design templates
+ptxcd,ptxcd,Marei Peischl,,,,2020-07-27,2020-07-27,Used for specific corporate design templates
 qrbill,qrbill,Marei Peischl,https://github.com/peiTeX/qrbill,https://github.com/peiTeX/qrbill.git,https://github.com/peiTeX/qrbill/issues,2020-06-27,2020-06-27,
 quark,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
 rainbow,beamertheme-rainbow,samcarter,https://github.com/samcarter/beamertheme-rainbow,https://github.com/samcarter/beamertheme-rainbow,https://github.com/samcarter/beamertheme-rainbow/issues,2023-07-04,2023-07-04,
@@ -250,7 +251,7 @@
 term,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 tex,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
 text,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2020-01-15,2020-01-15,
-tikzfill,tikzfill,Thomas F. Sturm,https://github.com/T-F-S/tikzfill,https://github.com/T-F-S/tikzfill.git,https://github.com/T-F-S/tikzfill/issues,2022-07-19,2022-07-19, 
+tikzfill,tikzfill,Thomas F. Sturm,https://github.com/T-F-S/tikzfill,https://github.com/T-F-S/tikzfill.git,https://github.com/T-F-S/tikzfill/issues,2022-07-19,2022-07-19,
 tikzlings,tikzlings,samcarter,https://github.com/samcarter/tikzlings,git@github.com:samcarter/tikzlings.git,https://github.com/samcarter/tikzlings/issues,2023-02-17,2023-02-17,
 tikzsymbols,tikzsymbols,Ben Vitecek,https://github.com/Vidabe/tikzsymbols,https://github.com/Vidabe/tikzsymbols.git,https://github.com/Vidabe/tikzsymbols/issues,2018-09-26,2018-09-26,
 tilde,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/source3.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex	2024-02-14 22:04:08 UTC (rev 69889)
@@ -272,7 +272,11 @@
     dependent and their name can change without warning, thus their
     use is \emph{strongly discouraged} in package code: programmers
     should instead use the interfaces documented in
-    \href{interface3.pdf}{interface3.pdf}.
+    \ifinterface
+      this documentation.
+    \else
+      \href{interface3.pdf}{interface3.pdf}.
+    \fi
 \end{description}
 Notice that the argument specifier describes how the argument is
 processed prior to being passed to the underlying function. For example,
@@ -607,8 +611,7 @@
 \DocInput{l3color.dtx}
 \DocInput{l3pdf.dtx}
 
-\part{Removals}
-
+% implementation part only
 \ExplSyntaxOn
 \clist_gput_right:Nn \g_docinput_clist { l3deprecation.dtx }
 \clist_gput_right:Nn \g_docinput_clist { l3debug.dtx }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -24,7 +24,7 @@
 %
 %<*driver|generic|package|2ekernel>
 %</driver|generic|package|2ekernel>
-\def\ExplFileDate{2024-01-22}%
+\def\ExplFileDate{2024-02-13}%
 %<*driver>
 \documentclass[full]{l3doc}
 \usepackage{graphicx}
@@ -51,7 +51,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -92,6 +92,9 @@
 %   to occur within the same function. Indeed, it is often necessary
 %   to start a group within one function and finish it within another,
 %   for example when seeking to use non-standard category codes.
+%   \begin{texnote}
+%     These are the \TeX{} primitives \tn{begingroup} and \tn{endgroup}.
+%   \end{texnote}
 % \end{function}
 %
 % \begin{function}{\group_insert_after:N}
@@ -1248,11 +1251,13 @@
 %
 % \begin{function}[EXP]{\if:w, \if_charcode:w, \if_catcode:w}
 %   \begin{syntax}
-%     "\if:w" <token_1> <token_2> <true code> "\else:" <false code> "\fi:" \\
-%     "\if_catcode:w" <token_1> <token_2> <true code> "\else:" <false code> "\fi:"
+%     "\if:w" <token(s)> <true code> "\else:" <false code> "\fi:" \\
+%     "\if_catcode:w" <token(s_> <true code> "\else:" <false code> "\fi:"
 %   \end{syntax}
-%   These conditionals expand any following tokens until two
-%   unexpandable tokens are left. If you wish to prevent this expansion,
+%   These conditionals expand \meta{token(s)} until two
+%   unexpandable tokens \meta{token_1} and \meta{tokens_2} are left;
+%   any further tokens become part of the \meta{true code}.
+%   If you wish to prevent this expansion,
 %   prefix the token in question with "\exp_not:N". "\if_catcode:w"
 %   tests if the category codes of the two tokens are the same whereas
 %   "\if:w" tests if the character codes are
@@ -2451,23 +2456,29 @@
 % (to be defined) if it does not already exist.
 %
 % \begin{macro}[pTF, EXP]{\cs_if_exist:N, \cs_if_exist:c}
+% \begin{macro}{\@@_if_exist_c_aux:,\@@_if_exist_c_aux:w}
 %   Two versions for checking existence. For the |N| form we firstly
 %   check for \cs{scan_stop:} and then if it is in the hash
 %   table. There is no problem when inputting something like \cs{else:}
 %   or \cs{fi:} as \TeX{} will only ever skip input in case the token
 %   tested against is \cs{scan_stop:}.
+%
+%   In both the |N| and |c| form we use the way \cs{prg_set_conditional:Npnn}
+%   optimizes the conditionals to negate the tests using \cs{else:} (the
+%   \cs{else:} in the top level functions will be removed by the optimization,
+%   and this usage of \cs{else:} will be fine).
 %    \begin{macrocode}
 \prg_set_conditional:Npnn \cs_if_exist:N #1 { p , T , F , TF }
   {
     \if_meaning:w #1 \scan_stop:
-      \prg_return_false:
+      \use_i:nnnn
     \else:
-      \if_cs_exist:N #1
-        \prg_return_true:
-      \else:
-        \prg_return_false:
-      \fi:
     \fi:
+    \if_cs_exist:N #1
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
   }
 %    \end{macrocode}
 %   For the |c| form we firstly check if it is in the hash table and
@@ -2477,24 +2488,39 @@
 %   disturb the scanner. Therefore, we ensure that the second test is
 %   performed after the first one has concluded completely.
 %    \begin{macrocode}
-\prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
+\cs_if_exist:NTF \tex_lastnamedcs:D
   {
-    \if_cs_exist:w #1 \cs_end:
-      \exp_after:wN \use_i:nn
-    \else:
-      \exp_after:wN \use_ii:nn
-    \fi:
-    {
-      \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop:
-        \prg_return_false:
-      \else:
-        \prg_return_true:
-      \fi:
-    }
-    \prg_return_false:
+    \prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_exist_c_aux:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \@@_if_exist_c_aux:
+      { \fi: \exp_after:wN \if_meaning:w \tex_lastnamedcs:D \scan_stop: \else: }
   }
+  {
+    \prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_exist_c_aux:w
+        \fi:
+        \use_none:n {#1}
+        \if_false:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \@@_if_exist_c_aux:w \fi: \use_none:n #1 \if_false:
+      { \fi: \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop: \else: }
+  }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}[pTF, EXP]{\cs_if_free:N, \cs_if_free:c}
 %   The logical reversal of the above.
@@ -2501,37 +2527,54 @@
 %    \begin{macrocode}
 \prg_set_conditional:Npnn \cs_if_free:N #1 { p , T , F , TF }
   {
+    \if_cs_exist:N #1
+    \else:
+      \use_none:nnnn
+    \fi:
     \if_meaning:w #1 \scan_stop:
       \prg_return_true:
     \else:
-      \if_cs_exist:N #1
-        \prg_return_false:
-      \else:
-        \prg_return_true:
-      \fi:
+      \prg_return_false:
     \fi:
   }
-\prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
+\cs_if_exist:NTF \tex_lastnamedcs:D
   {
-    \if_cs_exist:w #1 \cs_end:
-      \exp_after:wN \use_i:nn
-    \else:
-      \exp_after:wN \use_ii:nn
-    \fi:
+    \prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
       {
-        \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop:
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_free_c_aux:w
+        \fi:
+        \if_true:
           \prg_return_true:
         \else:
           \prg_return_false:
         \fi:
       }
-      { \prg_return_true: }
+    \cs_set:Npn \@@_if_free_c_aux:w \fi: \if_true:
+      { \fi: \exp_after:wN \if_meaning:w \tex_lastnamedcs:D \scan_stop: }
   }
+  {
+    \prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_free_c_aux:w
+        \fi:
+        \use_none:n {#1}
+        \if_true:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \@@_if_free_c_aux:w \fi: \use_none:n #1 \if_true:
+      { \fi: \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop: }
+  }
 %    \end{macrocode}
 % \end{macro}
 %
 % \begin{macro}[EXP,noTF,added=2011-10-10]
 %   {\cs_if_exist_use:N, \cs_if_exist_use:c}
+% \begin{macro}{\@@_if_exist_use_aux:w, \@@_if_exist_use_aux:Nnn}
 %   The \cs[index=cs_if_exist_use:N]{cs_if_exist_use:\ldots{}}
 %   functions cannot be implemented
 %   as conditionals because the true branch must leave both the control
@@ -2538,26 +2581,55 @@
 %   sequence itself and the true code in the input stream.
 %   For the \texttt{c} variants, we are careful not to put the control
 %   sequence in the hash table if it does not exist.
-%   In \LuaTeX{} we could use the \tn{lastnamedcs} primitive.
+%   If available we use the \tn{lastnamedcs} primitive.
 %    \begin{macrocode}
 \cs_set:Npn \cs_if_exist_use:NTF #1#2
   { \cs_if_exist:NTF #1 { #1 #2 } }
 \cs_set:Npn \cs_if_exist_use:NF #1
-  { \cs_if_exist:NTF #1 { #1 } }
+  { \cs_if_exist:NTF #1 #1 }
 \cs_set:Npn \cs_if_exist_use:NT #1 #2
-  { \cs_if_exist:NTF #1 { #1 #2 } { } }
+  { \cs_if_exist:NT #1 { #1 #2 } }
 \cs_set:Npn \cs_if_exist_use:N #1
-  { \cs_if_exist:NTF #1 { #1 } { } }
-\cs_set:Npn \cs_if_exist_use:cTF #1#2
-  { \cs_if_exist:cTF {#1} { \use:c {#1} #2 } }
+  { \cs_if_exist:NT #1 #1 }
+\cs_if_exist:NTF \tex_lastnamedcs:D
+  {
+    \cs_set:Npn \cs_if_exist_use:cTF #1
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_exist_use_aux:w
+        \fi:
+        \use_ii:nn
+      }
+    \cs_set:Npn \@@_if_exist_use_aux:w \fi: \use_ii:nn
+      { \fi: \exp_after:wN \@@_if_exist_use_aux:Nnn \tex_lastnamedcs:D }
+  }
+  {
+    \cs_set:Npn \cs_if_exist_use:cTF #1
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \@@_if_exist_use_aux:w
+        \fi:
+        \use_iii:nnn {#1}
+      }
+    \cs_set:Npn \@@_if_exist_use_aux:w \fi: \use_iii:nnn #1
+      { \fi: \exp_after:wN \@@_if_exist_use_aux:Nnn \cs:w #1 \cs_end: }
+  }
+\cs_set:Npn \@@_if_exist_use_aux:Nnn #1#2
+  {
+    \if_meaning:w #1 \scan_stop:
+      \exp_after:wN \use_iii:nnn
+    \fi:
+    \use_i:nn { #1 #2 }
+  }
 \cs_set:Npn \cs_if_exist_use:cF #1
-  { \cs_if_exist:cTF {#1} { \use:c {#1} } }
+  { \cs_if_exist_use:cTF {#1} {} }
 \cs_set:Npn \cs_if_exist_use:cT #1#2
-  { \cs_if_exist:cTF {#1} { \use:c {#1} #2 } { } }
+  { \cs_if_exist_use:cTF {#1} {#2} {} }
 \cs_set:Npn \cs_if_exist_use:c #1
-  { \cs_if_exist:cTF {#1} { \use:c {#1} } { } }
+  { \cs_if_exist_use:cTF {#1} {} {} }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \subsection{Preliminaries for new functions}
 %
@@ -3270,7 +3342,7 @@
 \cs_new_protected:Npn \group_show_list:
   { \@@_group_show:NN \use_none:n 1 }
 \cs_new_protected:Npn \group_log_list:
-  { \@@_group_show:NN \int_zero:N 0 }
+  { \@@_group_show:NN \int_gzero:N 0 }
 \cs_new_protected:Npn \@@_group_show:NN #1#2
   {
     \use:e
@@ -3280,7 +3352,7 @@
         \int_set:Nn \tex_errorcontextlines:D { -1 }
         \exp_not:N \exp_after:wN \scan_stop:
         \tex_showgroups:D
-        \int_set:Nn \tex_interactionmode:D
+        \int_gset:Nn \tex_interactionmode:D
           { \int_use:N \tex_interactionmode:D }
         \int_set:Nn \tex_tracingonline:D
           { \int_use:N \tex_tracingonline:D }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -1343,9 +1343,9 @@
   { \exp_args:No \@@_log:nNnn { \tex_the:D \tex_interactionmode:D } }
 \cs_new_protected:Npn \@@_log:nNnn #1#2#3#4
   {
-    \int_set:Nn \tex_interactionmode:D { 0 }
+    \int_gset:Nn \tex_interactionmode:D { 0 }
     \@@_show:NNff 0 #2 { \int_eval:n {#3} } { \int_eval:n {#4} }
-    \int_set:Nn \tex_interactionmode:D {#1}
+    \int_gset:Nn \tex_interactionmode:D {#1}
   }
 \cs_generate_variant:Nn \box_log:Nnn { c }
 %    \end{macrocode}
@@ -2279,7 +2279,7 @@
 \cs_generate_variant:Nn \box_autosize_to_wd_and_ht:Nnn { c }
 \cs_new_protected:Npn \box_gautosize_to_wd_and_ht:Nnn #1#2#3
   { \@@_autosize:NnnnN #1 {#2} {#3} { \box_ht:N #1 } \hbox_gset:Nn }
-\cs_generate_variant:Nn \box_autosize_to_wd_and_ht:Nnn { c }
+\cs_generate_variant:Nn \box_gautosize_to_wd_and_ht:Nnn { c }
 \cs_new_protected:Npn \box_autosize_to_wd_and_ht_plus_dp:Nnn #1#2#3
   {
     \@@_autosize:NnnnN #1 {#2} {#3} { \box_ht:N #1 + \box_dp:N #1 }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -128,7 +128,7 @@
 %   environment in a coffin.
 % \end{function}
 %
-% \begin{function}[added = 2011-08-17, updated = 2019-01-21]
+% \begin{function}[added = 2011-08-17, updated = 2023-02-03]
 %   {
 %     \vcoffin_set:Nnn, \vcoffin_set:cnn,
 %     \vcoffin_gset:Nnn, \vcoffin_gset:cnn
@@ -142,7 +142,7 @@
 %   size of the typeset material.
 % \end{function}
 %
-% \begin{function}[added = 2011-09-10, updated = 2019-01-21]
+% \begin{function}[added = 2011-09-10, updated = 2023-02-03]
 %   {
 %     \vcoffin_set:Nnw, \vcoffin_set:cnw, \vcoffin_set_end:,
 %     \vcoffin_gset:Nnw, \vcoffin_gset:cnw, \vcoffin_gset_end:
@@ -702,7 +702,7 @@
 %     \vcoffin_set:Nnn, \vcoffin_set:cnn,
 %     \vcoffin_gset:Nnn, \vcoffin_gset:cnn
 %   }
-%  \begin{macro}{\@@_set_vertical:NnnNN}
+%  \begin{macro}{\@@_set_vertical:NnnNNN}
 %  \begin{macro}{\@@_set_vertical_aux:}
 %   Setting vertical coffins is more complex. First, the material is
 %   typeset with a given width. The default handles and poles are set as
@@ -714,17 +714,17 @@
 %    \begin{macrocode}
 \cs_new_protected:Npn \vcoffin_set:Nnn #1#2#3
   {
-    \@@_set_vertical:NnnNN #1 {#2} {#3}
-      \vbox_set:Nn \coffin_reset_poles:N
+    \@@_set_vertical:NnnNNN #1 {#2} {#3}
+      \vbox_set:Nn \coffin_reset_poles:N \@@_set_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_set:Nnn { c }
 \cs_new_protected:Npn \vcoffin_gset:Nnn #1#2#3
   {
-    \@@_set_vertical:NnnNN #1 {#2} {#3}
-      \vbox_gset:Nn \coffin_greset_poles:N
+    \@@_set_vertical:NnnNNN #1 {#2} {#3}
+      \vbox_gset:Nn \coffin_greset_poles:N \@@_gset_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_gset:Nnn { c }
-\cs_new_protected:Npn \@@_set_vertical:NnnNN #1#2#3#4#5
+\cs_new_protected:Npn \@@_set_vertical:NnnNNN #1#2#3#4#5#6
   {
     \@@_if_exist:NT #1
       {
@@ -736,7 +736,7 @@
           }
         #5 #1
         \vbox_set_top:Nn \l_@@_internal_box { \vbox_unpack:N #1 }
-        \@@_set_pole:Nne #1 { T }
+        #6 #1 { T }
           {
             { 0pt }
             {
@@ -804,25 +804,25 @@
 %
 % \begin{macro}
 %   {\vcoffin_set:Nnw, \vcoffin_set:cnw, \vcoffin_gset:Nnw, \vcoffin_gset:cnw}
-% \begin{macro}{\@@_set_vertical:NnNNNNw}
+% \begin{macro}{\@@_set_vertical:NnNNNNNw}
 % \begin{macro}{\vcoffin_set_end:, \vcoffin_gset_end:}
 %   The same for vertical coffins.
 %    \begin{macrocode}
 \cs_new_protected:Npn \vcoffin_set:Nnw #1#2
   {
-    \@@_set_vertical:NnNNNNw #1 {#2} \vbox_set:Nw
+    \@@_set_vertical:NnNNNNNw #1 {#2} \vbox_set:Nw
       \vcoffin_set_end:
-      \vbox_set_end: \coffin_reset_poles:N
+      \vbox_set_end: \coffin_reset_poles:N \@@_set_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_set:Nnw { c }
 \cs_new_protected:Npn \vcoffin_gset:Nnw #1#2
   {
-    \@@_set_vertical:NnNNNNw #1 {#2} \vbox_gset:Nw
+    \@@_set_vertical:NnNNNNNw #1 {#2} \vbox_gset:Nw
       \vcoffin_gset_end:
-      \vbox_gset_end: \coffin_greset_poles:N
+      \vbox_gset_end: \coffin_greset_poles:N \@@_gset_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_gset:Nnw { c }
-\cs_new_protected:Npn \@@_set_vertical:NnNNNNw #1#2#3#4#5#6
+\cs_new_protected:Npn \@@_set_vertical:NnNNNNNw #1#2#3#4#5#6#7
   {
     \@@_if_exist:NT #1
       {
@@ -834,7 +834,7 @@
               #5
               #6 #1
               \vbox_set_top:Nn \l_@@_internal_box { \vbox_unpack:N #1 }
-              \@@_set_pole:Nne #1 { T }
+              #7 #1 { T }
                 {
                   { 0pt }
                   {
@@ -989,7 +989,7 @@
 %     \coffin_gset_vertical_pole:Nnn, \coffin_gset_vertical_pole:cnn
 %   }
 % \begin{macro}{\@@_set_vertical_pole:NnnN}
-% \begin{macro}{\@@_set_pole:Nnn, \@@_set_pole:Nne}
+% \begin{macro}{\@@_set_pole:Nnn, \@@_gset_pole:Nnn}
 %   Setting the pole of a coffin at the user/designer level requires a
 %   bit more care. The idea here is to provide a reasonable interface to
 %   the system, then to do the setting with full expansion. The
@@ -1033,10 +1033,14 @@
   }
 \cs_new_protected:Npn \@@_set_pole:Nnn #1#2#3
   {
-    \prop_put:cnn { coffin ~ \@@_to_value:N #1 ~ poles }
+    \prop_put:cne { coffin ~ \@@_to_value:N #1 ~ poles }
       {#2} {#3}
   }
-\cs_generate_variant:Nn \@@_set_pole:Nnn { Nne }
+\cs_new_protected:Npn \@@_gset_pole:Nnn #1#2#3
+  {
+    \prop_gput:cne { coffin ~ \@@_to_value:N #1 ~ poles }
+      {#2} {#3}
+  }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
@@ -2068,7 +2072,7 @@
     \tl_if_in:nnTF {#2} { - }
       { \tl_set:Nn \l_@@_internal_tl { {#2} } }
       { \tl_set:Nn \l_@@_internal_tl { { #1 - #2 } } }
-    \exp_last_unbraced:NNo \@@_set_pole:Nne \l_@@_aligned_coffin
+    \exp_last_unbraced:NNo \@@_set_pole:Nnn \l_@@_aligned_coffin
       { \l_@@_internal_tl }
       {
         { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim }
@@ -2130,11 +2134,11 @@
   {
     \dim_compare:nNnTF {#2} < {#6}
       {
-        \@@_set_pole:Nne #9 { T }
+        \@@_set_pole:Nnn #9 { T }
           { { 0pt } {#6} { 1000pt } { 0pt } }
       }
       {
-        \@@_set_pole:Nne #9 { T }
+        \@@_set_pole:Nnn #9 { T }
           { { 0pt } {#2} { 1000pt } { 0pt } }
       }
   }
@@ -2142,11 +2146,11 @@
   {
     \dim_compare:nNnTF {#2} < {#6}
       {
-        \@@_set_pole:Nne #9 { B }
+        \@@_set_pole:Nnn #9 { B }
           { { 0pt } {#2}  { 1000pt } { 0pt } }
       }
       {
-        \@@_set_pole:Nne #9 { B }
+        \@@_set_pole:Nnn #9 { B }
           { { 0pt } {#6} { 1000pt } { 0pt } }
       }
   }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -55,72 +55,10 @@
 %
 % \section{\pkg{l3debug} implementation}
 %
-% \begin{function}{\__kernel_chk_var_local:N, \__kernel_chk_var_global:N}
-%   \begin{syntax}
-%     \cs{__kernel_chk_var_local:N} \meta{var}
-%     \cs{__kernel_chk_var_global:N} \meta{var}
-%   \end{syntax}
-%   Applies \cs{__kernel_chk_var_exist:N} \meta{var} as well as
-%   \cs{__kernel_chk_var_scope:NN} \meta{scope} \meta{var}, where
-%   \meta{scope} is |l| or~|g|.
-% \end{function}
+% Internal kernel functions that are only defined here are listed in
+% \pkg{l3kernel-functions},
+% see~\ref{sec:l3kernel-functions:l3debug-internals}.
 %
-% \begin{function}{\__kernel_chk_var_scope:NN}
-%   \begin{syntax}
-%     \cs{__kernel_chk_var_scope:NN} \meta{scope} \meta{var}
-%   \end{syntax}
-%   Checks the \meta{var} has the correct \meta{scope}, and if not
-%   raises a kernel-level error.  This function is only created if
-%   debugging is enabled.  The \meta{scope} is a single letter |l|, |g|,
-%   |c| denoting local variables, global variables, or constants.  More
-%   precisely, if the variable name starts with a letter and an
-%   underscore (normal \pkg{expl3} convention) the function checks that
-%   this single letter matches the \meta{scope}.  Otherwise the function
-%   cannot know the scope \meta{var} the first time: instead, it defines
-%   |\__debug_chk_/|\meta{var name} to store that information for the
-%   next call.  Thus, if a given \meta{var} is subject to assignments of
-%   different scopes a kernel error will result.
-% \end{function}
-%
-% \begin{function}
-%   {
-%     \__kernel_chk_cs_exist:N,
-%     \__kernel_chk_cs_exist:c,
-%     \__kernel_chk_var_exist:N
-%   }
-%   \begin{syntax}
-%     \cs{__kernel_chk_cs_exist:N} \meta{cs}
-%     \cs{__kernel_chk_var_exist:N} \meta{var}
-%   \end{syntax}
-%   These functions are only created if debugging is enabled.  They
-%   check that their argument is defined according to the criteria for
-%   \cs{cs_if_exist_p:N},
-%   and if not raises a kernel-level error.  Error messages are
-%   different.
-% \end{function}
-%
-% \begin{function}[EXP]{\__kernel_chk_flag_exist:NN}
-%   \begin{syntax}
-%     \cs{__kernel_chk_flag_exist:NN}
-%     \meta{function} \meta{flag}
-%   \end{syntax}
-%   This function is only created if debugging is enabled.  It checks
-%   that the \meta{flag} is defined according to the criterion for
-%   \cs{flag_if_exist_p:N}, and if not raises a kernel-level error and
-%   calls the function with the argument \cs{l_tmpa_flag} to proceed
-%   somehow without producing too many errors.
-% \end{function}
-%
-% \begin{function}{\__kernel_debug_log:e}
-%   \begin{syntax}
-%     \cs{__kernel_debug_log:e} \Arg{message text}
-%   \end{syntax}
-%   If the \texttt{log-functions} option is active, this function writes
-%   the \meta{message text} to the log file using \cs{iow_log:e}.
-%   Otherwise, the \meta{message text} is ignored using \cs{use_none:n}.
-%   This function is only created if debugging is enabled.
-% \end{function}
-%
 %    \begin{macrocode}
 %<*package>
 %    \end{macrocode}
@@ -131,7 +69,7 @@
 %
 % Standard file identification.
 %    \begin{macrocode}
-\ProvidesExplFile{l3debug.def}{2024-01-22}{}{L3 Debugging support}
+\ProvidesExplFile{l3debug.def}{2024-02-13}{}{L3 Debugging support}
 %    \end{macrocode}
 %
 % \begin{variable}{\s_@@_stop}
@@ -782,6 +720,8 @@
     {
       \clist_concat:NNN
       \clist_gconcat:NNN
+      \prop_concat:NNN
+      \prop_gconcat:NNN
       \seq_concat:NNN
       \seq_gconcat:NNN
       \str_concat:NNN
@@ -836,6 +776,17 @@
       \muskip_add:Nn
       \muskip_sub:Nn
       \muskip_set_eq:NN
+      \prop_clear:N
+      \prop_concat:NNN
+      \prop_pop:NnN
+      \prop_pop:NnNT
+      \prop_pop:NnNF
+      \prop_pop:NnNTF
+      \prop_put:Nnn
+      \prop_put_if_new:Nnn
+      \prop_put_from_keyval:Nn
+      \prop_remove:Nn
+      \prop_set_eq:NN
       \seq_set_eq:NN
       \skip_zero:N
       \skip_set:Nn
@@ -905,6 +856,17 @@
       \muskip_gadd:Nn
       \muskip_gsub:Nn
       \muskip_gset_eq:NN
+      \prop_gclear:N
+      \prop_gconcat:NNN
+      \prop_gpop:NnN
+      \prop_gpop:NnNT
+      \prop_gpop:NnNF
+      \prop_gpop:NnNTF
+      \prop_gput:Nnn
+      \prop_gput_if_new:Nnn
+      \prop_gput_from_keyval:Nn
+      \prop_gremove:Nn
+      \prop_gset_eq:NN
       \seq_gset_eq:NN
       \skip_gzero:N
       \skip_gset:Nn
@@ -952,6 +914,8 @@
       \int_const:Nn
       \intarray_const_from_clist:Nn
       \muskip_const:Nn
+      \prop_const_from_keyval:Nn
+      \prop_const_linked_from_keyval:Nn
       \skip_const:Nn
       \str_const:Nn
       \tl_const:Nn

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -313,6 +313,21 @@
 % \end{macro}
 % \end{macro}
 %
+% \subsection{Deprecated \pkg{l3msg} functions}
+%
+%    \begin{macrocode}
+%<@@=msg>
+%    \end{macrocode}
+%
+% \begin{macro}[deprecated]{\msg_gset:nnnn, \msg_gset:nnn}
+%    \begin{macrocode}
+\__kernel_patch_deprecation:nnNNpn { 2024-01-17 } { \msg_set:nnnn }
+\cs_new_protected:Npn \msg_gset:nnnn { \msg_set:nnnn }
+\__kernel_patch_deprecation:nnNNpn { 2024-01-17 } { \msg_set:nnn }
+\cs_new_protected:Npn \msg_gset:nnn { \msg_set:nnn }
+%    \end{macrocode}
+% \end{macro}
+%
 % \subsection{Deprecated \pkg{l3pdf} functions}
 %
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -71,7 +71,7 @@
 % This isn't included in the typeset documentation because it's a bit
 % ugly:
 %<*class>
-\ProvidesExplClass{l3doc}{2024-01-22}{}
+\ProvidesExplClass{l3doc}{2024-02-13}{}
   {L3 Experimental documentation class}
 %</class>
 % \fi
@@ -84,7 +84,7 @@
 %    require you to do updates, if the class changes.}}
 %
 % \author{\Team}
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 % \maketitle
 % \tableofcontents
 %
@@ -1740,7 +1740,7 @@
 %   A document-level command.
 %    \begin{macrocode}
 \DeclareDocumentCommand \meta { m }
-  { \@@_meta:n {#1} }
+  { \texttt{ \@@_meta:n {#1} } }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -1760,10 +1760,11 @@
   { < \tl_to_str:n {#1} > }
 \pdfstringdefDisableCommands
   {
-    \cs_set_eq:NN \cmd  \@@_pdfstring_cmd:w
-    \cs_set_eq:NN \cs   \@@_pdfstring_cs:w
-    \cs_set_eq:NN \tn   \@@_pdfstring_cs:w
-    \cs_set_eq:NN \meta \@@_pdfstring_meta:w
+    \cs_set_eq:NN \cmd       \@@_pdfstring_cmd:w
+    \cs_set_eq:NN \cs        \@@_pdfstring_cs:w
+    \cs_set_eq:NN \tn        \@@_pdfstring_cs:w
+    \cs_set_eq:NN \meta      \@@_pdfstring_meta:w
+    \cs_set_eq:NN \@@_meta:n \@@_pdfstring_meta:w
   }
 %    \end{macrocode}
 % \end{macro}
@@ -1775,10 +1776,10 @@
 %   Finally, \cs{Arg} is the same as \cs{marg}.
 %    \begin{macrocode}
 \newcommand\Arg[1]
-  { \texttt{\char`\{} \meta{#1} \texttt{\char`\}} }
+  { \texttt{\char`\{} \@@_meta:n {#1} \texttt{\char`\}} }
 \providecommand\marg[1]{ \Arg{#1} }
-\providecommand\oarg[1]{ \texttt[ \meta{#1} \texttt] }
-\providecommand\parg[1]{ \texttt( \meta{#1} \texttt) }
+\providecommand\oarg[1]{ \texttt[ \@@_meta:n {#1} \texttt] }
+\providecommand\parg[1]{ \texttt( \@@_meta:n {#1} \texttt) }
 %    \end{macrocode}
 % \end{macro}
 %
@@ -3727,10 +3728,22 @@
 %
 % \begin{macro}{\DocInclude}
 %   More or less exactly the same as \tn{include}, but uses
-%   \tn{DocInput} on a \file{.dtx} file, not \tn{input} on a \file{.tex}
-%   file.
+%   \tn{DocInput} on a \file{.fdd} or \file{.dtx} file (without file
+%   extension), not \tn{input} on a \file{.tex} file.
 %
 %    \begin{macrocode}
+\msg_new:nnn { l3doc } { missing-endgroup }
+  {
+    \str_if_eq:VnTF \@currenvir { document }
+      {
+        There~are~\int_use:N \tex_currentgrouplevel:D
+        \c_space_tl unclosed~groups~in~#1.
+      }
+      {
+        The~\@currenvir \c_space_tl environment~on~line~\@currenvline
+        \c_space_tl doesn't~have~a~matching~\iow_char:N\\end{\@currenvir}.
+      }
+  }
 \NewDocumentCommand \DocInclude { m }
   {
     \relax\clearpage
@@ -3741,6 +3754,13 @@
     \int_compare:nNnTF \@auxout = \@partaux
       { \@latexerr{\string\include\space cannot~be~nested}\@eha }
       { \@docinclude {#1} }
+    % check missing \endgroup, e.g., missing "\end{macro}" in time
+    \int_compare:nNnF { \tex_currentgrouplevel:D } = { 0 }
+      {
+        \int_compare:nNnT { \tex_interactionmode:D } = { 0 }
+          { \int_gset:Nn \tex_interactionmode:D { 1 } }
+        \msg_fatal:nne { l3doc } { missing-endgroup } { \currentfile }
+      }
   }
 %    \end{macrocode}
 %
@@ -4087,7 +4107,7 @@
             \int_set:Nn \l_@@_tmpa_int { \tex_interactionmode:D }
             \errorstopmode
             \ClassError { l3doc } { \l_@@_tmpa_tl } { }
-            \int_set:Nn \tex_interactionmode:D { \l_@@_tmpa_int }
+            \int_gset:Nn \tex_interactionmode:D { \l_@@_tmpa_int }
           }
       }
   }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -63,7 +63,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -571,6 +571,7 @@
 %     \exp_last_unbraced:Nco,
 %     \exp_last_unbraced:NcV,
 %     \exp_last_unbraced:Nno,
+%     \exp_last_unbraced:Nnf,
 %     \exp_last_unbraced:Noo,
 %     \exp_last_unbraced:Nfo,
 %     \exp_last_unbraced:NNNo,
@@ -1436,6 +1437,7 @@
 %     \exp_last_unbraced:NNNV,
 %     \exp_last_unbraced:NNNf,
 %     \exp_last_unbraced:Nno,
+%     \exp_last_unbraced:Nnf,
 %     \exp_last_unbraced:Noo,
 %     \exp_last_unbraced:Nfo,
 %     \exp_last_unbraced:NnNo,
@@ -1494,6 +1496,7 @@
     \exp:w \exp_end_continue_f:w #4
   }
 \cs_new:Npn \exp_last_unbraced:Nno { \::n \::o_unbraced \::: }
+\cs_new:Npn \exp_last_unbraced:Nnf { \::n \::f_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:Noo { \::o \::o_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:Nfo { \::f \::o_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:NnNo { \::n \::N \::o_unbraced \::: }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -718,10 +718,8 @@
 %     \cs{file_if_exist_p:n} \Arg{file name}
 %     \cs{file_if_exist:nTF} \Arg{file name} \Arg{true code} \Arg{false code}
 %   \end{syntax}
-%   Expands the argument of the \cs{file name} to give a string, then
-%   searches for this string using the current \TeX{} search
-%   path and the additional paths controlled by
-%   \cs{l_file_search_path_seq}.
+%   Tests if \meta{file name} is found in the path as detailed for
+%   \cs{file_if_exist:nTF}.
 % \end{function}
 %
 % \subsection{Information about files and file contents}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 % \maketitle
 %
 % \begin{documentation}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 % \maketitle
 %
 % \begin{documentation}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -49,7 +49,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -55,17 +55,77 @@
 %
 % \section{\pkg{l3kernel-functions}: kernel-reserved functions}
 %
-% \subsection{Internal kernel functions}
+% \subsection{Internal \pkg{l3debug} kernel functions}
+% \label{sec:l3kernel-functions:l3debug-internals}
 %
-% \begin{function}{\__kernel_chk_cs_exist:N, \__kernel_chk_cs_exist:c}
+% These function are only created if debugging is enabled, hence they are
+% actually defined in \pkg{l3debug}.
+%
+% \begin{function}{\__kernel_chk_var_local:N, \__kernel_chk_var_global:N}
 %   \begin{syntax}
+%     \cs{__kernel_chk_var_local:N} \meta{var}
+%     \cs{__kernel_chk_var_global:N} \meta{var}
+%   \end{syntax}
+%   Applies \cs{__kernel_chk_var_exist:N} \meta{var} as well as
+%   \cs{__kernel_chk_var_scope:NN} \meta{scope} \meta{var}, where
+%   \meta{scope} is |l| or~|g|.
+% \end{function}
+%
+% \begin{function}{\__kernel_chk_var_scope:NN}
+%   \begin{syntax}
+%     \cs{__kernel_chk_var_scope:NN} \meta{scope} \meta{var}
+%   \end{syntax}
+%   Checks the \meta{var} has the correct \meta{scope}, and if not
+%   raises a kernel-level error.
+%   The \meta{scope} is a single letter |l|, |g|,
+%   |c| denoting local variables, global variables, or constants.  More
+%   precisely, if the variable name starts with a letter and an
+%   underscore (normal \pkg{expl3} convention) the function checks that
+%   this single letter matches the \meta{scope}.  Otherwise the function
+%   cannot know the scope \meta{var} the first time: instead, it defines
+%   |\__debug_chk_/|\meta{var name} to store that information for the
+%   next call.  Thus, if a given \meta{var} is subject to assignments of
+%   different scopes a kernel error will result.
+% \end{function}
+%
+% \begin{function}
+%   {
+%     \__kernel_chk_cs_exist:N,
+%     \__kernel_chk_cs_exist:c,
+%     \__kernel_chk_var_exist:N
+%   }
+%   \begin{syntax}
 %     \cs{__kernel_chk_cs_exist:N} \meta{cs}
+%     \cs{__kernel_chk_var_exist:N} \meta{var}
 %   \end{syntax}
-%   This function is only created if debugging is enabled.  It checks
-%   that \meta{cs} exists according to the criteria for
-%   \cs{cs_if_exist_p:N}, and if not raises a kernel-level error.
+%   Checks that their argument is defined according to the criteria for
+%   \cs{cs_if_exist_p:N},
+%   and if not raises a kernel-level error.  Error messages are
+%   different.
 % \end{function}
 %
+% \begin{function}[EXP]{\__kernel_chk_flag_exist:NN}
+%   \begin{syntax}
+%     \cs{__kernel_chk_flag_exist:NN}
+%     \meta{function} \meta{flag}
+%   \end{syntax}
+%   Checks that the \meta{flag} is defined according to the criterion for
+%   \cs{flag_if_exist_p:N}, and if not raises a kernel-level error and
+%   calls the function with the argument \cs{l_tmpa_flag} to proceed
+%   somehow without producing too many errors.
+% \end{function}
+%
+% \begin{function}{\__kernel_debug_log:e}
+%   \begin{syntax}
+%     \cs{__kernel_debug_log:e} \Arg{message text}
+%   \end{syntax}
+%   If the \texttt{log-functions} option is active, this function writes
+%   the \meta{message text} to the log file using \cs{iow_log:e}.
+%   Otherwise, the \meta{message text} is ignored using \cs{use_none:n}.
+% \end{function}
+%
+% \subsection{Internal kernel functions}
+%
 % \begin{function}{\__kernel_chk_defined:NT}
 %   \begin{syntax}
 %     \cs{__kernel_chk_defined:NT} \meta{variable} \Arg{true code}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -89,7 +89,7 @@
 % \end{verbatim}
 % As illustrated, keys are created inside a \meta{module}: a set of related
 % keys, typically those for a single module/\LaTeXe{} package. See
-% Section~\label{sec:l3keys:subdivision} for suggestions on how to divide
+% Section~\ref{sec:l3keys:subdivision} for suggestions on how to divide
 % large numbers of keys for a single module.
 %
 % At a document level, \cs{keys_set:nn} is used within a
@@ -3504,7 +3504,7 @@
       { \prg_return_true: }
       { \prg_return_false: }
   }
-\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
 %    \end{macrocode}
 % \end{macro}
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -120,7 +120,7 @@
 %   used. An error is raised if the \meta{message} already exists.
 % \end{function}
 %
-% \begin{function}{\msg_set:nnnn, \msg_set:nnn, \msg_gset:nnnn, \msg_gset:nnn}
+% \begin{function}{\msg_set:nnnn, \msg_set:nnn}
 %   \begin{syntax}
 %     \cs{msg_set:nnnn} \Arg{module} \Arg{message} \Arg{text} \Arg{more text}
 %   \end{syntax}
@@ -809,15 +809,17 @@
 %     \msg_new:nnnn, \msg_new:nnee, \msg_new:nnxx,
 %     \msg_new:nnn,  \msg_new:nne,  \msg_new:nnx
 %   }
-% \begin{macro}{\msg_gset:nnnn, \msg_gset:nnn}
 % \begin{macro}{\msg_set:nnnn, \msg_set:nnn}
 %   Setting a message simply means saving the appropriate text
 %   into two functions. A sanity check first.
 %    \begin{macrocode}
-\cs_new_protected:Npn \msg_new:nnnn #1#2
+\cs_new_protected:Npn \msg_new:nnnn #1#2#3#4
   {
     \@@_chk_free:nn {#1} {#2}
-    \msg_gset:nnnn {#1} {#2}
+    \cs_gset:cpn { \c_@@_text_prefix_tl #1 / #2 }
+      ##1##2##3##4 {#3}
+    \cs_gset:cpn { \c_@@_more_text_prefix_tl #1 / #2 }
+      ##1##2##3##4 {#4}
   }
 \cs_generate_variant:Nn \msg_new:nnnn { nnee , nnxx }
 \cs_new_protected:Npn \msg_new:nnn #1#2#3
@@ -832,19 +834,9 @@
   }
 \cs_new_protected:Npn \msg_set:nnn #1#2#3
   { \msg_set:nnnn {#1} {#2} {#3} { } }
-\cs_new_protected:Npn \msg_gset:nnnn #1#2#3#4
-  {
-    \cs_gset:cpn { \c_@@_text_prefix_tl #1 / #2 }
-      ##1##2##3##4 {#3}
-    \cs_gset:cpn { \c_@@_more_text_prefix_tl #1 / #2 }
-      ##1##2##3##4 {#4}
-  }
-\cs_new_protected:Npn \msg_gset:nnn #1#2#3
-  { \msg_gset:nnnn {#1} {#2} {#3} { } }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
-% \end{macro}
 %
 % \subsection{Messages: support functions and text}
 %
@@ -2227,6 +2219,14 @@
           { internal~structure:\\\\\iow_indent:n {#4} }
       }
   }
+\msg_new:nnnn { prop } { bad-link }
+  { Variable~'#1'~is~not~a~valid~(linked)~prop. }
+  {
+    \c_@@_coding_error_text_tl
+    The~variable~'#1'~has~an~incorrect~internal~structure.~
+    Its~internal~entry~'#2'~points~to~'#3',~whose~name~is~not~of~the~
+    form~'#4~<key>'.
+  }
 \msg_new:nnnn { clist } { non-clist }
   { Variable~'#1'~is~not~a~valid~clist. }
   {
@@ -2236,6 +2236,24 @@
     should~be~a~clist~variable,~but~it~includes~empty~or~blank~items~
     without~braces.
   }
+\msg_new:nnnn { prop } { misused }
+  { A~property~list~was~misused. }
+  {
+    \c_@@_coding_error_text_tl
+    A~property~list~variable~was~used~without~an~accessor~function.~
+    It~
+    \tl_if_empty:nTF {#1}
+      { is~empty. }
+      { contains~the~key-value~pairs \use_none:n #1 . }
+  }
+\msg_new:nnnn { prop } { inner-make }
+  { '#1'~ cannot~ be~ used~ in~ a~ group. }
+  {
+    \c_@@_coding_error_text_tl
+    The~ command~ '#1'~ was~ applied~ to~ the~ property~ list~
+    variable~ '#2', but~ the~ storage~ type~ can~ only~ be~ changed~
+    at~ the~ outermost~ group~ level.
+  }
 %    \end{macrocode}
 %
 % Some errors only appear in expandable settings,
@@ -2247,8 +2265,6 @@
   { Erroneous~variable~#1 used! }
 \msg_new:nnn { seq } { misused }
   { A~sequence~was~misused. }
-\msg_new:nnn { prop } { misused }
-  { A~property~list~was~misused. }
 \msg_new:nnn { prg } { negative-replication }
   { Negative~argument~for~\iow_char:N\\prg_replicate:nn. }
 \msg_new:nnn { prop } { prop-keyval }
@@ -2272,7 +2288,8 @@
   { The~integer~array~#1~contains~#2~items: \\ #3 . }
 \msg_new:nnn { prop } { show }
   {
-    The~property~list~#1~
+    The~ \str_if_eq:nnF {#3} { flat } { #3~ }
+    property~list~#1~
     \tl_if_empty:nTF {#2}
       { is~empty \\>~ . }
       { contains~the~pairs~(without~outer~braces): #2 . }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,19 +43,19 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
 % \begin{documentation}
 %
-% \pkg{expl3} implements a property list data type, which contain
-% an unordered list of entries each of which consists of a \meta{key} and
-% an associated \meta{value}. The \meta{key} and \meta{value} may both
-% be any balanced text, and the \meta{key} is processed using
-% \cs{tl_to_str:n}, meaning that category codes are ignored. It is possible to
-% map functions to property lists such that the function is applied to every
-% key--value pair within the list.
+% \pkg{expl3} implements a \enquote{property list} data type, which contain
+% an unordered list of entries each of which consists of a \meta{key} (string)
+% and an associated \meta{value} (token list). The \meta{key} and \meta{value}
+% may both be given as any balanced text, and the \meta{key} is processed using
+% \cs{tl_to_str:n}, meaning that category codes are ignored. Entries can be
+% manipulated individually, as well as collectively by applying a function to
+% every key--value pair within the list.
 %
 % Each entry in a property list must have a unique \meta{key}: if an entry is
 % added to a property list which already contains the \meta{key} then the new
@@ -63,9 +63,42 @@
 % string basis, using the same method as \cs{str_if_eq:nnTF}.
 %
 % Property lists are intended for storing key-based information for use within
-% code.  This is in contrast to key--value lists, which are a form of
-% \emph{input} parsed by the \pkg{l3keys} module.
+% code. They can be converted from and to key--value lists, which are a form of
+% \emph{input} parsed by the \pkg{l3keys} module.  If a key--value list contains
+% a \meta{key} multiple times, only the last \meta{value} associated to it will
+% be kept in the conversion to a property list.
 %
+% Internally, property lists can use two distinct implementations with different
+% data storage, which are decided when declaring the property list variable
+% using \cs{prop_new:N} (\enquote{flat} storage) or \cs{prop_new_linked:N}
+% (\enquote{linked} storage). After a property list is declared with
+% \cs{prop_new:N} or \cs{prop_new_linked:N}, the type of internal data storage
+% can be changed by \cs{prop_make_flat:N} or \cs{prop_make_linked:N}, but only
+% at the outermost group level. All other \pkg{l3prop} functions transparently
+% manipulate either storage method and convert as needed.
+% \begin{itemize}
+%   \item
+%     The (default) \enquote{flat} storage method is suited for a relatively
+%     small number of entries, or when the property list is likely to be
+%     manipulated (copied, mapped) as a whole rather than entry-wise.  It is
+%     significantly faster for \cs{prop_set_eq:NN}, and only slightly faster for
+%     \cs{prop_clear:N}, \cs{prop_concat:NNN}, and mapping functions
+%     \cs[no-index]{prop_map_\ldots{}}.
+%
+%   \item
+%     The \enquote{linked} storage method is meant for property lists with a
+%     large numbers of entries.  It takes up more of \TeX{}'s memory during a run, but is
+%     significantly faster (for long lists) when accessing or modifying
+%     individual entries using functions such as \cs{prop_if_in:Nn},
+%     \cs{prop_item:Nn}, \cs{prop_put:Nnn}, \cs{prop_get:NnN},
+%     \cs{prop_pop:NnN}, \cs{prop_remove:Nn}, as it takes a constant
+%     time for these operations (rather than the number of items for a
+%     \enquote{flat} property list).  A technical drawback is that
+%     memory is permanently used\footnote{Until the end of the run, that
+%     is.} by \meta{keys} stored in a \enquote{linked} property list,
+%     even after they are removed and the property list is deleted.
+% \end{itemize}
+%
 % \section{Creating and initialising property lists}
 %
 % \begin{function}{\prop_new:N, \prop_new:c}
@@ -72,11 +105,22 @@
 %   \begin{syntax}
 %     \cs{prop_new:N} \meta{property list}
 %   \end{syntax}
-%   Creates a new \meta{property list} or raises an error if the name is
-%   already taken. The declaration is global. The \meta{property list}
-%   initially contains no entries.
+%   Creates a new \enquote{flat} \meta{property list} or raises an error if the
+%   name is already taken. The declaration is global. The \meta{property list}
+%   initially contains no entries.  See also \cs{prop_new_linked:N}.
 % \end{function}
 %
+% \begin{function}[added = 2024-02-12]{\prop_new_linked:N, \prop_new_linked:c}
+%   \begin{syntax}
+%     \cs{prop_new_linked:N} \meta{property list}
+%   \end{syntax}
+%   Creates a new \enquote{linked} \meta{property list} or raises an error if
+%   the name is already taken. The declaration is global. The \meta{property
+%   list} initially contains no entries.  The internal data storage differs from
+%   that produced by \cs{prop_new:N} and it is optimized for property lists with
+%   a large number of entries.
+% \end{function}
+%
 % \begin{function}
 %   {\prop_clear:N, \prop_clear:c, \prop_gclear:N, \prop_gclear:c}
 %   \begin{syntax}
@@ -95,10 +139,30 @@
 %   \end{syntax}
 %   Ensures that the \meta{property list} exists globally by applying
 %   \cs{prop_new:N} if necessary, then applies
-%   \cs[index=prop_clear:N]{prop_(g)clear:N} to leave
-%   the list empty.
+%   \cs[index=prop_clear:N]{prop_(g)clear:N} to leave the list empty.
+%   \begin{texnote}
+%     If the property list exists and is of \enquote{linked} type, it
+%     is cleared but not made into a flat property list.
+%   \end{texnote}
 % \end{function}
 %
+% \begin{function}[added = 2024-02-12]
+%   {
+%     \prop_clear_new_linked:N,  \prop_clear_new_linked:c,
+%     \prop_gclear_new_linked:N, \prop_gclear_new_linked:c
+%   }
+%   \begin{syntax}
+%     \cs{prop_clear_new_linked:N} \meta{property list}
+%   \end{syntax}
+%   Ensures that the \meta{property list} exists globally by applying
+%   \cs{prop_new_linked:N} if necessary, then applies
+%   \cs[index=prop_clear:N]{prop_(g)clear:N} to leave the list empty.
+%   \begin{texnote}
+%     If the property list exists and is of \enquote{flat} type, it
+%     is cleared but not made into a linked property list.
+%   \end{texnote}
+% \end{function}
+%
 % \begin{function}
 %   {
 %     \prop_set_eq:NN,  \prop_set_eq:cN,  \prop_set_eq:Nc,  \prop_set_eq:cc,
@@ -107,8 +171,8 @@
 %   \begin{syntax}
 %     \cs{prop_set_eq:NN} \meta{property list_1} \meta{property list_2}
 %   \end{syntax}
-%   Sets the content of \meta{property list_1} equal to that of
-%   \meta{property list_2}.
+%   Sets the content of \meta{property list_1} equal to that of \meta{property
+%   list_2}.  This converts as needed between the two storage types.
 % \end{function}
 %
 % \begin{function}[added = 2017-11-28, updated = 2021-11-07]
@@ -125,6 +189,9 @@
 %   \end{syntax}
 %   Sets \meta{property list} to contain key--value pairs given in the second
 %   argument.  If duplicate keys appear only the last of the values is kept.
+%   In contrast to most keyval lists (\emph{e.g.}~those in \pkg{l3keys}), each
+%   key here \emph{must} be followed with an \texttt{=} sign even to specify an
+%   empty \meta{value}.
 %
 %   Spaces are trimmed around every \meta{key} and every \meta{value},
 %   and if the result of trimming spaces consists of a single brace
@@ -133,10 +200,6 @@
 %   signs.  The \meta{key} is then processed by \cs{tl_to_str:n}.
 %   This function correctly detects the |=| and |,| signs provided they
 %   have the standard category code~$12$ or they are active.
-%
-%   Notice that in contrast to most keyval lists (\emph{e.g.}~those in
-%   \pkg{l3keys}), each key here \emph{must} be followed with an \texttt{=}
-%   sign.
 % \end{function}
 %
 % \begin{function}[added = 2017-11-28, updated = 2021-11-07]
@@ -148,7 +211,8 @@
 %         \meta{key2} |=| \meta{value2} |,| \ldots{}
 %       \}
 %   \end{syntax}
-%   Creates a new constant \meta{property list} or raises an error if the
+%   Creates a new constant \enquote{flat} \meta{property list} or raises
+%   an error if the
 %   name is already taken. The \meta{property list} is set globally to
 %   contain key--value pairs given in the second argument, processed in
 %   the way described for \cs{prop_set_from_keyval:Nn}.  If duplicate
@@ -155,12 +219,49 @@
 %   keys appear only the last of the values is kept.
 %   This function correctly detects the |=| and |,| signs provided they
 %   have the standard category code~$12$ or they are active.
+% \end{function}
 %
-%   Notice that in contrast to most keyval lists (\emph{e.g.}~those in
-%   \pkg{l3keys}), each key here \emph{must} be followed with an \texttt{=}
-%   sign.
+% \begin{function}[added = 2024-02-12]
+%   {\prop_const_linked_from_keyval:Nn, \prop_const_linked_from_keyval:cn}
+%   \begin{syntax}
+%     \cs{prop_const_linked_from_keyval:Nn} \meta{prop~var}
+%       \{
+%         \meta{key1} |=| \meta{value1} |,|
+%         \meta{key2} |=| \meta{value2} |,| \ldots{}
+%       \}
+%   \end{syntax}
+%   Creates a new constant \enquote{linked} \meta{prop~var} or raises an
+%   error if the
+%   name is already taken. The \meta{prop~var} is set globally to
+%   contain key--value pairs given in the second argument, processed in
+%   the way described for \cs{prop_set_from_keyval:Nn}.  If duplicate
+%   keys appear only the last of the values is kept.
+%   This function correctly detects the |=| and |,| signs provided they
+%   have the standard category code~$12$ or they are active.
 % \end{function}
 %
+% \begin{function}[added = 2024-02-12]{\prop_make_flat:N, \prop_make_flat:c}
+%   \begin{syntax}
+%     \cs{prop_make_flat:N} \meta{property list}
+%   \end{syntax}
+%   Changes the internal storage type of the \meta{property list} to be
+%   the same \enquote{flat} storage as \cs{prop_new:N}.  The key--value
+%   pairs of the \meta{property list} are preserved by the change.  If
+%   the property list was already flat then nothing is done.  This
+%   function can only be used at the outermost group level.
+% \end{function}
+%
+% \begin{function}[added = 2024-02-12]{\prop_make_linked:N, \prop_make_linked:c}
+%   \begin{syntax}
+%     \cs{prop_make_linked:N} \meta{property list}
+%   \end{syntax}
+%   Changes the internal storage type of the \meta{property list} to be
+%   the same \enquote{linked} storage as \cs{prop_new_linked:N}.  The
+%   key--value pairs of the \meta{property list} are preserved by the
+%   change.  If the property list was already linked then nothing is
+%   done.  This function can only be used at the outermost group level.
+% \end{function}
+%
 % \section{Adding and updating property list entries}
 %
 % \begin{function}[updated = 2012-07-09]
@@ -171,11 +272,10 @@
 %     \prop_put:Nen,  \prop_put:NeV,  \prop_put:Nev,  \prop_put:Nee,
 %     \prop_put:Nno,  \prop_put:Non,  \prop_put:Noo,
 %     \prop_put:cnn,  \prop_put:cnV,  \prop_put:cnv,  \prop_put:cne,
-%     \prop_put:cno,
 %     \prop_put:cVn,  \prop_put:cVV,  \prop_put:cVv,  \prop_put:cVe,
 %     \prop_put:cvn,  \prop_put:cvV,  \prop_put:cvv,  \prop_put:cve, 
 %     \prop_put:cen,  \prop_put:ceV,  \prop_put:cev,  \prop_put:cee,
-%     \prop_put:con,  \prop_put:coo,
+%     \prop_put:cno,  \prop_put:con,  \prop_put:coo,
 %     \prop_gput:Nnn, \prop_gput:NnV, \prop_gput:Nnv, \prop_gput:Nne,
 %     \prop_gput:NVn, \prop_gput:NVV, \prop_gput:NVv, \prop_gput:NVe,
 %     \prop_gput:Nvn, \prop_gput:NvV, \prop_gput:Nvv, \prop_gput:Nve, 
@@ -182,11 +282,10 @@
 %     \prop_gput:Nen, \prop_gput:NeV, \prop_gput:Nev, \prop_gput:Nee,
 %     \prop_gput:Nno, \prop_gput:Non, \prop_gput:Noo,
 %     \prop_gput:cnn, \prop_gput:cnV, \prop_gput:cnv, \prop_gput:cne,
-%     \prop_gput:cno,
 %     \prop_gput:cVn, \prop_gput:cVV, \prop_gput:cVv, \prop_gput:cVe,
 %     \prop_gput:cvn, \prop_gput:cvV, \prop_gput:cvv, \prop_gput:cve, 
 %     \prop_gput:cen, \prop_gput:ceV, \prop_gput:cev, \prop_gput:cee,
-%     \prop_gput:con, \prop_gput:coo
+%     \prop_gput:cno, \prop_gput:con, \prop_gput:coo
 %   }
 %   \begin{syntax}
 %     \cs{prop_put:Nnn} \meta{property list} \Arg{key} \Arg{value}
@@ -227,6 +326,7 @@
 %   \meta{property list_3}, and saves the result in \meta{property list_1}.  If a
 %   key appears in both \meta{property list_2} and \meta{property list_3} then the
 %   last value, namely the value in \meta{property list_3} is kept.
+%   This converts as needed between the two storage types.
 % \end{function}
 %
 % \begin{function}[added = 2021-05-16, updated = 2021-11-07]
@@ -319,7 +419,7 @@
 %   the \meta{token list variable} is local.  See also \cs{prop_gpop:NnNTF}.
 % \end{function}
 %
-% \begin{function}[added = 2014-07-17, EXP]
+% \begin{function}[EXP, added = 2014-07-17]
 %   {
 %     \prop_item:Nn, \prop_item:NV, \prop_item:Ne, \prop_item:No,
 %     \prop_item:cn, \prop_item:cV, \prop_item:ce, \prop_item:co
@@ -331,8 +431,11 @@
 %   the \meta{property list}. If the \meta{key} is missing, this has
 %   an empty expansion.
 %   \begin{texnote}
-%     This function is slower than the non-expandable analogue
-%     \cs{prop_get:NnN}.
+%     For \enquote{flat} property lists, this expandable function iterates
+%     through every key--value pair and is therefore slower than a
+%     non-expandable approach based on \cs{prop_get:NnN}.
+%     (For \enquote{linked} property lists both functions are fast.)
+%
 %     The result is returned within the \tn{unexpanded}
 %     primitive (\cs{exp_not:n}), which means that the \meta{value}
 %     does not expand further when appearing in an \texttt{e}-type
@@ -354,7 +457,7 @@
 %   \end{syntax}
 %   Expands to the \meta{property list} in a key--value notation. Keep in mind
 %   that a \meta{property list} is \emph{unordered}, while key--value interfaces
-%   don't necessarily are, so this can't be used for arbitrary interfaces.
+%   are not necessarily, so this cannot be used for arbitrary interfaces.
 %   \begin{texnote}
 %     The result is returned within the \tn{unexpanded} primitive
 %     (\cs{exp_not:n}), which means that the key--value list does not expand
@@ -402,7 +505,7 @@
 %   Tests if the \meta{property list} is empty (containing no entries).
 % \end{function}
 %
-% \begin{function}[updated = 2011-09-15, EXP, pTF]
+% \begin{function}[EXP, pTF, updated = 2011-09-15]
 %   {
 %     \prop_if_in:Nn, \prop_if_in:NV, \prop_if_in:Ne, \prop_if_in:No,
 %     \prop_if_in:cn, \prop_if_in:cV, \prop_if_in:ce, \prop_if_in:co
@@ -414,9 +517,10 @@
 %   Tests if the \meta{key} is present in the \meta{property list},
 %   making the comparison using the method described by \cs{str_if_eq:nnTF}.
 %   \begin{texnote}
-%     This function iterates through every key--value pair in the
-%     \meta{property list} and is therefore slower than using the
-%     non-expandable \cs{prop_get:NnNTF}.
+%     For \enquote{flat} property lists, this expandable function iterates
+%     through every key--value pair and is therefore slower than a
+%     non-expandable approach based on \cs{prop_get:NnNTF}.
+%     (For \enquote{linked} property lists both functions are fast.)
 %   \end{texnote}
 % \end{function}
 %
@@ -424,11 +528,11 @@
 %
 % The functions in this section combine tests for the presence of a key
 % in a property list with  recovery of the associated valued. This makes them
-% useful for cases where different cases follow dependent on the presence
+% useful for cases where different code follows depending on the presence
 % or absence of a key in a property list. They offer increased readability
 % and performance over separate testing and recovery phases.
 %
-% \begin{function}[updated = 2012-05-19, TF]
+% \begin{function}[TF, updated = 2012-05-19]
 %   {
 %     \prop_get:NnN, \prop_get:NVN, \prop_get:NvN, \prop_get:NeN,
 %     \prop_get:NoN,
@@ -458,7 +562,8 @@
 %     \prop_pop:coN
 %   }
 %   \begin{syntax}
-%     \cs{prop_pop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \Arg{true code} \Arg{false code}
+%     \cs{prop_pop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \\
+%     ~~\Arg{true code} \Arg{false code}
 %   \end{syntax}
 %   If the \meta{key} is not present in the \meta{property list}, leaves
 %   the \meta{false code} in the input stream.  The value of the
@@ -479,7 +584,8 @@
 %     \prop_gpop:coN
 %   }
 %   \begin{syntax}
-%     \cs{prop_gpop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \Arg{true code} \Arg{false code}
+%     \cs{prop_gpop:NnNTF} \meta{property list} \Arg{key} \meta{token list variable} \\
+%     ~~\Arg{true code} \Arg{false code}
 %   \end{syntax}
 %   If the \meta{key} is not present in the \meta{property list}, leaves
 %   the \meta{false code} in the input stream.  The value of the
@@ -498,7 +604,7 @@
 % local assignments made by the \meta{function} or \meta{code} discussed
 % below remain in effect after the loop.
 %
-% \begin{function}[updated = 2013-01-08, rEXP]
+% \begin{function}[rEXP, updated = 2013-01-08]
 %   {\prop_map_function:NN, \prop_map_function:cN}
 %   \begin{syntax}
 %     \cs{prop_map_function:NN} \meta{property list} \meta{function}
@@ -509,7 +615,7 @@
 %   The order in which \meta{entries} are returned is not defined and
 %   should not be relied upon.
 %   To pass further arguments to the \meta{function}, see
-%   \cs{prop_map_tokens:Nn}.
+%   \cs{prop_map_inline:Nn} (non-expandable) or \cs{prop_map_tokens:Nn}.
 % \end{function}
 %
 % \begin{function}[updated = 2013-01-08]
@@ -543,7 +649,7 @@
 %   arguments.  For that specific task, \cs{prop_item:Nn} is faster.
 % \end{function}
 %
-% \begin{function}[updated = 2012-06-29, rEXP]{\prop_map_break:}
+% \begin{function}[rEXP, updated = 2012-06-29]{\prop_map_break:}
 %   \begin{syntax}
 %     \cs{prop_map_break:}
 %   \end{syntax}
@@ -570,7 +676,7 @@
 %   \end{texnote}
 % \end{function}
 %
-% \begin{function}[updated = 2012-06-29, rEXP]{\prop_map_break:n}
+% \begin{function}[rEXP, updated = 2012-06-29]{\prop_map_break:n}
 %   \begin{syntax}
 %     \cs{prop_map_break:n} \Arg{code}
 %   \end{syntax}
@@ -604,7 +710,8 @@
 %   \begin{syntax}
 %     \cs{prop_show:N} \meta{property list}
 %   \end{syntax}
-%   Displays the entries in the \meta{property list} in the terminal.
+%   Displays the entries in the \meta{property list} in the terminal,
+%   and specifies its storage type.
 % \end{function}
 %
 % \begin{function}[added = 2014-08-12, updated = 2021-04-29]{\prop_log:N, \prop_log:c}
@@ -611,13 +718,18 @@
 %   \begin{syntax}
 %     \cs{prop_log:N} \meta{property list}
 %   \end{syntax}
-%   Writes the entries in the \meta{property list} in the log file.
+%   Writes the entries in the \meta{property list} in the log file,
+%   and specifies its storage type.
 % \end{function}
 %
 % \section{Scratch property lists}
 %
+% There is no need to include both flat and linked property lists as
+% scratch variables.  We arbitrarily pick the older implementation.
+%
 % \begin{variable}[added = 2012-06-23]{\l_tmpa_prop, \l_tmpb_prop}
-%   Scratch property lists for local assignment. These are never used by
+%   Scratch \enquote{flat} property lists for local assignment.
+%   These are never used by
 %   the kernel code, and so are safe for use with any \LaTeX3-defined
 %   function. However, they may be overwritten by other non-kernel
 %   code and so should only be used for short-term storage.
@@ -624,7 +736,8 @@
 % \end{variable}
 %
 % \begin{variable}[added = 2012-06-23]{\g_tmpa_prop, \g_tmpb_prop}
-%   Scratch property lists for global assignment. These are never used by
+%   Scratch \enquote{flat} property lists for global assignment.
+%   These are never used by
 %   the kernel code, and so are safe for use with any \LaTeX3-defined
 %   function. However, they may be overwritten by other non-kernel
 %   code and so should only be used for short-term storage.
@@ -652,97 +765,98 @@
 %<@@=prop>
 %    \end{macrocode}
 %
-% A property list is a macro whose top-level expansion is of the form
+% With the (default) flat data storage, a property list is a macro whose
+% top-level expansion is of the form
 % \begin{quote}
-%   \cs{s_@@}
+%   \cs{s_@@} \cs{@@_chk:w}
 %   \cs{@@_pair:wn} \meta{key_1} \cs{s_@@} \Arg{value_1} \\
 %   \ldots{} \\
 %   \cs{@@_pair:wn} \meta{key_n} \cs{s_@@} \Arg{value_n} \\
 % \end{quote}
-% where \cs{s_@@} is a scan mark
-% (equal to \cs{scan_stop:}), and \cs{@@_pair:wn} can be used to map
-% through the property list.
+% where \cs{s_@@} is a scan mark (equal to \cs{scan_stop:}), \cs{@@_chk:w}
+% produces a suitable error if the property list is used directly in the input
+% stream, and \cs{@@_pair:wn} can be used to map through the property list.
 %
-% \begin{variable}{\s_@@}
-%   The internal token used at the beginning of property lists.  This is
-%   also used after each \meta{key} (see \cs{@@_pair:wn}).
-% \end{variable}
+% With the linked data storage, each property list entry
+% \meta{key_i}--\meta{value_i} is stored into a token list
+% \cs[no-index]{@@~\meta{prefix}~\meta{key_i}}.  The \meta{prefix} is one or
+% more characters (no spaces), constructed automatically only once, when the
+% property list is initially declared.  The control sequence name does not
+% conform to standard naming for variables because (1) this is an internal
+% control sequence, not really a \pkg{expl3} variable; (2) keeping track of the
+% scope |l| or~|g| throughout all functions would be a pretty big mess,
+% especially if users accidentally mix local and global use (we would have to
+% always check for such mistakes, rather than only checking when suitable debug
+% options are set); (3) shorter control sequence names use less memory and are
+% quicker in case of hash collisions, which may matter since we are using many
+% control sequences.
 %
-% \begin{variable}{\@@_pair:wn}
-%   \begin{syntax}
-%     \cs{@@_pair:wn} \meta{key} \cs{s_@@} \Arg{item}
-%   \end{syntax}
-%   The internal token used to begin each key--value pair in the
-%   property list.  If expanded outside of a mapping or manipulation
-%   function, an error is raised.  The definition should always be
-%   set globally.
-% \end{variable}
+% We need to enable mapping through such a property list, but without storing a
+% list of all entries anywhere: this is achieved by making each of these token
+% lists also store a pointer to the next entry.  To enable efficient deletion,
+% the token lists also store a pointer to the previous entry.  This means we
+% have a doubly-linked list.  To avoid having to special-case the two ends of
+% the doubly-linked list when deleting entries, we include as a zeroth entry in
+% the doubly-linked list the property list variable itself, and we include as an
+% $(n+1)$-th entry in the doubly-linked list an end-pointer
+% \cs{@@~\meta{prefix}} (no trailing space, so it differs from an empty key).
+% The space before \meta{prefix} ensures there is no collision with
+% other \pkg{l3prop} internal functions, even if we have very many
+% linked property lists being defined.
 %
-% \begin{variable}{\l_@@_internal_tl}
-%   Token list used to store new key--value pairs to be inserted by
-%   functions of the \cs{prop_put:Nnn} family.
-% \end{variable}
+% The property list variable itself is a token list of the form
+% \begin{quote}
+%   \cs{@@_flatten:w} \cs{@@~\meta{prefix}} \cs{s_@@} \Arg{prefix}
+%   \cs[no-index]{@@~\meta{prefix}~\meta{key_1}}
+% \end{quote}
+% Here, \cs{@@_flatten:w} serves as an efficiently recognized marker, and when
+% \texttt{f}-expanded it is tasked with fully unpacking the property list into
+% the same form as the default data storage so as to ease conversion.  The
+% \meta{prefix} is used when looking up an entry.  The token list
+% \cs{@@~\meta{prefix}} (see below) contains a pointer to the last key to help
+% insert a new entry.  The pointer to \meta{key_1} is needed to start a mapping.
+% The token list labeled by \meta{key_i} is of the form
+% \begin{quote}
+%   \cs{use_none:n} \cs[no-index]{@@~\meta{prefix}~\meta{key$_{i-1}$}}
+%   \cs{@@_pair:wn} \meta{key_i} \cs{s_@@} \Arg{value_i}
+%   \cs[no-index]{@@~\meta{prefix}~\meta{key$_{i+1}$}}
+% \end{quote}
+% where the pointer to \meta{key$_{i-1}$} is needed when deleting the
+% \meta{key_i}.  Expanding this will run \cs{@@_pair:wn} on the
+% \meta{key_i}--\meta{value_i} pair (for speed, \meta{key_i} is kept as explicit
+% tokens rather than slowly extracting it from a control sequence name), then
+% move on to the next key, thus mapping through the whole list.  The mapping is
+% ended upon expanding \cs{@@~\meta{prefix}}, which is the token list
+% \begin{quote}
+%   \cs{use_none:n} \cs[no-index]{@@~\meta{prefix}~\meta{key_n}}
+% \end{quote}
+% Let us think about deleting the \meta{key_i}.  We need to update the
+% \meta{key$_{i-1}$} and \meta{key$_{i+1}$} to point to each other
+% instead of \meta{key_i}.  To edit the corresponding token lists, it is
+% important that \cs[no-index]{@@~\meta{prefix}~\meta{key_i}} be at the
+% \enquote{same place} in the token lists also in the boundary cases
+% $i=1$ or $i=n$, namely as the second token, or as the second argument
+% after \cs{s_@@}.
 %
-% \begin{function}[updated = 2013-01-08]{\@@_split:NnTF}
-%   \begin{syntax}
-%     \cs{@@_split:NnTF} \meta{property list} \Arg{key} \Arg{true code} \Arg{false code}
-%   \end{syntax}
-%   Splits the \meta{property list} at the \meta{key}, giving three
-%   token lists: the \meta{extract} of \meta{property list} before the
-%   \meta{key}, the \meta{value} associated with the \meta{key} and the
-%   \meta{extract} of the \meta{property list} after the \meta{value}.
-%   Both \meta{extracts} retain the internal structure of a property
-%   list, and the concatenation of the two \meta{extracts} is a
-%   property list.
-%   If the \meta{key} is present in the \meta{property list} then the
-%   \meta{true code} is left in the input stream, with |#1|, |#2|, and
-%   |#3| replaced by the first \meta{extract}, the \meta{value}, and the
-%   second extract.
-%   If the \meta{key} is not present in the \meta{property list} then
-%   the \meta{false code} is left in the input stream, with no trailing
-%   material.
-%   Both \meta{true code} and \meta{false code} are used in the
-%   replacement text of a macro defined internally, hence macro
-%   parameter characters should be doubled, except |#1|, |#2|, and |#3|
-%   which stand in the \meta{true code} for the three extracts from the
-%   property list.
-%   The \meta{key} comparison takes place as described for \cs{str_if_eq:nn}.
-% \end{function}
+% \subsection{Internal auxiliaries}
 %
-% \begin{macro}{\s_@@}
-%   A private scan mark is used as a marker after each key, and at the
-%   very beginning of the property list.
+% \begin{macro}{\@@_tmp:w}
+%   Scratch macro, defined as needed, for instance to save \cs{@@_pair:wn} when
+%   concatenating.
 %    \begin{macrocode}
-\scan_new:N \s_@@
+\cs_new_eq:NN \@@_tmp:w ?
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\@@_pair:wn}
-%   The delimiter is always defined, but when misused simply triggers an
-%   error and removes its argument.
-%    \begin{macrocode}
-\cs_new:Npn \@@_pair:wn #1 \s_@@ #2
-  { \msg_expandable_error:nn { prop } { misused } }
-%    \end{macrocode}
-% \end{macro}
-%
 % \begin{variable}{\l_@@_internal_tl}
-%   Token list used to store the new key--value pair inserted by
-%   \cs{prop_put:Nnn} and friends.
+%   Token list used in various places: for the prefix; when converting from flat
+%   to linked props; and to store the new key--value pair inserted by
+%   \cs{prop_put:Nnn}.
 %    \begin{macrocode}
 \tl_new:N \l_@@_internal_tl
 %    \end{macrocode}
 % \end{variable}
 %
-% \begin{variable}[tested = m3prop004]{\c_empty_prop}
-%   An empty prop.
-%    \begin{macrocode}
-\tl_const:Nn \c_empty_prop { \s_@@ }
-%    \end{macrocode}
-% \end{variable}
-%
-% \subsection{Internal auxiliaries}
-%
 % \begin{variable}{\s_@@_mark,\s_@@_stop}
 %   Internal scan marks.
 %    \begin{macrocode}
@@ -769,10 +883,160 @@
 % \end{macro}
 % \end{macro}
 %
+% \subsection{Structure of a property list}
+%
+% \begin{macro}{\s_@@}
+%   A private scan mark is used as a marker after each key, and at the
+%   very beginning of the property list.
+%    \begin{macrocode}
+\scan_new:N \s_@@
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_chk:w, \@@_chk_loop:nw, \@@_chk_get:nw}
+%   This removes the flat property list from the input stream and complains
+%   about a bad use of a property list.  Since property lists do not have an
+%   end-marker, we slowly peek ahead in a loop.  Speed does not matter since
+%   this is for an error situation.  While \cs{@@_pair:wn} does not keep a fixed
+%   definition, it always includes the internal \cs{s_@@} in its argument
+%   specification, so that there is no risk of accidentally picking up a public
+%   token instead of \cs{@@_pair:wn} when doing a meaning test.  We collect the
+%   keys and values to produce a more useful error message.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_chk:w { \@@_chk_loop:nw { } }
+\cs_new_protected:Npn \@@_chk_loop:nw #1
+  {
+    \peek_meaning:NTF \@@_pair:wn
+      { \@@_chk_get:nw {#1} }
+      { \msg_error:nne { prop } { misused } {#1} }
+  }
+\cs_new_protected:Npn \@@_chk_get:nw #1 \@@_pair:wn #2 \s_@@ #3
+  { \@@_chk_loop:nw { #1 , ~ {#2} = { \tl_to_str:n {#3} } } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_pair:wn}
+%   Used as \cs{@@_pair:wn} \meta{key} \cs{s_@@} \Arg{item} for both storage
+%   types, this internal token starts each key--value pair in the property list.
+%   This default definition is changed globally by any mapping function, so
+%   there is not much point trying to make it an error.  Instead, the error is
+%   produced by \cs{@@_chk:w}.
+%    \begin{macrocode}
+\cs_new:Npn \@@_pair:wn #1 \s_@@ #2 { }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_flatten:w}
+%   We implement here the fact that \texttt{f}-expanding a linked property list
+%   gives a flat property list.
+%   Leaving a linked property list in the input stream will turn it into a flat
+%   property list so that the error implemented by \cs{@@_chk:w} will correctly
+%   be triggered.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_flatten:w #1 \s_@@ #2#3
+  { \use:e { \@@_flatten_aux:N #3 } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[rEXP]
+%   {\@@_flatten:N, \@@_flatten_aux:w, \@@_flatten_aux:N, \@@_flatten_loop:w}
+%   The main function \cs{@@_flatten:N} receives a linked property list and
+%   flattens it.  The auxiliary \cs{@@_flatten_aux:N} receives a pointer to the
+%   first key and flattens the linked property list into a flat property list.
+%   This is only restricted-expandable as it involves mapping through all of the
+%   property list's entries starting from \meta{key_1}.  The looping function
+%   \cs{@@_flatten_loop:w} removes \cs{use_none:n} and a backwards pointer~|#2|,
+%   leaves the key--value pair for \cs{use:e} to receive, and calls itself
+%   again after expanding the next key's token list.  Its argument |#3| is
+%   empty, except at the end where it is the \cs{use_none:nnnn} appearing in the
+%   definition of~\cs{@@_flatten_aux:N}, which ends the loop.
+%    \begin{macrocode}
+\cs_new:Npn \@@_flatten:N #1
+  { \exp_after:wN \@@_flatten_aux:w #1 }
+\cs_new:Npn \@@_flatten_aux:w #1 \s_@@ #2 { \@@_flatten_aux:N }
+\cs_new:Npn \@@_flatten_aux:N #1
+  {
+    \s_@@ \@@_chk:w
+    \exp_after:wN \@@_flatten_loop:w #1
+    \use_none:nnnn \@@_pair:wn \s_@@ { }
+  }
+\cs_new:Npn \@@_flatten_loop:w #1#2#3 \@@_pair:wn #4 \s_@@ #5
+  {
+    #3
+    \exp_not:n { \@@_pair:wn #4 \s_@@ {#5} }
+    \exp_after:wN \@@_flatten_loop:w
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\g_@@_prefix_int, \c_@@_basis_int}
+%   Used to assign prefixes for each linked property list.  It is converted to
+%   base \cs{c_@@_basis_int}, then each digit is converted to a character,
+%   starting at |!| (the character after space).
+%    \begin{macrocode}
+\int_new:N \g_@@_prefix_int
+\int_const:Nn \c_@@_basis_int { \c_max_char_int - `\! }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_next_prefix:, \@@_to_prefix:n}
+%   Store in \cs{l_@@_internal_tl} the conversion of \cs{g_@@_prefix_int} to
+%   characters, and increment this integer for use in the next linked property
+%   list.  No need to optimize since this is only used when declaring the
+%   property list the first time.
+%   The aim here is to make this string as short as we can, given the
+%   range of distinct characters available.  This speeds up the work of
+%   \cs{cs:w} \ldots{} \cs{cs_end:} that looks up keys in the hash table.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_next_prefix:
+  {
+    \tl_set:Ne \l_@@_internal_tl
+      { \@@_to_prefix:n { \g_@@_prefix_int } }
+    \int_gincr:N \g_@@_prefix_int
+  }
+\cs_new:Npn \@@_to_prefix:n #1
+  {
+    \int_compare:nNnTF {#1} > \c_@@_basis_int
+      {
+        \exp_args:Nf \@@_to_prefix:n
+          { \int_div_truncate:nn {#1} \c_@@_basis_int }
+        \exp_args:Nf \@@_to_prefix:n
+          { \int_mod:nn {#1} \c_@@_basis_int }
+      }
+      { \char_generate:nn { `\! + #1 } { 12 } }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_if_flat:NTF, \@@_if_flat_aux:w}
+%   We could either test for the presence of \cs{@@_chk:w} (flat
+%   property list) or of \cs{@@_flatten:w} (linked property list).  We
+%   make the second choice; this way props that are accidentally
+%   \tn{relax} are treated as they were before.  The auxiliary receives
+%   \cs{use_i:nn} or \cs{use_ii:nn} as~|#3|.
+%    \begin{macrocode}
+\cs_new:Npn \@@_if_flat:NTF #1
+  {
+    \exp_after:wN \@@_if_flat_aux:w #1
+    \s_@@_mark \use_ii:nn
+    \@@_flatten:w \s_@@_mark \use_i:nn \s_@@_stop
+  }
+\cs_new:Npn \@@_if_flat_aux:w
+    #1 \@@_flatten:w #2 \s_@@_mark #3 #4 \s_@@_stop {#3}
+%    \end{macrocode}
+% \end{macro}
+%
 % \subsection{Allocation and initialisation}
 %
+% \begin{variable}[tested = m3prop004]{\c_empty_prop}
+%   An empty flat prop.
+%    \begin{macrocode}
+\tl_const:Nn \c_empty_prop { \s_@@ \@@_chk:w }
+%    \end{macrocode}
+% \end{variable}
+%
 % \begin{macro}[tested = m3prop001]{\prop_new:N, \prop_new:c}
-%   Property lists are initialized with the value \cs{c_empty_prop}.
+%   Flat property lists are initialized with the value \cs{c_empty_prop}.
 %    \begin{macrocode}
 \cs_new_protected:Npn \prop_new:N #1
   {
@@ -783,23 +1047,107 @@
 %    \end{macrocode}
 % \end{macro}
 %
+% \begin{macro}[tested = m3prop001]{\prop_new_linked:N, \prop_new_linked:c}
+% \begin{macro}{\@@_new_linked:N}
+%   The auxiliary is used in \cs{prop_make_linked:N}.
+%   For linked property lists, get a new prefix in \cs{l_@@_internal_tl}, then
+%   use it to set up the internal structure: the last token in~|#1| is usually a
+%   pointer to the first key, which is here the end-pointer.  That end-pointer
+%   has a pointer to the previous key (usually the last key), which is the
+%   variable~|#1| itself that begins the doubly-linked list.
+%    \begin{macrocode}
+\cs_new_protected:Npn \prop_new_linked:N #1
+  {
+    \__kernel_chk_if_free_cs:N #1
+    \@@_new_linked:N #1
+  }
+\cs_new_protected:Npn \@@_new_linked:N #1
+  {
+    \@@_next_prefix:
+    \cs_gset_nopar:Npe #1
+      {
+        \@@_flatten:w
+        \exp_not:c { @@ ~ \l_@@_internal_tl }
+        \s_@@ { \l_@@_internal_tl }
+        \exp_not:c { @@ ~ \l_@@_internal_tl }
+      }
+    \cs_gset_nopar:cpe { @@ ~ \l_@@_internal_tl }
+      {
+        \exp_not:N \use_none:n
+        \exp_not:N #1
+      }
+  }
+\cs_generate_variant:Nn \prop_new_linked:N { c }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
 % \begin{macro}[tested = m3prop001]{\prop_clear:N, \prop_clear:c}
 % \begin{macro}[tested = m3prop001]{\prop_gclear:N, \prop_gclear:c}
-%   The same idea for clearing.
+% \begin{macro}{\@@_clear:NNN, \@@_clear:wNNN, \@@_clear_loop:Nw}
+%   Clearing a flat property list is like declaring it anew, simply setting it
+%   equal to \cs{c_empty_prop}.  For linked property lists we must clear all of
+%   the variables storing individual keys, which requires a loop.  At each step
+%   of the loop, \cs{@@_clear_loop:Nw} receives
+%   \cs[index=cs_set_eq:NN]{cs_(g)set_eq:NN}, \cs{use_none:n}, the backwards
+%   pointer, an empty~|#4| (except at the end of the loop), and the key--value
+%   pair |#5=#6| which we disregard.  The looping auxiliary undefines the
+%   previous key's token list (this includes the main token list, but that is
+%   fine because it is restored at the end) and calls itself after expanding the
+%   next key's token list.  The loop ends when |#4| is \cs{use_none:nnnn}.
+%   After the loop, \cs{@@_clear:wNNN} correctly sets up the main variable~|#6|
+%   and the end-pointer~|#1|.  Importantly, this is done using
+%   \cs[index=cs_set_nopar:Npe]{cs_(g)set_nopar:Npe} and \cs{exp_not:n} because
+%   the almost-equivalent \cs{tl_set:Nn} would complain in debug mode about the
+%   fact that the main variable is undefined at this stage.  Importantly,
+%   \cs{@@_clear_entries:NN} is used in the implementation of
+%   \cs{prop_set_eq:NN}.
 %    \begin{macrocode}
-\cs_new_protected:Npn \prop_clear:N  #1
-  { \prop_set_eq:NN #1 \c_empty_prop }
+\cs_new_protected:Npn \prop_clear:N
+  { \@@_clear:NNN \cs_set_eq:NN \cs_set_nopar:Npe }
 \cs_generate_variant:Nn \prop_clear:N  { c }
-\cs_new_protected:Npn \prop_gclear:N #1
-  { \prop_gset_eq:NN #1 \c_empty_prop }
+\cs_new_protected:Npn \prop_gclear:N
+  { \@@_clear:NNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_gclear:N { c }
+\cs_new_protected:Npn \@@_clear:NNN #1#2#3
+  {
+    \@@_if_flat:NTF #3
+      { #1 #3 \c_empty_prop }
+      { \exp_after:wN \@@_clear:wNNN #3 #1 #2 #3 }
+  }
+\cs_new_protected:Npn \@@_clear:wNNN
+    \@@_flatten:w #1 \s_@@ #2#3#4#5#6
+  {
+    \@@_clear_entries:NN #4 #3
+    #5 #6 { \exp_not:n { \@@_flatten:w #1 \s_@@ {#2} #1 } }
+    #5 #1 { \exp_not:n { \use_none:n #6 } }
+  }
+\cs_new_protected:Npn \@@_clear_entries:NN #1#2
+  {
+    \exp_after:wN \@@_clear_loop:Nw \exp_after:wN #1 #2
+    \use_none:nnnn \@@_pair:wn \s_@@ { }
+  }
+\cs_new_protected:Npn \@@_clear_loop:Nw
+    #1#2#3#4 \@@_pair:wn #5 \s_@@ #6
+  {
+    #1 #3 \tex_undefined:D
+    #4
+    \exp_after:wN \@@_clear_loop:Nw
+    \exp_after:wN #1
+  }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
-% \begin{macro}[tested = m3prop001]{\prop_clear_new:N, \prop_clear_new:c}
-% \begin{macro}[tested = m3prop001]{\prop_gclear_new:N, \prop_gclear_new:c}
-%   Once again a simple variation of the token list functions.
+% \begin{macro}[tested = m3prop001]
+%   {
+%     \prop_clear_new:N, \prop_clear_new:c,
+%     \prop_gclear_new:N, \prop_gclear_new:c,
+%     \prop_clear_new_linked:N, \prop_clear_new_linked:c,
+%     \prop_gclear_new_linked:N, \prop_gclear_new_linked:c
+%   }
+%   A simple variation of the token list functions.
 %    \begin{macrocode}
 \cs_new_protected:Npn \prop_clear_new:N  #1
   { \prop_if_exist:NTF #1 { \prop_clear:N #1 } { \prop_new:N #1 } }
@@ -807,28 +1155,178 @@
 \cs_new_protected:Npn \prop_gclear_new:N #1
   { \prop_if_exist:NTF #1 { \prop_gclear:N #1 } { \prop_new:N #1 } }
 \cs_generate_variant:Nn \prop_gclear_new:N { c }
+\cs_new_protected:Npn \prop_clear_new_linked:N  #1
+  { \prop_if_exist:NTF #1 { \prop_clear:N #1 } { \prop_new_linked:N #1 } }
+\cs_generate_variant:Nn \prop_clear_new_linked:N  { c }
+\cs_new_protected:Npn \prop_gclear_new_linked:N #1
+  { \prop_if_exist:NTF #1 { \prop_gclear:N #1 } { \prop_new_linked:N #1 } }
+\cs_generate_variant:Nn \prop_gclear_new_linked:N { c }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
 %
 % \begin{macro}[tested = m3prop001]
 %   {\prop_set_eq:NN, \prop_set_eq:cN, \prop_set_eq:Nc, \prop_set_eq:cc}
 % \begin{macro}[tested = m3prop001]
 %   {\prop_gset_eq:NN, \prop_gset_eq:cN, \prop_gset_eq:Nc, \prop_gset_eq:cc}
-%   These are simply copies from the token list functions.
+% \begin{macro}
+%   {
+%     \@@_set_eq:NNNN, \@@_set_eq:wNNNN, \@@_set_eq:nNnNN,
+%     \@@_set_eq_loop:NNnw, \@@_set_eq_end:w
+%   }
+%   If both variables are accidentally the same variable (or equal flat property
+%   lists, as it turns out) we do nothing, otherwise the following code would
+%   lose all entries.  If the target variable~|#3| is a flat prop, either copy
+%   directly or flatten before copying.  If it is a linked prop, we must clear
+%   it, then go through the entries in~|#4| to add them to~|#3|.
 %    \begin{macrocode}
-\cs_new_eq:NN \prop_set_eq:NN  \tl_set_eq:NN
-\cs_new_eq:NN \prop_set_eq:Nc  \tl_set_eq:Nc
-\cs_new_eq:NN \prop_set_eq:cN  \tl_set_eq:cN
-\cs_new_eq:NN \prop_set_eq:cc  \tl_set_eq:cc
-\cs_new_eq:NN \prop_gset_eq:NN \tl_gset_eq:NN
-\cs_new_eq:NN \prop_gset_eq:Nc \tl_gset_eq:Nc
-\cs_new_eq:NN \prop_gset_eq:cN \tl_gset_eq:cN
-\cs_new_eq:NN \prop_gset_eq:cc \tl_gset_eq:cc
+\cs_new_protected:Npn \prop_set_eq:NN
+  { \@@_set_eq:NNNN \cs_set_eq:NN \cs_set_nopar:Npe }
+\cs_generate_variant:Nn \prop_set_eq:NN { Nc , cN , cc }
+\cs_new_protected:Npn \prop_gset_eq:NN
+  { \@@_set_eq:NNNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
+\cs_generate_variant:Nn \prop_gset_eq:NN { Nc , cN , cc }
+\cs_new_protected:Npn \@@_set_eq:NNNN #1#2#3#4
+  {
+    \cs_if_eq:NNF #3#4
+      {
+        \@@_if_flat:NTF #3
+          {
+            \@@_if_flat:NTF #4
+              { #1 #3 #4 }
+              { #2 #3 { \@@_flatten:N #4 } }
+          }
+          { \exp_after:wN \@@_set_eq:wNNNN #3 #1#2#3#4 }
+      }
+  }
+\cs_new_protected:Npn \@@_set_eq:wNNNN
+    \@@_flatten:w #1 \s_@@ #2#3#4#5#6#7
+  {
+    \@@_clear_entries:NN #4 #3
+    \exp_args:Nf \@@_set_eq:nNnNN {#7} #1 {#2} #5 #6
+  }
 %    \end{macrocode}
+%   We have used that |f|-expanding either type of prop gives a flat prop.  At
+%   this stage \cs{@@_set_eq:nNnNN} receives the second variable as a flat prop,
+%   the end-pointer, the prefix, the suitable
+%   \cs[index=cs_set_nopar:Npe]{cs_(g)set_nopar:Npe} assignment, and the first
+%   variable itself.  Remove the leading \cs{s_@@} and \cs{@@_chk:w} with
+%   \cs{use_i:nnn}, then start the loop.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_eq:nNnNN #1#2#3#4#5
+  {
+    \use_i:nnn
+      {
+        \@@_set_eq_loop:NNnw #5 #4 {#3}
+        \@@_flatten:w #2 \s_@@ {#3}
+      }
+      #1
+    \use_none:n \@@_pair:wn ? \s_@@
+  }
+%    \end{macrocode}
+%   The looping function receives the current pointer~|#1| (initially the
+%   variable itself), the defining function~|#2| and the prefix~|#3|, then a
+%   partial definition~|#4| (which in later stages includes the backwards
+%   pointer), followed by the current value as \cs{s_@@} |{#5}|.  It seeks the
+%   next key~|#7| to construct in \cs{l_@@_internal_tl} the next pointer
+%   \cs[no-index]{@@~\meta{prefix}~\meta{next key}} (the
+%   argument~|#6| is empty, except at the end of the loop, where it is
+%   \cs{use_none:n} in such a way as to delete the \meta{space} and \meta{next
+%   key}).  Then the token list (current pointer)~|#1| is set-up to contain the
+%   partial definition and current value, as well as the newly constructed next
+%   pointer.  After a line responsible for correctly ending the loop with
+%   \cs{@@_set_eq_end:w}, we loop, setting up the next definition, which starts
+%   with \cs{use_none:n} and a backwards pointer to~|#1| followed by the
+%   \meta{next key}~|#7| and so on.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_eq_loop:NNnw
+    #1#2#3#4 \s_@@ #5#6 \@@_pair:wn #7 \s_@@
+  {
+    \tl_set:Ne \l_@@_internal_tl { \exp_not:c { @@ ~ #3 #6 ~ #7 } }
+    #2 #1 { \exp_not:n { #4 \s_@@ {#5} } \exp_not:o \l_@@_internal_tl }
+    \use_none:n #6 \@@_set_eq_end:w
+    \exp_after:wN \@@_set_eq_loop:NNnw \l_@@_internal_tl #2 {#3}
+    \use_none:n #1 \@@_pair:wn #7 \s_@@
+  }
+%    \end{macrocode}
+%   The end-code picks up what is needed to correctly assign the last token
+%   list (the end pointer), which is simply \cs{use_none:n}
+%   \cs[no-index]{@@_\meta{prefix}\meta{space}\meta{key_n}}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_set_eq_end:w
+    \exp_after:wN \@@_set_eq_loop:NNnw #1#2#3
+    \use_none:n #4#5 \s_@@
+  {
+    \exp_after:wN #2 \l_@@_internal_tl { \exp_not:n { \use_none:n #4 } }
+  }
+%    \end{macrocode}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
+% \begin{macro}{\prop_make_flat:N, \prop_make_flat:c \@@_make_flat:Nn}
+%   The only interesting case is when given a linked prop.  Clear the
+%   linked property list using \cs{@@_clear:wNNN} with local assignments
+%   (it does not matter since we are at the outermost group level, and
+%   \cs{cs_set_eq:NN} is very slightly faster than its global version.
+%   Then store the contents (expanded preventively by \cs{exp_args:NNf})
+%   with an assignment \cs{cs_set_nopar:Npe} that does not perform
+%   \pkg{l3debug} checks.
+%    \begin{macrocode}
+\cs_new_protected:Npn \prop_make_flat:N #1
+  {
+    \int_compare:nNnTF { \tex_currentgrouplevel:D } = 0
+      {
+        \@@_if_flat:NTF #1 { }
+          { \exp_args:NNf \@@_make_flat:Nn #1 {#1} }
+      }
+      {
+        \msg_error:nnee { prop } { inner-make }
+          { \token_to_str:N \prop_make_flat:N } { \token_to_str:N #1 }
+      }
+  }
+\cs_generate_variant:Nn \prop_make_flat:N { c }
+\cs_new_protected:Npn \@@_make_flat:Nn #1#2
+  {
+    \exp_after:wN \@@_clear:wNNN #1 \cs_set_eq:NN \cs_set_nopar:Npe #1
+    \cs_set_nopar:Npe #1 { \exp_not:n {#2} }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\prop_make_linked:N, \prop_make_linked:c, \@@_make_linked:Nn}
+%   The only interesting case is when given a flat prop.  We expand the
+%   contents for later use.  Then \cs{@@_new_linked:N} disregards that
+%   previous value of~|#1| and initializes the linked prop.  We can then
+%   use an auxiliary \cs{@@_set_eq:wNNNN} underlying
+%   \cs{prop_set_eq:NN}, with the prop contents saved as
+%   \cs{l_@@_internal_tl}.  That step is a bit unsafe, as
+%   \cs{l_@@_internal_tl} (really, a flat prop here) is used within
+%   \cs{@@_set_eq:wNNNN} itself, but it is in fact expanded early enough
+%   to be ok.
+%    \begin{macrocode}
+\cs_new_protected:Npn \prop_make_linked:N #1
+  {
+    \int_compare:nNnTF { \tex_currentgrouplevel:D } = 0
+      {
+        \@@_if_flat:NTF #1
+          { \exp_args:NNo \@@_make_linked:Nn #1 {#1} } { }
+      }
+      {
+        \msg_error:nnee { prop } { inner-make }
+          { \token_to_str:N \prop_make_linked:N } { \token_to_str:N #1 }
+      }
+  }
+\cs_generate_variant:Nn \prop_make_linked:N { c }
+\cs_new_protected:Npn \@@_make_linked:Nn #1#2
+  {
+    \@@_new_linked:N #1
+    \tl_set:Nn \l_@@_internal_tl {#2}
+    \exp_after:wN \@@_set_eq:wNNNN #1
+      \cs_set_eq:NN \cs_set_nopar:Npe #1 \l_@@_internal_tl
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{variable}[tested = m3prop004]{\l_tmpa_prop, \l_tmpb_prop}
 % \begin{variable}[tested = m3prop004]{\g_tmpa_prop, \g_tmpb_prop}
 %   We can now initialize the scratch variables.
@@ -841,156 +1339,223 @@
 % \end{variable}
 % \end{variable}
 %
-% \begin{variable}{\l_@@_internal_prop}
-%   Property list used by \cs{prop_concat:NNN},
-%   \cs{prop_set_from_keyval:Nn} and others.
-%    \begin{macrocode}
-\prop_new:N \l_@@_internal_prop
-%    \end{macrocode}
-% \end{variable}
-%
 % \begin{macro}
 %   {
 %     \prop_concat:NNN, \prop_concat:ccc,
-%     \prop_gconcat:NNN, \prop_gconcat:ccc, \@@_concat:NNNN
+%     \prop_gconcat:NNN, \prop_gconcat:ccc,
+%     \@@_concat:NNNNN, \@@_concat:nNNN
 %   }
-%   Combine two property lists.  We cannot use a simple
-%   \cs{tl_concat:NNN} because there may be some duplicate keys between
-%   the two property lists.
+%   The basic strategy is to copy the first variable into the target,
+%   then loop through the second variable, calling
+%   \cs[index=prop_put:Nnn]{prop_(g)put:Nnn} on each item.  To avoid
+%   running the \pkg{l3debug} scope checks on each of these steps, we
+%   use the auxiliaries that underly \cs{prop_set_eq:NN} and
+%   \cs{prop_put:Nnn}, whose syntax is a bit unwieldy.
+%   We work directly with the target prop |#3| as a scratch space,
+%   because copying over from a temporary variable to |#3| would be slow
+%   in the linked case.  If |#5| is |#3| itself we have to be careful
+%   not to lose the data, and we even take the opportunity to skip the
+%   copying step completely.  To keep the correct version of the
+%   duplicate keys we use the code underlying \cs{prop_put_if_new:Nnn},
+%   which involves passing \cs{use_none:nnn} to the auxiliary instead of
+%   nothing.
+%   There is no need to check for the case where |#3| is equal to~|#4|
+%   because in that case \cs[index=prop_set_eq:NN]{prop_(g)set_eq:NN}
+%   |#3| |#4| (or rather the underlying auxiliary) is correctly set up
+%   to do no needless work.
 %    \begin{macrocode}
 \cs_new_protected:Npn \prop_concat:NNN
-  { \@@_concat:NNNN \prop_set_eq:NN }
+  { \@@_concat:NNNNN \cs_set_eq:NN \cs_set_nopar:Npe }
 \cs_generate_variant:Nn \prop_concat:NNN { ccc }
 \cs_new_protected:Npn \prop_gconcat:NNN
-  { \@@_concat:NNNN \prop_gset_eq:NN }
+  { \@@_concat:NNNNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_gconcat:NNN { ccc }
-\cs_new_protected:Npn \@@_concat:NNNN #1#2#3#4
+\cs_new_protected:Npn \@@_concat:NNNNN #1#2#3#4#5
   {
-    \prop_set_eq:NN \l_@@_internal_prop #3
-    \prop_map_inline:Nn #4 { \prop_put:Nnn \l_@@_internal_prop {##1} {##2} }
-    #1 #2 \l_@@_internal_prop
+    \cs_if_eq:NNTF #3 #5
+      { \@@_concat:nNNN \use_none:nnn #2 #3 #4 }
+      {
+        \@@_set_eq:NNNN #1 #2 #3 #4
+        \@@_concat:nNNN { } #2 #3 #5
+      }
   }
+\cs_new_protected:Npn \@@_concat:nNNN #1#2#3#4
+  {
+    \cs_gset_eq:NN \@@_tmp:w \@@_pair:wn
+    \cs_gset_protected:Npn \@@_pair:wn ##1 \s_@@
+      { \@@_put:nNNnn {#1} #2 #3 {##1} }
+    \exp_last_unbraced:Nf \use_none:nn #4
+    \cs_gset_eq:NN \@@_pair:wn \@@_tmp:w
+  }
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}{\prop_set_from_keyval:Nn, \prop_set_from_keyval:cn}
-% \begin{macro}{\prop_gset_from_keyval:Nn, \prop_gset_from_keyval:cn}
-% \begin{macro}{\prop_const_from_keyval:Nn, \prop_const_from_keyval:cn}
-% \begin{macro}{\prop_put_from_keyval:Nn, \prop_put_from_keyval:cn}
-% \begin{macro}{\prop_gput_from_keyval:Nn, \prop_gput_from_keyval:cn}
-% \begin{macro}{\@@_missing_eq:n}
-%   To avoid tracking throughout the loop the variable name and whether
-%   the assignment is local/global, do everything in a scratch variable
-%   and empty it afterwards to avoid wasting memory.  Loop through items
-%   separated by commas, with \cs{prg_do_nothing:} to avoid losing
-%   braces.  After checking for termination, split the item at the first
-%   and then at the second |=| (which ought to be the first of the
-%   trailing~|=| that we added).  For both splits trim spaces and call a
-%   function (first \cs{@@_from_keyval_key:w} then
-%   \cs{@@_from_keyval_value:w}), followed by the trimmed material,
-%   \cs{s_@@_mark}, the subsequent part of the item, and the trailing |=|'s
-%   and \cs{s_@@_stop}.  After finding the \meta{key} just store it after
-%   \cs{s_@@_stop}.  After finding the \meta{value} ignore completely empty
-%   items (both trailing~|=| were used as delimiters and all parts are
-%   empty); if the remaining part~|#2| consists exactly of the second
-%   trailing~|=| (namely there was exactly one |=|~in the item) then
-%   output one key--value pair for the property list; otherwise complain
-%   about a missing or extra~|=|.
+% \begin{macro}
+%   {
+%     \prop_put_from_keyval:Nn, \prop_put_from_keyval:cn,
+%     \prop_gput_from_keyval:Nn, \prop_gput_from_keyval:cn,
+%     \@@_from_keyval:nn, \@@_from_keyval:Nnn,
+%     \@@_missing_eq:n
+%   }
+%   The core is a call to \cs{keyval_parse:nnn}, with an error message
+%   \cs{@@_missing_eq:n} for entries without~|=|, and a call to (essentially)
+%   \cs[index=prop_put:Nnn]{prop_(g)put:Nnn} for valid key--value pairs.
+%   To avoid repeated scope checks (and errors) when \pkg{l3debug} is
+%   active, we instead use the auxiliary underlying \cs{prop_put:Nnn}.
+%   Because blank keys are valid here, in contrast to \pkg{l3keys}, we set and
+%   restore \cs{l__kernel_keyval_allow_blank_keys_bool}.
+%   The key--value argument may be quite large so we avoid reading it until it
+%   is really necessary.
 %    \begin{macrocode}
+\cs_new_protected:Npn \prop_put_from_keyval:Nn #1
+  { \@@_from_keyval:nn { \@@_put:nNNnn { } \cs_set_nopar:Npe #1 } }
+\cs_generate_variant:Nn \prop_put_from_keyval:Nn { c }
+\cs_new_protected:Npn \prop_gput_from_keyval:Nn #1
+  { \@@_from_keyval:nn { \@@_put:nNNnn { } \cs_gset_nopar:Npe #1 } }
+\cs_generate_variant:Nn \prop_gput_from_keyval:Nn { c }
+\cs_new_protected:Npn \@@_from_keyval:nn
+  {
+    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
+      { \@@_from_keyval:Nnn \c_true_bool }
+      { \@@_from_keyval:Nnn \c_false_bool }
+  }
+\cs_new_protected:Npn \@@_from_keyval:Nnn #1#2#3
+  {
+    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool \c_true_bool
+    \keyval_parse:nnn \@@_missing_eq:n {#2} {#3}
+    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool #1
+  }
+\cs_new_protected:Npn \@@_missing_eq:n
+  { \msg_error:nnn { prop } { prop-keyval } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+%   {
+%     \prop_set_from_keyval:Nn, \prop_set_from_keyval:cn,
+%     \prop_gset_from_keyval:Nn, \prop_gset_from_keyval:cn,
+%   }
+%   Just empty the prop (with the auxiliary underlying
+%   \cs{prop_clear:N} to avoid \pkg{l3debug} problems) and push
+%   key--value entries using
+%   \cs[index=prop_put_from_keyval:Nn]{prop_(g)put_from_keyval:Nn}.
+%    \begin{macrocode}
 \cs_new_protected:Npn \prop_set_from_keyval:Nn #1
   {
-    \prop_clear:N #1
+    \@@_clear:NNN \cs_set_eq:NN \cs_set_nopar:Npe #1
     \prop_put_from_keyval:Nn #1
   }
 \cs_generate_variant:Nn \prop_set_from_keyval:Nn { c }
 \cs_new_protected:Npn \prop_gset_from_keyval:Nn #1
   {
-    \prop_gclear:N #1
+    \@@_clear:NNN \cs_gset_eq:NN \cs_gset_nopar:Npe #1
     \prop_gput_from_keyval:Nn #1
   }
 \cs_generate_variant:Nn \prop_gset_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_const_from_keyval:Nn #1#2
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+%   {
+%     \prop_const_from_keyval:Nn, \prop_const_from_keyval:cn,
+%     \prop_const_linked_from_keyval:Nn, \prop_const_linked_from_keyval:cn
+%   }
+%   For both flat and linked constant props, we create |#1| then use the
+%   same auxiliary as for \cs{prop_gput_from_keyval:Nn}.  It is most
+%   natural to use the already packaged \cs{prop_gput:Nnn}, but that
+%   would mean doing an assignment on a supposedly constant property
+%   list.  To avoid errors when \pkg{l3debug} is activated, we use the
+%   auxiliary underlying \cs{prop_gput:Nnn}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \prop_const_from_keyval:Nn #1
   {
-    \prop_set_from_keyval:Nn \l_@@_internal_prop {#2}
-    \tl_const:Ne #1 { \exp_not:o \l_@@_internal_prop }
-    \prop_clear:N \l_@@_internal_prop
+    \prop_new:N #1
+    \@@_from_keyval:nn { \@@_put:nNNnn { } \cs_gset_nopar:Npe #1 }
   }
 \cs_generate_variant:Nn \prop_const_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_put_from_keyval:Nn
+\cs_new_protected:Npn \prop_const_linked_from_keyval:Nn #1
   {
-    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
-      { \@@_keyval_parse:NNNn \c_true_bool }
-      { \@@_keyval_parse:NNNn \c_false_bool }
-      \prop_put:Nnn
+    \prop_new_linked:N #1
+    \@@_from_keyval:nn { \@@_put:nNNnn { } \cs_gset_nopar:Npe #1 }
   }
-\cs_generate_variant:Nn \prop_put_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_gput_from_keyval:Nn
-  {
-    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
-      { \@@_keyval_parse:NNNn \c_true_bool }
-      { \@@_keyval_parse:NNNn \c_false_bool }
-      \prop_gput:Nnn
-  }
-\cs_generate_variant:Nn \prop_gput_from_keyval:Nn { c }
-\cs_new_protected:Npn \@@_missing_eq:n
-  { \msg_error:nnn { prop } { prop-keyval } }
-\cs_new_protected:Npn \@@_keyval_parse:NNNn #1#2#3#4
-  {
-    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool \c_true_bool
-    \keyval_parse:nnn \@@_missing_eq:n { #2 #3 } {#4}
-    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool #1
-  }
+\cs_generate_variant:Nn \prop_const_linked_from_keyval:Nn { c }
 %    \end{macrocode}
 % \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
 %
 % \subsection{Accessing data in property lists}
 %
-% \begin{macro}{\@@_split:NnTF}
-% \begin{macro}{\@@_split_aux:NnTF}
-% \begin{macro}[EXP]{\@@_split_aux:w}
-%   This function is used by most of the module, and hence must be fast.
-%   It receives a \meta{property list}, a \meta{key}, a \meta{true code}
-%   and a \meta{false code}.  The aim is to split the \meta{property
-%     list} at the given \meta{key} into the \meta{extract_1} before the
-%   key--value pair, the \meta{value} associated with the \meta{key} and
-%   the \meta{extract_2} after the key--value pair.  This is done using
-%   a delimited function, whose definition is as follows, where the
-%   \meta{key} is turned into a string.
+% Accessing/deleting/adding entries is mostly done by \cs{@@_split:NnTFn}, which
+% must be fast because it is used in many \pkg{l3prop} functions.  Its syntax is
+% as follows.
+% \begin{quote}
+%   \cs{@@_split:NnTFn} \meta{property list} \Arg{key} \\
+%   ~~\Arg{true code} \Arg{false code} \Arg{link code}
+% \end{quote}
+% If the \meta{property list} uses the linked data storage, then it runs
+% the \meta{link code}, otherwise it does as follows.
+%
+% It splits the \meta{property list} at the \meta{key}, giving three
+% token lists: the \meta{entries before} the \meta{key}, the
+% \meta{value} associated with the \meta{key} and the \meta{entries
+% after} the \meta{key}.  Both the \meta{entries before} and the
+% \meta{entries after} can be empty or consist of some number of
+% consecutive entries \cs{@@_pair:wn} \meta{key_i} \cs{s_@@}
+% \Arg{value_i}.  If the \meta{key} is present in the \meta{property
+% list} then the \meta{true code} is left in the input stream, with
+% |#2|, |#3|, and |#4| replaced by the \meta{entries before},
+% \meta{value}, and \meta{entries after}.  If the \meta{key} is not
+% present in the \meta{property list} then the \meta{false code} is left
+% in the input stream.  Only the \meta{true code} is used in the
+% replacement text of a macro defined internally, which requires
+% |##|~doubling.
+%
+% \begin{macro}{\@@_split:NnTFn}
+% \begin{macro}{\@@_split_aux:NnTFn}
+% \begin{macro}{\@@_split_aux:w}
+%   The aim is to split the \meta{property list} at the given \meta{key}
+%   into the \meta{extract_1} before the key--value pair, the
+%   \meta{value} associated with the \meta{key} and the \meta{extract_2}
+%   after the key--value pair.  This is done using a delimited function,
+%   whose definition is as follows, where the \meta{key} is turned into
+%   a string.
 %   \begin{quote}
-%     \cs{cs_set:Npn} \cs{@@_split_aux:w} |#1| \\
-%     \quad \cs{@@_pair:wn} \meta{key} \cs{s_@@} |#2| \\
-%     \quad |#3| \cs{s_@@_mark} |#4| |#5| \cs{s_@@_stop} \\
-%     \quad |{| |#4| \Arg{true code} \Arg{false code} |}|
+%     \cs{cs_set:Npn} \cs{@@_split_aux:w} |#1| \cs{@@_chk:w} |#2| \\
+%     \quad \cs{@@_pair:wn} \meta{key} \cs{s_@@} |#3| \\
+%     \quad |#4| \cs{s_@@_mark} |#5| |#6| \cs{s_@@_stop} \\
+%     \quad |{| |#5| \Arg{true code} |}|
 %   \end{quote}
 %
 %   If the \meta{key} is present in the property list,
-%   \cs{@@_split_aux:w}'s |#1| is the part before the \meta{key}, |#2|
-%   is the \meta{value}, |#3| is the part after the \meta{key}, |#4| is
-%   \cs{use_i:nn}, and |#5| is additional tokens that we do not care
+%   \cs{@@_split_aux:w}'s |#2| is the part before the \meta{key}, |#3|
+%   is the \meta{value}, |#4| is the part after the \meta{key}, |#5| is
+%   \cs{use_i:nnn}, and |#6| is additional tokens that we do not care
 %   about.  The \meta{true code} is left in the input stream, and can
-%   use the parameters |#1|, |#2|, |#3| for the three parts of the
+%   use the parameters |#2|, |#3|, |#4| for the three parts of the
 %   property list as desired.  Namely, the original property list is in
-%   this case |#1| \cs{@@_pair:wn} \meta{key} \cs{s_@@} |{#2}| |#3|.
+%   this case \cs{s_@@} \cs{@@_chk:w} |#2| \cs{@@_pair:wn} \meta{key}
+%   \cs{s_@@} |{#3}| |#4|.
 %
 %   If the \meta{key} is not there, then the \meta{function} is
-%   \cs{use_ii:nn}, which keeps the \meta{false code}.
+%   \cs{use_ii:nnn}, which keeps the \meta{false code}.  If the property
+%   list uses the doubly-linked list storage, then the argument
+%   delimited by \cs{@@_chk:w} includes the whole property list, |#2|,
+%   |#3|, |#4| are empty, and |#5| is \cs{use_iii:nnn}.  In all three
+%   cases, the appopriate code among \meta{true code}, \meta{false
+%   code}, and \meta{linked code} is run.
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_split:NnTF #1#2
-  { \exp_args:NNo \@@_split_aux:NnTF #1 { \tl_to_str:n {#2} } }
-\cs_new_protected:Npn \@@_split_aux:NnTF #1#2#3#4
+\cs_new_protected:Npn \@@_split:NnTFn #1#2
+  { \exp_args:NNo \@@_split_aux:NnTFn #1 { \tl_to_str:n {#2} } }
+\cs_new_protected:Npn \@@_split_aux:NnTFn #1#2#3
   {
-    \cs_set:Npn \@@_split_aux:w ##1
-      \@@_pair:wn #2 \s_@@ ##2 ##3 \s_@@_mark ##4 ##5 \s_@@_stop
-      { ##4 {#3} {#4} }
-    \exp_after:wN \@@_split_aux:w #1 \s_@@_mark \use_i:nn
-      \@@_pair:wn #2 \s_@@ { } \s_@@_mark \use_ii:nn \s_@@_stop
+    \cs_set:Npn \@@_split_aux:w ##1 \@@_chk:w ##2
+      \@@_pair:wn #2 \s_@@ ##3 ##4 \s_@@_mark ##5 ##6 \s_@@_stop
+      { ##5 {#3} }
+    \exp_after:wN \@@_split_aux:w #1 \s_@@_mark \use_i:nnn
+      \@@_pair:wn #2 \s_@@ { } \s_@@_mark \use_ii:nnn
+      \@@_chk:w
+      \@@_pair:wn #2 \s_@@ { } \s_@@_mark \use_iii:nnn
+      \s_@@_stop
   }
-\cs_new:Npn \@@_split_aux:w { }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
@@ -998,38 +1563,14 @@
 %
 % \begin{macro}[tested = m3prop002]
 %   {
-%     \prop_remove:Nn, \prop_remove:NV, \prop_remove:Ne,
-%     \prop_remove:cn, \prop_remove:cV, \prop_remove:ce
+%     \prop_get:NnN, \prop_get:NVN, \prop_get:NvN, \prop_get:NeN,
+%     \prop_get:NoN, \prop_get:NxN,
+%     \prop_get:cnN, \prop_get:cVN, \prop_get:cvN, \prop_get:ceN,
+%     \prop_get:coN, \prop_get:cxN,
+%     \prop_get:cnc
 %   }
-% \begin{macro}[tested = m3prop002]
+% \begin{macro}[TF, tested = m3prop004]
 %   {
-%     \prop_gremove:Nn, \prop_gremove:NV, \prop_gremove:Ne,
-%     \prop_gremove:cn, \prop_gremove:cV, \prop_gremove:ce
-%   }
-%   Deleting from a property starts by splitting the list.
-%   If the key is present in the property list, the returned value is ignored.
-%   If the key is missing, nothing happens.
-%    \begin{macrocode}
-\cs_new_protected:Npn \prop_remove:Nn #1#2
-  {
-    \@@_split:NnTF #1 {#2}
-      { \tl_set:Nn #1 { ##1 ##3 } }
-      { }
-  }
-\cs_new_protected:Npn \prop_gremove:Nn #1#2
-  {
-    \@@_split:NnTF #1 {#2}
-      { \tl_gset:Nn #1 { ##1 ##3 } }
-      { }
-  }
-\cs_generate_variant:Nn \prop_remove:Nn  { NV , Ne , c , cV , ce }
-\cs_generate_variant:Nn \prop_gremove:Nn { NV , Ne , c , cV , ce }
-%    \end{macrocode}
-% \end{macro}
-% \end{macro}
-%
-% \begin{macro}[tested = m3prop002]
-%   {
 %     \prop_get:NnN, \prop_get:NVN, \prop_get:NvN, \prop_get:NeN,
 %     \prop_get:NoN, \prop_get:NxN,
 %     \prop_get:cnN, \prop_get:cVN, \prop_get:cvN, \prop_get:ceN,
@@ -1036,66 +1577,77 @@
 %     \prop_get:coN, \prop_get:cxN,
 %     \prop_get:cnc
 %   }
-%   Getting an item from a list is very easy: after splitting,
-%   if the key is in the property list, just set the token list variable
-%   to the return value, otherwise to \cs{q_no_value}.
+% \begin{macro}{\@@_get:NnnTF}
+% \begin{macro}[EXP]{\@@_get_linked:w, \@@_get_linked_aux:w}
+%   Here we implement both \cs{prop_get:NnN} and its branching version through
+%   \cs{@@_get:NnnTF}.  It receives the prop and key, followed by an assignment
+%   used when the value is found, \meta{true code} to run after the assignment,
+%   and some fall-back \meta{false code} for absent values.  It relies on
+%   \cs{@@_split:NnTFn}.  For a flat prop, the first four arguments of
+%   \cs{@@_split:NnTFn} are used, and run either the assignment~|#3{##3}| and
+%   \meta{true code}~|#4|, or the \meta{false code}~|#5|.
 %    \begin{macrocode}
 \cs_new_protected:Npn \prop_get:NnN #1#2#3
   {
-    \@@_split:NnTF #1 {#2}
-      { \tl_set:Nn #3 {##2} }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \@@_get:NnnTF #1 {#2}
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
   }
 \cs_generate_variant:Nn \prop_get:NnN { NV , Nv , Ne , c , cV , cv , ce }
 \cs_generate_variant:Nn \prop_get:NnN { No , Nx , co , cx }
 \cs_generate_variant:Nn \prop_get:NnN { cnc }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}[tested = m3prop002]
-%   {
-%     \prop_pop:NnN, \prop_pop:NVN, 
-%     \prop_pop:NoN,
-%     \prop_pop:cnN, \prop_pop:cVN,
-%     \prop_pop:coN,
-%   }
-% \begin{macro}[tested = m3prop002]
-%   {
-%     \prop_gpop:NnN, \prop_gpop:NVN,
-%     \prop_gpop:NoN,
-%     \prop_gpop:cnN, \prop_gpop:cVN,
-%     \prop_gpop:coN,
-%   }
-%   Popping a value also starts by doing the split.
-%   If the key is present, save the value in the token list and update the
-%   property list as when deleting.
-%   If the key is missing, save \cs{q_no_value} in the token list.
-%    \begin{macrocode}
-\cs_new_protected:Npn \prop_pop:NnN #1#2#3
+\prg_new_protected_conditional:Npnn \prop_get:NnN #1#2#3 { T , F , TF }
   {
-    \@@_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_set:Nn #1 { ##1 ##3 }
-      }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \@@_get:NnnTF #1 {#2}
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
   }
-\cs_new_protected:Npn \prop_gpop:NnN #1#2#3
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { NV , Nv , Ne , c , cV , cv , ce } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { No , Nx , co , cx } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { cnc } { T , F , TF }
+\cs_new_protected:Npn \@@_get:NnnTF #1#2#3#4#5
   {
-    \@@_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_gset:Nn #1 { ##1 ##3 }
-      }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \@@_split:NnTFn #1 {#2}
+      { #3 {##3} #4 }
+      {#5}
+      { \exp_after:wN \@@_get_linked:w #1 {#2} {#3} {#4} {#5} }
   }
-\cs_generate_variant:Nn \prop_pop:NnN  {     NV , No }
-\cs_generate_variant:Nn \prop_pop:NnN  { c , cV , co }
-\cs_generate_variant:Nn \prop_gpop:NnN {     NV , No }
-\cs_generate_variant:Nn \prop_gpop:NnN { c , cV , co }
 %    \end{macrocode}
+%   For a linked prop we must work a bit: \cs{@@_get_linked:w} is followed by
+%   the expansion of the prop, then by four brace groups: the key~|#4|, the
+%   assignment code~|#5|, \meta{true code}~|#6|, and \meta{false code}~|#7|.  If
+%   the key is present, its value is stored in the token list
+%   \cs[no-index]{@@_}|#2~#4|.  If that token list exists,
+%   \cs{@@_get_linked_aux:w} gets called followed by the expansion of that token
+%   list and we grab as~|#2| the value associated to that key, which we feed to
+%   the assignment code and follow-up code.  If the key is absent the token list
+%   can be \tn{undefined} or \tn{relax}.  In both cases \cs{@@_get_linked_aux:w}
+%   finds an empty brace group as~|#2|, \cs{use_none:n} as~|#4| and the
+%   \meta{false code} as~|#5|.
+%   Note that we made \cs{@@_get_linked:w} and subsequent auxiliaries
+%   expandable, because they are also used in \cs{prop_item:Nn}.
+%    \begin{macrocode}
+\cs_new:Npn \@@_get_linked:w
+    \@@_flatten:w #1 \s_@@ #2#3#4#5#6#7
+  {
+    \if_cs_exist:w @@ ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      \exp_after:wN \exp_after:wN \exp_after:wN \@@_get_linked_aux:w
+      \cs:w @@ ~ #2 ~ \tl_to_str:n {#4} \exp_after:wN \cs_end:
+    \else:
+      \exp_after:wN \@@_get_linked_aux:w
+    \fi:
+    \s_@@_mark {#5} {#6}
+    \s_@@ { } \s_@@_mark \use_none:n {#7}
+    \s_@@_stop
+  }
+\cs_new:Npn \@@_get_linked_aux:w
+    #1 \s_@@ #2 #3 \s_@@_mark #4 #5 #6 \s_@@_stop { #4 {#2} #5 }
+%    \end{macrocode}
 % \end{macro}
 % \end{macro}
+% \end{macro}
+% \end{macro}
 %
 % \begin{macro}[EXP]
 %   {
@@ -1103,7 +1655,7 @@
 %     \prop_item:cn, \prop_item:cV, \prop_item:co, \prop_item:ce
 %   }
 % \begin{macro}[EXP]{\@@_item:nnn}
-%   Getting the value corresponding to a key in a property list in an
+%   Getting the value corresponding to a key in a flat property list in an
 %   expandable fashion simply uses \cs{prop_map_tokens:Nn} to go through
 %   the property list.  The auxiliary \cs{@@_item:nnn} receives the
 %   search string~|#1|, the key~|#2| and the value~|#3| and returns as
@@ -1111,8 +1663,15 @@
 %    \begin{macrocode}
 \cs_new:Npn \prop_item:Nn #1#2
   {
-    \exp_args:NNo \prop_map_tokens:Nn #1
-      { \exp_after:wN \@@_item:nnn \exp_after:wN { \tl_to_str:n {#2} } }
+    \@@_if_flat:NTF #1
+      {
+        \exp_args:NNo \prop_map_tokens:Nn #1
+          {
+            \exp_after:wN \@@_item:nnn
+            \exp_after:wN { \tl_to_str:n {#2} }
+          }
+      }
+      { \exp_after:wN \@@_get_linked:w #1 {#2} \use:n { } { } }
   }
 \cs_new:Npn \@@_item:nnn #1#2#3
   {
@@ -1124,161 +1683,259 @@
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}[EXP]{\prop_count:N, \prop_count:c}
-% \begin{macro}[EXP]{\@@_count:nn}
-%   Counting the key--value pairs in a property list is done using the
-%   same approach as for other count functions: turn each entry into a
-%   \texttt{+1} then use integer evaluation to actually do the
-%   mathematics.
+% \subsection{Removing data from property lists}
+%
+% \begin{macro}
+%   {
+%     \@@_pop:NnNNnTF,
+%     \@@_pop_linked:wnNNnTF, \@@_pop_linked:NNNn,
+%     \@@_pop_linked:w,
+%     \@@_pop_linked_prev:w, \@@_pop_linked_next:w
+%   }
+%   This auxiliary is used by both the \cs[no-index]{prop_pop} family and
+%   the \cs[no-index]{prop_remove} family of functions.
+%   It receives a \meta{prop} and a \Arg{key}, three assignment functions
+%   (\cs{tl_set:Nn} \cs{cs_set_eq:NN} \cs{cs_set_nopar:Npe} or their global
+%   versions), then \Arg{code} \Arg{true code} \Arg{false code}.
+%
+%   For a flat prop, split it.  If the \meta{key} is there, reconstruct the rest
+%   of the prop from the two extracts |##2| |##4| and assign using
+%   \cs[index=tl_set:Nn]{tl_(g)set:Nn}, then run \meta{code} \Arg{value} with
+%   the \meta{value} found, and run the \meta{true code}.  If the \meta{key} is
+%   absent, run the \meta{false code}.
+%
+%   For a linked prop, the removal is done by \cs{@@_pop_linked:wnNNnTF}, which
+%   removes the key--value pair from the doubly-linked list and runs its last
+%   three arguments \Arg{code} \Arg{true code} \Arg{false code} depending on
+%   whether the key--value is found, in the same way as for flat props.
 %    \begin{macrocode}
-\cs_new:Npn \prop_count:N #1
+\cs_new_protected:Npn \@@_pop:NnNNnTF #1#2#3#4#5#6#7
   {
-    \int_eval:n
+    \@@_split:NnTFn #1 {#2}
       {
-        0
-        \prop_map_function:NN #1 \@@_count:nn
+        #4 #1 { \exp_not:n { \s_@@ \@@_chk:w ##2 ##4 } }
+        #5 {##3}
+        #6
       }
+      {#7}
+      {
+        \exp_after:wN \@@_pop_linked:wnNNnTF #1 {#2}
+          #3 #4 {#5} {#6} {#7}
+      }
   }
-\cs_new:Npn \@@_count:nn #1#2 { + 1 }
-\cs_generate_variant:Nn \prop_count:N { c }
 %    \end{macrocode}
+%   The next auxiliary \cs{@@_pop_linked:wnNNnTF}, together with the |NNNn|
+%   auxiliary, checks if the key is present in the \meta{linked prop}, then the
+%   corresponding value (if present) is passed as a braced argument to the
+%   \meta{code} and the \meta{true code} or \meta{false code} is run as
+%   appropriate.  Before that, there are also three assignments: the token lists
+%   for the previous key and next key are made to point to each other, cf.\
+%   \cs{@@_pop_linked:w}, and the token list for the given key is made
+%   undefined.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_pop_linked:wnNNnTF
+    \@@_flatten:w #1 \s_@@ #2#3#4#5#6#7
+  {
+    \if_cs_exist:w @@ ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      \exp_after:wN \@@_pop_linked:NNNn
+      \cs:w @@ ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      #5 #6 {#7}
+    \else:
+      \exp_after:wN \use_iii:nnn
+    \fi:
+    \use_i:nn
+  }
+\cs_new_protected:Npn \@@_pop_linked:NNNn #1#2#3#4
+  {
+    \if_meaning:w \scan_stop: #1
+      \exp_after:wN \exp_after:wN \exp_after:wN \use_iii:nnn
+    \else:
+      \exp_after:wN \@@_pop_linked:w #1 #1 #2 #3 {#4}
+    \fi:
+  }
+\cs_new_protected:Npn \@@_pop_linked:w
+    \use_none:n #1#2 \s_@@ #3#4#5#6#7#8
+  {
+    #6 #5 \tex_undefined:D
+    #7 #1
+      {
+        \exp_after:wN \@@_pop_linked_prev:w #1
+        \exp_not:N #4
+      }
+    #7 #4
+      {
+        \exp_not:n { \use_none:n #1 }
+        \exp_not:f { \exp_after:wN \@@_pop_linked_next:w #4 }
+      }
+    #8 {#3}
+  }
+\cs_new:Npn \@@_pop_linked_prev:w #1 \s_@@ #2#3
+  { \exp_not:n { #1 \s_@@ {#2} } }
+\cs_new:Npn \@@_pop_linked_next:w \use_none:n #1 { \exp_stop_f: }
+%    \end{macrocode}
 % \end{macro}
-% \end{macro}
 %
-% \begin{macro}[EXP]{\prop_to_keyval:N}
-% \begin{macro}[EXP]
-%   {\@@_to_keyval_exp_after:wN, \@@_to_keyval:nn, \@@_to_keyval:nnw}
-%   Each property name and value pair will be returned in the form
-%   \verb*| |\marg{name}\verb*|= |\marg{value}. As one of the main use cases for
-%   this macro is to pass the \meta{property list} on to a key--value parser, we
-%   have to make sure that the behaviour is as good as possible. Using a space
-%   before the opening brace we get the correct brace stripping behaviour for
-%   most of the key--value parsers available in \LaTeX.
-%   Iterate over the
-%   \meta{property list} and remove the leading comma afterwards. Only the value
-%   has to be protected in \cs{__kernel_exp_not:w} as the property name is
-%   always a string. After the loop the leading comma is removed by
-%   \cs{use_none:n} and afterwards \cs{__kernel_exp_not:w} eventually finds the
-%   opening brace of its argument.
+% \begin{macro}[tested = m3prop002]
+%   {
+%     \prop_remove:Nn, \prop_remove:NV, \prop_remove:Ne,
+%     \prop_remove:cn, \prop_remove:cV, \prop_remove:ce
+%   }
+% \begin{macro}[tested = m3prop002]
+%   {
+%     \prop_gremove:Nn, \prop_gremove:NV, \prop_gremove:Ne,
+%     \prop_gremove:cn, \prop_gremove:cV, \prop_gremove:ce
+%   }
+%   Deleting from a property relies on \cs{@@_pop:NnNNnTF}.  The three
+%   assignment functions are suitably local or global.  The last three arguments
+%   are \cs{use_none:n} and two empty brace groups: if the key is found we get
+%   \cs{use_none:n} \Arg{key} \meta{empty}, which expands to nothing, and
+%   otherwise we just get \meta{empty}.  The auxiliary takes care of actually
+%   removing the entry from the prop.
 %    \begin{macrocode}
-\cs_new:Npn \prop_to_keyval:N #1
+\cs_new_protected:Npn \prop_remove:Nn #1#2
   {
-    \__kernel_exp_not:w
-      \prop_if_empty:NTF #1
-        { {} }
-        {
-          \exp_after:wN \exp_after:wN \exp_after:wN
-          {
-            \tex_expanded:D
-              {
-                \__kernel_exp_not:w { \use_none:n }
-                \prop_map_function:NN #1 \@@_to_keyval:nn
-              }
-          }
-        }
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      \use_none:n { } { }
   }
-\cs_new:Npn \@@_to_keyval:nn #1#2
-  { , ~ {#1} =~ { \__kernel_exp_not:w {#2} } }
+\cs_new_protected:Npn \prop_gremove:Nn #1#2
+  {
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      \use_none:n { } { }
+  }
+\cs_generate_variant:Nn \prop_remove:Nn  { NV , Ne , c , cV , ce }
+\cs_generate_variant:Nn \prop_gremove:Nn { NV , Ne , c , cV , ce }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 %
+% \begin{macro}[tested = m3prop002]
+%   {
+%     \prop_pop:NnN, \prop_pop:NVN, \prop_pop:NoN,
+%     \prop_pop:cnN, \prop_pop:cVN, \prop_pop:coN,
+%     \prop_gpop:NnN, \prop_gpop:NVN, \prop_gpop:NoN,
+%     \prop_gpop:cnN, \prop_gpop:cVN, \prop_gpop:coN,
+%   }
 % \begin{macro}[TF, tested = m3prop004]
 %   {
-%     \prop_pop:NnN,  \prop_pop:NVN, 
-%     \prop_pop:cnN,  \prop_pop:cVN,
-%     \prop_gpop:NnN, \prop_gpop:NVN,
-%     \prop_gpop:NoN,
-%     \prop_gpop:cnN, \prop_gpop:cVN,
+%     \prop_pop:NnN, \prop_pop:NVN, \prop_pop:NoN,
+%     \prop_pop:cnN, \prop_pop:cVN, \prop_pop:coN,
+%     \prop_gpop:NnN, \prop_gpop:NVN, \prop_gpop:NoN,
+%     \prop_gpop:cnN, \prop_gpop:cVN, \prop_gpop:coN,
 %   }
-%   Popping an item from a property list, keeping track of whether
-%   the key was present or not, is implemented as a conditional.
-%   If the key was missing, neither the property list, nor the token
-%   list are altered. Otherwise, \cs{prg_return_true:} is used after
-%   the assignments.
+%   Popping a value is almost the same, but the value found is kept.
+%   For the non-branching version, we additionally set the target token list to
+%   \cs{q_no_value}, while for the branching version we must produce
+%   \cs{prg_return_true:} or \cs{prg_return_false:}.
 %    \begin{macrocode}
+\cs_new_protected:Npn \prop_pop:NnN #1#2#3
+  {
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
+  }
+\cs_new_protected:Npn \prop_gpop:NnN #1#2#3
+  {
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
+  }
+\cs_generate_variant:Nn \prop_pop:NnN  {     NV , No }
+\cs_generate_variant:Nn \prop_pop:NnN  { c , cV , co }
+\cs_generate_variant:Nn \prop_gpop:NnN {     NV , No }
+\cs_generate_variant:Nn \prop_gpop:NnN { c , cV , co }
 \prg_new_protected_conditional:Npnn \prop_pop:NnN #1#2#3 { T , F , TF }
   {
-    \@@_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_set:Nn #1 { ##1 ##3 }
-        \prg_return_true:
-      }
-      { \prg_return_false: }
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
   }
 \prg_new_protected_conditional:Npnn \prop_gpop:NnN #1#2#3 { T , F , TF }
   {
-    \@@_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_gset:Nn #1 { ##1 ##3 }
-        \prg_return_true:
-      }
-      { \prg_return_false: }
+    \@@_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
   }
-\prg_generate_conditional_variant:Nnn \prop_pop:NnN  { NV , c , cV } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_gpop:NnN { NV , c , cV } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_pop:NnN
+  { NV , No , c , cV , co } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_gpop:NnN
+  { NV , No , c , cV , co } { T , F , TF }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
+% \subsection{Adding data to property lists}
+%
 % \begin{macro}[tested = m3prop002]
 %   {
 %     \prop_put:Nnn,  \prop_put:NnV,  \prop_put:Nnv,  \prop_put:Nne,
 %     \prop_put:NVn,  \prop_put:NVV,  \prop_put:NVv,  \prop_put:NVe,
-%     \prop_put:Nvn,  \prop_put:NvV,  \prop_put:Nvv,  \prop_put:Nve, 
+%     \prop_put:Nvn,  \prop_put:NvV,  \prop_put:Nvv,  \prop_put:Nve,
 %     \prop_put:Nen,  \prop_put:NeV,  \prop_put:Nev,  \prop_put:Nee,
-%     \prop_put:Nno,  \prop_put:Nnx,  \prop_put:NVx,
-%     \prop_put:Non,  \prop_put:Noo,  \prop_put:NxV,  \prop_put:Nxx,
+%     \prop_put:Nno,  \prop_put:Non,  \prop_put:Noo,
+%     \prop_put:Nnx,  \prop_put:NVx,  \prop_put:NxV,  \prop_put:Nxx,
 %     \prop_put:cnn,  \prop_put:cnV,  \prop_put:cnv,  \prop_put:cne,
-%     \prop_put:cno,  \prop_put:cnx,  
 %     \prop_put:cVn,  \prop_put:cVV,  \prop_put:cVv,  \prop_put:cVe,
-%     \prop_put:cvn,  \prop_put:cvV,  \prop_put:cvv,  \prop_put:cve, 
+%     \prop_put:cvn,  \prop_put:cvV,  \prop_put:cvv,  \prop_put:cve,
 %     \prop_put:cen,  \prop_put:ceV,  \prop_put:cev,  \prop_put:cee,
-%     \prop_put:con,  \prop_put:coo,  \prop_put:cxV,  \prop_put:cxx
+%     \prop_put:cno,  \prop_put:con,  \prop_put:coo,
+%     \prop_put:cnx,  \prop_put:cVx,  \prop_put:cxV,  \prop_put:cxx
 %   }
 % \begin{macro}[tested = m3prop002]
 %   {
 %     \prop_gput:Nnn, \prop_gput:NnV, \prop_gput:Nnv, \prop_gput:Nne,
 %     \prop_gput:NVn, \prop_gput:NVV, \prop_gput:NVv, \prop_gput:NVe,
-%     \prop_gput:Nvn, \prop_gput:NvV, \prop_gput:Nvv, \prop_gput:Nve, 
+%     \prop_gput:Nvn, \prop_gput:NvV, \prop_gput:Nvv, \prop_gput:Nve,
 %     \prop_gput:Nen, \prop_gput:NeV, \prop_gput:Nev, \prop_gput:Nee,
 %     \prop_gput:Nno, \prop_gput:Non, \prop_gput:Noo,
-%     \prop_gput:Nnx, \prop_gput:NVx, \prop_gput:Nxn, \prop_gput:Nxx,
+%     \prop_gput:Nnx, \prop_gput:NVx, \prop_gput:NxV, \prop_gput:Nxx,
 %     \prop_gput:cnn, \prop_gput:cnV, \prop_gput:cnv, \prop_gput:cne,
-%     \prop_gput:cno, \prop_gput:cnx,
 %     \prop_gput:cVn, \prop_gput:cVV, \prop_gput:cVv, \prop_gput:cVe,
-%     \prop_gput:cvn, \prop_gput:cvV, \prop_gput:cvv, \prop_gput:cve, 
+%     \prop_gput:cvn, \prop_gput:cvV, \prop_gput:cvv, \prop_gput:cve,
 %     \prop_gput:cen, \prop_gput:ceV, \prop_gput:cev, \prop_gput:cee,
-%     \prop_gput:cno, \prop_gput:con, \prop_gput:cxn, \prop_gput:cxx
+%     \prop_gput:cno, \prop_gput:con, \prop_gput:coo,
+%     \prop_gput:cnx, \prop_gput:cVx, \prop_gput:cxV, \prop_gput:cxx
 %   }
-% \begin{macro}{\@@_put:NNnn}
-%   Since the branches of \cs{@@_split:NnTF} are used as the replacement
-%   text of an internal macro, and since the \meta{key} and new
-%   \meta{value} may contain arbitrary tokens, it is not safe to include
-%   them in the argument of \cs{@@_split:NnTF}.  We thus start by
-%   storing in \cs{l_@@_internal_tl} tokens which (after
-%   \texttt{e}-expansion) encode the key--value pair.  This variable can
-%   safely be used in \cs{@@_split:NnTF}.  If the \meta{key} was absent,
-%   append the new key--value to the list.
-%   Otherwise concatenate the extracts |##1|
-%   and |##3| with the new key--value pair \cs{l_@@_internal_tl}.  The
-%   updated entry is placed at the same spot as the original \meta{key}
-%   in the property list, preserving the order of entries.
+% \begin{macro}[tested = m3prop002]
+%   {
+%     \prop_put_if_new:Nnn, \prop_put_if_new:NVn, \prop_put_if_new:NnV,
+%     \prop_put_if_new:cnn, \prop_put_if_new:cVn, \prop_put_if_new:cnV,
+%     \prop_gput_if_new:Nnn, \prop_gput_if_new:NVn, \prop_gput_if_new:NnV,
+%     \prop_gput_if_new:cnn, \prop_gput_if_new:cVn, \prop_gput_if_new:cnV
+%   }
+% \begin{macro}[tested = m3prop002]
+%   {
+%     \@@_put:NNNnn, \@@_put_linked:wnNN,
+%     \@@_put_linked:NNNN, \@@_put_linked_old:w,
+%     \@@_put_linked_new:w
+%   }
+%   All of the \cs[no-index]{prop_(g)put(_if_new):Nnn} functions are
+%   based on the same auxiliary, which receives \meta{code} and an
+%   \enquote{assignment}, followed by \meta{prop} \Arg{key} \Arg{new
+%   value}.  The
+%   assignment \cs[index=cs_set_nopar:Npe]{cs_(g)set_nopar:Npe} is the
+%   primitive assignment without any checking: in the case of linked
+%   props it is applied to individual
+%   pieces of the linked prop, which are typically not yet defined.
+%   Debugging the scope of the variable is done at a higher level by
+%   letting \pkg{l3debug} change \cs{prop_put:Nnn} and friends.  This
+%   allows other \pkg{l3prop} commands to directly call the underlying
+%   auxiliary to skip this checking step and avoid getting multiple
+%   error messages for the same error.
+%   The \meta{code} (empty for |put| and \cs{use_none:nnn} for
+%   |put_if_new|) is placed before the assignment in cases where the key
+%   is already present, in order to suppress the assignment in the
+%   |put_if_new| case.
 %    \begin{macrocode}
-\cs_new_protected:Npn \prop_put:Nnn  { \@@_put:NNnn \__kernel_tl_set:Nx }
-\cs_new_protected:Npn \prop_gput:Nnn { \@@_put:NNnn \__kernel_tl_gset:Nx }
-\cs_new_protected:Npn \@@_put:NNnn #1#2#3#4
-  {
-    \tl_set:Nn \l_@@_internal_tl
-      {
-        \exp_not:N \@@_pair:wn \tl_to_str:n {#3}
-        \s_@@ { \exp_not:n {#4} }
-      }
-    \@@_split:NnTF #2 {#3}
-      { #1 #2 { \exp_not:n {##1} \l_@@_internal_tl \exp_not:n {##3} } }
-      { #1 #2 { \exp_not:o {#2} \l_@@_internal_tl } }
-  }
+\cs_new_protected:Npn \prop_put:Nnn
+  { \@@_put:nNNnn { } \cs_set_nopar:Npe }
+\cs_new_protected:Npn \prop_gput:Nnn
+  { \@@_put:nNNnn { } \cs_gset_nopar:Npe }
+\cs_new_protected:Npn \prop_put_if_new:Nnn
+  { \@@_put:nNNnn \use_none:nnn \cs_set_nopar:Npe }
+\cs_new_protected:Npn \prop_gput_if_new:Nnn
+  { \@@_put:nNNnn \use_none:nnn \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_put:Nnn
   {
          NnV , Nnv , Nne , NV , NVV , NVv , NVe ,
@@ -1307,50 +1964,104 @@
   }
 \cs_generate_variant:Nn \prop_gput:Nnn
   { cno , co , coo , cnx , cVx , cxV , cxx }
+\cs_generate_variant:Nn \prop_put_if_new:Nnn
+  { NnV , NV , cnV , cV }
+\cs_generate_variant:Nn \prop_gput_if_new:Nnn
+  { NnV , NV , cnV , cV }
 %    \end{macrocode}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-%
-% \begin{macro}[tested = m3prop002]
-%   {
-%     \prop_put_if_new:Nnn, \prop_put_if_new:NVn, \prop_put_if_new:NnV,
-%     \prop_put_if_new:cnn, \prop_put_if_new:cVn, \prop_put_if_new:cnV
-%   }
-% \begin{macro}[tested = m3prop002]
-%   {
-%     \prop_gput_if_new:Nnn, \prop_gput_if_new:NVn, \prop_gput_if_new:NnV,
-%     \prop_gput_if_new:cnn, \prop_gput_if_new:cVn, \prop_gput_if_new:cnV
-%   }
-% \begin{macro}{\@@_put_if_new:NNnn}
-%   Adding conditionally also splits. If the key is already present,
-%   the three brace groups given by \cs{@@_split:NnTF} are removed.
-%   If the key is new, then the value is added, being careful to
-%   convert the key to a string using \cs{tl_to_str:n}.
+%   Since the true branch of \cs{@@_split:NnTFn} is used as the
+%   replacement text of an internal macro, and since the \meta{key} and
+%   new \meta{value} may contain arbitrary tokens, it is not safe to
+%   include them in the argument of \cs{@@_split:NnTFn}.  We thus start
+%   by storing in \cs{l_@@_internal_tl} tokens which (after
+%   \texttt{x}-expansion) encode the key--value pair.  This variable can
+%   safely be used in \cs{@@_split:NnTFn}.  For a flat prop, if the
+%   \meta{key} was absent, append the new key--value to the list;
+%   otherwise concatenate the extracts |##2| and |##4| with the new
+%   key--value pair \cs{l_@@_internal_tl}.  The updated entry is placed
+%   at the same spot as the original \meta{key} in the property list,
+%   preserving the order of entries.  For a linked prop, call
+%   \cs{@@_put_linked:wnNN}, which constructs the control sequence in
+%   which we will place the new value.  If it matches \cs{scan_stop:}
+%   then the key was not yet there and we add it using
+%   \cs{@@_put_linked_new:w}, otherwise it was already there and we use
+%   \cs{@@_put_linked_old:w}.
 %    \begin{macrocode}
-\cs_new_protected:Npn \prop_put_if_new:Nnn
-  { \@@_put_if_new:NNnn \__kernel_tl_set:Nx }
-\cs_new_protected:Npn \prop_gput_if_new:Nnn
-  { \@@_put_if_new:NNnn \__kernel_tl_gset:Nx }
-\cs_new_protected:Npn \@@_put_if_new:NNnn #1#2#3#4
+\cs_new_protected:Npn \@@_put:nNNnn #1#2#3#4#5
   {
     \tl_set:Nn \l_@@_internal_tl
       {
-        \exp_not:N \@@_pair:wn \tl_to_str:n {#3}
-        \s_@@ \exp_not:n { {#4} }
+        \exp_not:N \@@_pair:wn \tl_to_str:n {#4}
+        \s_@@ { \exp_not:n {#5} }
       }
-    \@@_split:NnTF #2 {#3}
-      { }
-      { #1 #2 { \exp_not:o {#2} \l_@@_internal_tl } }
+    \@@_split:NnTFn #3 {#4}
+      {
+        #1 #2 #3
+          {
+            \s_@@ \@@_chk:w \exp_not:n {##2}
+            \l_@@_internal_tl \exp_not:n {##4}
+          }
+      }
+      { #2 #3 { \exp_not:o {#3} \l_@@_internal_tl } }
+      { \exp_after:wN \@@_put_linked:wnnN #3 {#4} {#1} #2 }
   }
-\cs_generate_variant:Nn \prop_put_if_new:Nnn
-  { NnV , NV , cnV , cV }
-\cs_generate_variant:Nn \prop_gput_if_new:Nnn
-  { NnV , NV , cnV , cV }
+\cs_new_protected:Npn \@@_put_linked:wnnN
+    \@@_flatten:w #1 \s_@@ #2#3#4
+  {
+    \exp_after:wN \@@_put_linked:NNnN
+    \cs:w @@ ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+    #1
+  }
+\cs_new_protected:Npn \@@_put_linked:NNnN #1#2#3#4
+  {
+    \if_meaning:w \scan_stop: #1
+      \exp_after:wN \@@_put_linked_new:w #2 #1 #2 #4
+    \else:
+      \exp_after:wN \@@_put_linked_old:w #1 { #3 #4 #1 }
+    \fi:
+  }
 %    \end{macrocode}
+%   To add a new entry, \cs{@@_put_linked_new:w} receives the expansion
+%   of the end-pointer, namely \cs{use_none:n} \meta{last key pointer},
+%   followed by the new key pointer~|#2|, the end pointer~|#3|, and an
+%   assignment function~|#4|.  Set up the doubly-linked list in the
+%   order |#1|, |#2|, |#3|, placing the key--value pair
+%   \cs{l_@@_internal_tl} in~|#2|.  To replace an old entry,
+%   \cs{@@_put_linked_old:w} receives the expansion of that entry, and
+%   it reassigns it (|#5|) using the assignment~|#6|, by simply
+%   replacing the payload |#2| \cs{s_@@} |#3| by \cs{l_@@_internal_tl}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_put_linked_new:w
+    \use_none:n #1#2#3#4
+  {
+    #4 #1
+      {
+        \exp_after:wN \@@_pop_linked_prev:w #1
+        \exp_not:N #2
+      }
+    #4 #2
+      {
+        \exp_not:n { \use_none:n #1 }
+        \l_@@_internal_tl
+        \exp_not:N #3
+      }
+    #4 #3 { \exp_not:n { \use_none:n #2 } }
+  }
+\cs_new_protected:Npn \@@_put_linked_old:w
+    \use_none:n #1#2 \s_@@ #3#4#5
+  {
+    #5
+      {
+        \exp_not:n { \use_none:n #1 }
+        \l_@@_internal_tl
+        \exp_not:N #4
+      }
+  }
+%    \end{macrocode}
 % \end{macro}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
 % \subsection{Property list conditionals}
 %
@@ -1365,17 +2076,39 @@
 % \end{macro}
 %
 % \begin{macro}[pTF, tested = m3prop003]{\prop_if_empty:N, \prop_if_empty:c}
-%   Same test as for token lists.
+% \begin{macro}{\@@_if_empty_return:w}
+%   A flat property list is empty if it matches \cs{c_empty_prop}.  A linked
+%   property list is empty if its second token (the end pointer) and last token
+%   (the first key pointer) are equal.  There cannot be false positives because
+%   the end pointer takes the form \cs{use_none:n} \meta{pointer} while the
+%   other pointers have more elaborate structure.  The subtle code branch here
+%   is when a non-empty flat property list is given: then \cs{@@_if_empty:w}
+%   reads the whole property list as~|#1| and |#2|, |#3|, |#4| are |2|, |3|,
+%   |4|, respectively.
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \prop_if_empty:N #1 { p , T , F , TF }
   {
-    \tl_if_eq:NNTF #1 \c_empty_prop
-      \prg_return_true: \prg_return_false:
+    \if_meaning:w #1 \c_empty_prop
+      \prg_return_true:
+    \else:
+      \exp_after:wN \@@_if_empty_return:w #1
+        \@@_flatten:w 2 \s_@@ 34 \s_@@_stop
+    \fi:
   }
+\cs_new:Npn \@@_if_empty_return:w
+    #1 \@@_flatten:w #2 \s_@@ #3#4#5 \s_@@_stop
+  {
+    \if_meaning:w #2 #4
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
+  }
 \prg_generate_conditional_variant:Nnn \prop_if_empty:N
   { c } { p , T , F , TF }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}[pTF, tested = m3prop003]
 %   {
@@ -1382,29 +2115,43 @@
 %     \prop_if_in:Nn, \prop_if_in:NV, \prop_if_in:Ne, \prop_if_in:No,
 %     \prop_if_in:cn, \prop_if_in:cV, \prop_if_in:ce, \prop_if_in:co
 %   }
-% \begin{macro}[EXP]{\@@_if_in:nnn}
-%   Testing expandably if a key is in a property list
-%   requires to go through the key--value pairs one by one.
-%   This is rather slow, and a faster test would be
+% \begin{macro}[EXP]{\@@_if_in_flat:nnn}
+%   For a linked prop, use \cs{@@_get_linked:w} to look up whether the control
+%   sequence constructed from the prefix and the sought-after key exists; this
+%   auxiliary calls \cs{use_none:n} \Arg{value} \cs{prg_return_true:} if the key
+%   is found, and otherwise \cs{prg_return_false:}.  For a flat prop, testing
+%   expandably if a key is there requires to go through the key--value pairs one
+%   by one.  This is rather slow, and a faster test would be
 %   \begin{verbatim}
 %     \prg_new_protected_conditional:Npnn \prop_if_in:Nn #1 #2
 %       {
-%         \@@_split:NnTF #1 {#2}
+%         \@@_split:NnTFn #1 {#2}
 %           { \prg_return_true: }
 %           { \prg_return_false: }
+%           { ... }
 %       }
 %   \end{verbatim}
-%   but \cs{@@_split:NnTF} is non-expandable.
+%   but \cs{@@_split:NnTFn} is non-expandable.
 %   Instead, we use \cs{prop_map_tokens:Nn} to compare the search key to
 %   each key in turn using \cs{str_if_eq:ee}, which is expandable.
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \prop_if_in:Nn #1#2 { p , T , F , TF }
   {
-    \exp_args:NNo \prop_map_tokens:Nn #1
-      { \exp_after:wN \@@_if_in:nnn \exp_after:wN { \tl_to_str:n {#2} } }
-    \prg_return_false:
+    \@@_if_flat:NTF #1
+      {
+        \exp_after:wN \prop_map_tokens:Nn \exp_after:wN #1
+          {
+            \exp_after:wN \@@_if_in_flat:nnn
+            \exp_after:wN { \tl_to_str:n {#2} }
+          }
+        \prg_return_false:
+      }
+      {
+        \exp_after:wN \@@_get_linked:w #1 {#2}
+        \use_none:n \prg_return_true: \prg_return_false:
+      }
   }
-\cs_new:Npn \@@_if_in:nnn #1#2#3
+\cs_new:Npn \@@_if_in_flat:nnn #1#2#3
   {
     \str_if_eq:eeT {#1} {#2}
       { \prop_map_break:n { \use_i:nn \prg_return_true: } }
@@ -1415,38 +2162,6 @@
 % \end{macro}
 % \end{macro}
 %
-% \subsection{Recovering values from property lists with branching}
-%
-% \begin{macro}[TF, tested = m3prop004]
-%   {
-%     \prop_get:NnN, \prop_get:NVN, \prop_get:NvN, \prop_get:NeN,
-%     \prop_get:NoN, \prop_get:NxN,
-%     \prop_get:cnN, \prop_get:cVN, \prop_get:cvN, \prop_get:ceN,
-%     \prop_get:coN, \prop_get:cxN,
-%     \prop_get:cnc
-%   }
-%   Getting the value corresponding to a key, keeping track of whether
-%   the key was present or not, is implemented as a conditional (with
-%   side effects). If the key was absent, the token list is not altered.
-%    \begin{macrocode}
-\prg_new_protected_conditional:Npnn \prop_get:NnN #1#2#3 { T , F , TF }
-  {
-    \@@_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \prg_return_true:
-      }
-      { \prg_return_false: }
-  }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { NV , Nv , Ne , c , cV , cv , ce } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { No , Nx , co , cx } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { cnc } { T , F , TF }
-%    \end{macrocode}
-% \end{macro}
-%
 % \subsection{Mapping over property lists}
 %
 % \begin{macro}[tested = m3prop003]
@@ -1455,6 +2170,9 @@
 %     \prop_map_function:cN, \prop_map_function:cc
 %   }
 % \begin{macro}{\@@_map_function:Nw}
+%   We first |f|-expand to flatten~|#1| in case it was a linked list.
+%   The \cs{use_i:nnn} removes the leading \cs{s_@@} \cs{@@_chk:w} of
+%   the flattened prop.
 %   The even-numbered arguments of \cs{@@_map_function:Nw} are keys,
 %   hence have string catcodes, except at the end where they are
 %   \cs{fi:} \cs{prop_map_break:}.  The \cs{fi:} ends the \cs{if_false:}
@@ -1463,10 +2181,8 @@
 %    \begin{macrocode}
 \cs_new:Npn \prop_map_function:NN #1#2
   {
-    \exp_after:wN \use_i_ii:nnn
-    \exp_after:wN \@@_map_function:Nw
-    \exp_after:wN #2
-    #1
+    \exp_last_unbraced:Nnf
+      \use_i:nnn { \@@_map_function:Nw #2 } #1
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
@@ -1506,7 +2222,7 @@
       { @@_map_ \int_use:N \g__kernel_prg_map_int :wn } \@@_pair:wn
     \int_gincr:N \g__kernel_prg_map_int
     \cs_gset_protected:Npn \@@_pair:wn ##1 \s_@@ ##2 {#2}
-    #1
+    \exp_last_unbraced:Nf \use_none:nn #1
     \prg_break_point:Nn \prop_map_break:
       {
         \int_gdecr:N \g__kernel_prg_map_int
@@ -1520,8 +2236,8 @@
 %
 % \begin{macro}[rEXP]{\prop_map_tokens:Nn, \prop_map_tokens:cn}
 % \begin{macro}{\@@_map_tokens:nw}
-%   The mapping is very similar to \cs{prop_map_function:NN}.  The
-%   \cs{use_i:nn} removes the leading \cs{s_@@}.  The odd construction
+%   The mapping is very similar to \cs{prop_map_function:NN}.
+%   The odd construction
 %   |\use:n {#1}| allows |#1| to contain any token without interfering
 %   with \cs{prop_map_break:}.  The loop stops when the \meta{key}
 %   between \cs{@@_pair:wn} and \cs{s_@@} is \cs{fi:}
@@ -1529,8 +2245,8 @@
 %    \begin{macrocode}
 \cs_new:Npn \prop_map_tokens:Nn #1#2
   {
-    \exp_last_unbraced:Nno
-      \use_i:nn { \@@_map_tokens:nw {#2} } #1
+    \exp_last_unbraced:Nnf
+      \use_i:nnn { \@@_map_tokens:nw {#2} } #1
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
     \@@_pair:wn \fi: \prop_map_break: \s_@@ { }
@@ -1567,16 +2283,98 @@
 % \end{macro}
 % \end{macro}
 %
+% \subsection{Uses of mapping over property lists}
+%
+% \begin{macro}[EXP]{\prop_count:N, \prop_count:c}
+% \begin{macro}[EXP]{\@@_count:nn}
+%   Counting the key--value pairs in a property list is done using the
+%   same approach as for other count functions: turn each entry into a
+%   \texttt{+1} then use integer evaluation to actually do the
+%   mathematics.
+%    \begin{macrocode}
+\cs_new:Npn \prop_count:N #1
+  {
+    \int_eval:n
+      {
+        0
+        \prop_map_function:NN #1 \@@_count:nn
+      }
+  }
+\cs_new:Npn \@@_count:nn #1#2 { + 1 }
+\cs_generate_variant:Nn \prop_count:N { c }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\prop_to_keyval:N}
+% \begin{macro}[EXP]
+%   {\@@_to_keyval_exp_after:wN, \@@_to_keyval:nn, \@@_to_keyval:nnw}
+%   Each property name and value pair will be returned in the form
+%   \verb*| |\marg{name}\verb*|= |\marg{value}. As one of the main use cases for
+%   this macro is to pass the \meta{property list} on to a key--value parser, we
+%   have to make sure that the behaviour is as good as possible. Using a space
+%   before the opening brace we get the correct brace stripping behaviour for
+%   most of the key--value parsers available in \LaTeX{}.
+%   Iterate over the
+%   \meta{property list} and remove the leading comma afterwards. Only the value
+%   has to be protected in \cs{__kernel_exp_not:w} as the property name is
+%   always a string. After the loop the leading comma is removed by
+%   \cs{use_none:n} and afterwards \cs{__kernel_exp_not:w} eventually finds the
+%   opening brace of its argument.
+%    \begin{macrocode}
+\cs_new:Npn \prop_to_keyval:N #1
+  {
+    \__kernel_exp_not:w
+      \prop_if_empty:NTF #1
+        { {} }
+        {
+          \exp_after:wN \exp_after:wN \exp_after:wN
+          {
+            \tex_expanded:D
+              {
+                \exp_not:N \use_none:n
+                \prop_map_function:NN #1 \@@_to_keyval:nn
+              }
+          }
+        }
+  }
+\cs_new:Npn \@@_to_keyval:nn #1#2
+  { , ~ {#1} =~ { \__kernel_exp_not:w {#2} } }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
 % \subsection{Viewing property lists}
 %
 % \begin{macro}[tested = m3show001]
 %   {\prop_show:N, \prop_show:c, \prop_log:N, \prop_log:c}
-% \begin{macro}{\@@_show:NN}
-% \begin{macro}[rEXP]{\@@_show_validate:w}
-%   Apply the general \cs{__kernel_chk_tl_type:NnnT}.
-%   Contrarily to sequences and comma lists,
-%   we use \cs{msg_show_item:nn} to format both the key and the value
-%   for each pair.
+% \begin{macro}{\@@_show:NN, \@@_show_finally:NNn, \@@_show_prepare:w, \@@_show_loop:NNw, \@@_show_bad_name:NNN, \@@_show_end:NNN, \@@_show_loop_key:wNNN}
+% \begin{macro}[rEXP]{\@@_show_flat:w, \@@_show_linked:w}
+%   Experience shows one source of problems is very hard to debug: when
+%   a data structure such as a |seq| or~|prop| gets corrupted.  In the
+%   past, \cs{prop_show:N} would in some cases happily show items of
+%   such a |prop|, even though other more demanding \pkg{l3prop}
+%   functions would choke.  It is thus best to make \cs{prop_show:N}
+%   check very thoroughly the structure and flag issues, even though
+%   that is very painful for linked props.  Throughout the code below,
+%   we strive to remain as safe as possible, but in the explanations we
+%   only state what the arguments are when the prop is correctly formed,
+%   rather than saying at every step that various arguments can be
+%   arbitrary junk, made safe by using \cs{tl_to_str:n} generously.
+%
+%   The general \cs{__kernel_chk_tl_type:NnnT} checks that its first
+%   argument is a token list, and if it is, then it \texttt{e}-expands
+%   its second argument and compares with the contents of its first
+%   argument.  Thus, within this \texttt{e}-expansion it is safe to use
+%   \cs{@@_if_flat:NTF} to check if the prop is flat or linked.  In the
+%   flat case we simply reconstruct the expected structure using
+%   \cs{@@_show_flat:w}, which loops through the prop and correctly
+%   turns all keys to strings for instance.  In the linked case, we use
+%   \cs{@@_show_linked:w}, which ensures the form \cs{@@_flatten:w}
+%   \cs[no-index]{@@~\meta{prefix}} \cs{s_@@} \Arg{prefix} \meta{rest},
+%   where \meta{prefix} is made into a string and \meta{rest} cannot be
+%   a brace group or multiple tokens since \cs{@@_show_linked:w} would
+%   in such cases give a different result from the original token list.
 %    \begin{macrocode}
 \cs_new_protected:Npn \prop_show:N { \@@_show:NN \msg_show:nneeee }
 \cs_generate_variant:Nn \prop_show:N { c }
@@ -1586,24 +2384,170 @@
   {
     \__kernel_chk_tl_type:NnnT #2 { prop }
       {
-        \s_@@
-        \exp_after:wN \use_i:nn \exp_after:wN \@@_show_validate:w #2
-        \@@_pair:wn \q_recursion_tail \s_@@ { } \q_recursion_stop
+        \@@_if_flat:NTF #2
+          {
+            \s_@@ \@@_chk:w
+            \exp_after:wN \@@_show_flat:w #2
+            \s_@@ { }
+            \@@_pair:wn \q_@@_recursion_tail \s_@@ { }
+            \q_@@_recursion_stop
+          }
+          { \exp_after:wN \@@_show_linked:w #2 \s_@@ ! ? \s_@@_stop }
       }
       {
-        #1 { prop } { show }
-          { \token_to_str:N #2 }
-          { \prop_map_function:NN #2 \msg_show_item:nn }
-          { } { }
+        \@@_if_flat:NTF #2
+          { \@@_show_finally:NNn #1 #2 { flat } }
+          {
+            \tl_set:Nn \l_@@_internal_tl { #1 #2 }
+            \exp_after:wN \@@_show_prepare:w #2 #2
+          }
       }
   }
-\cs_new:Npn \@@_show_validate:w #1 \@@_pair:wn #2 \s_@@ #3
+\cs_new:Npn \@@_show_flat:w #1 \@@_pair:wn #2 \s_@@ #3
   {
-    \quark_if_recursion_tail_stop:n {#2}
+    \@@_if_recursion_tail_stop:n {#2}
     \exp_not:N \@@_pair:wn \tl_to_str:n {#2} \s_@@ \exp_not:n { {#3} }
-    \@@_show_validate:w
+    \@@_show_flat:w
   }
+\cs_new:Npn \@@_show_linked:w #1 \s_@@ #2#3#4 \s_@@_stop
+  {
+    \exp_not:N \@@_flatten:w
+    \exp_not:c { @@ ~ \tl_to_str:n {#2} }
+    \s_@@ { \tl_to_str:n {#2} }
+    \exp_not:n {#3}
+  }
 %    \end{macrocode}
+%   For flat props we are done by using \cs{msg_show:nneeee} or
+%   \cs{msg_log:nneeee}.  The auxiliary \cs{@@_show_finally:NNn} is
+%   eventually also used in the linked case after some more tests.  To
+%   avoid having to bring along the message function and the property
+%   list, we store them into \cs{l_@@_internal_tl}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_finally:NNn #1#2#3
+  {
+    #1 { prop } { show }
+      { \token_to_str:N #2 }
+      { \prop_map_function:NN #2 \msg_show_item:nn }
+      {#3} { }
+  }
+%    \end{macrocode}
+%   For linked props, we now know they have a reasonable form so that we
+%   are calling \cs{@@_show_prepare:w} \cs{@@_flatten:w}
+%   \cs[no-index]{@@~\meta{prefix}} \cs{s_@@} \Arg{prefix} \meta{token}
+%   \meta{property list}, and the task is to loop through the linked
+%   list and check integrity.  We first set things up: the auxiliary
+%   \cs{@@_tmp:w} will be in charge of checking that various tokens
+%   start with \cs[no-index]{@@~\meta{prefix}} (in the sense of string
+%   representations), and calling one of \cs{@@_show_loop_key:wNNN},
+%   \cs{@@_show_end:NNN}, \cs{@@_show_bad_name:NNN}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_prepare:w
+    \@@_flatten:w #1 \s_@@ #2#3#4
+  {
+    \use:e
+      {
+        \cs_set_nopar:Npn \exp_not:N \@@_tmp:w
+            ##1 \token_to_str:N #1 ##2 \s_@@_mark ##3 \s_@@_stop
+          {
+            \exp_not:N \tl_if_empty:nTF {##1}
+              {
+                \exp_not:N \tl_if_head_is_space:nTF {##2}
+                  { \exp_not:N \exp_args:Nf \@@_show_loop_key:wNNN }
+                  { \exp_not:N \tl_if_empty:nTF }
+                {##2}
+              }
+              { \exp_not:N \use_ii:nn }
+            \@@_show_end:NNN
+            \@@_show_bad_name:NNN
+          }
+      }
+    \exp_last_unbraced:NNNo \@@_show_loop:NNw #1 #4 #4
+  }
+%    \end{macrocode}
+%   The loop will consist of calls to \cs{@@_show_loop:NNw}
+%   \cs[no-index]{@@~\meta{prefix}} \meta{token} \meta{expansion}, where
+%   \meta{token} is one of the items in the list, specifically the key
+%   container for \meta{key$_{i-1}$} (starting at $i=1$ with the
+%   property list variable itself), and \meta{expansion} stands for the
+%   expansion of that token, which has already been checked, and takes
+%   the form \meta{junk} \cs{s_@@} \Arg{value}
+%   \cs[no-index]{@@~\meta{prefix}~\meta{key_i}}.  Thus, the loop
+%   auxiliary receives the prefix command as |#1|, and the $(i-1)$-th
+%   and $i$-th key containers as |#2| and~|#5|.  Then \cs{@@_tmp:w}
+%   checks that the name of the $i$-th key container is valid.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_loop:NNw #1#2 #3 \s_@@ #4#5
+  {
+    \exp_last_two_unbraced:Noo \@@_tmp:w
+      { \token_to_str:N #5 \s_@@_mark }
+      { \token_to_str:N #1 \s_@@_mark \s_@@_stop }
+      #1 #2 #5
+  }
+%    \end{macrocode}
+%   If the $i$-th key container has the wrong name we get
+%   \cs{@@_show_bad_name:NNN} \cs[no-index]{@@~\meta{prefix}}
+%   \meta{previous container} \meta{current container with bad name}.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_bad_name:NNN #1#2#3
+  {
+    \msg_error:nneeee { prop } { bad-link }
+      { \tl_tail:N \l_@@_internal_tl }
+      { \token_to_str:N #2 }
+      { \token_to_str:N #3 }
+      { \token_to_str:N #1 }
+  }
+%    \end{macrocode}
+%   If the $i$-th key container has the name
+%   \cs[no-index]{@@~\meta{prefix}} (without space), it is the trailing
+%   one.  We check that it is the right kind of macro to be a token
+%   list, and that it has the right contents \cs{use_none:n}
+%   \meta{previous container}.  If so, we are done checking everything,
+%   and we display the property list using the message function and
+%   property list name stored in \cs{l_@@_internal_tl}.  Note that we
+%   also use this \cs{l_@@_internal_tl} in the type argument of
+%   \cs{__kernel_chk_tl_type:NnnT}, to build up the name
+%   \enquote{\meta{property list} prop entry} used in error messages.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_end:NNN #1#2#3
+  {
+    \__kernel_chk_tl_type:NnnT #3
+      { \tl_tail:N \l_@@_internal_tl prop~entry }
+      { \exp_not:n { \use_none:n #2 } }
+      {
+        \exp_after:wN \@@_show_finally:NNn
+        \l_@@_internal_tl { linked }
+      }
+  }
+%    \end{macrocode}
+%   If the $i$-th container has a name \cs[no-index]{@@~\meta{prefix}
+%   \meta{key}} (with a space before the key), then we have a call to
+%   \cs{@@_show_loop_key:wNNN} \Arg{key} \meta{junk_1} \meta{junk_2}
+%   \cs[no-index]{@@~\meta{prefix}} \meta{previous container}
+%   \meta{current container}. (with an \texttt{f}-expansion to eliminate
+%   the space).  The first argument is the \meta{key} without a leading
+%   space, thanks to a judicious \texttt{f}-expansion earlier on.  We
+%   check that the \meta{current container} is a token list with the
+%   expected structure \cs{use_none:n} \meta{previous container}
+%   \cs{@@_pair:wn} \meta{string} \cs{s_@@} \Arg{anything} \meta{single
+%   token}.  The auxiliary \cs{@@_show_flat:w} is reused to produce the
+%   \cs{@@_pair:wn} part, and the last token is produced by
+%   \cs{tl_item:Nn} (we don't waste a specialized auxiliary to speed
+%   that up).  If the check succeed, move on to the next item.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_show_loop_key:wNNN #1#2#3#4#5#6
+  {
+    \__kernel_chk_tl_type:NnnT #6
+      { \tl_tail:N \l_@@_internal_tl prop~entry }
+      {
+        \exp_not:n { \use_none:n #5 }
+        \exp_after:wN \@@_show_flat:w #6 \s_@@ { }
+        \@@_pair:wn \q_@@_recursion_tail \s_@@ { }
+        \q_@@_recursion_stop
+        \tl_item:Nn #6 { -1 }
+      }
+      { \exp_last_unbraced:NNNo \@@_show_loop:NNw #4 #6 #6 }
+  }
+%    \end{macrocode}
 % \end{macro}
 % \end{macro}
 % \end{macro}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -196,10 +196,7 @@
 %  codepoint is uppercased, irrespective of the general code of the character.
 %
 % \begin{function}[added = 2022-07-04]
-%   {
-%     \text_declare_case_equivalent:Nn ,
-%     \text_declare_case_equivalent:cn
-%   }
+%   {\text_declare_case_equivalent:Nn}
 %   \begin{syntax}
 %     \cs{text_declare_case_equivalent:Nn} \meta{cmd} \Arg{replacement}
 %   \end{syntax}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -196,18 +196,15 @@
 % \end{variable}
 %
 % \begin{variable}
-%   {\l_@@_analysis_token, \l_@@_analysis_char_token, \l_@@_analysis_next_token}
+%   {\l_@@_analysis_token, \l_@@_analysis_char_token}
 %   The tokens in the token list are probed with the \TeX{} primitive
 %   \tn{futurelet}. We use \cs{l_@@_analysis_token} in that
 %   construction. In some cases, we convert the following token to a
 %   string before probing it: then the token variable used is
-%   \cs{l_@@_analysis_char_token}. When getting tokens from the input
-%   stream we may need to look two tokens ahead, for which we use
-%   \cs{l_@@_analysis_next_token}.
+%   \cs{l_@@_analysis_char_token}.
 %    \begin{macrocode}
 \cs_new_eq:NN \l_@@_analysis_token ?
 \cs_new_eq:NN \l_@@_analysis_char_token ?
-\cs_new_eq:NN \l_@@_analysis_next_token ?
 %    \end{macrocode}
 % \end{variable}
 %
@@ -1223,13 +1220,12 @@
 %     \@@_peek_analysis_nonexp:N, \@@_peek_analysis_cs:N,
 %     \@@_peek_analysis_char:N, \@@_peek_analysis_char:w,
 %     \@@_peek_analysis_special:, \@@_peek_analysis_retest:,
-%     \@@_peek_analysis_next:, \@@_peek_analysis_nextii:,
 %     \@@_peek_analysis_str:,
 %     \@@_peek_analysis_str:w, \@@_peek_analysis_str:n,
 %     \@@_peek_analysis_active_str:n, \@@_peek_analysis_explicit:n,
 %     \@@_peek_analysis_escape:, \@@_peek_analysis_collect:w,
 %     \@@_peek_analysis_collect:n, \@@_peek_analysis_collect_loop:,
-%     \@@_peek_analysis_collect_test:, \@@_peek_analysis_collect_end:NNN
+%     \@@_peek_analysis_collect_test:, \@@_peek_analysis_collect_end:NNNN
 %   }
 %   Save the user's code in a control sequence that is suitable for
 %   nested maps.  We may wish to pass to this function an \tn{outer}
@@ -1254,7 +1250,10 @@
         #1
         \@@_peek_analysis_loop:NNn
           \prg_break_point:Nn \peek_analysis_map_break:
-            { \group_align_safe_end: }
+            {
+              \int_gdecr:N \g__kernel_prg_map_int
+              \group_align_safe_end:
+            }
       }
     \@@_peek_analysis_loop:NNn ? ? ?
   }
@@ -1460,7 +1459,7 @@
     \if_meaning:w \l_@@_analysis_token \scan_stop:
       \exp_after:wN \@@_peek_analysis_normal:N
     \else:
-      \exp_after:wN \@@_peek_analysis_next:
+      \exp_after:wN \@@_peek_analysis_str:
     \fi:
   }
 %    \end{macrocode}
@@ -1469,41 +1468,22 @@
 %   begin-group or end-group token (catcode $1$ or~$2$), and we excluded
 %   a few cases that would be difficult later (empty control sequence,
 %   active character with the same character code as its meaning or as
-%   the escape character).  Now look at the \meta{next token} following
-%   it using a combination of \tn{afterassignment} and \tn{futurelet}.
-%   (In fact look twice to reset an internal \TeX{} flag in case the
-%   \meta{next token} had been hit with \cs{exp_not:N}.)
-%   The syntax of this primitive is \tn{futurelet} \meta{peek token}
-%   \meta{first token} \meta{next token}, and it sets \meta{peek token}
-%   equal to \meta{next token}.  Traditionally, one takes \meta{first
-%   token} to be some macro that regains control of the code and, e.g.,
-%   analyses \meta{peek token}.  Here, both \meta{first token} and
-%   \meta{next token} are mostly unknown tokens in the input stream (but
-%   we know the \meta{first token} has catcode $1$, $2$ or $10$), where
-%   \meta{first token} was already stored as \cs{l_peek_token}, and we
-%   regain control using \tn{afterassignment}, which inserts its
-%   argument after the assignment, hence after \meta{peek token} but
-%   before \meta{first token}.
-%    \begin{macrocode}
-\cs_new_protected:Npn \@@_peek_analysis_next:
-  {
-    \tl_if_empty:oT { \tex_the:D \tex_everyeof:D }
-      { \tex_everyeof:D { \scan_stop: } }
-    \tex_afterassignment:D \@@_peek_analysis_nextii:
-    \tex_futurelet:D \l_@@_analysis_next_token
-  }
-\cs_new_protected:Npn \@@_peek_analysis_nextii:
-  {
-    \tex_afterassignment:D \@@_peek_analysis_str:
-    \tex_futurelet:D \l_@@_analysis_next_token
-  }
-%    \end{macrocode}
-%   We then hit the \meta{first token} with \cs{token_to_str:N} and grab
-%   characters until finding \cs{l_@@_analysis_next_token}.  More
+%   the escape character).  The idea is to apply \cs{token_to_str:N} to
+%   the \meta{token} then grab characters (of category code~$12$ except
+%   for spaces that have category code~$10$) to reconstruct it.  In
+%   earlier versions of the code we would peek at the \meta{next token}
+%   that lies after \meta{token} in the input stream, which would help
+%   us be more accurate in reconstructing the \meta{token} case in edge
+%   cases (mentioned below), but this had the side-effect of tokenizing
+%   the input stream (turning characters into tokens) farther ahead than
+%   needed.
+%
+%   We hit the \meta{token} with \cs{token_to_str:N} and start grabbing
+%   characters.  More
 %   precisely, by looking at the first character in the string
-%   representation of the \meta{first token} we distinguish three cases:
+%   representation of the \meta{token} we distinguish three cases:
 %   a stringified control sequence starts with the escape character; for
-%   an explicit character we find that same character; for an explicit
+%   an explicit character we find that same character; for an active
 %   character we find anything else (we made sure to exclude the case of
 %   an active character whose string representation coincides with the
 %   other two cases).
@@ -1591,14 +1571,11 @@
 %   know that until we had run all the various tests including
 %   stringifying the token.  We are thus left with the hard work of
 %   picking up one by one the characters in the csname (being careful
-%   about spaces), until finding a token that matches the \meta{next
-%   token} picked up earlier (which was not stringified), such that the
-%   control sequence that we found so far indeed has the expected
-%   meaning \cs{l_peek_token}.  This comparison with \cs{l_peek_token}
-%   catches a reasonably common case like \cs{c_group_begin_token} |_|
-%   in which the trailing |_| has category code other: without
-%   comparison of the constructed csname with \cs{l_peek_token}
-%   collection would stop at \cs[no-index]{c}, which is wrong.
+%   about spaces), until the constructed csname has the expected
+%   meaning.  This fails if someone defines a token like
+%   \cs[no-index]{bgroup at my} whose string representation starts the same
+%   as another token with the same meaning being an implicit character
+%   token of category code $1$, $2$, or $10$.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_peek_analysis_escape:
   {
@@ -1615,25 +1592,27 @@
   }
 \cs_new_protected:Npn \@@_peek_analysis_collect_loop:
   {
-    \tex_futurelet:D \l_@@_analysis_token
-      \@@_peek_analysis_collect_test:
-  }
-\cs_new_protected:Npn \@@_peek_analysis_collect_test:
-  {
-    \if_meaning:w \l_@@_analysis_token \l_@@_analysis_next_token
-      \exp_after:wN \if_meaning:w \cs:w \l_@@_internal_a_tl \cs_end: \l_peek_token
-        \@@_peek_analysis_collect_end:NNN
+    \exp_after:wN \if_meaning:w
+      \cs:w
+      \if_cs_exist:w \l_@@_internal_a_tl \cs_end:
+        \l_@@_internal_a_tl
+      \else:
+        c_one % anything short
       \fi:
+      \cs_end:
+      \l_peek_token
+      \@@_peek_analysis_collect_end:NNNN
     \fi:
-    \@@_peek_analysis_collect:w
+    \tex_futurelet:D \l_@@_analysis_token
+      \@@_peek_analysis_collect:w
   }
 %    \end{macrocode}
 %   End by calling the user code with suitable arguments (here |#1|,
 %   |#2| are \cs{fi:}), which closes the group begun early on.
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_peek_analysis_collect_end:NNN #1#2#3
+\cs_new_protected:Npn \@@_peek_analysis_collect_end:NNNN #1#2#3#4
   {
-    #1 #2
+    #1
     \tl_put_right:Ne \l_@@_peek_code_tl
       {
         { \exp_not:N \exp_not:n { \exp_not:c { \l_@@_internal_a_tl } } }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %
@@ -102,7 +102,8 @@
 %   \end{syntax}
 %   Sets the behaviour of the \meta{char} in situations where it is
 %   active (category code $13$) to be equivalent to that of the
-%   \meta{function}. The category code of the \meta{char} is
+%   definition of the \meta{function} at the time \cs{char_set_active_eq:NN}
+%   is used. The category code of the \meta{char} is
 %   \emph{unchanged} by this process. The \meta{function} may itself
 %   be an active character.
 % \end{function}
@@ -118,7 +119,8 @@
 %   Sets the behaviour of the \meta{char} which has character
 %   code as given by the \meta{integer expression} in situations
 %   where it is active (category code $13$) to be equivalent to that of the
-%   \meta{function}. The category code of the \meta{char} is
+%   \meta{function} at the time \cs{char_set_active_eq:nN}
+%   is used. The category code of the \meta{char} is
 %   \emph{unchanged} by this process. The \meta{function} may itself
 %   be an active character.
 % \end{function}
@@ -424,6 +426,11 @@
 %   These are implicit tokens which have the category code described
 %   by their name. They are used internally for test purposes but
 %   are also available to the programmer for other uses.
+%   \begin{texnote}
+%     The tokens \cs{c_group_begin_token}, \cs{c_group_end_token}, and
+%     \cs{c_space_token} are \pkg{expl3} counterparts of \LaTeXe{}'s
+%     \tn{bgroup}, \tn{egroup}, and \cs{@sptoken}.
+%   \end{texnote}
 % \end{variable}
 %
 % \begin{variable}
@@ -786,11 +793,11 @@
 % There is often a need to look ahead at the next token in the input
 % stream while leaving it in place. This is handled using the
 % \enquote{peek} functions. The generic \cs{peek_after:Nw} is provided
-% along with a family of predefined tests for common cases.  As peeking
-% ahead does \emph{not} skip spaces the predefined tests include both a
-% space-respecting and space-skipping version.  In addition, using
-% \cs{peek_analysis_map_inline:n}, one can map through the following
-% tokens in the input stream and repeatedly perform some tests.
+% along with a family of predefined tests for common cases.  Peeking
+% ahead does \emph{not} skip spaces: rather, \cs{peek_remove_spaces:n}.
+% should be used. In addition, using \cs{peek_analysis_map_inline:n},
+% one can map through the following tokens in the input stream and
+% repeatedly perform some tests.
 %
 % \begin{function}{\peek_after:Nw}
 %   \begin{syntax}
@@ -957,7 +964,7 @@
 %   (as appropriate to the result of the test).
 % \end{function}
 %
-% \begin{function}[added = 2020-12-03, updated = 2022-10-03]
+% \begin{function}[added = 2020-12-03, updated = 2024-02-07]
 %   {\peek_analysis_map_inline:n}
 %   \begin{syntax}
 %     \cs{peek_analysis_map_inline:n} \Arg{inline function}
@@ -998,6 +1005,12 @@
 %   effect after the loop.  Within the code, \cs{l_peek_token} is set
 %   equal (as a token, not a token list) to the token under
 %   consideration.
+%   \begin{texnote}
+%     In case the input stream has not yet been tokenized (converted
+%     from characters to tokens), characters are tokenized one by one as
+%     needed by \cs{peek_analysis_map_inline:n} using the current
+%     category code regime.
+%   \end{texnote}
 % \end{function}
 %
 % \begin{function}[added = 2020-12-03]

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2024-01-22}
+% \date{Released 2024-02-13}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex	2024-02-14 22:04:08 UTC (rev 69889)
@@ -75,7 +75,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2024-01-22}%
+\def\ExplFileDate{2024-02-13}%
 \begingroup
   \def\next{\endgroup}%
   \expandafter\ifx\csname PackageError\endcsname\relax
@@ -1878,75 +1878,133 @@
 \prg_set_conditional:Npnn \cs_if_exist:N #1 { p , T , F , TF }
   {
     \if_meaning:w #1 \scan_stop:
-      \prg_return_false:
+      \use_i:nnnn
     \else:
-      \if_cs_exist:N #1
-        \prg_return_true:
-      \else:
-        \prg_return_false:
-      \fi:
     \fi:
-  }
-\prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
-  {
-    \if_cs_exist:w #1 \cs_end:
-      \exp_after:wN \use_i:nn
+    \if_cs_exist:N #1
+      \prg_return_true:
     \else:
-      \exp_after:wN \use_ii:nn
+      \prg_return_false:
     \fi:
-    {
-      \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop:
-        \prg_return_false:
-      \else:
-        \prg_return_true:
-      \fi:
-    }
-    \prg_return_false:
   }
+\cs_if_exist:NTF \tex_lastnamedcs:D
+  {
+    \prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_exist_c_aux:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \__cs_if_exist_c_aux:
+      { \fi: \exp_after:wN \if_meaning:w \tex_lastnamedcs:D \scan_stop: \else: }
+  }
+  {
+    \prg_set_conditional:Npnn \cs_if_exist:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_exist_c_aux:w
+        \fi:
+        \use_none:n {#1}
+        \if_false:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \__cs_if_exist_c_aux:w \fi: \use_none:n #1 \if_false:
+      { \fi: \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop: \else: }
+  }
 \prg_set_conditional:Npnn \cs_if_free:N #1 { p , T , F , TF }
   {
+    \if_cs_exist:N #1
+    \else:
+      \use_none:nnnn
+    \fi:
     \if_meaning:w #1 \scan_stop:
       \prg_return_true:
     \else:
-      \if_cs_exist:N #1
-        \prg_return_false:
-      \else:
-        \prg_return_true:
-      \fi:
+      \prg_return_false:
     \fi:
   }
-\prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
+\cs_if_exist:NTF \tex_lastnamedcs:D
   {
-    \if_cs_exist:w #1 \cs_end:
-      \exp_after:wN \use_i:nn
-    \else:
-      \exp_after:wN \use_ii:nn
-    \fi:
+    \prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
       {
-        \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop:
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_free_c_aux:w
+        \fi:
+        \if_true:
           \prg_return_true:
         \else:
           \prg_return_false:
         \fi:
       }
-      { \prg_return_true: }
+    \cs_set:Npn \__cs_if_free_c_aux:w \fi: \if_true:
+      { \fi: \exp_after:wN \if_meaning:w \tex_lastnamedcs:D \scan_stop: }
   }
+  {
+    \prg_set_conditional:Npnn \cs_if_free:c #1 { p , T , F , TF }
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_free_c_aux:w
+        \fi:
+        \use_none:n {#1}
+        \if_true:
+          \prg_return_true:
+        \else:
+          \prg_return_false:
+        \fi:
+      }
+    \cs_set:Npn \__cs_if_free_c_aux:w \fi: \use_none:n #1 \if_true:
+      { \fi: \exp_after:wN \if_meaning:w \cs:w #1 \cs_end: \scan_stop: }
+  }
 \cs_set:Npn \cs_if_exist_use:NTF #1#2
   { \cs_if_exist:NTF #1 { #1 #2 } }
 \cs_set:Npn \cs_if_exist_use:NF #1
-  { \cs_if_exist:NTF #1 { #1 } }
+  { \cs_if_exist:NTF #1 #1 }
 \cs_set:Npn \cs_if_exist_use:NT #1 #2
-  { \cs_if_exist:NTF #1 { #1 #2 } { } }
+  { \cs_if_exist:NT #1 { #1 #2 } }
 \cs_set:Npn \cs_if_exist_use:N #1
-  { \cs_if_exist:NTF #1 { #1 } { } }
-\cs_set:Npn \cs_if_exist_use:cTF #1#2
-  { \cs_if_exist:cTF {#1} { \use:c {#1} #2 } }
+  { \cs_if_exist:NT #1 #1 }
+\cs_if_exist:NTF \tex_lastnamedcs:D
+  {
+    \cs_set:Npn \cs_if_exist_use:cTF #1
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_exist_use_aux:w
+        \fi:
+        \use_ii:nn
+      }
+    \cs_set:Npn \__cs_if_exist_use_aux:w \fi: \use_ii:nn
+      { \fi: \exp_after:wN \__cs_if_exist_use_aux:Nnn \tex_lastnamedcs:D }
+  }
+  {
+    \cs_set:Npn \cs_if_exist_use:cTF #1
+      {
+        \if_cs_exist:w #1 \cs_end:
+          \__cs_if_exist_use_aux:w
+        \fi:
+        \use_iii:nnn {#1}
+      }
+    \cs_set:Npn \__cs_if_exist_use_aux:w \fi: \use_iii:nnn #1
+      { \fi: \exp_after:wN \__cs_if_exist_use_aux:Nnn \cs:w #1 \cs_end: }
+  }
+\cs_set:Npn \__cs_if_exist_use_aux:Nnn #1#2
+  {
+    \if_meaning:w #1 \scan_stop:
+      \exp_after:wN \use_iii:nnn
+    \fi:
+    \use_i:nn { #1 #2 }
+  }
 \cs_set:Npn \cs_if_exist_use:cF #1
-  { \cs_if_exist:cTF {#1} { \use:c {#1} } }
+  { \cs_if_exist_use:cTF {#1} {} }
 \cs_set:Npn \cs_if_exist_use:cT #1#2
-  { \cs_if_exist:cTF {#1} { \use:c {#1} #2 } { } }
+  { \cs_if_exist_use:cTF {#1} {#2} {} }
 \cs_set:Npn \cs_if_exist_use:c #1
-  { \cs_if_exist:cTF {#1} { \use:c {#1} } { } }
+  { \cs_if_exist_use:cTF {#1} {} {} }
 \cs_set_protected:Npn \msg_error:nnee #1#2#3#4
   {
     \tex_newlinechar:D = `\^^J \scan_stop:
@@ -2304,7 +2362,7 @@
 \cs_new_protected:Npn \group_show_list:
   { \__kernel_group_show:NN \use_none:n 1 }
 \cs_new_protected:Npn \group_log_list:
-  { \__kernel_group_show:NN \int_zero:N 0 }
+  { \__kernel_group_show:NN \int_gzero:N 0 }
 \cs_new_protected:Npn \__kernel_group_show:NN #1#2
   {
     \use:e
@@ -2314,7 +2372,7 @@
         \int_set:Nn \tex_errorcontextlines:D { -1 }
         \exp_not:N \exp_after:wN \scan_stop:
         \tex_showgroups:D
-        \int_set:Nn \tex_interactionmode:D
+        \int_gset:Nn \tex_interactionmode:D
           { \int_use:N \tex_interactionmode:D }
         \int_set:Nn \tex_tracingonline:D
           { \int_use:N \tex_tracingonline:D }
@@ -2634,6 +2692,7 @@
     \exp:w \exp_end_continue_f:w #4
   }
 \cs_new:Npn \exp_last_unbraced:Nno { \::n \::o_unbraced \::: }
+\cs_new:Npn \exp_last_unbraced:Nnf { \::n \::f_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:Noo { \::o \::o_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:Nfo { \::f \::o_unbraced \::: }
 \cs_new:Npn \exp_last_unbraced:NnNo { \::n \::N \::o_unbraced \::: }
@@ -9397,11 +9456,8 @@
 \cs_new_protected:Npn \peek_N_type:F
   { \__peek_token_generic:NNF \__peek_execute_branches_N_type: \scan_stop: }
 %% File: l3prop.dtx
-\scan_new:N \s__prop
-\cs_new:Npn \__prop_pair:wn #1 \s__prop #2
-  { \msg_expandable_error:nn { prop } { misused } }
+\cs_new_eq:NN \__prop_tmp:w ?
 \tl_new:N \l__prop_internal_tl
-\tl_const:Nn \c_empty_prop { \s__prop }
 \scan_new:N \s__prop_mark
 \scan_new:N \s__prop_stop
 \quark_new:N \q__prop_recursion_tail
@@ -9408,6 +9464,62 @@
 \quark_new:N \q__prop_recursion_stop
 \__kernel_quark_new_test:N \__prop_if_recursion_tail_stop:n
 \cs_generate_variant:Nn \__prop_if_recursion_tail_stop:n { o }
+\scan_new:N \s__prop
+\cs_new_protected:Npn \__prop_chk:w { \__prop_chk_loop:nw { } }
+\cs_new_protected:Npn \__prop_chk_loop:nw #1
+  {
+    \peek_meaning:NTF \__prop_pair:wn
+      { \__prop_chk_get:nw {#1} }
+      { \msg_error:nne { prop } { misused } {#1} }
+  }
+\cs_new_protected:Npn \__prop_chk_get:nw #1 \__prop_pair:wn #2 \s__prop #3
+  { \__prop_chk_loop:nw { #1 , ~ {#2} = { \tl_to_str:n {#3} } } }
+\cs_new:Npn \__prop_pair:wn #1 \s__prop #2 { }
+\cs_new_protected:Npn \__prop_flatten:w #1 \s__prop #2#3
+  { \use:e { \__prop_flatten_aux:N #3 } }
+\cs_new:Npn \__prop_flatten:N #1
+  { \exp_after:wN \__prop_flatten_aux:w #1 }
+\cs_new:Npn \__prop_flatten_aux:w #1 \s__prop #2 { \__prop_flatten_aux:N }
+\cs_new:Npn \__prop_flatten_aux:N #1
+  {
+    \s__prop \__prop_chk:w
+    \exp_after:wN \__prop_flatten_loop:w #1
+    \use_none:nnnn \__prop_pair:wn \s__prop { }
+  }
+\cs_new:Npn \__prop_flatten_loop:w #1#2#3 \__prop_pair:wn #4 \s__prop #5
+  {
+    #3
+    \exp_not:n { \__prop_pair:wn #4 \s__prop {#5} }
+    \exp_after:wN \__prop_flatten_loop:w
+  }
+\int_new:N \g__prop_prefix_int
+\int_const:Nn \c__prop_basis_int { \c_max_char_int - `\! }
+\cs_new_protected:Npn \__prop_next_prefix:
+  {
+    \tl_set:Ne \l__prop_internal_tl
+      { \__prop_to_prefix:n { \g__prop_prefix_int } }
+    \int_gincr:N \g__prop_prefix_int
+  }
+\cs_new:Npn \__prop_to_prefix:n #1
+  {
+    \int_compare:nNnTF {#1} > \c__prop_basis_int
+      {
+        \exp_args:Nf \__prop_to_prefix:n
+          { \int_div_truncate:nn {#1} \c__prop_basis_int }
+        \exp_args:Nf \__prop_to_prefix:n
+          { \int_mod:nn {#1} \c__prop_basis_int }
+      }
+      { \char_generate:nn { `\! + #1 } { 12 } }
+  }
+\cs_new:Npn \__prop_if_flat:NTF #1
+  {
+    \exp_after:wN \__prop_if_flat_aux:w #1
+    \s__prop_mark \use_ii:nn
+    \__prop_flatten:w \s__prop_mark \use_i:nn \s__prop_stop
+  }
+\cs_new:Npn \__prop_if_flat_aux:w
+    #1 \__prop_flatten:w #2 \s__prop_mark #3 #4 \s__prop_stop {#3}
+\tl_const:Nn \c_empty_prop { \s__prop \__prop_chk:w }
 \cs_new_protected:Npn \prop_new:N #1
   {
     \__kernel_chk_if_free_cs:N #1
@@ -9414,12 +9526,60 @@
     \cs_gset_eq:NN #1 \c_empty_prop
   }
 \cs_generate_variant:Nn \prop_new:N { c }
-\cs_new_protected:Npn \prop_clear:N  #1
-  { \prop_set_eq:NN #1 \c_empty_prop }
+\cs_new_protected:Npn \prop_new_linked:N #1
+  {
+    \__kernel_chk_if_free_cs:N #1
+    \__prop_new_linked:N #1
+  }
+\cs_new_protected:Npn \__prop_new_linked:N #1
+  {
+    \__prop_next_prefix:
+    \cs_gset_nopar:Npe #1
+      {
+        \__prop_flatten:w
+        \exp_not:c { __prop ~ \l__prop_internal_tl }
+        \s__prop { \l__prop_internal_tl }
+        \exp_not:c { __prop ~ \l__prop_internal_tl }
+      }
+    \cs_gset_nopar:cpe { __prop ~ \l__prop_internal_tl }
+      {
+        \exp_not:N \use_none:n
+        \exp_not:N #1
+      }
+  }
+\cs_generate_variant:Nn \prop_new_linked:N { c }
+\cs_new_protected:Npn \prop_clear:N
+  { \__prop_clear:NNN \cs_set_eq:NN \cs_set_nopar:Npe }
 \cs_generate_variant:Nn \prop_clear:N  { c }
-\cs_new_protected:Npn \prop_gclear:N #1
-  { \prop_gset_eq:NN #1 \c_empty_prop }
+\cs_new_protected:Npn \prop_gclear:N
+  { \__prop_clear:NNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_gclear:N { c }
+\cs_new_protected:Npn \__prop_clear:NNN #1#2#3
+  {
+    \__prop_if_flat:NTF #3
+      { #1 #3 \c_empty_prop }
+      { \exp_after:wN \__prop_clear:wNNN #3 #1 #2 #3 }
+  }
+\cs_new_protected:Npn \__prop_clear:wNNN
+    \__prop_flatten:w #1 \s__prop #2#3#4#5#6
+  {
+    \__prop_clear_entries:NN #4 #3
+    #5 #6 { \exp_not:n { \__prop_flatten:w #1 \s__prop {#2} #1 } }
+    #5 #1 { \exp_not:n { \use_none:n #6 } }
+  }
+\cs_new_protected:Npn \__prop_clear_entries:NN #1#2
+  {
+    \exp_after:wN \__prop_clear_loop:Nw \exp_after:wN #1 #2
+    \use_none:nnnn \__prop_pair:wn \s__prop { }
+  }
+\cs_new_protected:Npn \__prop_clear_loop:Nw
+    #1#2#3#4 \__prop_pair:wn #5 \s__prop #6
+  {
+    #1 #3 \tex_undefined:D
+    #4
+    \exp_after:wN \__prop_clear_loop:Nw
+    \exp_after:wN #1
+  }
 \cs_new_protected:Npn \prop_clear_new:N  #1
   { \prop_if_exist:NTF #1 { \prop_clear:N #1 } { \prop_new:N #1 } }
 \cs_generate_variant:Nn \prop_clear_new:N  { c }
@@ -9426,134 +9586,236 @@
 \cs_new_protected:Npn \prop_gclear_new:N #1
   { \prop_if_exist:NTF #1 { \prop_gclear:N #1 } { \prop_new:N #1 } }
 \cs_generate_variant:Nn \prop_gclear_new:N { c }
-\cs_new_eq:NN \prop_set_eq:NN  \tl_set_eq:NN
-\cs_new_eq:NN \prop_set_eq:Nc  \tl_set_eq:Nc
-\cs_new_eq:NN \prop_set_eq:cN  \tl_set_eq:cN
-\cs_new_eq:NN \prop_set_eq:cc  \tl_set_eq:cc
-\cs_new_eq:NN \prop_gset_eq:NN \tl_gset_eq:NN
-\cs_new_eq:NN \prop_gset_eq:Nc \tl_gset_eq:Nc
-\cs_new_eq:NN \prop_gset_eq:cN \tl_gset_eq:cN
-\cs_new_eq:NN \prop_gset_eq:cc \tl_gset_eq:cc
+\cs_new_protected:Npn \prop_clear_new_linked:N  #1
+  { \prop_if_exist:NTF #1 { \prop_clear:N #1 } { \prop_new_linked:N #1 } }
+\cs_generate_variant:Nn \prop_clear_new_linked:N  { c }
+\cs_new_protected:Npn \prop_gclear_new_linked:N #1
+  { \prop_if_exist:NTF #1 { \prop_gclear:N #1 } { \prop_new_linked:N #1 } }
+\cs_generate_variant:Nn \prop_gclear_new_linked:N { c }
+\cs_new_protected:Npn \prop_set_eq:NN
+  { \__prop_set_eq:NNNN \cs_set_eq:NN \cs_set_nopar:Npe }
+\cs_generate_variant:Nn \prop_set_eq:NN { Nc , cN , cc }
+\cs_new_protected:Npn \prop_gset_eq:NN
+  { \__prop_set_eq:NNNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
+\cs_generate_variant:Nn \prop_gset_eq:NN { Nc , cN , cc }
+\cs_new_protected:Npn \__prop_set_eq:NNNN #1#2#3#4
+  {
+    \cs_if_eq:NNF #3#4
+      {
+        \__prop_if_flat:NTF #3
+          {
+            \__prop_if_flat:NTF #4
+              { #1 #3 #4 }
+              { #2 #3 { \__prop_flatten:N #4 } }
+          }
+          { \exp_after:wN \__prop_set_eq:wNNNN #3 #1#2#3#4 }
+      }
+  }
+\cs_new_protected:Npn \__prop_set_eq:wNNNN
+    \__prop_flatten:w #1 \s__prop #2#3#4#5#6#7
+  {
+    \__prop_clear_entries:NN #4 #3
+    \exp_args:Nf \__prop_set_eq:nNnNN {#7} #1 {#2} #5 #6
+  }
+\cs_new_protected:Npn \__prop_set_eq:nNnNN #1#2#3#4#5
+  {
+    \use_i:nnn
+      {
+        \__prop_set_eq_loop:NNnw #5 #4 {#3}
+        \__prop_flatten:w #2 \s__prop {#3}
+      }
+      #1
+    \use_none:n \__prop_pair:wn ? \s__prop
+  }
+\cs_new_protected:Npn \__prop_set_eq_loop:NNnw
+    #1#2#3#4 \s__prop #5#6 \__prop_pair:wn #7 \s__prop
+  {
+    \tl_set:Ne \l__prop_internal_tl { \exp_not:c { __prop ~ #3 #6 ~ #7 } }
+    #2 #1 { \exp_not:n { #4 \s__prop {#5} } \exp_not:o \l__prop_internal_tl }
+    \use_none:n #6 \__prop_set_eq_end:w
+    \exp_after:wN \__prop_set_eq_loop:NNnw \l__prop_internal_tl #2 {#3}
+    \use_none:n #1 \__prop_pair:wn #7 \s__prop
+  }
+\cs_new_protected:Npn \__prop_set_eq_end:w
+    \exp_after:wN \__prop_set_eq_loop:NNnw #1#2#3
+    \use_none:n #4#5 \s__prop
+  {
+    \exp_after:wN #2 \l__prop_internal_tl { \exp_not:n { \use_none:n #4 } }
+  }
+\cs_new_protected:Npn \prop_make_flat:N #1
+  {
+    \int_compare:nNnTF { \tex_currentgrouplevel:D } = 0
+      {
+        \__prop_if_flat:NTF #1 { }
+          { \exp_args:NNf \__prop_make_flat:Nn #1 {#1} }
+      }
+      {
+        \msg_error:nnee { prop } { inner-make }
+          { \token_to_str:N \prop_make_flat:N } { \token_to_str:N #1 }
+      }
+  }
+\cs_generate_variant:Nn \prop_make_flat:N { c }
+\cs_new_protected:Npn \__prop_make_flat:Nn #1#2
+  {
+    \exp_after:wN \__prop_clear:wNNN #1 \cs_set_eq:NN \cs_set_nopar:Npe #1
+    \cs_set_nopar:Npe #1 { \exp_not:n {#2} }
+  }
+\cs_new_protected:Npn \prop_make_linked:N #1
+  {
+    \int_compare:nNnTF { \tex_currentgrouplevel:D } = 0
+      {
+        \__prop_if_flat:NTF #1
+          { \exp_args:NNo \__prop_make_linked:Nn #1 {#1} } { }
+      }
+      {
+        \msg_error:nnee { prop } { inner-make }
+          { \token_to_str:N \prop_make_linked:N } { \token_to_str:N #1 }
+      }
+  }
+\cs_generate_variant:Nn \prop_make_linked:N { c }
+\cs_new_protected:Npn \__prop_make_linked:Nn #1#2
+  {
+    \__prop_new_linked:N #1
+    \tl_set:Nn \l__prop_internal_tl {#2}
+    \exp_after:wN \__prop_set_eq:wNNNN #1
+      \cs_set_eq:NN \cs_set_nopar:Npe #1 \l__prop_internal_tl
+  }
 \prop_new:N \l_tmpa_prop
 \prop_new:N \l_tmpb_prop
 \prop_new:N \g_tmpa_prop
 \prop_new:N \g_tmpb_prop
-\prop_new:N \l__prop_internal_prop
 \cs_new_protected:Npn \prop_concat:NNN
-  { \__prop_concat:NNNN \prop_set_eq:NN }
+  { \__prop_concat:NNNNN \cs_set_eq:NN \cs_set_nopar:Npe }
 \cs_generate_variant:Nn \prop_concat:NNN { ccc }
 \cs_new_protected:Npn \prop_gconcat:NNN
-  { \__prop_concat:NNNN \prop_gset_eq:NN }
+  { \__prop_concat:NNNNN \cs_gset_eq:NN \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_gconcat:NNN { ccc }
-\cs_new_protected:Npn \__prop_concat:NNNN #1#2#3#4
+\cs_new_protected:Npn \__prop_concat:NNNNN #1#2#3#4#5
   {
-    \prop_set_eq:NN \l__prop_internal_prop #3
-    \prop_map_inline:Nn #4 { \prop_put:Nnn \l__prop_internal_prop {##1} {##2} }
-    #1 #2 \l__prop_internal_prop
+    \cs_if_eq:NNTF #3 #5
+      { \__prop_concat:nNNN \use_none:nnn #2 #3 #4 }
+      {
+        \__prop_set_eq:NNNN #1 #2 #3 #4
+        \__prop_concat:nNNN { } #2 #3 #5
+      }
   }
+\cs_new_protected:Npn \__prop_concat:nNNN #1#2#3#4
+  {
+    \cs_gset_eq:NN \__prop_tmp:w \__prop_pair:wn
+    \cs_gset_protected:Npn \__prop_pair:wn ##1 \s__prop
+      { \__prop_put:nNNnn {#1} #2 #3 {##1} }
+    \exp_last_unbraced:Nf \use_none:nn #4
+    \cs_gset_eq:NN \__prop_pair:wn \__prop_tmp:w
+  }
+\cs_new_protected:Npn \prop_put_from_keyval:Nn #1
+  { \__prop_from_keyval:nn { \__prop_put:nNNnn { } \cs_set_nopar:Npe #1 } }
+\cs_generate_variant:Nn \prop_put_from_keyval:Nn { c }
+\cs_new_protected:Npn \prop_gput_from_keyval:Nn #1
+  { \__prop_from_keyval:nn { \__prop_put:nNNnn { } \cs_gset_nopar:Npe #1 } }
+\cs_generate_variant:Nn \prop_gput_from_keyval:Nn { c }
+\cs_new_protected:Npn \__prop_from_keyval:nn
+  {
+    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
+      { \__prop_from_keyval:Nnn \c_true_bool }
+      { \__prop_from_keyval:Nnn \c_false_bool }
+  }
+\cs_new_protected:Npn \__prop_from_keyval:Nnn #1#2#3
+  {
+    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool \c_true_bool
+    \keyval_parse:nnn \__prop_missing_eq:n {#2} {#3}
+    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool #1
+  }
+\cs_new_protected:Npn \__prop_missing_eq:n
+  { \msg_error:nnn { prop } { prop-keyval } }
 \cs_new_protected:Npn \prop_set_from_keyval:Nn #1
   {
-    \prop_clear:N #1
+    \__prop_clear:NNN \cs_set_eq:NN \cs_set_nopar:Npe #1
     \prop_put_from_keyval:Nn #1
   }
 \cs_generate_variant:Nn \prop_set_from_keyval:Nn { c }
 \cs_new_protected:Npn \prop_gset_from_keyval:Nn #1
   {
-    \prop_gclear:N #1
+    \__prop_clear:NNN \cs_gset_eq:NN \cs_gset_nopar:Npe #1
     \prop_gput_from_keyval:Nn #1
   }
 \cs_generate_variant:Nn \prop_gset_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_const_from_keyval:Nn #1#2
+\cs_new_protected:Npn \prop_const_from_keyval:Nn #1
   {
-    \prop_set_from_keyval:Nn \l__prop_internal_prop {#2}
-    \tl_const:Ne #1 { \exp_not:o \l__prop_internal_prop }
-    \prop_clear:N \l__prop_internal_prop
+    \prop_new:N #1
+    \__prop_from_keyval:nn { \__prop_put:nNNnn { } \cs_gset_nopar:Npe #1 }
   }
 \cs_generate_variant:Nn \prop_const_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_put_from_keyval:Nn
+\cs_new_protected:Npn \prop_const_linked_from_keyval:Nn #1
   {
-    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
-      { \__prop_keyval_parse:NNNn \c_true_bool }
-      { \__prop_keyval_parse:NNNn \c_false_bool }
-      \prop_put:Nnn
+    \prop_new_linked:N #1
+    \__prop_from_keyval:nn { \__prop_put:nNNnn { } \cs_gset_nopar:Npe #1 }
   }
-\cs_generate_variant:Nn \prop_put_from_keyval:Nn { c }
-\cs_new_protected:Npn \prop_gput_from_keyval:Nn
+\cs_generate_variant:Nn \prop_const_linked_from_keyval:Nn { c }
+\cs_new_protected:Npn \__prop_split:NnTFn #1#2
+  { \exp_args:NNo \__prop_split_aux:NnTFn #1 { \tl_to_str:n {#2} } }
+\cs_new_protected:Npn \__prop_split_aux:NnTFn #1#2#3
   {
-    \bool_if:NTF \l__kernel_keyval_allow_blank_keys_bool
-      { \__prop_keyval_parse:NNNn \c_true_bool }
-      { \__prop_keyval_parse:NNNn \c_false_bool }
-      \prop_gput:Nnn
+    \cs_set:Npn \__prop_split_aux:w ##1 \__prop_chk:w ##2
+      \__prop_pair:wn #2 \s__prop ##3 ##4 \s__prop_mark ##5 ##6 \s__prop_stop
+      { ##5 {#3} }
+    \exp_after:wN \__prop_split_aux:w #1 \s__prop_mark \use_i:nnn
+      \__prop_pair:wn #2 \s__prop { } \s__prop_mark \use_ii:nnn
+      \__prop_chk:w
+      \__prop_pair:wn #2 \s__prop { } \s__prop_mark \use_iii:nnn
+      \s__prop_stop
   }
-\cs_generate_variant:Nn \prop_gput_from_keyval:Nn { c }
-\cs_new_protected:Npn \__prop_missing_eq:n
-  { \msg_error:nnn { prop } { prop-keyval } }
-\cs_new_protected:Npn \__prop_keyval_parse:NNNn #1#2#3#4
-  {
-    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool \c_true_bool
-    \keyval_parse:nnn \__prop_missing_eq:n { #2 #3 } {#4}
-    \bool_set_eq:NN \l__kernel_keyval_allow_blank_keys_bool #1
-  }
-\cs_new_protected:Npn \__prop_split:NnTF #1#2
-  { \exp_args:NNo \__prop_split_aux:NnTF #1 { \tl_to_str:n {#2} } }
-\cs_new_protected:Npn \__prop_split_aux:NnTF #1#2#3#4
-  {
-    \cs_set:Npn \__prop_split_aux:w ##1
-      \__prop_pair:wn #2 \s__prop ##2 ##3 \s__prop_mark ##4 ##5 \s__prop_stop
-      { ##4 {#3} {#4} }
-    \exp_after:wN \__prop_split_aux:w #1 \s__prop_mark \use_i:nn
-      \__prop_pair:wn #2 \s__prop { } \s__prop_mark \use_ii:nn \s__prop_stop
-  }
-\cs_new:Npn \__prop_split_aux:w { }
-\cs_new_protected:Npn \prop_remove:Nn #1#2
-  {
-    \__prop_split:NnTF #1 {#2}
-      { \tl_set:Nn #1 { ##1 ##3 } }
-      { }
-  }
-\cs_new_protected:Npn \prop_gremove:Nn #1#2
-  {
-    \__prop_split:NnTF #1 {#2}
-      { \tl_gset:Nn #1 { ##1 ##3 } }
-      { }
-  }
-\cs_generate_variant:Nn \prop_remove:Nn  { NV , Ne , c , cV , ce }
-\cs_generate_variant:Nn \prop_gremove:Nn { NV , Ne , c , cV , ce }
 \cs_new_protected:Npn \prop_get:NnN #1#2#3
   {
-    \__prop_split:NnTF #1 {#2}
-      { \tl_set:Nn #3 {##2} }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \__prop_get:NnnTF #1 {#2}
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
   }
 \cs_generate_variant:Nn \prop_get:NnN { NV , Nv , Ne , c , cV , cv , ce }
 \cs_generate_variant:Nn \prop_get:NnN { No , Nx , co , cx }
 \cs_generate_variant:Nn \prop_get:NnN { cnc }
-\cs_new_protected:Npn \prop_pop:NnN #1#2#3
+\prg_new_protected_conditional:Npnn \prop_get:NnN #1#2#3 { T , F , TF }
   {
-    \__prop_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_set:Nn #1 { ##1 ##3 }
-      }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \__prop_get:NnnTF #1 {#2}
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
   }
-\cs_new_protected:Npn \prop_gpop:NnN #1#2#3
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { NV , Nv , Ne , c , cV , cv , ce } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { No , Nx , co , cx } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_get:NnN
+  { cnc } { T , F , TF }
+\cs_new_protected:Npn \__prop_get:NnnTF #1#2#3#4#5
   {
-    \__prop_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_gset:Nn #1 { ##1 ##3 }
-      }
-      { \tl_set:Nn #3 { \q_no_value } }
+    \__prop_split:NnTFn #1 {#2}
+      { #3 {##3} #4 }
+      {#5}
+      { \exp_after:wN \__prop_get_linked:w #1 {#2} {#3} {#4} {#5} }
   }
-\cs_generate_variant:Nn \prop_pop:NnN  {     NV , No }
-\cs_generate_variant:Nn \prop_pop:NnN  { c , cV , co }
-\cs_generate_variant:Nn \prop_gpop:NnN {     NV , No }
-\cs_generate_variant:Nn \prop_gpop:NnN { c , cV , co }
+\cs_new:Npn \__prop_get_linked:w
+    \__prop_flatten:w #1 \s__prop #2#3#4#5#6#7
+  {
+    \if_cs_exist:w __prop ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      \exp_after:wN \exp_after:wN \exp_after:wN \__prop_get_linked_aux:w
+      \cs:w __prop ~ #2 ~ \tl_to_str:n {#4} \exp_after:wN \cs_end:
+    \else:
+      \exp_after:wN \__prop_get_linked_aux:w
+    \fi:
+    \s__prop_mark {#5} {#6}
+    \s__prop { } \s__prop_mark \use_none:n {#7}
+    \s__prop_stop
+  }
+\cs_new:Npn \__prop_get_linked_aux:w
+    #1 \s__prop #2 #3 \s__prop_mark #4 #5 #6 \s__prop_stop { #4 {#2} #5 }
 \cs_new:Npn \prop_item:Nn #1#2
   {
-    \exp_args:NNo \prop_map_tokens:Nn #1
-      { \exp_after:wN \__prop_item:nnn \exp_after:wN { \tl_to_str:n {#2} } }
+    \__prop_if_flat:NTF #1
+      {
+        \exp_args:NNo \prop_map_tokens:Nn #1
+          {
+            \exp_after:wN \__prop_item:nnn
+            \exp_after:wN { \tl_to_str:n {#2} }
+          }
+      }
+      { \exp_after:wN \__prop_get_linked:w #1 {#2} \use:n { } { } }
   }
 \cs_new:Npn \__prop_item:nnn #1#2#3
   {
@@ -9561,69 +9823,113 @@
       { \prop_map_break:n { \exp_not:n {#3} } }
   }
 \cs_generate_variant:Nn \prop_item:Nn { NV , No , Ne , c , cV , co , ce }
-\cs_new:Npn \prop_count:N #1
+\cs_new_protected:Npn \__prop_pop:NnNNnTF #1#2#3#4#5#6#7
   {
-    \int_eval:n
+    \__prop_split:NnTFn #1 {#2}
       {
-        0
-        \prop_map_function:NN #1 \__prop_count:nn
+        #4 #1 { \exp_not:n { \s__prop \__prop_chk:w ##2 ##4 } }
+        #5 {##3}
+        #6
       }
+      {#7}
+      {
+        \exp_after:wN \__prop_pop_linked:wnNNnTF #1 {#2}
+          #3 #4 {#5} {#6} {#7}
+      }
   }
-\cs_new:Npn \__prop_count:nn #1#2 { + 1 }
-\cs_generate_variant:Nn \prop_count:N { c }
-\cs_new:Npn \prop_to_keyval:N #1
+\cs_new_protected:Npn \__prop_pop_linked:wnNNnTF
+    \__prop_flatten:w #1 \s__prop #2#3#4#5#6#7
   {
-    \__kernel_exp_not:w
-      \prop_if_empty:NTF #1
-        { {} }
-        {
-          \exp_after:wN \exp_after:wN \exp_after:wN
-          {
-            \tex_expanded:D
-              {
-                \__kernel_exp_not:w { \use_none:n }
-                \prop_map_function:NN #1 \__prop_to_keyval:nn
-              }
-          }
-        }
+    \if_cs_exist:w __prop ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      \exp_after:wN \__prop_pop_linked:NNNn
+      \cs:w __prop ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+      #5 #6 {#7}
+    \else:
+      \exp_after:wN \use_iii:nnn
+    \fi:
+    \use_i:nn
   }
-\cs_new:Npn \__prop_to_keyval:nn #1#2
-  { , ~ {#1} =~ { \__kernel_exp_not:w {#2} } }
-\prg_new_protected_conditional:Npnn \prop_pop:NnN #1#2#3 { T , F , TF }
+\cs_new_protected:Npn \__prop_pop_linked:NNNn #1#2#3#4
   {
-    \__prop_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \tl_set:Nn #1 { ##1 ##3 }
-        \prg_return_true:
-      }
-      { \prg_return_false: }
+    \if_meaning:w \scan_stop: #1
+      \exp_after:wN \exp_after:wN \exp_after:wN \use_iii:nnn
+    \else:
+      \exp_after:wN \__prop_pop_linked:w #1 #1 #2 #3 {#4}
+    \fi:
   }
-\prg_new_protected_conditional:Npnn \prop_gpop:NnN #1#2#3 { T , F , TF }
+\cs_new_protected:Npn \__prop_pop_linked:w
+    \use_none:n #1#2 \s__prop #3#4#5#6#7#8
   {
-    \__prop_split:NnTF #1 {#2}
+    #6 #5 \tex_undefined:D
+    #7 #1
       {
-        \tl_set:Nn #3 {##2}
-        \tl_gset:Nn #1 { ##1 ##3 }
-        \prg_return_true:
+        \exp_after:wN \__prop_pop_linked_prev:w #1
+        \exp_not:N #4
       }
-      { \prg_return_false: }
-  }
-\prg_generate_conditional_variant:Nnn \prop_pop:NnN  { NV , c , cV } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_gpop:NnN { NV , c , cV } { T , F , TF }
-\cs_new_protected:Npn \prop_put:Nnn  { \__prop_put:NNnn \__kernel_tl_set:Nx }
-\cs_new_protected:Npn \prop_gput:Nnn { \__prop_put:NNnn \__kernel_tl_gset:Nx }
-\cs_new_protected:Npn \__prop_put:NNnn #1#2#3#4
-  {
-    \tl_set:Nn \l__prop_internal_tl
+    #7 #4
       {
-        \exp_not:N \__prop_pair:wn \tl_to_str:n {#3}
-        \s__prop { \exp_not:n {#4} }
+        \exp_not:n { \use_none:n #1 }
+        \exp_not:f { \exp_after:wN \__prop_pop_linked_next:w #4 }
       }
-    \__prop_split:NnTF #2 {#3}
-      { #1 #2 { \exp_not:n {##1} \l__prop_internal_tl \exp_not:n {##3} } }
-      { #1 #2 { \exp_not:o {#2} \l__prop_internal_tl } }
+    #8 {#3}
   }
+\cs_new:Npn \__prop_pop_linked_prev:w #1 \s__prop #2#3
+  { \exp_not:n { #1 \s__prop {#2} } }
+\cs_new:Npn \__prop_pop_linked_next:w \use_none:n #1 { \exp_stop_f: }
+\cs_new_protected:Npn \prop_remove:Nn #1#2
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      \use_none:n { } { }
+  }
+\cs_new_protected:Npn \prop_gremove:Nn #1#2
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      \use_none:n { } { }
+  }
+\cs_generate_variant:Nn \prop_remove:Nn  { NV , Ne , c , cV , ce }
+\cs_generate_variant:Nn \prop_gremove:Nn { NV , Ne , c , cV , ce }
+\cs_new_protected:Npn \prop_pop:NnN #1#2#3
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
+  }
+\cs_new_protected:Npn \prop_gpop:NnN #1#2#3
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      { \tl_set:Nn #3 } { } { \tl_set:Nn #3 { \q_no_value } }
+  }
+\cs_generate_variant:Nn \prop_pop:NnN  {     NV , No }
+\cs_generate_variant:Nn \prop_pop:NnN  { c , cV , co }
+\cs_generate_variant:Nn \prop_gpop:NnN {     NV , No }
+\cs_generate_variant:Nn \prop_gpop:NnN { c , cV , co }
+\prg_new_protected_conditional:Npnn \prop_pop:NnN #1#2#3 { T , F , TF }
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_set_eq:NN \cs_set_nopar:Npe
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
+  }
+\prg_new_protected_conditional:Npnn \prop_gpop:NnN #1#2#3 { T , F , TF }
+  {
+    \__prop_pop:NnNNnTF #1 {#2}
+      \cs_gset_eq:NN \cs_gset_nopar:Npe
+      { \tl_set:Nn #3 } \prg_return_true: \prg_return_false:
+  }
+\prg_generate_conditional_variant:Nnn \prop_pop:NnN
+  { NV , No , c , cV , co } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \prop_gpop:NnN
+  { NV , No , c , cV , co } { T , F , TF }
+\cs_new_protected:Npn \prop_put:Nnn
+  { \__prop_put:nNNnn { } \cs_set_nopar:Npe }
+\cs_new_protected:Npn \prop_gput:Nnn
+  { \__prop_put:nNNnn { } \cs_gset_nopar:Npe }
+\cs_new_protected:Npn \prop_put_if_new:Nnn
+  { \__prop_put:nNNnn \use_none:nnn \cs_set_nopar:Npe }
+\cs_new_protected:Npn \prop_gput_if_new:Nnn
+  { \__prop_put:nNNnn \use_none:nnn \cs_gset_nopar:Npe }
 \cs_generate_variant:Nn \prop_put:Nnn
   {
          NnV , Nnv , Nne , NV , NVV , NVv , NVe ,
@@ -9652,25 +9958,69 @@
   }
 \cs_generate_variant:Nn \prop_gput:Nnn
   { cno , co , coo , cnx , cVx , cxV , cxx }
-\cs_new_protected:Npn \prop_put_if_new:Nnn
-  { \__prop_put_if_new:NNnn \__kernel_tl_set:Nx }
-\cs_new_protected:Npn \prop_gput_if_new:Nnn
-  { \__prop_put_if_new:NNnn \__kernel_tl_gset:Nx }
-\cs_new_protected:Npn \__prop_put_if_new:NNnn #1#2#3#4
+\cs_generate_variant:Nn \prop_put_if_new:Nnn
+  { NnV , NV , cnV , cV }
+\cs_generate_variant:Nn \prop_gput_if_new:Nnn
+  { NnV , NV , cnV , cV }
+\cs_new_protected:Npn \__prop_put:nNNnn #1#2#3#4#5
   {
     \tl_set:Nn \l__prop_internal_tl
       {
-        \exp_not:N \__prop_pair:wn \tl_to_str:n {#3}
-        \s__prop \exp_not:n { {#4} }
+        \exp_not:N \__prop_pair:wn \tl_to_str:n {#4}
+        \s__prop { \exp_not:n {#5} }
       }
-    \__prop_split:NnTF #2 {#3}
-      { }
-      { #1 #2 { \exp_not:o {#2} \l__prop_internal_tl } }
+    \__prop_split:NnTFn #3 {#4}
+      {
+        #1 #2 #3
+          {
+            \s__prop \__prop_chk:w \exp_not:n {##2}
+            \l__prop_internal_tl \exp_not:n {##4}
+          }
+      }
+      { #2 #3 { \exp_not:o {#3} \l__prop_internal_tl } }
+      { \exp_after:wN \__prop_put_linked:wnnN #3 {#4} {#1} #2 }
   }
-\cs_generate_variant:Nn \prop_put_if_new:Nnn
-  { NnV , NV , cnV , cV }
-\cs_generate_variant:Nn \prop_gput_if_new:Nnn
-  { NnV , NV , cnV , cV }
+\cs_new_protected:Npn \__prop_put_linked:wnnN
+    \__prop_flatten:w #1 \s__prop #2#3#4
+  {
+    \exp_after:wN \__prop_put_linked:NNnN
+    \cs:w __prop ~ #2 ~ \tl_to_str:n {#4} \cs_end:
+    #1
+  }
+\cs_new_protected:Npn \__prop_put_linked:NNnN #1#2#3#4
+  {
+    \if_meaning:w \scan_stop: #1
+      \exp_after:wN \__prop_put_linked_new:w #2 #1 #2 #4
+    \else:
+      \exp_after:wN \__prop_put_linked_old:w #1 { #3 #4 #1 }
+    \fi:
+  }
+\cs_new_protected:Npn \__prop_put_linked_new:w
+    \use_none:n #1#2#3#4
+  {
+    #4 #1
+      {
+        \exp_after:wN \__prop_pop_linked_prev:w #1
+        \exp_not:N #2
+      }
+    #4 #2
+      {
+        \exp_not:n { \use_none:n #1 }
+        \l__prop_internal_tl
+        \exp_not:N #3
+      }
+    #4 #3 { \exp_not:n { \use_none:n #2 } }
+  }
+\cs_new_protected:Npn \__prop_put_linked_old:w
+    \use_none:n #1#2 \s__prop #3#4#5
+  {
+    #5
+      {
+        \exp_not:n { \use_none:n #1 }
+        \l__prop_internal_tl
+        \exp_not:N #4
+      }
+  }
 \prg_new_eq_conditional:NNn \prop_if_exist:N \cs_if_exist:N
   { TF , T , F , p }
 \prg_new_eq_conditional:NNn \prop_if_exist:c \cs_if_exist:c
@@ -9677,18 +10027,41 @@
   { TF , T , F , p }
 \prg_new_conditional:Npnn \prop_if_empty:N #1 { p , T , F , TF }
   {
-    \tl_if_eq:NNTF #1 \c_empty_prop
-      \prg_return_true: \prg_return_false:
+    \if_meaning:w #1 \c_empty_prop
+      \prg_return_true:
+    \else:
+      \exp_after:wN \__prop_if_empty_return:w #1
+        \__prop_flatten:w 2 \s__prop 34 \s__prop_stop
+    \fi:
   }
+\cs_new:Npn \__prop_if_empty_return:w
+    #1 \__prop_flatten:w #2 \s__prop #3#4#5 \s__prop_stop
+  {
+    \if_meaning:w #2 #4
+      \prg_return_true:
+    \else:
+      \prg_return_false:
+    \fi:
+  }
 \prg_generate_conditional_variant:Nnn \prop_if_empty:N
   { c } { p , T , F , TF }
 \prg_new_conditional:Npnn \prop_if_in:Nn #1#2 { p , T , F , TF }
   {
-    \exp_args:NNo \prop_map_tokens:Nn #1
-      { \exp_after:wN \__prop_if_in:nnn \exp_after:wN { \tl_to_str:n {#2} } }
-    \prg_return_false:
+    \__prop_if_flat:NTF #1
+      {
+        \exp_after:wN \prop_map_tokens:Nn \exp_after:wN #1
+          {
+            \exp_after:wN \__prop_if_in_flat:nnn
+            \exp_after:wN { \tl_to_str:n {#2} }
+          }
+        \prg_return_false:
+      }
+      {
+        \exp_after:wN \__prop_get_linked:w #1 {#2}
+        \use_none:n \prg_return_true: \prg_return_false:
+      }
   }
-\cs_new:Npn \__prop_if_in:nnn #1#2#3
+\cs_new:Npn \__prop_if_in_flat:nnn #1#2#3
   {
     \str_if_eq:eeT {#1} {#2}
       { \prop_map_break:n { \use_i:nn \prg_return_true: } }
@@ -9695,27 +10068,10 @@
   }
 \prg_generate_conditional_variant:Nnn \prop_if_in:Nn
   { NV , Ne , No , c , cV , ce , co } { p , T , F , TF }
-\prg_new_protected_conditional:Npnn \prop_get:NnN #1#2#3 { T , F , TF }
-  {
-    \__prop_split:NnTF #1 {#2}
-      {
-        \tl_set:Nn #3 {##2}
-        \prg_return_true:
-      }
-      { \prg_return_false: }
-  }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { NV , Nv , Ne , c , cV , cv , ce } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { No , Nx , co , cx } { T , F , TF }
-\prg_generate_conditional_variant:Nnn \prop_get:NnN
-  { cnc } { T , F , TF }
 \cs_new:Npn \prop_map_function:NN #1#2
   {
-    \exp_after:wN \use_i_ii:nnn
-    \exp_after:wN \__prop_map_function:Nw
-    \exp_after:wN #2
-    #1
+    \exp_last_unbraced:Nnf
+      \use_i:nnn { \__prop_map_function:Nw #2 } #1
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
@@ -9741,7 +10097,7 @@
       { __prop_map_ \int_use:N \g__kernel_prg_map_int :wn } \__prop_pair:wn
     \int_gincr:N \g__kernel_prg_map_int
     \cs_gset_protected:Npn \__prop_pair:wn ##1 \s__prop ##2 {#2}
-    #1
+    \exp_last_unbraced:Nf \use_none:nn #1
     \prg_break_point:Nn \prop_map_break:
       {
         \int_gdecr:N \g__kernel_prg_map_int
@@ -9752,8 +10108,8 @@
 \cs_generate_variant:Nn \prop_map_inline:Nn { c }
 \cs_new:Npn \prop_map_tokens:Nn #1#2
   {
-    \exp_last_unbraced:Nno
-      \use_i:nn { \__prop_map_tokens:nw {#2} } #1
+    \exp_last_unbraced:Nnf
+      \use_i:nnn { \__prop_map_tokens:nw {#2} } #1
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
     \__prop_pair:wn \fi: \prop_map_break: \s__prop { }
@@ -9777,6 +10133,34 @@
   { \prg_map_break:Nn \prop_map_break: { } }
 \cs_new:Npn \prop_map_break:n
   { \prg_map_break:Nn \prop_map_break: }
+\cs_new:Npn \prop_count:N #1
+  {
+    \int_eval:n
+      {
+        0
+        \prop_map_function:NN #1 \__prop_count:nn
+      }
+  }
+\cs_new:Npn \__prop_count:nn #1#2 { + 1 }
+\cs_generate_variant:Nn \prop_count:N { c }
+\cs_new:Npn \prop_to_keyval:N #1
+  {
+    \__kernel_exp_not:w
+      \prop_if_empty:NTF #1
+        { {} }
+        {
+          \exp_after:wN \exp_after:wN \exp_after:wN
+          {
+            \tex_expanded:D
+              {
+                \exp_not:N \use_none:n
+                \prop_map_function:NN #1 \__prop_to_keyval:nn
+              }
+          }
+        }
+  }
+\cs_new:Npn \__prop_to_keyval:nn #1#2
+  { , ~ {#1} =~ { \__kernel_exp_not:w {#2} } }
 \cs_new_protected:Npn \prop_show:N { \__prop_show:NN \msg_show:nneeee }
 \cs_generate_variant:Nn \prop_show:N { c }
 \cs_new_protected:Npn \prop_log:N { \__prop_show:NN \msg_log:nneeee }
@@ -9785,23 +10169,105 @@
   {
     \__kernel_chk_tl_type:NnnT #2 { prop }
       {
-        \s__prop
-        \exp_after:wN \use_i:nn \exp_after:wN \__prop_show_validate:w #2
-        \__prop_pair:wn \q_recursion_tail \s__prop { } \q_recursion_stop
+        \__prop_if_flat:NTF #2
+          {
+            \s__prop \__prop_chk:w
+            \exp_after:wN \__prop_show_flat:w #2
+            \s__prop { }
+            \__prop_pair:wn \q__prop_recursion_tail \s__prop { }
+            \q__prop_recursion_stop
+          }
+          { \exp_after:wN \__prop_show_linked:w #2 \s__prop ! ? \s__prop_stop }
       }
       {
-        #1 { prop } { show }
-          { \token_to_str:N #2 }
-          { \prop_map_function:NN #2 \msg_show_item:nn }
-          { } { }
+        \__prop_if_flat:NTF #2
+          { \__prop_show_finally:NNn #1 #2 { flat } }
+          {
+            \tl_set:Nn \l__prop_internal_tl { #1 #2 }
+            \exp_after:wN \__prop_show_prepare:w #2 #2
+          }
       }
   }
-\cs_new:Npn \__prop_show_validate:w #1 \__prop_pair:wn #2 \s__prop #3
+\cs_new:Npn \__prop_show_flat:w #1 \__prop_pair:wn #2 \s__prop #3
   {
-    \quark_if_recursion_tail_stop:n {#2}
+    \__prop_if_recursion_tail_stop:n {#2}
     \exp_not:N \__prop_pair:wn \tl_to_str:n {#2} \s__prop \exp_not:n { {#3} }
-    \__prop_show_validate:w
+    \__prop_show_flat:w
   }
+\cs_new:Npn \__prop_show_linked:w #1 \s__prop #2#3#4 \s__prop_stop
+  {
+    \exp_not:N \__prop_flatten:w
+    \exp_not:c { __prop ~ \tl_to_str:n {#2} }
+    \s__prop { \tl_to_str:n {#2} }
+    \exp_not:n {#3}
+  }
+\cs_new_protected:Npn \__prop_show_finally:NNn #1#2#3
+  {
+    #1 { prop } { show }
+      { \token_to_str:N #2 }
+      { \prop_map_function:NN #2 \msg_show_item:nn }
+      {#3} { }
+  }
+\cs_new_protected:Npn \__prop_show_prepare:w
+    \__prop_flatten:w #1 \s__prop #2#3#4
+  {
+    \use:e
+      {
+        \cs_set_nopar:Npn \exp_not:N \__prop_tmp:w
+            ##1 \token_to_str:N #1 ##2 \s__prop_mark ##3 \s__prop_stop
+          {
+            \exp_not:N \tl_if_empty:nTF {##1}
+              {
+                \exp_not:N \tl_if_head_is_space:nTF {##2}
+                  { \exp_not:N \exp_args:Nf \__prop_show_loop_key:wNNN }
+                  { \exp_not:N \tl_if_empty:nTF }
+                {##2}
+              }
+              { \exp_not:N \use_ii:nn }
+            \__prop_show_end:NNN
+            \__prop_show_bad_name:NNN
+          }
+      }
+    \exp_last_unbraced:NNNo \__prop_show_loop:NNw #1 #4 #4
+  }
+\cs_new_protected:Npn \__prop_show_loop:NNw #1#2 #3 \s__prop #4#5
+  {
+    \exp_last_two_unbraced:Noo \__prop_tmp:w
+      { \token_to_str:N #5 \s__prop_mark }
+      { \token_to_str:N #1 \s__prop_mark \s__prop_stop }
+      #1 #2 #5
+  }
+\cs_new_protected:Npn \__prop_show_bad_name:NNN #1#2#3
+  {
+    \msg_error:nneeee { prop } { bad-link }
+      { \tl_tail:N \l__prop_internal_tl }
+      { \token_to_str:N #2 }
+      { \token_to_str:N #3 }
+      { \token_to_str:N #1 }
+  }
+\cs_new_protected:Npn \__prop_show_end:NNN #1#2#3
+  {
+    \__kernel_chk_tl_type:NnnT #3
+      { \tl_tail:N \l__prop_internal_tl prop~entry }
+      { \exp_not:n { \use_none:n #2 } }
+      {
+        \exp_after:wN \__prop_show_finally:NNn
+        \l__prop_internal_tl { linked }
+      }
+  }
+\cs_new_protected:Npn \__prop_show_loop_key:wNNN #1#2#3#4#5#6
+  {
+    \__kernel_chk_tl_type:NnnT #6
+      { \tl_tail:N \l__prop_internal_tl prop~entry }
+      {
+        \exp_not:n { \use_none:n #5 }
+        \exp_after:wN \__prop_show_flat:w #6 \s__prop { }
+        \__prop_pair:wn \q__prop_recursion_tail \s__prop { }
+        \q__prop_recursion_stop
+        \tl_item:Nn #6 { -1 }
+      }
+      { \exp_last_unbraced:NNNo \__prop_show_loop:NNw #4 #6 #6 }
+  }
 %% File: l3msg.dtx
 \tl_new:N \l__msg_internal_tl
 \str_new:N \l__msg_name_str
@@ -9824,10 +10290,13 @@
           {#1} {#2}
       }
   }
-\cs_new_protected:Npn \msg_new:nnnn #1#2
+\cs_new_protected:Npn \msg_new:nnnn #1#2#3#4
   {
     \__msg_chk_free:nn {#1} {#2}
-    \msg_gset:nnnn {#1} {#2}
+    \cs_gset:cpn { \c__msg_text_prefix_tl #1 / #2 }
+      ##1##2##3##4 {#3}
+    \cs_gset:cpn { \c__msg_more_text_prefix_tl #1 / #2 }
+      ##1##2##3##4 {#4}
   }
 \cs_generate_variant:Nn \msg_new:nnnn { nnee , nnxx }
 \cs_new_protected:Npn \msg_new:nnn #1#2#3
@@ -9842,15 +10311,6 @@
   }
 \cs_new_protected:Npn \msg_set:nnn #1#2#3
   { \msg_set:nnnn {#1} {#2} {#3} { } }
-\cs_new_protected:Npn \msg_gset:nnnn #1#2#3#4
-  {
-    \cs_gset:cpn { \c__msg_text_prefix_tl #1 / #2 }
-      ##1##2##3##4 {#3}
-    \cs_gset:cpn { \c__msg_more_text_prefix_tl #1 / #2 }
-      ##1##2##3##4 {#4}
-  }
-\cs_new_protected:Npn \msg_gset:nnn #1#2#3
-  { \msg_gset:nnnn {#1} {#2} {#3} { } }
 \tl_const:Nn \c__msg_coding_error_text_tl
   {
     This~is~a~coding~error.
@@ -10608,6 +11068,14 @@
           { internal~structure:\\\\\iow_indent:n {#4} }
       }
   }
+\msg_new:nnnn { prop } { bad-link }
+  { Variable~'#1'~is~not~a~valid~(linked)~prop. }
+  {
+    \c__msg_coding_error_text_tl
+    The~variable~'#1'~has~an~incorrect~internal~structure.~
+    Its~internal~entry~'#2'~points~to~'#3',~whose~name~is~not~of~the~
+    form~'#4~<key>'.
+  }
 \msg_new:nnnn { clist } { non-clist }
   { Variable~'#1'~is~not~a~valid~clist. }
   {
@@ -10617,6 +11085,24 @@
     should~be~a~clist~variable,~but~it~includes~empty~or~blank~items~
     without~braces.
   }
+\msg_new:nnnn { prop } { misused }
+  { A~property~list~was~misused. }
+  {
+    \c__msg_coding_error_text_tl
+    A~property~list~variable~was~used~without~an~accessor~function.~
+    It~
+    \tl_if_empty:nTF {#1}
+      { is~empty. }
+      { contains~the~key-value~pairs \use_none:n #1 . }
+  }
+\msg_new:nnnn { prop } { inner-make }
+  { '#1'~ cannot~ be~ used~ in~ a~ group. }
+  {
+    \c__msg_coding_error_text_tl
+    The~ command~ '#1'~ was~ applied~ to~ the~ property~ list~
+    variable~ '#2', but~ the~ storage~ type~ can~ only~ be~ changed~
+    at~ the~ outermost~ group~ level.
+  }
 \msg_new:nnn { kernel } { bad-exp-end-f }
   { Misused~\exp_end_continue_f:w or~:nw }
 \msg_new:nnn { kernel } { bad-variable }
@@ -10623,8 +11109,6 @@
   { Erroneous~variable~#1 used! }
 \msg_new:nnn { seq } { misused }
   { A~sequence~was~misused. }
-\msg_new:nnn { prop } { misused }
-  { A~property~list~was~misused. }
 \msg_new:nnn { prg } { negative-replication }
   { Negative~argument~for~\iow_char:N\\prg_replicate:nn. }
 \msg_new:nnn { prop } { prop-keyval }
@@ -10644,7 +11128,8 @@
   { The~integer~array~#1~contains~#2~items: \\ #3 . }
 \msg_new:nnn { prop } { show }
   {
-    The~property~list~#1~
+    The~ \str_if_eq:nnF {#3} { flat } { #3~ }
+    property~list~#1~
     \tl_if_empty:nTF {#2}
       { is~empty \\>~ . }
       { contains~the~pairs~(without~outer~braces): #2 . }
@@ -14221,7 +14706,7 @@
       { \prg_return_true: }
       { \prg_return_false: }
   }
-\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { T , F , TF }
+\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
 \prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3
   { p , T , F , TF }
   {
@@ -23882,7 +24367,6 @@
 \scan_new:N \s__tl
 \cs_new_eq:NN \l__tl_analysis_token ?
 \cs_new_eq:NN \l__tl_analysis_char_token ?
-\cs_new_eq:NN \l__tl_analysis_next_token ?
 \tl_new:N \l__tl_peek_code_tl
 \group_begin:
 \char_set_active_eq:NN \  \scan_stop:
@@ -24373,7 +24857,10 @@
         #1
         \__tl_peek_analysis_loop:NNn
           \prg_break_point:Nn \peek_analysis_map_break:
-            { \group_align_safe_end: }
+            {
+              \int_gdecr:N \g__kernel_prg_map_int
+              \group_align_safe_end:
+            }
       }
     \__tl_peek_analysis_loop:NNn ? ? ?
   }
@@ -24500,21 +24987,9 @@
     \if_meaning:w \l__tl_analysis_token \scan_stop:
       \exp_after:wN \__tl_peek_analysis_normal:N
     \else:
-      \exp_after:wN \__tl_peek_analysis_next:
+      \exp_after:wN \__tl_peek_analysis_str:
     \fi:
   }
-\cs_new_protected:Npn \__tl_peek_analysis_next:
-  {
-    \tl_if_empty:oT { \tex_the:D \tex_everyeof:D }
-      { \tex_everyeof:D { \scan_stop: } }
-    \tex_afterassignment:D \__tl_peek_analysis_nextii:
-    \tex_futurelet:D \l__tl_analysis_next_token
-  }
-\cs_new_protected:Npn \__tl_peek_analysis_nextii:
-  {
-    \tex_afterassignment:D \__tl_peek_analysis_str:
-    \tex_futurelet:D \l__tl_analysis_next_token
-  }
 \cs_new_protected:Npn \__tl_peek_analysis_str:
   {
     \exp_after:wN \tex_futurelet:D
@@ -24590,21 +25065,23 @@
   }
 \cs_new_protected:Npn \__tl_peek_analysis_collect_loop:
   {
-    \tex_futurelet:D \l__tl_analysis_token
-      \__tl_peek_analysis_collect_test:
-  }
-\cs_new_protected:Npn \__tl_peek_analysis_collect_test:
-  {
-    \if_meaning:w \l__tl_analysis_token \l__tl_analysis_next_token
-      \exp_after:wN \if_meaning:w \cs:w \l__tl_internal_a_tl \cs_end: \l_peek_token
-        \__tl_peek_analysis_collect_end:NNN
+    \exp_after:wN \if_meaning:w
+      \cs:w
+      \if_cs_exist:w \l__tl_internal_a_tl \cs_end:
+        \l__tl_internal_a_tl
+      \else:
+        c_one % anything short
       \fi:
+      \cs_end:
+      \l_peek_token
+      \__tl_peek_analysis_collect_end:NNNN
     \fi:
-    \__tl_peek_analysis_collect:w
+    \tex_futurelet:D \l__tl_analysis_token
+      \__tl_peek_analysis_collect:w
   }
-\cs_new_protected:Npn \__tl_peek_analysis_collect_end:NNN #1#2#3
+\cs_new_protected:Npn \__tl_peek_analysis_collect_end:NNNN #1#2#3#4
   {
-    #1 #2
+    #1
     \tl_put_right:Ne \l__tl_peek_code_tl
       {
         { \exp_not:N \exp_not:n { \exp_not:c { \l__tl_internal_a_tl } } }
@@ -28756,9 +29233,9 @@
   { \exp_args:No \__box_log:nNnn { \tex_the:D \tex_interactionmode:D } }
 \cs_new_protected:Npn \__box_log:nNnn #1#2#3#4
   {
-    \int_set:Nn \tex_interactionmode:D { 0 }
+    \int_gset:Nn \tex_interactionmode:D { 0 }
     \__box_show:NNff 0 #2 { \int_eval:n {#3} } { \int_eval:n {#4} }
-    \int_set:Nn \tex_interactionmode:D {#1}
+    \int_gset:Nn \tex_interactionmode:D {#1}
   }
 \cs_generate_variant:Nn \box_log:Nnn { c }
 \cs_new_protected:Npn \__box_show:NNnn #1#2#3#4
@@ -29248,7 +29725,7 @@
 \cs_generate_variant:Nn \box_autosize_to_wd_and_ht:Nnn { c }
 \cs_new_protected:Npn \box_gautosize_to_wd_and_ht:Nnn #1#2#3
   { \__box_autosize:NnnnN #1 {#2} {#3} { \box_ht:N #1 } \hbox_gset:Nn }
-\cs_generate_variant:Nn \box_autosize_to_wd_and_ht:Nnn { c }
+\cs_generate_variant:Nn \box_gautosize_to_wd_and_ht:Nnn { c }
 \cs_new_protected:Npn \box_autosize_to_wd_and_ht_plus_dp:Nnn #1#2#3
   {
     \__box_autosize:NnnnN #1 {#2} {#3} { \box_ht:N #1 + \box_dp:N #1 }
@@ -31430,17 +31907,17 @@
 \cs_generate_variant:Nn \hcoffin_gset:Nn { c }
 \cs_new_protected:Npn \vcoffin_set:Nnn #1#2#3
   {
-    \__coffin_set_vertical:NnnNN #1 {#2} {#3}
-      \vbox_set:Nn \coffin_reset_poles:N
+    \__coffin_set_vertical:NnnNNN #1 {#2} {#3}
+      \vbox_set:Nn \coffin_reset_poles:N \__coffin_set_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_set:Nnn { c }
 \cs_new_protected:Npn \vcoffin_gset:Nnn #1#2#3
   {
-    \__coffin_set_vertical:NnnNN #1 {#2} {#3}
-      \vbox_gset:Nn \coffin_greset_poles:N
+    \__coffin_set_vertical:NnnNNN #1 {#2} {#3}
+      \vbox_gset:Nn \coffin_greset_poles:N \__coffin_gset_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_gset:Nnn { c }
-\cs_new_protected:Npn \__coffin_set_vertical:NnnNN #1#2#3#4#5
+\cs_new_protected:Npn \__coffin_set_vertical:NnnNNN #1#2#3#4#5#6
   {
     \__coffin_if_exist:NT #1
       {
@@ -31452,7 +31929,7 @@
           }
         #5 #1
         \vbox_set_top:Nn \l__coffin_internal_box { \vbox_unpack:N #1 }
-        \__coffin_set_pole:Nne #1 { T }
+        #6 #1 { T }
           {
             { 0pt }
             {
@@ -31505,19 +31982,19 @@
 \cs_new_protected:Npn \hcoffin_gset_end: { }
 \cs_new_protected:Npn \vcoffin_set:Nnw #1#2
   {
-    \__coffin_set_vertical:NnNNNNw #1 {#2} \vbox_set:Nw
+    \__coffin_set_vertical:NnNNNNNw #1 {#2} \vbox_set:Nw
       \vcoffin_set_end:
-      \vbox_set_end: \coffin_reset_poles:N
+      \vbox_set_end: \coffin_reset_poles:N \__coffin_set_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_set:Nnw { c }
 \cs_new_protected:Npn \vcoffin_gset:Nnw #1#2
   {
-    \__coffin_set_vertical:NnNNNNw #1 {#2} \vbox_gset:Nw
+    \__coffin_set_vertical:NnNNNNNw #1 {#2} \vbox_gset:Nw
       \vcoffin_gset_end:
-      \vbox_gset_end: \coffin_greset_poles:N
+      \vbox_gset_end: \coffin_greset_poles:N \__coffin_gset_pole:Nnn
   }
 \cs_generate_variant:Nn \vcoffin_gset:Nnw { c }
-\cs_new_protected:Npn \__coffin_set_vertical:NnNNNNw #1#2#3#4#5#6
+\cs_new_protected:Npn \__coffin_set_vertical:NnNNNNNw #1#2#3#4#5#6#7
   {
     \__coffin_if_exist:NT #1
       {
@@ -31529,7 +32006,7 @@
               #5
               #6 #1
               \vbox_set_top:Nn \l__coffin_internal_box { \vbox_unpack:N #1 }
-              \__coffin_set_pole:Nne #1 { T }
+              #7 #1 { T }
                 {
                   { 0pt }
                   {
@@ -31644,10 +32121,14 @@
   }
 \cs_new_protected:Npn \__coffin_set_pole:Nnn #1#2#3
   {
-    \prop_put:cnn { coffin ~ \__coffin_to_value:N #1 ~ poles }
+    \prop_put:cne { coffin ~ \__coffin_to_value:N #1 ~ poles }
       {#2} {#3}
   }
-\cs_generate_variant:Nn \__coffin_set_pole:Nnn { Nne }
+\cs_new_protected:Npn \__coffin_gset_pole:Nnn #1#2#3
+  {
+    \prop_gput:cne { coffin ~ \__coffin_to_value:N #1 ~ poles }
+      {#2} {#3}
+  }
 \cs_new_protected:Npn \coffin_reset_poles:N #1
   {
     \__coffin_reset_structure:N #1
@@ -32235,7 +32716,7 @@
     \tl_if_in:nnTF {#2} { - }
       { \tl_set:Nn \l__coffin_internal_tl { {#2} } }
       { \tl_set:Nn \l__coffin_internal_tl { { #1 - #2 } } }
-    \exp_last_unbraced:NNo \__coffin_set_pole:Nne \l__coffin_aligned_coffin
+    \exp_last_unbraced:NNo \__coffin_set_pole:Nnn \l__coffin_aligned_coffin
       { \l__coffin_internal_tl }
       {
         { \dim_use:N \l__coffin_x_dim } { \dim_use:N \l__coffin_y_dim }
@@ -32275,11 +32756,11 @@
   {
     \dim_compare:nNnTF {#2} < {#6}
       {
-        \__coffin_set_pole:Nne #9 { T }
+        \__coffin_set_pole:Nnn #9 { T }
           { { 0pt } {#6} { 1000pt } { 0pt } }
       }
       {
-        \__coffin_set_pole:Nne #9 { T }
+        \__coffin_set_pole:Nnn #9 { T }
           { { 0pt } {#2} { 1000pt } { 0pt } }
       }
   }
@@ -32287,11 +32768,11 @@
   {
     \dim_compare:nNnTF {#2} < {#6}
       {
-        \__coffin_set_pole:Nne #9 { B }
+        \__coffin_set_pole:Nnn #9 { B }
           { { 0pt } {#2}  { 1000pt } { 0pt } }
       }
       {
-        \__coffin_set_pole:Nne #9 { B }
+        \__coffin_set_pole:Nnn #9 { B }
           { { 0pt } {#6} { 1000pt } { 0pt } }
       }
   }
@@ -36596,6 +37077,10 @@
 \cs_generate_variant:Nn \keys_set_filter:nnnN { nnV , nnv , nno }
 \cs_set_protected:Npn  \keys_set_filter:nnnnN { \keys_set_exclude_groups:nnnnN }
 \cs_generate_variant:Nn \keys_set_filter:nnnnN { nnV , nnv , nno }
+\__kernel_patch_deprecation:nnNNpn { 2024-01-17 } { \msg_set:nnnn }
+\cs_new_protected:Npn \msg_gset:nnnn { \msg_set:nnnn }
+\__kernel_patch_deprecation:nnNNpn { 2024-01-17 } { \msg_set:nnn }
+\cs_new_protected:Npn \msg_gset:nnn { \msg_set:nnn }
 \prop_new:N \g__pdf_object_prop
 \__kernel_patch_deprecation:nnNNpn { 2022-08-30 } { [\pdf_object_new:n] }
 \cs_new_protected:Npn \pdf_object_new:nn #1#2

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex	2024-02-14 22:04:08 UTC (rev 69889)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2024-01-22}%
+\def\ExplFileDate{2024-02-13}%
 \let\ExplLoaderFileDate\ExplFileDate
 \begingroup
   \catcode`\_=11

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx	2024-02-14 22:04:08 UTC (rev 69889)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2024-01-22}%
+\def\ExplFileDate{2024-02-13}%
 \let\ExplLoaderFileDate\ExplFileDate
 \begingroup
   \catcode`\_=11

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty	2024-02-14 22:04:08 UTC (rev 69889)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2024-01-22}%
+\def\ExplFileDate{2024-02-13}%
 \let\ExplLoaderFileDate\ExplFileDate
 \ProvidesPackage{expl3}
   [%

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def	2024-02-14 22:04:08 UTC (rev 69889)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: l3debug.dtx
-\ProvidesExplFile{l3debug.def}{2024-01-22}{}{L3 Debugging support}
+\ProvidesExplFile{l3debug.def}{2024-02-13}{}{L3 Debugging support}
 \scan_new:N \s__debug_stop
 \cs_new:Npn \__debug_use_i_delimit_by_s_stop:nw #1 #2 \s__debug_stop {#1}
 \quark_new:N \q__debug_recursion_tail
@@ -417,6 +417,8 @@
     {
       \clist_concat:NNN
       \clist_gconcat:NNN
+      \prop_concat:NNN
+      \prop_gconcat:NNN
       \seq_concat:NNN
       \seq_gconcat:NNN
       \str_concat:NNN
@@ -461,6 +463,17 @@
       \muskip_add:Nn
       \muskip_sub:Nn
       \muskip_set_eq:NN
+      \prop_clear:N
+      \prop_concat:NNN
+      \prop_pop:NnN
+      \prop_pop:NnNT
+      \prop_pop:NnNF
+      \prop_pop:NnNTF
+      \prop_put:Nnn
+      \prop_put_if_new:Nnn
+      \prop_put_from_keyval:Nn
+      \prop_remove:Nn
+      \prop_set_eq:NN
       \seq_set_eq:NN
       \skip_zero:N
       \skip_set:Nn
@@ -530,6 +543,17 @@
       \muskip_gadd:Nn
       \muskip_gsub:Nn
       \muskip_gset_eq:NN
+      \prop_gclear:N
+      \prop_gconcat:NNN
+      \prop_gpop:NnN
+      \prop_gpop:NnNT
+      \prop_gpop:NnNF
+      \prop_gpop:NnNTF
+      \prop_gput:Nnn
+      \prop_gput_if_new:Nnn
+      \prop_gput_from_keyval:Nn
+      \prop_gremove:Nn
+      \prop_gset_eq:NN
       \seq_gset_eq:NN
       \skip_gzero:N
       \skip_gset:Nn
@@ -573,6 +597,8 @@
       \int_const:Nn
       \intarray_const_from_clist:Nn
       \muskip_const:Nn
+      \prop_const_from_keyval:Nn
+      \prop_const_linked_from_keyval:Nn
       \skip_const:Nn
       \str_const:Nn
       \tl_const:Nn

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls	2024-02-14 21:45:18 UTC (rev 69888)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls	2024-02-14 22:04:08 UTC (rev 69889)
@@ -20,7 +20,7 @@
 %% 
 %% File: l3doc.dtx
 \RequirePackage{calc}
-\ProvidesExplClass{l3doc}{2024-01-22}{}
+\ProvidesExplClass{l3doc}{2024-02-13}{}
   {L3 Experimental documentation class}
 \clist_new:N \g_docinput_clist
 \seq_new:N \g_doc_functions_seq
@@ -534,7 +534,7 @@
       { \c_backslash_str #2 }
   }
 \DeclareDocumentCommand \meta { m }
-  { \__codedoc_meta:n {#1} }
+  { \texttt{ \__codedoc_meta:n {#1} } }
 \DeclareExpandableDocumentCommand
   { \__codedoc_pdfstring_cmd:w } { o m } { \token_to_str:N #2 }
 \DeclareExpandableDocumentCommand
@@ -543,16 +543,17 @@
   { < \tl_to_str:n {#1} > }
 \pdfstringdefDisableCommands
   {
-    \cs_set_eq:NN \cmd  \__codedoc_pdfstring_cmd:w
-    \cs_set_eq:NN \cs   \__codedoc_pdfstring_cs:w
-    \cs_set_eq:NN \tn   \__codedoc_pdfstring_cs:w
-    \cs_set_eq:NN \meta \__codedoc_pdfstring_meta:w
+    \cs_set_eq:NN \cmd       \__codedoc_pdfstring_cmd:w
+    \cs_set_eq:NN \cs        \__codedoc_pdfstring_cs:w
+    \cs_set_eq:NN \tn        \__codedoc_pdfstring_cs:w
+    \cs_set_eq:NN \meta      \__codedoc_pdfstring_meta:w
+    \cs_set_eq:NN \__codedoc_meta:n \__codedoc_pdfstring_meta:w
   }
 \newcommand\Arg[1]
-  { \texttt{\char`\{} \meta{#1} \texttt{\char`\}} }
+  { \texttt{\char`\{} \__codedoc_meta:n {#1} \texttt{\char`\}} }
 \providecommand\marg[1]{ \Arg{#1} }
-\providecommand\oarg[1]{ \texttt[ \meta{#1} \texttt] }
-\providecommand\parg[1]{ \texttt( \meta{#1} \texttt) }
+\providecommand\oarg[1]{ \texttt[ \__codedoc_meta:n {#1} \texttt] }
+\providecommand\parg[1]{ \texttt( \__codedoc_meta:n {#1} \texttt) }
 \DeclareRobustCommand \file {\nolinkurl}
 \DeclareRobustCommand \env {\texttt}
 \DeclareRobustCommand \pkg {\textsf}
@@ -1796,6 +1797,18 @@
   }
 \DeclareDocumentCommand \DocInputAgain { }
   { \clist_map_function:NN \g_docinput_clist \__codedoc_input:n }
+\msg_new:nnn { l3doc } { missing-endgroup }
+  {
+    \str_if_eq:VnTF \@currenvir { document }
+      {
+        There~are~\int_use:N \tex_currentgrouplevel:D
+        \c_space_tl unclosed~groups~in~#1.
+      }
+      {
+        The~\@currenvir \c_space_tl environment~on~line~\@currenvline
+        \c_space_tl doesn't~have~a~matching~\iow_char:N\\end{\@currenvir}.
+      }
+  }
 \NewDocumentCommand \DocInclude { m }
   {
     \relax\clearpage
@@ -1806,6 +1819,13 @@
     \int_compare:nNnTF \@auxout = \@partaux
       { \@latexerr{\string\include\space cannot~be~nested}\@eha }
       { \@docinclude {#1} }
+    % check missing \endgroup, e.g., missing "\end{macro}" in time
+    \int_compare:nNnF { \tex_currentgrouplevel:D } = { 0 }
+      {
+        \int_compare:nNnT { \tex_interactionmode:D } = { 0 }
+          { \int_gset:Nn \tex_interactionmode:D { 1 } }
+        \msg_fatal:nne { l3doc } { missing-endgroup } { \currentfile }
+      }
   }
 \cs_gset:Npn \@docinclude #1
   {
@@ -2086,7 +2106,7 @@
             \int_set:Nn \l__codedoc_tmpa_int { \tex_interactionmode:D }
             \errorstopmode
             \ClassError { l3doc } { \l__codedoc_tmpa_tl } { }
-            \int_set:Nn \tex_interactionmode:D { \l__codedoc_tmpa_int }
+            \int_gset:Nn \tex_interactionmode:D { \l__codedoc_tmpa_int }
           }
       }
   }



More information about the tex-live-commits mailing list.