Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: testing @compileError #513

Open
PavelVozenilek opened this issue Oct 1, 2017 · 5 comments
Open

Proposal: testing @compileError #513

PavelVozenilek opened this issue Oct 1, 2017 · 5 comments
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@PavelVozenilek
Copy link

PavelVozenilek commented Oct 1, 2017

Minor feature to test edge cases.

Zig allows custom made compiler errors. Example is here:
http://andrewkelley.me/post/zig-programming-language-blurs-line-compile-time-run-time.html

There should be some easy way to check if they are implemented correctly.


My proposal:

test "expected to fail"
{
  something_generating_error();  /// DOES NOT COMPILE
}

If compiler reaches @compileError inside a test it should then look for comment DOES NOT COMPILE at the offending line. If it finds it there, all is OK, testing continues. If it doesn't, original error is reported, it is a bug to be fixed.

If the test compiles OK but contains DOES NOT COMPILE comment, then it should fail.


Alternative is special keyword for failing test, but this feels as overkill.

Feature should be limited to user written @compileError only: syntactic errors (like unbalanced parenthesis) should be always error, wrong use of the language (x = 1 + "abc") as well.

DOES NOT COMPILE comment outside a test means nothing.

Expected failed test would generate no code.

Failing tests could be certainly implemented as separately compiled files, but this is nuisance most people would avoid.

@thejoshwolfe
Copy link
Sponsor Contributor

@compileError() takes a msg parameter, so testing for such an error should also take a msg that must match. How about:

test "fork" {
    switch(builtin.os) {
        Os.linux, Os.darwin, Os.macosx, Os.ios => testFork(),
        else => @expextCompileError("Unsupported OS", testFork()),
    };
}

@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. accepted This proposal is planned. labels Oct 1, 2017
@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Oct 1, 2017
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Feb 28, 2018
@PavelVozenilek
Copy link
Author

One small improvement: compiler should also remove the line annotated as failing, and then try to compile (but not run) the remaining code. This would eliminate mistakes that would otherwise go unnoticed.

So this:

test "expected to fail"
{
  something_generating_error();  /// DOES NOT COMPILE
}

would be internally transformed into two tests:

test "expected to fail"
{
  xyz
  something_generating_error();  /// DOES NOT COMPILE
}

test "expected to compile, but not executed"
{
  xyz
}

@marler8997
Copy link
Contributor

marler8997 commented Aug 31, 2019

Not knowing this proposal existed, I had made a duplicate of this proposal here: #3144

Along with testing compile errors, it's important to note that this feature would also enable much more powerful generic specialization. With this builtin function, generics can now test types for any feature that does or does not compile. I've included some contrived examples to demonstrate some of the capabilities this enables.

Note that I'm not trying to say whether these examples are good or bad or even whether supporting them is good or bad. I'm just pointing out what this new builtin function would enable. I think testing for compile failures is probably the "lesser" factor to consider.

Silly Example

// find some way to call f with 1234
pub fn callWith1234(f: var) void {
    if (!@isCompileError(f(0))) {
        f(1234);
    } else if (!@isCompileError(f(""))) {
        f("1234");
    } else @compileError("f does not take integers or strings");
}

Another Example

pub fn supportsPlus(comptime T: type) bool {
    return !@compileError({ T a; _ = a + a;});
}
pub fn hasAddFieldForItself(comptime T: type) bool {
    return !@compileError({ T a; _ = a.add(a);});
}
// find some way to double the given foo value
pub fn double(foo: var) @typeOf(foo) {
    if (!@compileError({@typeOf(foo) f; _ = f * 2; })
        return foo * 2;
    if (supportsPlus(@typeOf(foo)))
        return foo + foo;
    else if (hasAddFieldForItself(@typeOf(foo)))
        return foo.add(foo);
    else @compileError("don't know how to add foo to itself");
}

More Realistic Example

pub fn isIterable(comptime T: type) bool {
    return !@isCompileError({ T it; while(it.next()) |e| { } });
}

// find some way to get the element at the given index
pub fn getElementAt(x: var, index: usize) {
    if (!@isCompileError(x[index])) {
        return x[index];
    } else if (isIterable(@typeOf(x))) {
        var i : usize = 0;
        while (x.next()) |e| {
            if (i == index)
                return e;
            i += 1;
        }
        return error.IndexOutOfBounds;
    } else @compileError("getElementAt does not support " ++ @typeName(@typeOf(x)));
}

pub fn supportsNull(comptime T: type) bool {
    return !isCompileError({var t: T = null;});
}

// return a variant of the given type T that can be assigned a null value
pub fn NullableType(comptime T: type) type {
    return if (supportsNull(T)) T else ?T;
}

// find some way to return the last element of x
pub fn last(x: var) var {
    if (!@compileError({var : usize = x.len;})) {
        return if (x.len == 0) ? null : getElementAt(x, x.len - 1);
    } else if (isIterable(@typeOf(x)) {
        var lastE : NullableType(@typeOf(x.next())) = null;
        while (x.next()) |e| {
            lastE = e;
        }
        return lastE;
    } else @compileError("don't know how to get last element of " ++ @typeName(@typeOf(x)))
}

@andrewrk andrewrk added this to the 0.11.0 milestone Apr 16, 2022
@matu3ba
Copy link
Contributor

matu3ba commented Jan 3, 2023

I would be in favor of a comptime-only growable slice, which only exists in a certain compilation mode
and can be accessed with expextCompileError based on comptime-formatted input.
Mixing fatal errors with regular user code sounds otherwise like a bad idea.

However, this should likely be deferred until the comptime allocator (interface) exists.

@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@matu3ba
Copy link
Contributor

matu3ba commented Jun 11, 2023

If we can spawn processes, then we can parse the output message like implemented in #15991.

@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jun 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

5 participants