Skip to content

feat(python): Add support for ** (power) operator#690

Merged
MikaelMayer merged 31 commits intomainfrom
feat/python-pow-operator
Mar 31, 2026
Merged

feat(python): Add support for ** (power) operator#690
MikaelMayer merged 31 commits intomainfrom
feat/python-pow-operator

Conversation

@MikaelMayer
Copy link
Copy Markdown
Contributor

The Python-to-Laurel pipeline rejected programs using the ** operator with "Binary operator not yet supported: Pow", causing an exit-4 crash.

This PR adds support by:

  • Mapping Pow to the existing PPow prelude function in PythonToLaurel.lean
  • Constant-folding ** with integer literal operands at translation time (e.g. 1024 ** 3 becomes 1073741824)
  • For non-constant operands, PPow delegates to an uninterpreted int_pow function, handling int×int, bool×int, and int×bool combinations
  • Replacing the PPow stub (which threw UnimplementedError) with the real implementation

Tested with a new test_power.py test that verifies 8 ** 2 == 64 and 2 ** 10 == 1024 through the full pipeline. Existing tests pass.

The Python-to-Laurel pipeline rejected programs using the ** operator
with 'Binary operator not yet supported: Pow'.

Add support by:
- Mapping Pow to the PPow prelude function in PythonToLaurel
- Constant-folding ** with integer literal operands at translation time
- For non-constant operands, PPow delegates to an uninterpreted int_pow
  function, handling int*int, bool*int, and int*bool combinations
- Replacing the PPow stub with a real implementation

Add test_power.py verifying 8**2==64 and 2**10==1024 through the full
pipeline.
@MikaelMayer MikaelMayer marked this pull request as ready for review March 27, 2026 16:57
@MikaelMayer MikaelMayer requested a review from a team March 27, 2026 16:57
joehendrix
joehendrix previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Contributor

@joehendrix joehendrix left a comment

Choose a reason for hiding this comment

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

I added a question above, but we could postpone that for a later commit.

@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Re: moving constant folding to concreteEval — investigated this. The issue is that int_pow must be declared in the prelude (so PPow can reference it), but adding a factory function with the same name causes a "function already exists" error during evaluation (addFactoryFunc rejects duplicates). For now, constant folding stays in PythonToLaurel for literal operands. Moving it to concreteEval would require allowing factory functions to override/merge with prelude declarations.

Moving concreteEval to PyFactory causes duplicate-name errors since
int_pow must be declared in the prelude for PPow to reference it, and
addFactoryFunc rejects duplicates. Revert PyFactory changes and restore
constant folding in PythonToLaurel for literal operands.
@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Re: moving constant folding to concreteEval — investigated this. The issue is that int_pow must be declared in the prelude (so PPow can reference it), but adding a factory function with the same name causes a "function already exists" error during evaluation (addFactoryFunc rejects duplicates). For now, constant folding stays in PythonToLaurel for literal operands. Moving it to concreteEval would require allowing factory functions to override/merge with prelude declarations.

We did that for regular expressions, please check the PR that introduced them. Regular expressions are core functions that were exposed in Laurel, we should do the same.

External Laurel functions (bodyless declarations) are still translated
to Core, unlike re_fullmatch_str which is filtered by the Laurel-to-Core
translator. Adding int_pow to the factory causes duplicate-name errors.
Constant folding for literal Pow operands stays in PythonToLaurel.
@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Investigated the regex pattern. The difference is that re_fullmatch_str (returning regex type) is filtered out by the Laurel-to-Core translator, so it never reaches Core's addFactoryFunc. But int_pow (returning int) IS translated to Core as a bodyless function, causing a duplicate when the factory also provides it.

To use concreteEval for int_pow, we'd need to either:

  1. Filter int_pow in the Laurel-to-Core translator (like regex functions)
  2. Allow addFactoryFunc to merge/override bodyless declarations

Both are larger changes. For now, constant folding for literal ** operands is done in PythonToLaurel. Non-constant cases use the uninterpreted int_pow from the prelude.

@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Investigated the regex pattern. The difference is that re_fullmatch_str (returning regex type) is filtered out by the Laurel-to-Core translator, so it never reaches Core's addFactoryFunc. But int_pow (returning int) IS translated to Core as a bodyless function, causing a duplicate when the factory also provides it.

To use concreteEval for int_pow, we'd need to either:

  1. Filter int_pow in the Laurel-to-Core translator (like regex functions)
  2. Allow addFactoryFunc to merge/override bodyless declarations

Both are larger changes. For now, constant folding for literal ** operands is done in PythonToLaurel. Non-constant cases use the uninterpreted int_pow from the prelude.

What do you mean re_fullmatch_str is filtered out by the Laurel to Core translator? There is an equivalent in Core's factory and I'm sure that the Laurel gets mapped to the Core equivalent, it was done recently in a PR that even has some tests to prove it (including PR 615 that proves that regex support works)
IF it's a matter of having different names you can do this.

The problem of doing constant folding in PythonToLaurel is that it's outside of Core. It's better if all the transformations remain in Core, including constant folding that can be done during partial evaluation of Core.
So figure out a way. We shouldn't need to merge or override bodiless declarations.

Move constant folding for ** from PythonToLaurel to a factory function
(intPowFunc) in PyFactory.lean with concreteEval. This follows the same
pattern as regex factory functions.

To avoid duplicate-name errors when the prelude declares int_pow and the
factory also provides it, ProgramEval now skips bodyless functions that
already exist in the factory (the factory version has concreteEval).
@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Moved constant folding to concreteEval in PyFactory.lean (intPowFunc). To resolve the duplicate-name issue, ProgramEval now skips bodyless functions that already exist in the factory — the factory version with concreteEval takes precedence. This follows the regex factory pattern.

…aurel

The regex pattern (factory concreteEval) works because re_fullmatch_str
uses the regex type which doesn't exist in Core, so the function is
naturally filtered during Laurel-to-Core translation. int_pow uses int
which translates fine to Core, causing duplicate-name conflicts with the
factory.

Constant folding for literal ** operands stays in PythonToLaurel.
Non-constant ** produces PPow which throws UnimplementedError (same as
before this PR, but now the pipeline doesn't crash on Pow).
@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Reverted the ProgramEval hack. Investigated why regex works: re_fullmatch_str uses the regex type which doesn't exist in Core, so the Laurel-to-Core translator naturally filters it out (fails in handleUnaryOps). The factory then provides it without conflict.

int_pow uses int which translates fine to Core, causing the duplicate. Constant folding for literal ** operands stays in PythonToLaurel. To move it to concreteEval, we'd need a mechanism to prevent specific prelude functions from being translated to Core.

Forward-declare int_pow before CoreOnlyDelimiter (like re_fullmatch_str)
so the DDM parser resolves it but it doesn't enter the Core program.
The factory provides int_pow with concreteEval for constant folding.
Remove constant folding from PythonToLaurel and ProgramEval hack.
@MikaelMayer
Copy link
Copy Markdown
Contributor Author

Moved int_pow to factory concreteEval following the regex pattern. Forward-declared before CoreOnlyDelimiter (like re_fullmatch_str) so it doesn't enter the Core program. Factory provides it with concreteEval. Removed constant folding from PythonToLaurel and the ProgramEval hack.

@MikaelMayer MikaelMayer marked this pull request as draft March 30, 2026 18:16
When exp < 0, Python returns a float (e.g. 2**-3 = 0.125). PPow now
returns from_float(float_pow(...)) for negative exponents. float_pow is
an uninterpreted function (forward-declared before CoreOnlyDelimiter,
provided by factory). Bool exponents don't need the guard since
bool_to_int returns 0 or 1.
@MikaelMayer MikaelMayer marked this pull request as ready for review March 30, 2026 18:37
joscoh
joscoh previously approved these changes Mar 30, 2026
joehendrix
joehendrix previously approved these changes Mar 30, 2026
Copy link
Copy Markdown
Contributor

@joehendrix joehendrix left a comment

Choose a reason for hiding this comment

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

I added a couple comments, but don't consider them blocking.

Per review feedback: float operands and operator overloading are not yet
supported, so UnimplementedError is more accurate than UndefinedError.
@MikaelMayer MikaelMayer dismissed stale reviews from joehendrix and joscoh via 71a403c March 30, 2026 19:13
Cover True**5 and False**3 per review feedback. Negative exponents and
float operands are handled but can't be asserted on in tests (results
are uninterpreted or exceptions).
Main moved PPow/PMod to Laurel prelude. Declare int_pow/float_pow with
explicit 'external;' syntax so computeSccDecls filters them during
Laurel-to-Core translation, avoiding duplicate-name conflicts with the
factory.
Adding int_pow/float_pow to the Laurel prelude shifted byte offsets.
2 ** -1 returns from_float(float_pow(...)) which is uninterpreted, so
the assertion r > 0 fails (cannot be proved). This exercises the
negative exponent path and documents the expected behavior.
@MikaelMayer MikaelMayer enabled auto-merge March 31, 2026 16:10
@MikaelMayer MikaelMayer added this pull request to the merge queue Mar 31, 2026
Merged via the queue into main with commit 720f0b3 Mar 31, 2026
15 checks passed
@MikaelMayer MikaelMayer deleted the feat/python-pow-operator branch March 31, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants