argotorg / argotorg/solidity

Support operator overloading by operand types for user-defined value types

Open
#16,098 0 comments 1 reaction 0 assignees View on GitHub
feature
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
2d 19h
Merged PRs (30d)
29

Description

## Abstract

Enable operator overloading based on operand types for user-defined value types to achieve zero-cost type-safe abstractions in Solidity.

## Motivation

Solidity's user-defined value types provide excellent zero-cost abstractions - they compile to the underlying primitive type with no runtime overhead while providing compile-time type safety. However, the current limitation that prevents mapping multiple functions with different signatures to the same operator significantly reduces their utility for creating domain-specific types.

## Current Behavior

Currently, attempting to define multiple operations for the same operator results in a compilation error:

```solidity
type Price is uint256;

using {
sub as -, // Price - Price = uint256
subU256 as - // ERROR: Duplicate operator definition
} for Price global;

function sub(Price a, Price b) pure returns (uint256) {
return Price.unwrap(a) - Price.unwrap(b);
}

function subU256(Price a, uint256 b) pure returns (Price) {
return Price.wrap(Price.unwrap(a) - b);
}
```

## Use Case

When modeling domain-specific types like `Price`, `Percentage`, or `Amount`, different operations with the same operator have different semantic meanings:

- `Price - Price → uint256`: Calculate price difference (not a Price!)
- `Price - uint256 → Price`: Adjust price downward
- `Price + uint256 → Price`: Adjust price upward

Without operator overloading by operand types, developers must choose between:

1. **Type safety with poor ergonomics**: `price1.sub(price2)` and `price1.subAmount(100)`
2. **Good ergonomics without type safety**: Using raw `uint256` everywhere
3. **Inconsistent APIs**: Mix of operators and method calls

## Proposed Solution

Allow the `using` directive to map multiple functions to the same operator, with the compiler selecting the appropriate function based on operand types:

```solidity
type Price is uint256;

using {
sub as -, // Price - Price = uint256
subU256 as -, // Price - uint256 = Price
addU256 as + // Price + uint256 = Price
} for Price global;

// Usage would be type-safe and intuitive:
Price p1 = Price.wrap(100e18);
Price p2 = Price.wrap(80e18);
uint256 diff = p1 - p2; // Calls sub(), returns uint256
Price p3 = p1 - 10e18; // Calls subU256(), returns Price
```

## Benefits

1. **Zero-cost abstraction**: No runtime overhead compared to raw uint256
2. **Type safety**: Prevents semantic errors at compile time
3. **Intuitive APIs**: Natural mathematical notation for domain types
4. **Code clarity**: Self-documenting operations through types

## Backwards Compatibility

This change would be backwards compatible as it only extends the existing functionality without breaking current behavior.

## References

Similar operator overloading based on operand types exists in many languages (C++, Rust, Swift) and has proven valuable for creating type-safe domain-specific abstractions.

Contributor guide

Open the contributing guide

Research direction

The issue names no files, tests, or entry points. Start by locating the compiler's handling of user-defined value types and operator resolution, then determine how overload selection should use operand types; done means the examples compile with the stated return types while existing operator mappings continue to work.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, solidity
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.