vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.81k stars 788 forks source link

Ternary operator in conditions does not compile #4075

Open ritzdorf opened 1 month ago

ritzdorf commented 1 month ago

Version Information

Issue description

When a ternary operator is used inside conditions, e.g. if or assert compilation fails.

Typically the error looks something like:

vyper.exceptions.CompilerPanic: missing symbols: {'self.foo()4'}

PoC

Minimal with assert:

@internal
def foo() -> uint256:
    return 42

@external
def bar():
    assert 43 == (self.foo() if True else self.foo()), "test"

Minimal with if:

@internal
def foo() -> uint256:
    return 42

@external
def bar():
    if 43 == (self.foo() if True else self.foo()):
        pass

More extensive:

#@version 0.4.0.rc5

@internal
def foo() -> uint256:
    return 42

@internal
def baz() -> bool:
    return False

@external
def bar():
    b: bool = False
    # These compile
    assert 43 == (self.foo() if self.baz() else self.foo()), "test"
    u: uint256 = self.foo() if empty(bool) else self.foo()

    # These do not compile
    #assert 43 == (self.foo() if empty(bool) else self.foo()), "test"
    #assert (self.foo() if empty(bool) else self.foo()) == 43, "test"
    #assert (self.foo() if empty(bool) else self.foo()) == (self.foo() if empty(bool) else self.foo()), "test"

Credit: @pcaversaccio and @trocher

cyberthirst commented 1 month ago

whe i run this example without optimizations --no-optimize, the revert doesn't happen

@internal
def foo() -> uint256:
    return 42

@external
def bar():
    assert 43 == (self.foo() if True else self.foo()), "test"
trocher commented 4 weeks ago

whe i run this example without optimizations --no-optimize, the revert doesn't happen

@internal
def foo() -> uint256:
    return 42

@external
def bar():
    assert 43 == (self.foo() if True else self.foo()), "test"

I'd expect such behaviour as I think the issue is due to the optimizer removing the if or else expression with the dead branch eliminator but the symbol check is not being deactivated since arithmetic operations optimizations have should_check_symbols set (and in those case the if/else is in an arithmetic op)

https://github.com/vyperlang/vyper/blob/24cfe0bcc10ec9418054c8def5cf4eeaa3ed0164/vyper/ir/optimizer.py#L448-L450

https://github.com/vyperlang/vyper/blob/24cfe0bcc10ec9418054c8def5cf4eeaa3ed0164/vyper/ir/optimizer.py#L498