c3c icon indicating copy to clipboard operation
c3c copied to clipboard

Macros with left-to-right dependent types

Open lerno opened this issue 6 months ago • 3 comments

This idea is to allow macros to use the already used parameters to infer the type of the next parameters:

// Today
macro add(a, b) => a + b;

The idea would be to allow using the types of arguments to infer the type of other parameters. For example:

macro $typeof(a) add(a, $typeof(a) b)
{
  return a + b;
}

It could also be expressed in a more complex way like this:

macro $Ty add($Ty a, $Ty b)
{
   return a + b;
}

Where we'd do a more conventional capture.

But in this case we might perhaps do some more clear inference:

macro $Ty add([$Ty] = a, $Ty b)
{
   return a + b;
}

lerno avatar Jun 01 '25 23:06 lerno

Thinking a bit more on this: the main usefulness is not creating type constraints but rather to allow type inference and implicit conversion based on the first element. And for that reason $typeof is enough.

lerno avatar Jun 02 '25 08:06 lerno

$typeof(a) is a dead end.

lerno avatar Jun 06 '25 19:06 lerno

being able to access previous arguments in a macro could also be useful for automatically setting defaults based on previous params that can be overwritten manually. this is a (slightly simplified) usecase I came up with when thinking about an internal rework of my emulator:

enum Width
{
  W8,
  W16,
  W32,
}

macro Width @width_of(#val) @const
{
  $switch $typeof(#val):
  $case char:
    return W8;
  ...
  $endswitch
}

macro void uint.set(&self, val, Width $tgt_width = @width_of(val))
{
  $switch $tgt_width:
  $case W8:
    *self = (*self & ~0x000000ff) | (uint)data;
  ...
  $endswitch
}

uint x = ...;
char c = ...;
x.set(c); // sets lowest byte of x to c
x.set(c, W16); // sets lowest byte of x to c & zeroes second lowest byte
uint y = ...;
x.set(y, W16); // sets lowest 2 bytes of x to lowest 2 bytes of y

ofc this can already be done with EMPTY_MACRO_SLOT, and can have type inference using a #expr parameter but it is a little less convenient & more confusing to work with:

// this should be equivalent to the above one
macro void uint.set(&self, val, #tgt_width = EMPTY_MACRO_SLOT) @safemacro
{
  Width $tgt_width;
  $switch:
  $case $defined(Size.$eval($stringify(#tgt_width))):
  $case @typeis(#tgt_width, Width):
    $tgt_width = #tgt_width;
  $case @is_empty_macro_slot(#width):
    $tgt_width = @width_of(val);
  $default:
    $error "Invalid type for #tgt_width: " +++ $typeof(#tgt_width).nameof;
  $endswitch

  $switch $tgt_width:
  $case W8:
    *self = (*self & ~0x000000ff) | (uint)data;
  ...
  $endswitch
}

I don't feel particularly strongly about this as there are other ways to do the same thing, but maybe it could be useful in other places as well.

Book-reader avatar Jun 15 '25 04:06 Book-reader

It seems to be too complex in the current model. Will close this and revisit it later.

lerno avatar Oct 10 '25 19:10 lerno