-
Notifications
You must be signed in to change notification settings - Fork 435
Open
Labels
A-spec-specsArea: Specification—The Ethereum specification itself (eg. `src/ethereum/*`)Area: Specification—The Ethereum specification itself (eg. `src/ethereum/*`)C-choreCategory: choreCategory: choreE-hardExperience: difficult, probably not for the faint of heartExperience: difficult, probably not for the faint of heart
Description
Background
Exceptions are somewhat difficult to reason about. They are non-local control flow, and it isn't obvious looking at a function what exception types it can throw. They also make line-based code coverage tools less useful: you can't see if a particular error is triggered in a particular call site because there is no line representing the error case. For example:
def do_some_work(input):
if input:
raise Exception()
def opcode_foo():
input = pop()
do_some_work()
def opcode_bar():
input = pop()
do_some_work()In the above, you can't tell if you've tested the error case in both opcode_foo and opcode_bar, only that at least one opcode has hit do_some_work's error condition. Contrast that with:
def do_some_work(input):
if input:
return Err()
return Ok()
def opcode_foo():
input = pop()
result = do_some_work(input)
if isinstance(result, Err):
return result
def opcode_bar():
input = pop()
result = do_some_work(input)
if isinstance(result, Err):
return resultIn this example, line-based code coverage can reveal whether the error case is tested for both opcodes.
Questions
- How practical is Go-style error handling in Python?
- Does it impact performance?
- How messy does it make our code?
- What insights do we get from the new line coverage information? Are we missing tests?
Implementation Notes
Here's one way to appease / make use of the typechecker:
O = TypeVar("O")
E = TypeVar("E")
@dataclass(frozen=True, slots=True)
class Ok(Generic[O]):
value: O
ok: ClassVar[Literal[True]] = True
err: ClassVar[Literal[False]] = False
@dataclass(frozen=True, slots=True)
class Err(Generic[E]):
error: E
ok: ClassVar[Literal[False]] = False
err: ClassVar[Literal[True]] = True
class ApplyBodyError:
...
def apply_body(
block_env: BlockEnvironment,
transactions: Tuple[Transaction, ...],
ommers: Tuple[Header, ...],
) -> Ok[BlockOutput] | Err[ApplyBodyError]:
return Ok(BlockOutput())
class FooError:
...
def foo() -> Ok[BlockOutput] | Err[ApplyBodyError] | Err[FooError]:
result = apply_body(
BlockEnvironment(), (Transaction(),), (Header(),)
)
if result.err:
return result
reveal_type(result.value) # Correctly narrowed to `BlockOutput`
return resultReactions are currently unavailable
Metadata
Metadata
Assignees
Labels
A-spec-specsArea: Specification—The Ethereum specification itself (eg. `src/ethereum/*`)Area: Specification—The Ethereum specification itself (eg. `src/ethereum/*`)C-choreCategory: choreCategory: choreE-hardExperience: difficult, probably not for the faint of heartExperience: difficult, probably not for the faint of heart