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

Metaprogramming - @typeInfo [DONE] #951

Merged
merged 36 commits into from May 4, 2018
Merged

Metaprogramming - @typeInfo [DONE] #951

merged 36 commits into from May 4, 2018

Conversation

alexnask
Copy link
Contributor

@alexnask alexnask commented Apr 25, 2018

Implements the proposal set in #383.

Checklist:

  • @typeinfo
    • Int
    • Float
    • Pointer
    • Array
    • Nullable
    • Promise
    • Struct
    • ErrorUnion
    • ErrorSet
    • Enum
    • Union
    • Fn
    • BoundFn
  • Definition info (methods, variables, types).
  • Tests
  • Docs

This PR also adds code for comptime union field access.

Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach looks correct to me.

I suggest solving @reify separately - I think @typeInfo is a big enough problem that we can focus entirely on it and get it tested and merged before trying to implement @reify.

Don't forget to add your tests to test/cases/type_info.zig (and corresponding entry in test/behavior.zig).

Also comptime union field access is complicated enough that it deserves its own tests (probably in test/cases/union.zig but feel free to use your own judgement)

src/codegen.cpp Outdated
@@ -6344,6 +6346,158 @@ static void define_builtin_compile_vars(CodeGen *g) {
}
buf_appendf(contents, "};\n\n");
}
{
// TODO: Add method info where methods are supported.
buf_appendf(contents,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll suggest a more idiomatic layout:

pub const TypeInfo = union(TypeId) {
    Type: void,
    Void: void,
    Bool: void,
    NoReturn: void,
    Int: Int,
    Float: Float,
    Pointer: Pointer,
    Array: Array,
    Struct: Struct,
    FloatLiteral: void,
    IntLiteral: void,
    UndefinedLiteral: void,
    NullLiteral: void,
    Nullable: Nullable,
    ErrorUnion: ErrorUnion,
    ErrorSet: ErrorSet,
    Enum: Enum,
    Union: Union,
    Fn: Fn,
    Namespace: void,
    Block: void,
    BoundFn: BoundFn,
    ArgTuple: void,
    Opaque: void,
    Promise: Promise,

    pub const Int = struct {
        is_signed: bool,
        bits: u8,
    };

    pub const Float = struct {
        bits: u8,
    };

    pub const Pointer = struct {
        is_const: bool,
        is_volatile: bool,
        alignment: u32,
        child: &TypeInfo,
    };

    pub const Array = struct {
        len: usize,
        child: &TypeInfo,
    };

    pub const ContainerLayout = enum {
        Auto,
        Extern,
        Packed,
    };

    pub const StructField = struct {
        name: []const u8,
        offset: usize,
        type_info: TypeInfo,
    };

    pub const Struct = struct {
        layout: ContainerLayout,
        fields: []StructField,
    };

    pub const Nullable = struct {
        child: &TypeInfo,
    };

    pub const ErrorUnion = struct {
        error_set: ErrorSetInfo,
        payload: &TypeInfo,
    };

    pub const Error = struct {
        name: []const u8,
        value: usize,
    };

    pub const ErrorSet = struct {
        errors: []Error,
    };

    pub const EnumField = struct {
        name: []const u8,
        value: usize,
    };

    pub const Enum = struct {
        layout: ContainerLayout,
        tag_type: Int,
        fields: []EnumField,
    };

    pub const UnionField = struct {
        name: []const u8,
        enum_field: EnumField,
        type_info: TypeInfo,
    };

    pub const Union = struct {
        layout: ContainerLayout,
        tag_type: ?Enum,
        fields: []UnionField,
    };

    pub const CallingConvention = enum {
        Unspecified,
        C,
        Cold,
        Naked,
        Stdcall,
        Async,
    };

    pub const FnArg = struct {
        is_comptime: bool,
        name: []const u8,
        type_info: TypeInfo,
    };

    pub const Fn = struct {
        calling_convention: CallingConvention,
        is_generic: bool,
        is_varargs: bool,
        return_type: &TypeInfo,
        args: []FnArg,
    };

    pub const BoundFn = struct {
        bound_type: &TypeInfo,
        fn_info: Fn,
    };

    pub const Promise = struct {
        child: ?&TypeInfo,
    };

};

feel free to use a c++ multiline string here if it's easier (but do try to get the formatting right in the resulting builtin.zig file).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also feel free to save this for last, it's just organization. To use this layout in ir.cpp you would take advantage of ir_build_field_ptr on the builtin that you get from get_builtin_value. You'd probably need to transform the builtin into a const ptr of it though because ir_build_field_ptr expects a pointer to the base thing.

Also you would need to extract the body of ir_analyze_instruction_field_ptr into something you can call from another "analyze" function -

Anyway go for it if it makes sense to you, but also this would be pretty easy for me to do on top of your work, so if it sounds like a chore, don't worry about it.

Copy link
Contributor Author

@alexnask alexnask Apr 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewrk
Will be done after all TypeInfo generation code is completed.
EDIT: Working on it now after all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewrk

I actually tried to implement the solution you suggested (all that was left was to get the type out of ir_analyze_instruction_field_ptr instead of getting the type of the type).
However I found out that ir_analyze_instruction_field_ptr prioritizes the tag when accessing a union, so having the same struct names as the tags was not an option.

I'm not sure if the current code is too hackish, it looks ok to me but I'm not acquainted to the compiler.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I hadn't considered that. Hm, that sounds a bit like a design flaw. Hmmmmmmm. Oh well. It's really cosmetic. We can solve that problem later. What's important is the functionality of @typeInfo working.

zig_unreachable();

if (field != actual_field) {
ir_add_error_node(ira, source_instr->source_node,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks for this compile error. Don't forget to add a corresponding test in test/compile_errors.zig. Feel free to add it right at the top, and you can run those tests only with: bin/zig build --build-file ../build.zig test-compile-errors (and probably just kill it after it gets past your new one)

if (field_type->id == TypeTableEntryIdVoid)
{
assert(payload_val == nullptr);
payload_val = create_const_vals(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming your question from IRC: this is in fact the correct way to create a void instance 👍

ConstExprValue *payload_val = union_val->data.x_union.payload;

TypeTableEntry *field_type = field->type_entry;
if (field_type->id == TypeTableEntryIdVoid)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if the type is void we just leave data.x_union.payload to be nullptr. But sounds like you ran into a problem with that?

...ah, I see, below, we need the result to have a pointee. Hmm. I think this solution is clever and correct, and let's go with it. In a future cleanup, maybe we just go ahead and create a void instance whenever we create a union const val for consistency. We could even cache a global instance of the void ConstExprValue because it's read only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewrk

Right, CodeGen::void_value could be a good place to have it.
As you said, I ran into trouble when I set the ref pointee to be nullptr, which is why I made the value this way.

src/ir.cpp Outdated
}

// is_signed: bool
fields[0].special = ConstValSpecialStatic;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunate that we have these footguns here. (this problem exists elsewhere in ir.cpp, so you're not introducing a new problem). but maybe we could add some asserts like this?

assert(find_struct_type_field(int_info_type->data.x_type, buf_create_from_str("is_signed"))->src_index == 0);

so that we at least get asserts in debug builds if the code in codegen.cpp that writes builtin.zig is changed. and then in release mode we don't do the find.

src/ir.cpp Outdated
payload->data.x_struct.parent.id = ConstParentIdUnion;
payload->data.x_struct.parent.data.p_union.union_val = parent;
}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a zig_panic("TODO") for the ConstParentIdArray case - this will save future us trouble when we need to handle this case. same goes for other switch cases in this function.

src/ir.cpp Outdated
assert(var_value->type->id == TypeTableEntryIdMetaType);
TypeTableEntry *result_type = var_value->data.x_type;

// TODO: We need to return a const pointer to the typeinfo, not the typeinfo by value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewrk

Well, tbh we don't "need" to, I just think it's cleaner if the compiler gives us a const ref to the TypeInfo instance.

The user can then copy it if they need to.
It's probably just fine to return it by value too though, I don't know why it struck me as necessary in the first place :P

@alexnask
Copy link
Contributor Author

alexnask commented Apr 25, 2018

@andrewrk
I think I will be done with @typeinfo today so I could work towards at @reify in this PR or I could split it off into a new PR if the final code looks good and you think it's worth merging on its own.

Btw, how would you suggest I go about removing old builtins?
Is there some kind of deprecation mechanism present (can't seem to find anything)?

I was thinking about deprecating them with warnings and moving them into a 'meta' package as-is (with additional functions for methods, slices instead of count + index functions etc.).

Then when we have @reify some kind of TypeBuilder struct with helper methods would be cool to include in the meta package (although I'm pretty certain it would need a comptime allocator at some point or another).

Thanks for the feedback.

@andrewrk
Copy link
Member

I definitely think @typeInfo is worth merging on its own. As for deprecation - before 1.0.0 anything is fair game to change without notice. We have no concept of warnings, only errors.

Compatibility is a very serious concept that is important to get right, but zig is not mature enough to start having compatibility promises.

I would propose getting all the tests passing and having this PR be purely additive, and then we can do another pass where we delete no longer needed builtins and add userland solutions (such as @typeId).

@alexnask alexnask changed the title Metaprogramming - @typeInfo/@reify [WIP] Metaprogramming - @typeInfo [WIP] Apr 26, 2018
@Hejsil Hejsil mentioned this pull request Apr 30, 2018
@alexnask alexnask changed the title Metaprogramming - @typeInfo [WIP] Metaprogramming - @typeInfo [DONE] May 1, 2018
@binary132
Copy link

binary132 commented May 1, 2018

Hi. I'm not sure this is the right place to comment (I've also left a comment here: #383 (comment)) but accessing fns from within the type's namespace (as opposed to strictly type members / fields) would be a valuable bit of functionality, since that is (as I understand it) how "methods" are sometimes implemented.

@alexnask
Copy link
Contributor Author

alexnask commented May 1, 2018

@binary132
Namespaced functions are available in the defs field of Struct, Enum and Union TypeInfos, unless you mean something different?

I also plan to add a @namespaceInfo builtin that returns the defs from a namespace (@import return value), if @andrewrk is ok with it.

EDIT: Here is an example that looks for a namespaced function with a &Self or &const Self first parameter (probably should accept Self for enums).

fn type_has_self_method(comptime T: type, comptime name: []const u8, is_const: ?&bool) bool {
    comptime {
        const info = @typeInfo(T);

        const defs = switch(info) {
            TypeInfo.Struct => |*s| blk: {
                break :blk s.defs;
            },
            TypeInfo.Enum => |*e| blk: {
                break :blk e.defs;
            },
            TypeInfo.Union => |*u| blk: {
                break :blk u.defs;
            },
            else => return false,
        };

        for (defs) |*def| {
            if (mem.eql(u8, name, def.name)) {
                switch (def.data) {
                    TypeInfo.Definition.Data.Fn => |*fn_def| {
                        const fn_info = @typeInfo(fn_def.fn_type);

                        if (fn_info.Fn.args.len == 0)
                            return false;

                        if (fn_info.Fn.args[0].arg_type == &T) {
                            if (is_const) |ic| {
                                *ic = false;
                            }
                            return true;
                        } else if (fn_info.Fn.args[0].arg_type == &const T) {
                            if (is_const) |ic| {
                                *ic = true;
                            }
                            return true;
                        } else {
                            return false;
                        }
                    },
                    else => return false,
                }
            }
        }

        return false;
    }
}

" defs: []Definition,\n"
" };\n"
"\n"
" pub const CallingConvention = enum {\n"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good as is, but just a note, this is going to become a top level export of builtin.zig after #661

" };\n"
" };\n"
"};\n\n");
assert(ContainerLayoutAuto == 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these asserts are appreciated, and will for sure prevent bugs in the future 👍


assert(type != nullptr && !type_is_invalid(type));
// Check for our field by creating a buffer in place then using the comma operator to free it so that we don't
// leak memory in debug mode.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attention to detail is appreciated, but just so you know: the c++ compiler doesn't even pretend to try to free memory. We treat the entire OS's memory as one big ArenaAllocator that gets freed upon process exit.

The self hosted compiler has a higher standard though. Because it is meant to be used as a long running process that performs multiple compilations, it will need to get these details correct.

@andrewrk
Copy link
Member

andrewrk commented May 4, 2018

I also plan to add a @namespaceinfo builtin that returns the defs from a namespace (@import return value), if @andrewrk is ok with it.

I would be ok with it - however I think there may be an even better route. I don't think I've typed this up yet, but @tgschultz pointed out that an empty struct and a namespace are pretty much the same thing. Why not make them actually the same thing?

Then @typeInfo as it already exists would work for this purpose, e.g. @typeInfo(@import("foo.zig")).

@andrewrk andrewrk merged commit b9e320d into ziglang:master May 4, 2018
@andrewrk
Copy link
Member

andrewrk commented May 4, 2018

@alexnask

This was an incredible amount of work that resulted in a highly demanded feature, at an impressive level of quality. Would you like to become one of the owners of the zig-lang organization and have commit access?

@alexnask
Copy link
Contributor Author

alexnask commented May 4, 2018

@andrewrk
Sure, I'd be honored to :)
My next target is @reify, I will be starting work on it on the weekend but I expect it to drag on for a while.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants