Commit 0d5ecc4312

Andrew Kelley <superjoe30@gmail.com>
2016-08-08 05:08:37
ability to have a return type of 'type'
1 parent 275410d
src/all_types.hpp
@@ -843,7 +843,6 @@ struct FnTypeId {
     bool is_naked;
     bool is_cold;
     bool is_extern;
-    bool is_inline;
     FnTypeParamInfo prealloc_param_info[fn_type_id_prealloc_param_info_count];
 };
 
@@ -1055,6 +1054,12 @@ enum WantPure {
     WantPureTrue,
 };
 
+enum FnInline {
+    FnInlineAuto,
+    FnInlineAlways,
+    FnInlineNever,
+};
+
 struct FnTableEntry {
     LLVMValueRef fn_value;
     AstNode *proto_node;
@@ -1070,8 +1075,10 @@ struct FnTableEntry {
     bool is_test;
     bool is_pure;
     WantPure want_pure;
+    AstNode *want_pure_attr_node;
+    AstNode *want_pure_return_type;
     bool safety_off;
-    bool is_noinline;
+    FnInline fn_inline;
     BlockContext *parent_block_context;
     FnAnalState anal_state;
 
src/analyze.cpp
@@ -113,9 +113,30 @@ static AstNode *first_executing_node(AstNode *node) {
     zig_unreachable();
 }
 
-static void mark_impure_fn(BlockContext *context) {
-    if (context->fn_entry) {
-        context->fn_entry->is_pure = false;
+static void mark_impure_fn(CodeGen *g, BlockContext *context, AstNode *node) {
+    if (!context->fn_entry) return;
+    if (!context->fn_entry->is_pure) return;
+
+    context->fn_entry->is_pure = false;
+    if (context->fn_entry->want_pure == WantPureTrue) {
+        context->fn_entry->proto_node->data.fn_proto.skip = true;
+
+        ErrorMsg *msg = add_node_error(g, context->fn_entry->proto_node,
+            buf_sprintf("failed to evaluate function at compile time"));
+
+        add_error_note(g, msg, node,
+            buf_sprintf("unable to evaluate this expression at compile time"));
+
+        if (context->fn_entry->want_pure_attr_node) {
+            add_error_note(g, msg, context->fn_entry->want_pure_attr_node,
+                buf_sprintf("required to be compile-time function here"));
+        }
+
+        if (context->fn_entry->want_pure_return_type) {
+            add_error_note(g, msg, context->fn_entry->want_pure_return_type,
+                buf_sprintf("required to be compile-time function because of return type '%s'",
+                buf_ptr(&context->fn_entry->type_entry->data.fn.fn_type_id.return_type->name)));
+        }
     }
 }
 
@@ -659,7 +680,7 @@ TypeTableEntry *get_typedecl_type(CodeGen *g, const char *name, TypeTableEntry *
 }
 
 // accepts ownership of fn_type_id memory
-TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) {
+TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id, bool gen_debug_info) {
     auto table_entry = g->fn_type_table.maybe_get(fn_type_id);
     if (table_entry) {
         return table_entry->value;
@@ -704,65 +725,67 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) {
         buf_appendf(&fn_type->name, " -> %s", buf_ptr(&fn_type_id->return_type->name));
     }
 
-    // next, loop over the parameters again and compute debug information
-    // and codegen information
-    bool first_arg_return = !fn_type_id->is_extern && handle_is_ptr(fn_type_id->return_type);
-    // +1 for maybe making the first argument the return value
-    LLVMTypeRef *gen_param_types = allocate<LLVMTypeRef>(1 + fn_type_id->param_count);
-    // +1 because 0 is the return type and +1 for maybe making first arg ret val
-    LLVMZigDIType **param_di_types = allocate<LLVMZigDIType*>(2 + fn_type_id->param_count);
-    param_di_types[0] = fn_type_id->return_type->di_type;
-    int gen_param_index = 0;
-    TypeTableEntry *gen_return_type;
-    if (!type_has_bits(fn_type_id->return_type)) {
-        gen_return_type = g->builtin_types.entry_void;
-    } else if (first_arg_return) {
-        TypeTableEntry *gen_type = get_pointer_to_type(g, fn_type_id->return_type, false);
-        gen_param_types[gen_param_index] = gen_type->type_ref;
-        gen_param_index += 1;
-        // after the gen_param_index += 1 because 0 is the return type
-        param_di_types[gen_param_index] = gen_type->di_type;
-        gen_return_type = g->builtin_types.entry_void;
-    } else {
-        gen_return_type = fn_type_id->return_type;
-    }
-    fn_type->data.fn.gen_return_type = gen_return_type;
-
-    fn_type->data.fn.gen_param_info = allocate<FnGenParamInfo>(fn_type_id->param_count);
-    for (int i = 0; i < fn_type_id->param_count; i += 1) {
-        FnTypeParamInfo *src_param_info = &fn_type->data.fn.fn_type_id.param_info[i];
-        TypeTableEntry *type_entry = src_param_info->type;
-        FnGenParamInfo *gen_param_info = &fn_type->data.fn.gen_param_info[i];
-
-        gen_param_info->src_index = i;
-        gen_param_info->gen_index = -1;
-
-        assert(type_is_complete(type_entry));
-        if (type_has_bits(type_entry)) {
-            TypeTableEntry *gen_type;
-            if (handle_is_ptr(type_entry)) {
-                gen_type = get_pointer_to_type(g, type_entry, true);
-                gen_param_info->is_byval = true;
-            } else {
-                gen_type = type_entry;
-            }
+    if (gen_debug_info) {
+        // next, loop over the parameters again and compute debug information
+        // and codegen information
+        bool first_arg_return = !fn_type_id->is_extern && handle_is_ptr(fn_type_id->return_type);
+        // +1 for maybe making the first argument the return value
+        LLVMTypeRef *gen_param_types = allocate<LLVMTypeRef>(1 + fn_type_id->param_count);
+        // +1 because 0 is the return type and +1 for maybe making first arg ret val
+        LLVMZigDIType **param_di_types = allocate<LLVMZigDIType*>(2 + fn_type_id->param_count);
+        param_di_types[0] = fn_type_id->return_type->di_type;
+        int gen_param_index = 0;
+        TypeTableEntry *gen_return_type;
+        if (!type_has_bits(fn_type_id->return_type)) {
+            gen_return_type = g->builtin_types.entry_void;
+        } else if (first_arg_return) {
+            TypeTableEntry *gen_type = get_pointer_to_type(g, fn_type_id->return_type, false);
             gen_param_types[gen_param_index] = gen_type->type_ref;
-            gen_param_info->gen_index = gen_param_index;
-            gen_param_info->type = gen_type;
-
             gen_param_index += 1;
-
             // after the gen_param_index += 1 because 0 is the return type
             param_di_types[gen_param_index] = gen_type->di_type;
+            gen_return_type = g->builtin_types.entry_void;
+        } else {
+            gen_return_type = fn_type_id->return_type;
         }
-    }
+        fn_type->data.fn.gen_return_type = gen_return_type;
+
+        fn_type->data.fn.gen_param_info = allocate<FnGenParamInfo>(fn_type_id->param_count);
+        for (int i = 0; i < fn_type_id->param_count; i += 1) {
+            FnTypeParamInfo *src_param_info = &fn_type->data.fn.fn_type_id.param_info[i];
+            TypeTableEntry *type_entry = src_param_info->type;
+            FnGenParamInfo *gen_param_info = &fn_type->data.fn.gen_param_info[i];
+
+            gen_param_info->src_index = i;
+            gen_param_info->gen_index = -1;
+
+            assert(type_is_complete(type_entry));
+            if (type_has_bits(type_entry)) {
+                TypeTableEntry *gen_type;
+                if (handle_is_ptr(type_entry)) {
+                    gen_type = get_pointer_to_type(g, type_entry, true);
+                    gen_param_info->is_byval = true;
+                } else {
+                    gen_type = type_entry;
+                }
+                gen_param_types[gen_param_index] = gen_type->type_ref;
+                gen_param_info->gen_index = gen_param_index;
+                gen_param_info->type = gen_type;
+
+                gen_param_index += 1;
 
-    fn_type->data.fn.gen_param_count = gen_param_index;
+                // after the gen_param_index += 1 because 0 is the return type
+                param_di_types[gen_param_index] = gen_type->di_type;
+            }
+        }
 
-    fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref,
-            gen_param_types, gen_param_index, fn_type_id->is_var_args);
-    fn_type->type_ref = LLVMPointerType(fn_type->data.fn.raw_type_ref, 0);
-    fn_type->di_type = LLVMZigCreateSubroutineType(g->dbuilder, param_di_types, gen_param_index + 1, 0);
+        fn_type->data.fn.gen_param_count = gen_param_index;
+
+        fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref,
+                gen_param_types, gen_param_index, fn_type_id->is_var_args);
+        fn_type->type_ref = LLVMPointerType(fn_type->data.fn.raw_type_ref, 0);
+        fn_type->di_type = LLVMZigCreateSubroutineType(g->dbuilder, param_di_types, gen_param_index + 1, 0);
+    }
 
     g->fn_type_table.put(&fn_type->data.fn.fn_type_id, fn_type);
 
@@ -862,8 +885,15 @@ static TypeTableEntry *analyze_type_expr(CodeGen *g, ImportTableEntry *import, B
     return analyze_type_expr_pointer_only(g, import, context, node, false);
 }
 
+static bool fn_wants_full_static_eval(FnTableEntry *fn_table_entry) {
+    assert(fn_table_entry);
+    AstNodeFnProto *fn_proto = &fn_table_entry->proto_node->data.fn_proto;
+    return fn_proto->inline_arg_count == fn_proto->params.length && fn_table_entry->want_pure == WantPureTrue;
+}
+
+// fn_table_entry is populated if and only if there is a function definition for this prototype
 static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, bool is_naked, bool is_cold)
+        TypeTableEntry *expected_type, AstNode *node, bool is_naked, bool is_cold, FnTableEntry *fn_table_entry)
 {
     assert(node->type == NodeTypeFnProto);
     AstNodeFnProto *fn_proto = &node->data.fn_proto;
@@ -876,7 +906,6 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
     fn_type_id.is_extern = fn_proto->is_extern || (fn_proto->top_level_decl.visib_mod == VisibModExport);
     fn_type_id.is_naked = is_naked;
     fn_type_id.is_cold = is_cold;
-    fn_type_id.is_inline = fn_proto->is_inline;
     fn_type_id.param_count = fn_proto->params.length;
 
     if (fn_type_id.param_count > fn_type_id_prealloc_param_info_count) {
@@ -902,14 +931,6 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
                 buf_sprintf("return type '%s' not allowed", buf_ptr(&fn_type_id.return_type->name)));
             break;
         case TypeTableEntryIdMetaType:
-            if (!fn_proto->is_inline) {
-                fn_proto->skip = true;
-                add_node_error(g, fn_proto->return_type,
-                    buf_sprintf("function with return type '%s' must be declared inline",
-                        buf_ptr(&fn_type_id.return_type->name)));
-                return g->builtin_types.entry_invalid;
-            }
-            break;
         case TypeTableEntryIdUnreachable:
         case TypeTableEntryIdVoid:
         case TypeTableEntryIdBool:
@@ -984,7 +1005,33 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
         return g->builtin_types.entry_invalid;
     }
 
-    return get_fn_type(g, &fn_type_id);
+    if (fn_table_entry && fn_type_id.return_type->id == TypeTableEntryIdMetaType) {
+        fn_table_entry->want_pure = WantPureTrue;
+        fn_table_entry->want_pure_return_type = fn_proto->return_type;
+
+        ErrorMsg *err_msg = nullptr;
+        for (int i = 0; i < fn_proto->params.length; i += 1) {
+            AstNode *param_decl_node = fn_proto->params.at(i);
+            assert(param_decl_node->type == NodeTypeParamDecl);
+            if (!param_decl_node->data.param_decl.is_inline) {
+                if (!err_msg) {
+                    err_msg = add_node_error(g, fn_proto->return_type,
+                        buf_sprintf("function with return type '%s' must declare all parameters inline",
+                        buf_ptr(&fn_type_id.return_type->name)));
+                }
+                add_error_note(g, err_msg, param_decl_node,
+                    buf_sprintf("non-inline parameter here"));
+            }
+        }
+        if (err_msg) {
+            fn_proto->skip = true;
+            return g->builtin_types.entry_invalid;
+        }
+    }
+
+
+    bool gen_debug_info = !(fn_table_entry && fn_wants_full_static_eval(fn_table_entry));
+    return get_fn_type(g, &fn_type_id, gen_debug_info);
 }
 
 static Buf *resolve_const_expr_str(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode **node) {
@@ -1110,10 +1157,12 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
                     bool enable;
                     bool ok = resolve_const_expr_bool(g, import, import->block_context,
                             &directive_node->data.directive.expr, &enable);
-                    if (!enable || !ok) {
+                    if (!ok || !enable) {
                         fn_table_entry->want_pure = WantPureFalse;
+                    } else if (ok && enable) {
+                        fn_table_entry->want_pure = WantPureTrue;
+                        fn_table_entry->want_pure_attr_node = directive_node->data.directive.expr;
                     }
-                    // TODO cause compile error if enable is true and impure fn
                 }
             } else {
                 add_node_error(g, directive_node,
@@ -1129,21 +1178,24 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
 
 
     TypeTableEntry *fn_type = analyze_fn_proto_type(g, import, containing_context, nullptr, node,
-            is_naked, is_cold);
+            is_naked, is_cold, fn_table_entry);
 
     fn_table_entry->type_entry = fn_type;
     fn_table_entry->is_test = is_test;
-    fn_table_entry->is_noinline = is_noinline;
 
     if (fn_type->id == TypeTableEntryIdInvalid) {
         fn_proto->skip = true;
         return;
     }
 
-    if (fn_proto->is_inline && fn_table_entry->is_noinline) {
+    if (fn_proto->is_inline && is_noinline) {
         add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
         fn_proto->skip = true;
         return;
+    } else if (fn_proto->is_inline) {
+        fn_table_entry->fn_inline = FnInlineAlways;
+    } else if (is_noinline) {
+        fn_table_entry->fn_inline = FnInlineNever;
     }
 
 
@@ -1159,48 +1211,54 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
         fn_table_entry->fn_def_node->data.fn_def.block_context = context;
     }
 
-    fn_table_entry->fn_value = LLVMAddFunction(g->module, buf_ptr(symbol_name), fn_type->data.fn.raw_type_ref);
-
-    if (fn_proto->is_inline) {
-        LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMAlwaysInlineAttribute);
-    }
-    if (fn_table_entry->is_noinline) {
-        LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoInlineAttribute);
-    }
-    if (fn_type->data.fn.fn_type_id.is_naked) {
-        LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNakedAttribute);
-    }
+    if (!fn_wants_full_static_eval(fn_table_entry)) {
+        fn_table_entry->fn_value = LLVMAddFunction(g->module, buf_ptr(symbol_name), fn_type->data.fn.raw_type_ref);
 
-    LLVMSetLinkage(fn_table_entry->fn_value, fn_table_entry->internal_linkage ?
-            LLVMInternalLinkage : LLVMExternalLinkage);
+        switch (fn_table_entry->fn_inline) {
+            case FnInlineAlways:
+                LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMAlwaysInlineAttribute);
+                break;
+            case FnInlineNever:
+                LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoInlineAttribute);
+                break;
+            case FnInlineAuto:
+                break;
+        }
+        if (fn_type->data.fn.fn_type_id.is_naked) {
+            LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNakedAttribute);
+        }
 
-    if (fn_type->data.fn.fn_type_id.return_type->id == TypeTableEntryIdUnreachable) {
-        LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoReturnAttribute);
-    }
-    LLVMSetFunctionCallConv(fn_table_entry->fn_value, fn_type->data.fn.calling_convention);
-    if (!fn_table_entry->is_extern) {
-        LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoUnwindAttribute);
-    }
-    if (!g->is_release_build && !fn_proto->is_inline) {
-        ZigLLVMAddFunctionAttr(fn_table_entry->fn_value, "no-frame-pointer-elim", "true");
-        ZigLLVMAddFunctionAttr(fn_table_entry->fn_value, "no-frame-pointer-elim-non-leaf", nullptr);
-    }
+        LLVMSetLinkage(fn_table_entry->fn_value, fn_table_entry->internal_linkage ?
+                LLVMInternalLinkage : LLVMExternalLinkage);
 
-    if (fn_table_entry->fn_def_node) {
-        // Add debug info.
-        unsigned line_number = node->line + 1;
-        unsigned scope_line = line_number;
-        bool is_definition = fn_table_entry->fn_def_node != nullptr;
-        unsigned flags = 0;
-        bool is_optimized = g->is_release_build;
-        LLVMZigDISubprogram *subprogram = LLVMZigCreateFunction(g->dbuilder,
-            containing_context->di_scope, buf_ptr(&fn_table_entry->symbol_name), "",
-            import->di_file, line_number,
-            fn_type->di_type, fn_table_entry->internal_linkage,
-            is_definition, scope_line, flags, is_optimized, nullptr);
+        if (fn_type->data.fn.fn_type_id.return_type->id == TypeTableEntryIdUnreachable) {
+            LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoReturnAttribute);
+        }
+        LLVMSetFunctionCallConv(fn_table_entry->fn_value, fn_type->data.fn.calling_convention);
+        if (!fn_table_entry->is_extern) {
+            LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoUnwindAttribute);
+        }
+        if (!g->is_release_build && !fn_proto->is_inline) {
+            ZigLLVMAddFunctionAttr(fn_table_entry->fn_value, "no-frame-pointer-elim", "true");
+            ZigLLVMAddFunctionAttr(fn_table_entry->fn_value, "no-frame-pointer-elim-non-leaf", nullptr);
+        }
 
-        fn_table_entry->fn_def_node->data.fn_def.block_context->di_scope = LLVMZigSubprogramToScope(subprogram);
-        ZigLLVMFnSetSubprogram(fn_table_entry->fn_value, subprogram);
+        if (fn_table_entry->fn_def_node) {
+            // Add debug info.
+            unsigned line_number = node->line + 1;
+            unsigned scope_line = line_number;
+            bool is_definition = fn_table_entry->fn_def_node != nullptr;
+            unsigned flags = 0;
+            bool is_optimized = g->is_release_build;
+            LLVMZigDISubprogram *subprogram = LLVMZigCreateFunction(g->dbuilder,
+                containing_context->di_scope, buf_ptr(&fn_table_entry->symbol_name), "",
+                import->di_file, line_number,
+                fn_type->di_type, fn_table_entry->internal_linkage,
+                is_definition, scope_line, flags, is_optimized, nullptr);
+
+            fn_table_entry->fn_def_node->data.fn_def.block_context->di_scope = LLVMZigSubprogramToScope(subprogram);
+            ZigLLVMFnSetSubprogram(fn_table_entry->fn_value, subprogram);
+        }
     }
 }
 
@@ -1609,29 +1667,31 @@ static void preview_fn_proto_instance(CodeGen *g, ImportTableEntry *import, AstN
 
 
     } else {
-        g->fn_protos.append(fn_table_entry);
+        resolve_function_proto(g, proto_node, fn_table_entry, import, containing_context);
 
-        if (fn_def_node) {
-            g->fn_defs.append(fn_table_entry);
-        }
+        if (!fn_wants_full_static_eval(fn_table_entry)) {
+            g->fn_protos.append(fn_table_entry);
 
-        bool is_main_fn = !is_generic_instance &&
-            !parent_decl && (import == g->root_import) &&
-            buf_eql_str(proto_name, "main");
-        if (is_main_fn) {
-            g->main_fn = fn_table_entry;
-        }
+            if (fn_def_node) {
+                g->fn_defs.append(fn_table_entry);
+            }
 
-        resolve_function_proto(g, proto_node, fn_table_entry, import, containing_context);
+            bool is_main_fn = !is_generic_instance &&
+                !parent_decl && (import == g->root_import) &&
+                buf_eql_str(proto_name, "main");
+            if (is_main_fn) {
+                g->main_fn = fn_table_entry;
+            }
 
-        if (is_main_fn && !g->link_libc) {
-            TypeTableEntry *err_void = get_error_type(g, g->builtin_types.entry_void);
-            TypeTableEntry *actual_return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type;
-            if (actual_return_type != err_void) {
-                AstNode *return_type_node = fn_table_entry->proto_node->data.fn_proto.return_type;
-                add_node_error(g, return_type_node,
-                        buf_sprintf("expected return type of main to be '%%void', instead is '%s'",
-                            buf_ptr(&actual_return_type->name)));
+            if (is_main_fn && !g->link_libc) {
+                TypeTableEntry *err_void = get_error_type(g, g->builtin_types.entry_void);
+                TypeTableEntry *actual_return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type;
+                if (actual_return_type != err_void) {
+                    AstNode *return_type_node = fn_table_entry->proto_node->data.fn_proto.return_type;
+                    add_node_error(g, return_type_node,
+                            buf_sprintf("expected return type of main to be '%%void', instead is '%s'",
+                                buf_ptr(&actual_return_type->name)));
+                }
             }
         }
     }
@@ -3022,7 +3082,7 @@ static TypeTableEntry *analyze_var_ref(CodeGen *g, AstNode *source_node, Variabl
 {
     get_resolved_expr(source_node)->variable = var;
     if (!var_is_pure(var, context)) {
-        mark_impure_fn(context);
+        mark_impure_fn(g, context, source_node);
     }
     if (var->is_const && var->val_node) {
         ConstExprValue *other_const_val = &get_resolved_expr(var->val_node)->const_val;
@@ -3102,7 +3162,7 @@ static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import,
         return g->builtin_types.entry_invalid;
     }
 
-    mark_impure_fn(context);
+    mark_impure_fn(g, context, node);
     add_node_error(g, node, buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
     return g->builtin_types.entry_invalid;
 }
@@ -3943,7 +4003,8 @@ static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import,
 static TypeTableEntry *analyze_fn_proto_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
-    TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node, false, false);
+    TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node,
+            false, false, nullptr);
 
     if (type_entry->id == TypeTableEntryIdInvalid) {
         return type_entry;
@@ -4386,7 +4447,7 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
         (wanted_type->data.structure.fields[0].type_entry->data.pointer.is_const ||
          !actual_type->data.structure.fields[0].type_entry->data.pointer.is_const))
     {
-        mark_impure_fn(context);
+        mark_impure_fn(g, context, node);
         return resolve_cast(g, context, node, expr_node, wanted_type, CastOpResizeSlice, true);
     }
 
@@ -4395,7 +4456,7 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
         actual_type->id == TypeTableEntryIdArray &&
         is_u8(actual_type->data.array.child_type))
     {
-        mark_impure_fn(context);
+        mark_impure_fn(g, context, node);
         uint64_t child_type_size = type_size(g,
                 wanted_type->data.structure.fields[0].type_entry->data.pointer.child_type);
         if (actual_type->data.array.len % child_type_size == 0) {
@@ -5276,11 +5337,11 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
         case BuiltinFnIdErrName:
             return analyze_err_name(g, import, context, node);
         case BuiltinFnIdBreakpoint:
-            mark_impure_fn(context);
+            mark_impure_fn(g, context, node);
             return g->builtin_types.entry_void;
         case BuiltinFnIdReturnAddress:
         case BuiltinFnIdFrameAddress:
-            mark_impure_fn(context);
+            mark_impure_fn(g, context, node);
             return builtin_fn->return_type;
         case BuiltinFnIdEmbedFile:
             return analyze_embed_file(g, import, context, node);
@@ -5388,6 +5449,9 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
     if (ok_invocation && fn_table_entry && fn_table_entry->is_pure && fn_table_entry->want_pure != WantPureFalse) {
         if (fn_table_entry->anal_state == FnAnalStateReady) {
             analyze_fn_body(g, fn_table_entry);
+            if (fn_table_entry->proto_node->data.fn_proto.skip) {
+                return g->builtin_types.entry_invalid;
+            }
         }
         if (all_args_const_expr) {
             if (fn_table_entry->is_pure && fn_table_entry->anal_state == FnAnalStateComplete) {
@@ -5401,7 +5465,10 @@ static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import,
     }
     if (!ok_invocation || !fn_table_entry || !fn_table_entry->is_pure || fn_table_entry->want_pure == WantPureFalse) {
         // calling an impure fn is impure
-        mark_impure_fn(context);
+        mark_impure_fn(g, context, node);
+        if (fn_table_entry && fn_table_entry->want_pure == WantPureTrue) {
+            return g->builtin_types.entry_invalid;
+        }
     }
 
     if (handle_is_ptr(return_type)) {
@@ -6298,7 +6365,7 @@ static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import,
 static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
-    mark_impure_fn(context);
+    mark_impure_fn(g, context, node);
 
     node->data.asm_expr.return_count = 0;
     TypeTableEntry *return_type = g->builtin_types.entry_void;
src/analyze.hpp
@@ -24,7 +24,7 @@ TypeTableEntry *get_int_type(CodeGen *g, bool is_signed, int size_in_bits);
 TypeTableEntry **get_c_int_type_ptr(CodeGen *g, CIntType c_int_type);
 TypeTableEntry *get_c_int_type(CodeGen *g, CIntType c_int_type);
 TypeTableEntry *get_typedecl_type(CodeGen *g, const char *name, TypeTableEntry *child_type);
-TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id);
+TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id, bool gen_debug_info);
 TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type);
 TypeTableEntry *get_array_type(CodeGen *g, TypeTableEntry *child_type, uint64_t array_size);
 TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *child_type, bool is_const);
src/parseh.cpp
@@ -600,7 +600,7 @@ static TypeTableEntry *resolve_type_with_table(Context *c, const Type *ty, const
                     param_info->is_noalias = qt.isRestrictQualified();
                 }
 
-                return get_fn_type(c->codegen, &fn_type_id);
+                return get_fn_type(c->codegen, &fn_type_id, true);
             }
         case Type::Record:
             {
std/hash_map.zig
@@ -6,8 +6,8 @@ const Allocator = mem.Allocator;
 const want_modification_safety = !@compile_var("is_release");
 const debug_u32 = if (want_modification_safety) u32 else void;
 
-pub inline fn HashMap(inline K: type, inline V: type,
-                      inline hash: fn(key: K)->u32, inline eql: fn(a: K, b: K)->bool)
+pub fn HashMap(inline K: type, inline V: type, inline hash: fn(key: K)->u32,
+    inline eql: fn(a: K, b: K)->bool) -> type
 {
     SmallHashMap(K, V, hash, eql, 8)
 }
@@ -258,7 +258,7 @@ fn global_free(self: &Allocator, old_mem: []u8) {
 
 #attribute("test")
 fn basic_hash_map_test() {
-    var map: SmallHashMap(i32, i32, hash_i32, eql_i32, 4) = undefined;
+    var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;
     map.init(&global_allocator);
     defer map.deinit();
 
std/list.zig
@@ -2,7 +2,7 @@ const assert = @import("debug.zig").assert;
 const mem = @import("mem.zig");
 const Allocator = mem.Allocator;
 
-pub inline fn List(inline T: type) -> type {
+pub fn List(inline T: type) -> type {
     SmallList(T, 8)
 }
 
@@ -77,7 +77,7 @@ fn global_free(self: &Allocator, old_mem: []u8) {
 
 #attribute("test")
 fn basic_list_test() {
-    var list: SmallList(i32, 4) = undefined;
+    var list: List(i32) = undefined;
     list.init(&global_allocator);
     defer list.deinit();
 
test/cases/return_type_type.zig
@@ -0,0 +1,21 @@
+const assert = @import("std").debug.assert;
+
+pub fn List(inline T: type) -> type {
+    SmallList(T, 8)
+}
+
+pub struct SmallList(inline T: type, inline STATIC_SIZE: usize) {
+    items: []T,
+    length: usize,
+    prealloc_items: [STATIC_SIZE]T,
+}
+
+#attribute("test")
+fn function_with_return_type_type() {
+    var list: List(i32) = undefined;
+    var list2: List(i32) = undefined;
+    list.length = 10;
+    list2.length = 10;
+    assert(list.prealloc_items.len == 8);
+    assert(list2.prealloc_items.len == 8);
+}
test/run_tests.cpp
@@ -1427,6 +1427,30 @@ fn f() {
     var foo = ([]u32)(array)[0];
 }
     )SOURCE", 1, ".tmp_source.zig:4:22: error: unable to convert [5]u8 to []u32: size mismatch");
+
+    add_compile_fail_case("non-pure function returns type", R"SOURCE(
+var a: u32 = 0;
+pub fn List(inline T: type) -> type {
+    a += 1;
+    SmallList(T, 8)
+}
+
+pub struct SmallList(inline T: type, inline STATIC_SIZE: usize) {
+    items: []T,
+    length: usize,
+    prealloc_items: [STATIC_SIZE]T,
+}
+
+#attribute("test")
+fn function_with_return_type_type() {
+    var list: List(i32) = undefined;
+    list.length = 10;
+}
+
+    )SOURCE", 3,
+            ".tmp_source.zig:3:5: error: failed to evaluate function at compile time",
+            ".tmp_source.zig:4:5: note: unable to evaluate this expression at compile time",
+            ".tmp_source.zig:3:32: note: required to be compile-time function because of return type 'type'");
 }
 
 //////////////////////////////////////////////////////////////////////////////
test/self_hosted.zig
@@ -3,6 +3,7 @@ const assert = std.debug.assert;
 const str = std.str;
 const cstr = std.cstr;
 const other = @import("other.zig");
+const cases_return_type_type = @import("cases/return_type_type.zig");
 
 // normal comment
 /// this is a documentation comment