Skip to content

allow functions prototypes to have their own scopes #15409

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

Open
wrongnull opened this issue Apr 22, 2023 · 5 comments
Open

allow functions prototypes to have their own scopes #15409

wrongnull opened this issue Apr 22, 2023 · 5 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@wrongnull
Copy link
Contributor

wrongnull commented Apr 22, 2023

Zig Version

zig-0.11.0-dev.2704+83970b6d9

Steps to Reproduce and Observed Behavior

Consider the following:

const FnProto = fn (arg1: anytype, arg2: @TypeOf(arg1.some_field)) u32;

Compiler says: error: use of undeclared identifier 'arg1'
On the other hand we can get that type using another function

fn dummy(arg1: anytype, arg2: @TypeOf(arg1.some_field)) u32 {
    _ = arg2;
    return 0;
}

const FnProto = @TypeOf(dummy);

but I think that this is not the way out. Moreover, we may face to incorrect error message if there is a declaration with the same name as the parameter

const FnProto = fn (arg1: anytype, arg2: @TypeOf(arg1.some_field)) u32;
const arg1: u64 = 0;
error: type 'u64' does not support field access

Expected Behavior

So maybe function prototypes should have their own scope?

@wrongnull wrongnull added the bug Observed behavior contradicts documented or intended behavior label Apr 22, 2023
@mlugg
Copy link
Member

mlugg commented Apr 22, 2023

This is because of how generic function types work in Zig - no type information is stored about generic parameters other than the fact that they are generic. The type of your dummy is considered to be fn(anytype, anytype) u32, because both arguments are generic in some way. Unfortunately, the design of comptime means that giving generic functions more accurate types turns out to be very complex (relevant: #9260, #14187), so likely won't happen.

@wrongnull
Copy link
Contributor Author

wrongnull commented Apr 22, 2023

The type of your dummy is considered to be fn(anytype, anytype) u32, because both arguments are generic in some way.

@mlugg , so can the same be applied to prototypes? For instance compiler can store temporary dummy function.

@Vexu Vexu added this to the 0.12.0 milestone Apr 22, 2023
@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. and removed bug Observed behavior contradicts documented or intended behavior labels Jan 15, 2024
@andrewrk andrewrk changed the title Name lookup rules in function prototypes allow functions prototypes to have their own scopes Jan 15, 2024
@mlugg
Copy link
Member

mlugg commented Jan 15, 2024

I'm just going to replicate my comments on #18044 here, since I think they effectively summarize my opinion on this proposal. The TL;DR is that I think this syntax would be misleading unless we significantly change how generic functions work, and that with status quo behavior, #14187 makes more sense.


I do not think this is a desirable change. Allowing references to prior parameters when writing function types implies stronger guarantees on generic function types than actually exist. For instance, under this change, the following equality holds:

fn (x: anytype) @TypeOf(x) == fn (x: anytype) ?@TypeOf(x)

Zig does not have a good way to represent specific generic function types at the type level; unless this changes. allowing you to write these types and then "demoting" them to the representable generic type (in this case printed as fn (anytype) anytype, although this is currently invalid syntax to actually write) seems to me misleading at best.


In function declarations, we have to allow backreferences, because this is fundamental to Zig's system of generics. The way the type system interacts with generics there is certainly unfortunate, but it does not call for us to eliminate backreferences entirely, since the type of a generic function is only a small detail of it (it's actually fairly rare to interact much, if at all, with generic function types in Zig code). On the other hand, in function types, there is zero practical use for these backreferences in a way which is not explicitly misleading - writing generic function types, where (since the whole thing you're doing is writing a type) the syntax strongly implies that the type is encoding the constraints you are writing when, in reality, it is not.

@wrongnull
Copy link
Contributor Author

I'm just going to replicate my comments on #18044 here, since I think they effectively summarize my opinion on this proposal. The TL;DR is that I think this syntax would be misleading unless we significantly change how generic functions work, and that with status quo behavior, #14187 makes more sense.


I do not think this is a desirable change. Allowing references to prior parameters when writing function types implies stronger guarantees on generic function types than actually exist. For instance, under this change, the following equality holds:

fn (x: anytype) @TypeOf(x) == fn (x: anytype) ?@TypeOf(x)

Zig does not have a good way to represent specific generic function types at the type level; unless this changes. allowing you to write these types and then "demoting" them to the representable generic type (in this case printed as fn (anytype) anytype, although this is currently invalid syntax to actually write) seems to me misleading at best.


In function declarations, we have to allow backreferences, because this is fundamental to Zig's system of generics. The way the type system interacts with generics there is certainly unfortunate, but it does not call for us to eliminate backreferences entirely, since the type of a generic function is only a small detail of it (it's actually fairly rare to interact much, if at all, with generic function types in Zig code). On the other hand, in function types, there is zero practical use for these backreferences in a way which is not explicitly misleading - writing generic function types, where (since the whole thing you're doing is writing a type) the syntax strongly implies that the type is encoding the constraints you are writing when, in reality, it is not.

I think this kind of false positive can be resolved by introducing some kind of existential types into the language. In this particular case, expressions like ?anytype will mean optional with any child type

@expikr
Copy link
Contributor

expikr commented Mar 27, 2024

What if we disallow argument names inside function prototypes entirely? Then there won't be any question of backreference any more to begin with because there isn't a name to refer to in the first place.

Like, are these two the same signature or not?

const Fn1 = fn(a: f32, b: f32) f64;
const Fn2 = fn(x: f32, y: f32) f64;
const Fn3 = fn(f32, f32) f64;
const eq = (Fn1 == Fn2 and Fn1 == Fn3);

If they are, it is not one obvious way of doing things.
If they aren't, then why is the third way even allowed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants