Skip to content

fix: std.format accepts boolean for numeric conversion codes#233

Closed
He-Pin wants to merge 1 commit into
deltarocks:masterfrom
He-Pin:fix-format-boolean-coercion
Closed

fix: std.format accepts boolean for numeric conversion codes#233
He-Pin wants to merge 1 commit into
deltarocks:masterfrom
He-Pin:fix-format-boolean-coercion

Conversation

@He-Pin

@He-Pin He-Pin commented Jun 26, 2026

Copy link
Copy Markdown

Summary

The official Jsonnet spec states:

The string formatting follows the same rules as Python.

In Python, bool is a subclass of int, so "%d" % True returns "1". However, jrsonnet currently rejects booleans for all numeric conversion codes with type error: expected number, got boolean.

This fix coerces Val::Bool(true) to Val::Num(1.0) and Val::Bool(false) to Val::Num(0.0) before numeric conversion in format_code(), matching Python's behavior.

The %s conversion is intentionally left unchanged, as Python's "%s" % True returns "True" (not "1").

Related upstream PRs: google/jsonnet#1328, google/go-jsonnet#884

Before / After

Expression Python jrsonnet (before) jrsonnet (after)
"%d" % true "1" ERROR "1"
"%f" % false "0.000000" ERROR "0.000000"
"%x" % true "1" ERROR "1"
"%o" % true "1" ERROR "1"
"%e" % true "1.000000e+00" ERROR "1.000000e+00"
"%g" % true "1" ERROR "1"
"%c" % true "\x01" ERROR "\x01"
"%s" % true "True" "true" "true" (unchanged)

Test plan

  • Existing format tests continue to pass (cargo test -p jrsonnet-evaluator -- 7/7 passed)
  • Boolean coercion matches Python's bool -> int behavior for all numeric codes (%d, %o, %x, %e, %f, %g)
  • Boolean coercion applied for %c (character code) matching Python
  • %s conversion unaffected -- booleans still render as "true"/"false"

The official Jsonnet spec states that std.format follows Python's
string formatting rules. In Python, bool is a subclass of int, so
"%d" % True returns "1". This fix coerces boolean values to 0.0/1.0
before processing numeric conversion codes, matching Python's behavior.
@CertainLach

Copy link
Copy Markdown
Member

The string formatting follows the same rules as Python.

In Python, bool is a subclass of int, so "%d" % True returns "1". However, jrsonnet currently rejects booleans for all numeric conversion codes with type error: expected number, got boolean.

But this is not a formatting rule, this is a type coercion rule

Formatting itself has nothing to do with it if it just expects input value to be an instance of int; the same rule is currently followed in jrsonnet/cpp-jsonnet/go-jsonnet with the exception that bool is not a subclass of int

@He-Pin

He-Pin commented Jun 27, 2026

Copy link
Copy Markdown
Author

in databricks/sjsonnet#1053 stephenamar-db @stephenamar-db revert to this kind of behavior and include the std.format description. So, for the time being, sjsonent has reverted to its previous behavior here, making it different from all other implementations. What, then, is the correct behavior for this case? @johnbartholomew @sparkprime cc

@stephenamar-db

stephenamar-db commented Jun 27, 2026

Copy link
Copy Markdown

as I said in my revert, the documentation states that std.format follows the same rules as Python2, which implicitly converts booleans to 1/0.

@CertainLach

CertainLach commented Jun 27, 2026

Copy link
Copy Markdown
Member

as I said in my revert, the documentation states that std.format follows the same rules as Python2, which implicitly converts booleans to 1/0.

But it doesn't. %f accepts numbers, and bool is instanceof number in python (but not in jsonnet).

No conversion happens on the formatting side, this just happens because of the unfortunate inheritance chain in the python language, and it is fine, as in jsonnet booleans are not accepted where the code expects numbers.

@stephenamar-db

Copy link
Copy Markdown

You are right, it's not a conversion. My point still stands, if the documentation says that it follows the rules of python2 format, this includes treating booleans as integers.

@CertainLach

Copy link
Copy Markdown
Member

this includes treating booleans as integers.

There is no such rule in python format

Why borrow footguns and unexpected non-type-safe behavior from python, when this behavior can be easily replicated if really wanted?

@CertainLach

CertainLach commented Jun 27, 2026

Copy link
Copy Markdown
Member

sjsonnet stuck in some weird state, because if folow the "rules" the way you propose, then this should be valid too:

# Boolean is accepted as precision in python because bool extends int
"%.*f" % (False, 3.14159) == '3'
"%.*f" % (True, 3.14159) == '3.1'

But it isn't the case for sjsonnet

"%.*f" % [false, 3.14159]
sjsonnet.Error: [std.format] A * was specified at position 0. An integer is expected for a precision
    at [<root>].(<exec>:1:8)

And I don't think it should, as this is dumb

@He-Pin

He-Pin commented Jun 27, 2026

Copy link
Copy Markdown
Author

I think it would be best to standardize and refine the spec and documentation; there are indeed quite a few details that need clarification.

@stephenamar-db

Copy link
Copy Markdown

fair point. Right now it's just back to the previous state. Maybe we'll rollforward behind a flag.

@CertainLach

Copy link
Copy Markdown
Member

Closing because of the reasons I already explained.

@He-Pin

He-Pin commented Jun 28, 2026

Copy link
Copy Markdown
Author

thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants