Skip to content

py: Add PEP 750 template strings support #17557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: master
Choose a base branch
from

Conversation

koxudaxi
Copy link

Summary

Implements PEP 750 template strings for MicroPython.

Started in discussion #17497. Template strings (t-strings) are new in Python 3.14 - they return Template objects instead of strings, so you can access the literal parts and expressions separately.

Changes:

  • t-string parsing in lexer/parser
  • Template and Interpolation objects
  • string.templatelib module
  • Conditional build with MICROPY_PY_TSTRINGS

Usage:

t = t"Hello {name}!"
str(t)  # "Hello World!"
t.args[1].expression  # "name" 
t.args[1].value  # "World"

Testing

Tested on unix port with both MICROPY_PY_TSTRINGS enabled and disabled.

Test coverage:

  • Basic template string functionality
  • Error handling and edge cases
  • Import system integration
  • Nested expression parsing

All existing tests continue to pass. New tests added in tests/basics/string_template*.py.

Ports tested: Unix (other ports need testing)

Trade-offs and Alternatives

Adds ~240 bytes when enabled on unix port. When disabled, no impact.

Worth it because:

  • Part of Python 3.14 standard
  • Optional feature (MICROPY_PY_TSTRINGS)
  • Useful for template processing

Config: Enabled by default when MICROPY_CONFIG_ROM_LEVEL >= EXTRA_FEATURES.
Needs MICROPY_PY_FSTRINGS=1.

To disable: make CFLAGS_EXTRA=-DMICROPY_PY_TSTRINGS=0

@WebReflection
Copy link
Contributor

for what is worth it, we'd love to have this available at least for the PyScript WASM variant as this unlocks tons of UI related use cases we'd like to deliver to our users.

/cc @dpgeorge @ntoll

@koxudaxi koxudaxi changed the title py: Add PEP 750 template strings support. py: Add PEP 750 template strings support Jun 24, 2025
Copy link

codecov bot commented Jun 25, 2025

Codecov Report

Attention: Patch coverage is 98.26389% with 10 lines in your changes missing coverage. Please review.

Project coverage is 98.43%. Comparing base (df05cae) to head (302e8e7).
Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
py/parse.c 97.48% 4 Missing ⚠️
py/lexer.c 96.90% 3 Missing ⚠️
py/tstring_expr_parser.c 95.16% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #17557      +/-   ##
==========================================
- Coverage   98.44%   98.43%   -0.01%     
==========================================
  Files         171      174       +3     
  Lines       22192    22758     +566     
==========================================
+ Hits        21847    22402     +555     
- Misses        345      356      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

github-actions bot commented Jun 25, 2025

Code size report:

   bare-arm:   +68 +0.120% 
minimal x86:  +117 +0.062% [incl +32(data)]
   unix x64:   +56 +0.007% standard[incl +32(data)]
      stm32:   +12 +0.003% PYBV10
     mimxrt:   +24 +0.006% TEENSY40
        rp2:   +16 +0.002% RPI_PICO_W
       samd:   +48 +0.018% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:   +23 +0.005% VIRT_RV32

@dpgeorge dpgeorge added the py-core Relates to py/ directory in source label Jun 25, 2025
Copy link
Member

@dpgeorge dpgeorge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! This will be nice to have for the webassembly port.

I didn't do a review yet, but there will need to be tests to get full coverage of the new code.

@koxudaxi
Copy link
Author

@dpgeorge Thank you for the feedback and for taking time to look at this! I'm glad this will be useful for the webassembly port.

I'll add more tests to ensure full coverage when I find time.

@ntoll
Copy link
Contributor

ntoll commented Jun 25, 2025

@koxudaxi slightly off topic - but I notice you'll be at EuroPython in Prague, as will I. We should look out for each other and have a coffee or lunch together! 🇪🇺 🐍

@koxudaxi
Copy link
Author

@ntoll Sounds good! See you in Prague. ☕

@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch 10 times, most recently from 7a1dc11 to 50dd4d1 Compare July 2, 2025 15:42
@koxudaxi koxudaxi requested a review from dpgeorge July 2, 2025 15:59
@koxudaxi
Copy link
Author

koxudaxi commented Jul 2, 2025

@dpgeorge

I didn't do a review yet, but there will need to be tests to get full coverage of the new code.

I tried really hard to make 100% coverage but some code is never called and I cannot cover it. Do you know how to fix this?

Comment on lines 153 to +159

A flag for `keys()`, `values()`, `items()` methods to specify that
A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that
scanning should be inclusive of the end key.

.. data:: DESC

A flag for `keys()`, `values()`, `items()` methods to specify that
A flag for :meth:`btree.keys`, :meth:`btree.values`, :meth:`btree.items` methods to specify that
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I faced an error when building with Sphinx because it conflicted with string.templatelib.Template.values. To solve this problem, I renamed the values attribute in btree to be more explicit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like a useful change, then --- it's just not a change related to PEP-750 t-string support, so it needs to be a separate commit and separate PR.
Git cherry-pick and rebase -i are your friends!

@koxudaxi
Copy link
Author

koxudaxi commented Jul 2, 2025

@dpgeorge
I'm not very familiar with the micropython codebase, so please let me know if you notice any issues with how I'm handling memory constraints. I think everything looks good, but I'd appreciate your review.

py/objtype.c Outdated
@@ -542,7 +542,18 @@ const byte mp_binary_op_method_name[MP_BINARY_OP_NUM_RUNTIME] = {
};

static mp_obj_t instance_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
#if MICROPY_PY_TSTRINGS
if (op == MP_BINARY_OP_ADD || op == MP_BINARY_OP_INPLACE_ADD) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole bit can be removed, it's not needed. There is already a check in template_binary_op().

@@ -822,6 +847,356 @@ static mp_obj_t extra_coverage(void) {
MICROPY_STACK_CHECK == 0 || old_stack_limit == new_stack_limit);
}


#if MICROPY_PY_TSTRINGS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised all of this coverage code is needed here, in C.

You should be able to move most (if not all) of this to pure Python tests. Did you have trouble doing that?

py/modtstring.c Outdated
mp_print_t repr_print;
vstr_init_print(&repr_vstr, 16, &repr_print);
mp_obj_print_helper(&repr_print, value, PRINT_REPR);
conv_value = mp_obj_new_str_from_vstr(&repr_vstr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 3 cases for r/s/a all look the same. Can you factor the code to reduce code duplication?

py/modtstring.c Outdated
}
dest[0] = mp_obj_new_tuple(interps->len, values);
} else {
mp_obj_t *values = m_new(mp_obj_t, interps->len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify the code here and not have any temporary memory by the following:

mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(interps->len, NULL));
for (size_t i = 0; i < interps->len; i++) {
    tuple->items[i] = interp->value;
}
dest[0] = MP_OBJ_FROM_PTR(tuple);

py/modtstring.c Outdated
return MP_OBJ_FROM_PTR(result);
}

case MP_BINARY_OP_REVERSE_ADD: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just remove this case, it's not needed.

py/parse.c Outdated
est_bytes += est_seg_cnt * sizeof(mp_parse_node_t) * 2; // Assume some growth
est_bytes += est_interp_cnt * sizeof(mp_parse_node_t) * 2;

if (est_bytes > MICROPY_PY_TSTRING_MAX_BYTES) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test is needed, what's wrong with the tstring growing large?

koxudaxi added 3 commits July 12, 2025 01:26
The fstring_args buffer always contains at least 9 characters
('.format()') for any f-string, making the 3-character access
always safe. The removed check was dead code that could never
be reached.

This is f-string specific code that doesn't affect t-strings.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
The est_bytes calculation and the pre-scan loop that counted segments
and interpolations became unused after t-string size limits were removed.
This fixes the -Wunused-but-set-variable warning in CI builds.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
Change t-strings from being automatically enabled at EXTRA_FEATURES
level to opt-in only. This prevents token number shifts in lexer
that were causing CI test failures for ports that don't need t-strings.

T-strings are now explicitly enabled only for:
- Unix/macOS port
- Windows port
- WebAssembly port

This keeps t-strings available for desktop environments while avoiding
memory overhead and test conflicts in embedded ports.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from e5f68df to 8ecf945 Compare July 12, 2025 01:26
koxudaxi added 3 commits July 12, 2025 01:38
Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
Change t-strings from being automatically enabled at EXTRA_FEATURES
level to opt-in only. This prevents token number shifts in lexer
that were causing CI test failures for ports that don't need t-strings.

T-strings are now explicitly enabled only for:
- Unix/macOS port
- Windows port
- WebAssembly port

This keeps t-strings available for desktop environments while avoiding
memory overhead and test conflicts in embedded ports.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
The token number changed from 16 to 17 due to t-strings being made
opt-in, which shifted the token numbering scheme.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from 20fbafe to 9e6fa7d Compare July 12, 2025 02:29
koxudaxi added 2 commits July 12, 2025 15:06
Change {expr=:fmt} to use conversion=None instead of 's' to match
the updated PEP-750 specification.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
Add tests for t-string debug specifier (=) to verify compliance with
updated PEP-750 specification:
- {expr=} uses conversion='r'
- {expr=:fmt} uses conversion=None
- {expr=!s} preserves explicit conversion

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from 3b55c40 to 966c931 Compare July 12, 2025 16:22
koxudaxi added 2 commits July 12, 2025 17:10
- Make t-strings conditional on ROM_LEVEL for Unix port
- Add skip_tstring check to run-tests.py

Fixes test failures on minimal builds and embedded ports.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
Replace math.pi/e/sqrt(2) with integer values to avoid precision
differences between single and double precision builds. Also show
interpolation count and format specs instead of actual float values
in the output.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch 3 times, most recently from 79f6712 to 51d8c21 Compare July 14, 2025 01:23
T-strings use recursive parsing that requires significant stack space.
In constrained builds (standard/standard_v2), thread tests create
threads with small 2KB stacks which overflow when t-strings are enabled.

This changes MICROPY_PY_TSTRINGS to only be enabled when
MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from 5601a24 to 29245de Compare July 14, 2025 03:18
koxudaxi added 4 commits July 14, 2025 05:48
T-strings were failing in threads because __template__ is not in the
thread's global namespace. Add special handling in mp_load_global to
return the builtin __template__ directly when not found in globals,
similar to how __build_class__ is handled.
This fixes NameError when using t-strings in threads and also resolves
the select_poll_eintr.py test failure.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
This commit implements PEP-750 compliant format specification evaluation
for t-strings. Format specs containing expressions (e.g., {width}) are
now evaluated at template creation time, matching Python 3.14 behavior.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
This commit adds extensive test coverage for escape sequence handling in
t-strings to improve lexer.c coverage

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from adb0c30 to 08697eb Compare July 14, 2025 08:45
koxudaxi added 4 commits July 14, 2025 10:28
When running in a thread context, __template__ was not being found
if it wasn't in the thread's local globals. This adds a fallback
to check the main thread's globals dictionary when __template__ is
not found in the current globals, ensuring t-strings work correctly
in threaded environments.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
__template__ is already registered as a builtin, so special handling
in mp_load_global() is redundant. The select_poll_eintr.py test failure
that this code was trying to fix is actually a pre-existing issue
unrelated to t-strings.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
T-strings use recursive parsing which requires significant stack space.
This causes thread initialization failures in constrained environments
when threads create small stacks (e.g., 8-16KB). The failure manifests
as NameError when accessing variables in thread functions.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
This commit fixes a critical GC vulnerability in modtstring.c
where temporary arrays were allocated but not protected from
 garbage collection. The issue manifested as random thread
  initialization failures in CI environments.

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from 199af33 to 71f63c0 Compare July 14, 2025 22:49
- Test empty and whitespace-only expressions in t-strings
- Test format spec and conversion specifier ordering validation
- Test integer overflow protection in template size calculation
- Test Unicode/octal escape sequences and invalid escape handling
- Test high byte values (0x80-0xFF) and lexer error states
- Test memory allocation failures in expression parser
- Test debug format with invalid conversion syntax
- Add coverage for all error branches in t-string implementation

Signed-off-by: Koudai Aono <koxudaxi@gmail.com>
@koxudaxi koxudaxi force-pushed the feature/pep750-template-strings branch from 71f63c0 to e0a7b89 Compare July 15, 2025 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
py-core Relates to py/ directory in source
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy