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

Using Return Type Of Fallible Function In Another Function Causes Incorrect Comptime Error #15023

Closed
ominitay opened this issue Mar 20, 2023 · 6 comments
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@ominitay
Copy link
Contributor

Zig Version

0.11.0-dev.2168+322ace70f

Steps to Reproduce and Observed Behavior

This is the following minimum reproduction case I could determine for this:

const HelperReturnType = @typeInfo(@TypeOf(helper)).Fn.return_type.?;

pub fn concat() HelperReturnType {
    return "";
}

fn helper(comptime string: []const u8) ![]const u8 {
    return string ++ " world!";
}

test {
    _ = try concat();
}

The following compile error is observed on invocation of zig test repro.zig:

repro.zig:8:12: error: unable to resolve comptime value
    return string ++ " world!";
           ^~~~~~
repro.zig:8:12: note: slice value being concatenated must be comptime-known

It is worth noting that helper doesn't need to be called for this to happen, and that using @typeInfo to get the error set for helper and use it as part of an error union for concat's return value also exhibits this behaviour.

Making helper an inline fn allows the program to compile successfully, as does removing the concat invocation. Writing _ = HelperReturnType without a concat invocation does not reproduce the error.

Expected Behavior

The code should have compiled successfully.

@ominitay ominitay added the bug Observed behavior contradicts documented or intended behavior label Mar 20, 2023
@Vexu Vexu added this to the 0.11.0 milestone Mar 20, 2023
@sengir
Copy link
Contributor

sengir commented Mar 20, 2023

I don't think it makes sense to be able to refect a generic function's inferred error set, but here's a reduction:

fn use(_: u8) void {}

fn func(comptime param: u8) !void {
    use(param); // ok: runtime use of comptime param
    comptime use(param); // not ok: comptime use of comptime param
    //           ^~~~~
    // example.zig:5:18: error: unable to resolve comptime value
    // example.zig:5:18: note: argument to function being called at comptime must be comptime-known
}

comptime {
    func(undefined) catch unreachable; // doesn't matter if `func` already instantiated
    const ErrorUnion = @typeInfo(@TypeOf(func)).Fn.return_type.?;
    const ErrorSet = @typeInfo(ErrorUnion).ErrorUnion.error_set;
    _ = @typeInfo(ErrorSet).ErrorSet; // not ok: comptime use of inferred error set of generic function
}

Some useful observations:

  • only occurs when using the function's inferred error set
  • only occurs with comptime parameters where the type has more than one possible value
  • only occurs if the parameter is being used in a way that requires being comptime known
    • does not occur of comptime context is needless (e.g. comptime block without comptime-only operation)
  • only occurs for generic functions
  • only occurs with inferred error sets
  • error is always on first operation that requires comptime-known value

It looks like reflecting on a generic function's inferred error set is analyzing the function with comptime parameters being runtime-known. Disallowing this reflection entirely is probably the more correct thing to do, but I wouldn't be surprised if it broke a lot of code that does minor, sensible things with comptime parameters. At the very least, the analysis caused by this reflection should catch the generic poison thrown by comptime parameter usage to output a more useful error to users.

Related: #12806

@mlugg
Copy link
Member

mlugg commented Mar 20, 2023

Yeah, the inferred error set is only known for specific instantiations since a comptime-known argument can change it arbitrarily. So the function return type depends on the instantiation, meaning return_type should straight up be null there I think?

@travisstaloch
Copy link
Contributor

maybe also related to #14991

@sengir
Copy link
Contributor

sengir commented Mar 20, 2023

Yeah, the inferred error set is only known for specific instantiations since a comptime-known argument can change it arbitrarily. So the function return type depends on the instantiation, meaning return_type should straight up be null there I think?

Yeah, that's what I meant by disallow entirely. However, that's sort of a sledgehammer solution since the unwrapped return type could be static and not depend on comptime parameter values. It's rather uncommon to reflect on a generic function's inferred error set, but it is rather common to reflect on a generic function's unwrapped return type as well as use inferred error sets.

@ominitay
Copy link
Contributor Author

I don't think it makes sense to be able to refect a generic function's inferred error set

Mhm, I agree. The error should reflect this though. But there are cases where it could make sense to do this. It could be useful to know the payload of the error union if that's known, as is the case with helper. Or even if the payload is unknown for the generic, but the error set is known?

These are probably questions worth asking, personally I'd say they probably shouldn't be allowed. However, the compiler should probably be able to determine whether or not the return type is invariant for a generic function, and allow it, like in this case.

@Vexu
Copy link
Member

Vexu commented May 11, 2023

Duplicate of #14991

@Vexu Vexu marked this as a duplicate of #14991 May 11, 2023
@Vexu Vexu closed this as completed May 11, 2023
@Vexu Vexu removed this from the 0.11.0 milestone May 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

No branches or pull requests

5 participants