Commit 183976b242

Andrew Kelley <superjoe30@gmail.com>
2016-09-27 05:47:30
add this keyword refers to thing in immediate scope
See #169
1 parent f4d7c91
doc/vim/syntax/zig.vim
@@ -14,7 +14,7 @@ syn keyword zigStatement goto break return continue asm defer
 syn keyword zigConditional if else switch
 syn keyword zigRepeat while for
 
-syn keyword zigConstant null undefined zeroes
+syn keyword zigConstant null undefined zeroes this
 syn keyword zigKeyword fn use
 syn keyword zigType bool f32 f64 void unreachable type error
 syn keyword zigType i8  u8  i16  u16  i32  u32  i64  u64  isize  usize
doc/langref.md
@@ -151,7 +151,7 @@ GotoExpression = "goto" Symbol
 
 GroupedExpression = "(" Expression ")"
 
-KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "zeroes" | "error" | "type"
+KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "zeroes" | "error" | "type" | "this"
 ```
 
 ## Operator Precedence
src/all_types.hpp
@@ -85,6 +85,7 @@ struct ConstExprValue {
         ConstArrayValue x_array;
         ConstPtrValue x_ptr;
         ImportTableEntry *x_import;
+        BlockContext *x_block;
     } data;
 };
 
@@ -174,6 +175,7 @@ enum NodeType {
     NodeTypeNullLiteral,
     NodeTypeUndefinedLiteral,
     NodeTypeZeroesLiteral,
+    NodeTypeThisLiteral,
     NodeTypeIfBoolExpr,
     NodeTypeIfVarExpr,
     NodeTypeWhileExpr,
@@ -719,6 +721,11 @@ struct AstNodeZeroesLiteral {
     Expr resolved_expr;
 };
 
+struct AstNodeThisLiteral {
+    // populated by semantic analyzer
+    Expr resolved_expr;
+};
+
 struct AstNodeSymbolExpr {
     Buf *symbol;
 
@@ -822,6 +829,7 @@ struct AstNode {
         AstNodeNullLiteral null_literal;
         AstNodeUndefinedLiteral undefined_literal;
         AstNodeZeroesLiteral zeroes_literal;
+        AstNodeThisLiteral this_literal;
         AstNodeSymbolExpr symbol_expr;
         AstNodeBoolLiteral bool_literal;
         AstNodeBreakExpr break_expr;
@@ -1005,6 +1013,7 @@ enum TypeTableEntryId {
     TypeTableEntryIdFn,
     TypeTableEntryIdTypeDecl,
     TypeTableEntryIdNamespace,
+    TypeTableEntryIdBlock,
     TypeTableEntryIdGenericFn,
 };
 
@@ -1102,7 +1111,6 @@ struct FnTableEntry {
     AstNode *want_pure_return_type;
     bool safety_off;
     FnInline fn_inline;
-    BlockContext *parent_block_context;
     FnAnalState anal_state;
 
     ZigList<AstNode *> cast_alloca_list;
@@ -1209,6 +1217,7 @@ struct CodeGen {
         TypeTableEntry *entry_type;
         TypeTableEntry *entry_invalid;
         TypeTableEntry *entry_namespace;
+        TypeTableEntry *entry_block;
         TypeTableEntry *entry_num_lit_int;
         TypeTableEntry *entry_num_lit_float;
         TypeTableEntry *entry_undef;
@@ -1340,7 +1349,6 @@ struct LabelTableEntry {
 };
 
 struct BlockContext {
-    // One of: NodeTypeFnDef, NodeTypeBlock, NodeTypeRoot, NodeTypeDefer, NodeTypeVariableDeclaration
     AstNode *node;
 
     // any variables that are introduced by this scope
src/analyze.cpp
@@ -91,6 +91,7 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeIfBoolExpr:
         case NodeTypeIfVarExpr:
         case NodeTypeLabel:
@@ -246,6 +247,7 @@ static bool type_is_complete(TypeTableEntry *type_entry) {
         case TypeTableEntryIdFn:
         case TypeTableEntryIdTypeDecl:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             return true;
     }
@@ -962,6 +964,7 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
             case TypeTableEntryIdNullLit:
             case TypeTableEntryIdUnreachable:
             case TypeTableEntryIdNamespace:
+            case TypeTableEntryIdBlock:
             case TypeTableEntryIdGenericFn:
                 fn_proto->skip = true;
                 add_node_error(g, child->data.param_decl.type,
@@ -1012,6 +1015,7 @@ static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *impor
         case TypeTableEntryIdUndefLit:
         case TypeTableEntryIdNullLit:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdVar:
             fn_proto->skip = true;
@@ -1896,6 +1900,7 @@ static void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only)
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeSymbol:
         case NodeTypePrefixOpExpr:
         case NodeTypeIfBoolExpr:
@@ -1960,6 +1965,7 @@ static bool type_has_codegen_value(TypeTableEntry *type_entry) {
         case TypeTableEntryIdUndefLit:
         case TypeTableEntryIdNullLit:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             return false;
 
@@ -2745,6 +2751,7 @@ static bool is_container(TypeTableEntry *type_entry) {
         case TypeTableEntryIdFn:
         case TypeTableEntryIdTypeDecl:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             return false;
     }
@@ -2791,6 +2798,7 @@ static void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) {
         case TypeTableEntryIdFn:
         case TypeTableEntryIdTypeDecl:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdInvalid:
         case TypeTableEntryIdVar:
@@ -2965,7 +2973,7 @@ static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *i
         }
         if (decl_node) {
             TopLevelDecl *tld = get_as_top_level_decl(decl_node);
-            if (tld->visib_mod == VisibModPrivate) {
+            if (tld->visib_mod == VisibModPrivate && decl_node->owner != import) {
                 ErrorMsg *msg = add_node_error(g, node,
                     buf_sprintf("'%s' is private", buf_ptr(field_name)));
                 add_error_note(g, msg, decl_node, buf_sprintf("declared here"));
@@ -3093,6 +3101,16 @@ static TypeTableEntry *resolve_expr_const_val_as_type(CodeGen *g, AstNode *node,
     return g->builtin_types.entry_type;
 }
 
+static TypeTableEntry *resolve_expr_const_val_as_block(CodeGen *g, AstNode *node, BlockContext *block_context,
+        bool depends_on_compile_var)
+{
+    Expr *expr = get_resolved_expr(node);
+    expr->const_val.ok = true;
+    expr->const_val.data.x_block = block_context;
+    expr->const_val.depends_on_compile_var = depends_on_compile_var;
+    return g->builtin_types.entry_block;
+}
+
 static TypeTableEntry *resolve_expr_const_val_as_other_expr(CodeGen *g, AstNode *node, AstNode *other,
         bool depends_on_compile_var)
 {
@@ -3221,6 +3239,13 @@ static TypeTableEntry *resolve_expr_const_val_as_unsigned_num_lit(CodeGen *g, As
     return g->builtin_types.entry_num_lit_int;
 }
 
+static TypeTableEntry *resolve_expr_const_val_as_import(CodeGen *g, AstNode *node, ImportTableEntry *import) {
+    Expr *expr = get_resolved_expr(node);
+    expr->const_val.ok = true;
+    expr->const_val.data.x_import = import;
+    return g->builtin_types.entry_namespace;
+}
+
 static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
         BlockContext *context, AstNode *node, Buf *err_name)
 {
@@ -3497,6 +3522,7 @@ static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *im
         case TypeTableEntryIdFn:
         case TypeTableEntryIdTypeDecl:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             if (!is_equality_cmp) {
                 add_node_error(g, node,
@@ -4120,6 +4146,25 @@ static TypeTableEntry *analyze_zeroes_literal_expr(CodeGen *g, ImportTableEntry
     return expected_type ? expected_type : g->builtin_types.entry_undef;
 }
 
+static TypeTableEntry *analyze_this_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node)
+{
+    if (!context->parent) {
+        return resolve_expr_const_val_as_import(g, node, import);
+    }
+    if (context->fn_entry && (!context->parent->fn_entry ||
+        (context->parent->parent && !context->parent->parent->fn_entry)))
+    {
+        return resolve_expr_const_val_as_fn(g, node, context->fn_entry, false);
+    }
+    if (context->node->type == NodeTypeContainerDecl) {
+        return resolve_expr_const_val_as_type(g, node, context->node->data.struct_decl.type_entry, false);
+    }
+    if (context->node->type == NodeTypeBlock) {
+        return resolve_expr_const_val_as_block(g, node, context, false);
+    }
+    zig_unreachable();
+}
 
 static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
         BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
@@ -4793,13 +4838,6 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
     return g->builtin_types.entry_invalid;
 }
 
-static TypeTableEntry *resolve_expr_const_val_as_import(CodeGen *g, AstNode *node, ImportTableEntry *import) {
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_import = import;
-    return g->builtin_types.entry_namespace;
-}
-
 static TypeTableEntry *analyze_import(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         AstNode *node)
 {
@@ -5394,6 +5432,7 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
                     case TypeTableEntryIdUndefLit:
                     case TypeTableEntryIdNullLit:
                     case TypeTableEntryIdNamespace:
+                    case TypeTableEntryIdBlock:
                     case TypeTableEntryIdGenericFn:
                     case TypeTableEntryIdVar:
                         add_node_error(g, expr_node,
@@ -6796,6 +6835,9 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn
         case NodeTypeZeroesLiteral:
             return_type = analyze_zeroes_literal_expr(g, import, context, expected_type, node);
             break;
+        case NodeTypeThisLiteral:
+            return_type = analyze_this_literal_expr(g, import, context, expected_type, node);
+            break;
         case NodeTypeSymbol:
             return_type = analyze_symbol_expr(g, import, context, expected_type, node, pointer_only);
             break;
@@ -7085,6 +7127,7 @@ static void scan_decls(CodeGen *g, ImportTableEntry *import, BlockContext *conte
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeSymbol:
         case NodeTypePrefixOpExpr:
         case NodeTypeIfBoolExpr:
@@ -7347,6 +7390,8 @@ Expr *get_resolved_expr(AstNode *node) {
             return &node->data.undefined_literal.resolved_expr;
         case NodeTypeZeroesLiteral:
             return &node->data.zeroes_literal.resolved_expr;
+        case NodeTypeThisLiteral:
+            return &node->data.this_literal.resolved_expr;
         case NodeTypeGoto:
             return &node->data.goto_expr.resolved_expr;
         case NodeTypeBreak:
@@ -7432,6 +7477,7 @@ static TopLevelDecl *get_as_top_level_decl(AstNode *node) {
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeLabel:
         case NodeTypeGoto:
         case NodeTypeBreak:
@@ -7499,6 +7545,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) {
         case TypeTableEntryIdUndefLit:
         case TypeTableEntryIdNullLit:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdVar:
              zig_unreachable();
@@ -7640,6 +7687,8 @@ static uint32_t hash_const_val(TypeTableEntry *type, ConstExprValue *const_val)
             return hash_ptr(const_val->data.x_type);
         case TypeTableEntryIdNamespace:
             return hash_ptr(const_val->data.x_import);
+        case TypeTableEntryIdBlock:
+            return hash_ptr(const_val->data.x_block);
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdInvalid:
         case TypeTableEntryIdUnreachable:
@@ -7715,6 +7764,7 @@ static TypeTableEntry *type_of_first_thing_in_memory(TypeTableEntry *type_entry)
         case TypeTableEntryIdMetaType:
         case TypeTableEntryIdVoid:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdVar:
             zig_unreachable();
src/ast_render.cpp
@@ -173,6 +173,8 @@ static const char *node_type_str(NodeType node_type) {
             return "UndefinedLiteral";
         case NodeTypeZeroesLiteral:
             return "ZeroesLiteral";
+        case NodeTypeThisLiteral:
+            return "ThisLiteral";
         case NodeTypeIfBoolExpr:
             return "IfBoolExpr";
         case NodeTypeIfVarExpr:
@@ -597,6 +599,8 @@ static void render_node(AstRender *ar, AstNode *node) {
             zig_panic("TODO");
         case NodeTypeZeroesLiteral:
             zig_panic("TODO");
+        case NodeTypeThisLiteral:
+            zig_panic("TODO");
         case NodeTypeIfBoolExpr:
             zig_panic("TODO");
         case NodeTypeIfVarExpr:
src/codegen.cpp
@@ -3605,6 +3605,7 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeErrorType:
         case NodeTypeTypeLiteral:
         case NodeTypeArrayType:
@@ -3826,6 +3827,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, TypeTableEntry *type_entry, ConstE
         case TypeTableEntryIdNullLit:
         case TypeTableEntryIdVoid:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdVar:
             zig_unreachable();
@@ -4349,6 +4351,13 @@ static void define_builtin_types(CodeGen *g) {
         entry->deep_const = true;
         g->builtin_types.entry_namespace = entry;
     }
+    {
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdBlock);
+        buf_init_from_str(&entry->name, "(block)");
+        entry->zero_bits = true;
+        entry->deep_const = true;
+        g->builtin_types.entry_block = entry;
+    }
     {
         TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdNumLitFloat);
         buf_init_from_str(&entry->name, "(float literal)");
@@ -5145,6 +5154,7 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) {
         case TypeTableEntryIdMetaType:
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBlock:
         case TypeTableEntryIdNumLitFloat:
         case TypeTableEntryIdNumLitInt:
         case TypeTableEntryIdUndefLit:
src/eval.cpp
@@ -83,6 +83,7 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b, TypeTableEntry *ty
             zig_panic("TODO");
         case TypeTableEntryIdNamespace:
             zig_panic("TODO");
+        case TypeTableEntryIdBlock:
             zig_panic("TODO");
         case TypeTableEntryIdGenericFn:
         case TypeTableEntryIdInvalid:
@@ -1373,6 +1374,7 @@ static bool eval_expr(EvalFn *ef, AstNode *node, ConstExprValue *out) {
         case NodeTypeNullLiteral:
         case NodeTypeUndefinedLiteral:
         case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
         case NodeTypeIfVarExpr:
         case NodeTypeSwitchExpr:
         case NodeTypeSwitchProng:
src/parser.cpp
@@ -620,7 +620,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m
 
 /*
 PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." "Symbol")
-KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "zeroes" | "error" | "type"
+KeywordLiteral = "true" | "false" | "null" | "break" | "continue" | "undefined" | "zeroes" | "error" | "type" | "this"
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -672,6 +672,10 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
         AstNode *node = ast_create_node(pc, NodeTypeZeroesLiteral, token);
         *token_index += 1;
         return node;
+    } else if (token->id == TokenIdKeywordThis) {
+        AstNode *node = ast_create_node(pc, NodeTypeThisLiteral, token);
+        *token_index += 1;
+        return node;
     } else if (token->id == TokenIdKeywordType) {
         AstNode *node = ast_create_node(pc, NodeTypeTypeLiteral, token);
         *token_index += 1;
@@ -2560,6 +2564,9 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
         case NodeTypeZeroesLiteral:
             // none
             break;
+        case NodeTypeThisLiteral:
+            // none
+            break;
         case NodeTypeIfBoolExpr:
             visit_field(&node->data.if_bool_expr.condition, visit, context);
             visit_field(&node->data.if_bool_expr.then_block, visit, context);
@@ -2840,6 +2847,9 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
         case NodeTypeZeroesLiteral:
             // none
             break;
+        case NodeTypeThisLiteral:
+            // none
+            break;
         case NodeTypeIfBoolExpr:
             clone_subtree_field(&new_node->data.if_bool_expr.condition, old_node->data.if_bool_expr.condition, next_node_index);
             clone_subtree_field(&new_node->data.if_bool_expr.then_block, old_node->data.if_bool_expr.then_block, next_node_index);
src/tokenizer.cpp
@@ -129,6 +129,7 @@ static const struct ZigKeyword zig_keywords[] = {
     {"return", TokenIdKeywordReturn},
     {"struct", TokenIdKeywordStruct},
     {"switch", TokenIdKeywordSwitch},
+    {"this", TokenIdKeywordThis},
     {"true", TokenIdKeywordTrue},
     {"type", TokenIdKeywordType},
     {"undefined", TokenIdKeywordUndefined},
@@ -1470,6 +1471,7 @@ const char * token_name(TokenId id) {
         case TokenIdKeywordSwitch: return "switch";
         case TokenIdKeywordUndefined: return "undefined";
         case TokenIdKeywordZeroes: return "zeroes";
+        case TokenIdKeywordThis: return "this";
         case TokenIdKeywordError: return "error";
         case TokenIdKeywordType: return "type";
         case TokenIdKeywordInline: return "inline";
src/tokenizer.hpp
@@ -45,6 +45,7 @@ enum TokenId {
     TokenIdKeywordType,
     TokenIdKeywordInline,
     TokenIdKeywordDefer,
+    TokenIdKeywordThis,
     TokenIdLParen,
     TokenIdRParen,
     TokenIdComma,
std/hash_map.zig
@@ -24,7 +24,7 @@ pub struct SmallHashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b
     // this is used to detect bugs where a hashtable is edited while an iterator is running.
     modification_count: debug_u32,
 
-    const Self = SmallHashMap(K, V, hash, eql, static_size);
+    const Self = this;
 
     pub struct Entry {
         used: bool,
std/list.zig
@@ -4,7 +4,7 @@ const mem = @import("mem.zig");
 const Allocator = mem.Allocator;
 
 pub struct List(T: type) {
-    const Self = List(T);
+    const Self = this;
 
     items: []T,
     len: usize,
std/rand.zig
@@ -101,7 +101,7 @@ struct MersenneTwister(
     t: int, c: int,
     l: int, f: int)
 {
-    const Self = MersenneTwister(int, n, m, r, a, u, d, s, b, t, c, l, f);
+    const Self = this;
 
     array: [n]int,
     index: usize,
test/cases/this.zig
@@ -0,0 +1,47 @@
+const assert = @import("std").debug.assert;
+const module = this;
+
+struct Point(inline T: type) {
+    const Self = this;
+    x: T,
+    y: T,
+
+    fn addOne(self: &Self) {
+        self.x += 1;
+        self.y += 1;
+    }
+}
+
+fn add(x: i32, y: i32) -> i32 {
+    x + y
+}
+
+fn factorial(x: i32) -> i32 {
+    const selfFn = this;
+    if (x == 0) {
+        1
+    } else {
+        x * selfFn(x - 1)
+    }
+}
+
+#attribute("test")
+fn thisReferToModuleCallPrivateFn() {
+    assert(module.add(1, 2) == 3);
+}
+
+#attribute("test")
+fn thisReferToContainer() {
+    var pt = Point(i32) {
+        .x = 12,
+        .y = 34,
+    };
+    pt.addOne();
+    assert(pt.x == 13);
+    assert(pt.y == 35);
+}
+
+#attribute("test")
+fn thisReferToFn() {
+    assert(factorial(5) == 120);
+}
test/run_tests.cpp
@@ -1597,6 +1597,22 @@ const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT;
 var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1);
     )SOURCE", 1, ".tmp_source.zig:4:60: error: unable to perform binary not operation on type '(integer literal)'");
 
+    {
+        TestCase *tc = add_compile_fail_case("multiple files with private function error", R"SOURCE(
+const foo = @import("foo.zig");
+
+fn callPrivFunction() {
+    foo.privateFunction();
+}
+        )SOURCE", 2, 
+            ".tmp_source.zig:5:8: error: 'privateFunction' is private",
+            "foo.zig:2:1: note: declared here");
+
+        add_source_file(tc, "foo.zig", R"SOURCE(
+fn privateFunction() { }
+        )SOURCE");
+    }
+
 }
 
 //////////////////////////////////////////////////////////////////////////////
test/self_hosted.zig
@@ -15,6 +15,7 @@ const test_switch_prong_implicit_cast = @import("cases/switch_prong_implicit_cas
 const test_switch_prong_err_enum = @import("cases/switch_prong_err_enum.zig");
 const test_enum_with_members = @import("cases/enum_with_members.zig");
 const test_struct_contains_slice_of_itself = @import("cases/struct_contains_slice_of_itself.zig");
+const test_this = @import("cases/this.zig");
 
 // normal comment
 /// this is a documentation comment