c3lang / c3c

Compiler for the C3 language
GNU Lesser General Public License v3.0
2.57k stars 148 forks source link

Error using $defined for missing functions and struct methods #1291

Open Dodzey opened 1 month ago

Dodzey commented 1 month ago

Consider the following example:

module output;

struct Foo { int a; }
fn void Foo.fun(&self) => {};

fn void bar(void*) => {};

Foo foo;

$echo "Functions:";
$echo $defined(bar(null)); // true
$echo $defined(bar()); // false - not callable without void*

$echo "---";
$echo "Foo type:";
$echo $defined(Foo.fun(null)); // true
$echo $defined(Foo.fun()); // false - not callable without self ptr

$echo "---";
$echo "Foo instance:";
$echo $defined(foo.fun(null)); // false
$echo $defined(foo.fun()); // true - implicit self

$echo "---";
$echo "Foo temporary:";
$echo $defined(Foo{}.fun(null)); // false
$echo $defined(Foo{}.fun()); // true - implicit self

$echo "---";
$echo "Missing function:";
$echo $defined(missing()); // echoes false but issues error
$echo $defined(foo.missing()); // echoes false but issues error
$echo $defined(Foo.missing(null)); // echoes false but issues error
$echo $defined(Foo{}.missing()); // echoes false but issues error

This appears to $echo false for the missing function examples, but also issues an error for all three. I would expect to be able to cleanly get the compile time result of false so I could do conditional stuff in a macro around the existing of the method or not. Is that not how this functionality is supposed to work?

] Functions:
] true
] false
] ---
] Foo type:
] true
] false
] ---
] Foo instance:
] false
] true
] ---
] Foo temporary:
] false
] true
] ---
] Missing function:
] false
] false
] false
] false
28: 
29: $echo "---";
30: $echo "Missing function:";
31: $echo $defined(missing()); // echoes false but issues error
                   ^^^^^^^
(<source>:31:16) Error: 'missing' could not be found, did you spell it right?

 29: $echo "---";
 30: $echo "Missing function:";
 31: $echo $defined(missing()); // echoes false but issues error
 32: $echo $defined(foo.missing()); // echoes false but issues error
                    ^^^^^^^^^^^
(<source>:32:16) Error: There is no field or method 'Foo.missing'.

 30: $echo "Missing function:";
 31: $echo $defined(missing()); // echoes false but issues error
 32: $echo $defined(foo.missing()); // echoes false but issues error
 33: $echo $defined(Foo.missing(null)); // echoes false but issues error
                    ^^^^^^^^^^^
(<source>:33:16) Error: No method or inner struct/union 'Foo.missing' found.

 31: $echo $defined(missing()); // echoes false but issues error
 32: $echo $defined(foo.missing()); // echoes false but issues error
 33: $echo $defined(Foo.missing(null)); // echoes false but issues error
 34: $echo $defined(Foo{}.missing()); // echoes false but issues error
                    ^^^^^^^^^^^^^
(<source>:34:16) Error: There is no field or method 'Foo.missing'.

Compiler returned: 1
lerno commented 1 month ago

This is known. There is a question on how far to test vs issue an error. I'll have a look at these and check if we should extend what is allowed to be wrong.

Dodzey commented 1 month ago

Okay. Good to know it is known. I've had another look and as you have previously suggested to me, but I misunderstood, it looks like:

$echo $defined(Foo.missing); // false
$echo $defined(Foo.fun);  // true

should work for now. I will test it in my specific scenario.

However it would be really great to be able to check a specific signature to check the validity of @dynamic functions where an interface is not used.

Thanks for the info!

lerno commented 1 month ago

How do you mean that you want to check the dynamic functions?

Dodzey commented 1 month ago

I have tried a few different things, but I was hoping that something along the lines of:


// Some struct that implements an interface I can check for explicitly...
// This is ok...
interface IBar
{
  fn void bar(int, double);
  fn void baz(int*);
}
struct Foo (IBar)
{ 
  int i;
  double d;
}
fn void Foo.bar(&self, int i_, double d_) @dynamic
{
  self.i = i_;
  self.d = d_;
}
fn void Foo.baz(&self, int* ip_) @dynamic
{
  self.i = ip_ != null ? *ip_ : self.i;
}

// Another struct that has bar with the correct signature, but doesn't have baz at all.
struct FooNoInterface
{
  int i;
  double d;
}
// Bar - but doesn't match expected signature
// I can check this case ok
fn void FooNoInterface.bar(&self, int i_)
{
  self.i = i_;
}

No FooNoInterface.baz - I can't check this without getting a compiler error. I tried....

$echo $defined(FooNoInterface.baz) && $defined(FooNoInterface.baz(int*{}));
$echo $defined(FooNoInterface.baz) ? $defined(FooNoInterface.baz(int*{})) : false;

Neither of which work (allowed to short circuit without compiler error)

 92: $echo $defined(FooNoInterface.baz) ? $defined(FooNoInterface.baz(int*{})) : false;
                                                   ^^^^^^^^^^^^^^^^^^
(<source>:92:47) Error: No method or inner struct/union 'FooNoInterface.baz' found.

I hope that explanation makes sense? Many thanks for the help!

Dodzey commented 1 month ago

Ahhh.... nested $if appears to work without compilation error:

$if $defined(FooNoInterface.baz):
    $if $defined(FooNoInterface{}.baz(int*{})):

    $endif
$endif

I might be able to work with that - I'll try to stick it in a macro and do it for $Type and #signature

lerno commented 1 month ago

The $and and $or compile time functions are explicitly for this, since && will evaluate both sides.

lerno commented 1 month ago

$echo $and($defined(FooNoInterface.baz), $defined(FooNoInterface.baz(int*{})))

lerno commented 1 month ago

BTW you might want to rethink this though, if you're already doing it at compile time, there is no need for having an interface. Maybe you're not quite clear on how they work, or maybe you're looking for a simpler feature.

lerno commented 1 month ago

For types implementing an interface ad hoc, the expectation is that such an interface is tested at runtime.

Dodzey commented 1 month ago

Okay, I think (based on what I've picked up of C3 so far) what I want to do is best served at compile time - not sure I have a need for the runtime reflection yet.

I have managed to get something that works, I just need to rework it for the specific use case...

// Match signature without checking return type
macro bool @hasfunctionsig(#s, #fs)
{
    return $and(
        $defined(#s),
        $defined(#fs)
    );
}
$assert( @hasfunctionsig(FooNoInterface.bar, FooNoInterface{}.bar(int{})) == true );
$assert( @hasfunctionsig(FooNoInterface.bar, FooNoInterface{}.bar(double{})) == false );

The first place I need to use this in a macro where I'm checking that the function exists and takes no arguments. I have a macro like this:

// @type_has_function_taking_no_args( Foo, bar )
@type_has_function_taking_no_args($Type, #func_name)
{
  return @hasfunctionsig( ......, ..... ) // Not sure what I should be using here...
}

But I'm not sure the best way to transform $Type and #func_name to what I need to call @hasfunctionsig(#s, #fs)

lerno commented 2 weeks ago

This should work in most cases:

macro bool @type_has_function_taking_no_args($Type, #func_name)
{
  return @hasfunctionsig($Type.$eval(#func_name), $Type{}.$eval(#func_name)());
}