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: for "enum inference" improvements (with a view towards succinct local "anonymous" but type-sound enums) #4241

Open
metaleap opened this issue Jan 19, 2020 · 7 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@metaleap
Copy link
Contributor

metaleap commented Jan 19, 2020

The below snippets may appear contrived (it's for brevity) but bear with for now, more real-world(ish) reasoning then at bottom of post.

Inside any fn, this compiles and works today:

const MyEnum = enum {none,case1,case2,case3};
var foo: MyEnum = if (someExpr1) MyEnum.case1 else (if (someExpr2) MyEnum.case2 else (if (someExpr3) MyEnum.case3 else MyEnum.none));

Knowing the rest of Zig's existing overall inference capabilities, the first "smoothening" one would like to see is "enumerant inference" so as to be able to:

const MyEnum = enum {none,case1,case2,case3};
var foo: MyEnum = if (someExpr1) .case1 else (if (someExpr2) .case2 else (if (someExpr3) .case3 else .none));

As of today (yesterday's nightly), this compiler-errors with values of type '(enum literal)' must be comptime known, arrow hinting at the 3rd if. (Same without the parens nesting btw.)

With that out of the way, it should also then in principle (putting subjective desirability discussions / style sensibilities aside) technically be possible, following Zigs similar powers (elsewhere outside the fn-local-enum situation, consider the anon-structs-aka-tuple-literals one can pass to std.debug.warn and std.fmt funcs) to be able to drop the named local type entirely:

var foo: enum {none,case1,case2,case3} = if (someExpr1) .case1 else (if (someExpr2) .case2 else (if (someExpr3) .case3 else .none));

Naturally same error as of today as above, as this sort of inference isn't present so far.

Now the cherry on top would then be the ability to actually:

var foo = if (someExpr1) .case1 else (if (someExpr2) .case2 else (if (someExpr3) .case3 else .none));

Don't choke! The structural unnamed underlying enum type to back var foo here gets derived/inferred from all the possible "value expressions" the right-hand-side expression can potentially resolve to. (If not all branches conform to the (shall we call it) "dotIdent" pattern, then simply just fail inference, no biggie --- I'm not suggesting "inference of unions" here =)

This kind of local-tagging can be a great readability helper in more expansive big-block multi-branching situations, to now switch on the just-decided local enum instead, which also lets one explain / delineate from the very outset of one's fn the various major cases being handled via the enumerant ("tag") names, while still preserving the short-circuiting evaluation of potentially involved runtime condition-expressions etc. And they're not scattered "deep way down there" but together and summarized under succinct names at the top --- and not as n different bool vars but inferred enumerants because one wanted neither to evaluate all of them, nor to noisily via AND/OR/NOT "manually shortcircuit" into such local named bools, a whole bunch of them (as a de-facto-quasi-enum-semantically-but-not-as-clearly-demarkated-to-the-compiler-typechecker-and-reader), such as one'd have to declare today for lack of the suggested "inferred anonymous local enumerants".

This whole consideration / proposal is also partially motivated by the fact that switch (currently) requires comptime-known cases (rather than desugaring-into-ifs-in-IR-for-noncompliant-switch-occurrences transparently). Otherwise one could switch (true) with the runtime-dynamic condition checks before the => arrows. Still more readable for multiple (>=3) big-block handlings than a multi-if or multiple top-located bools.

Now, does this mean asking for || as well, or "generalizing existing error-sets capabilities to all userland enums"? Not so, in my view. No need to go too wild with cross-func/cross-module inferencings or whatever. Just hook into the current failure path for the above and if all possible-result-expressions have the same "dotIdent" form ( .foo , .bar , .baz etc.) the inference to an exactly defined enum is both clear, unambiguous and tractable. (And once Zig-LSP is up and feature complete, hover over var foo to see the enum "def" as-if-code..)

This is for light-weight, mostly-for-explanatory/understandability-purposes enumerants: a full-blown named-type-def would make it falsely look more important (or false suggesting it's part of business logic) than it usually is for such cases. Locally-scoped "anonymous-enum" enumerants could neatly help tag/annotate/document/illuminate local logic flow --- a bit of a "fluffy/sugary" suggestion at first sight but I feel it when used habitually (enabled by the-near-zero-cost-to-the-typist thanks to maximal inferencing) could be positively impactful on intricate code-bases such as the ones likely to be attacked in or ported to Zig

@daurnimator daurnimator added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jan 19, 2020
@mikdusan
Copy link
Member

what would this proposal emit for @compileLog shown below?

export fn entry() void {
    foo(5);
}

fn foo(comptime a: u8) void { 
    comptime var x = switch (a) {
        0 => .zero,
        1 => .one,
        2 => .two,
        else => .none,
    };
    @compileLog(@TypeOf(x)); // (enum literal)
}

@ghost
Copy link

ghost commented Jan 20, 2020

I'm not going to comment on the cherry on top proposal, but as for the points before that, I think it's a general bug or missing feature in Zig that type resolution doesn't work on nested conditionals. There are a few github issues which are related to this, e.g. #3750.

@metaleap
Copy link
Contributor Author

metaleap commented Jan 20, 2020

@mikdusan my guess would be enum{zero,one,two,none} (or however the current fmt rendering logic for any structural/underlying enum type arg does it). Might wanna disallow to/from-int conversions (they're compiler-provided built-ins IIRC anyway) of such "derived anonymous" enums for footgun prevention, same as currently the ints for the "global error-set implicit enum" aren't guaranteed.

@mikdusan
Copy link
Member

#4241 (comment) is only meant to show a case of when inferring an (enum literal) is desired. This proposal overshadows that and then would require a workaround:

comptime var x: `@TypeOf(.foo)` = switch (a) {

@metaleap
Copy link
Contributor Author

metaleap commented Jan 20, 2020

@mikdusan interesting, but why should the current @compileLog of @TypeOf logic have to change... AFAICT it doesn't have to for this proposal. It can keep producing | (enum literal) if that's considered generally useful enough. =)

@mikdusan
Copy link
Member

I'm assuming in this PR that the nested-if example above a switch would equally result in "cherry on top" inferral of type "anon-enum" which conflicts with an inferral of type "enum-literal".

@metaleap
Copy link
Contributor Author

True that. So this scenario, does it come up a lot in practice? I mean I was actually slightly surprised it wouldn't print | .none there.

@andrewrk andrewrk added this to the 0.7.0 milestone Jan 28, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 27, 2020
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 23, 2021
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@andrewrk andrewrk modified the milestones: 0.13.0, 0.12.0 Jul 9, 2023
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

No branches or pull requests

4 participants