Commit 6fbe1632d0

Andrew Kelley <superjoe30@gmail.com>
2017-04-06 11:34:04
Update zig build system to support user defined options
* Fix assertion failure when switching on type. Closes #310 * Update zig build system to support user defined options. See #204 * fmt.format supports {sNNN} to set padding for a buffer arg. * add std.fmt.bufPrint and std.fmt.allocPrint * std.hash_map.HashMap.put returns the previous value * add std.mem.startsWith
1 parent d15bcdc
src/ir.cpp
@@ -57,6 +57,7 @@ static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *ins
 static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, TypeTableEntry *expected_type);
 
 ConstExprValue *const_ptr_pointee(ConstExprValue *const_val) {
+    assert(const_val->type->id == TypeTableEntryIdPointer);
     assert(const_val->special == ConstValSpecialStatic);
     switch (const_val->data.x_ptr.special) {
         case ConstPtrSpecialInvalid:
@@ -10350,10 +10351,21 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira,
     if (type_is_invalid(target_value_ptr->value.type))
         return ira->codegen->builtin_types.entry_invalid;
 
+    if (target_value_ptr->value.type->id == TypeTableEntryIdMetaType) {
+        assert(instr_is_comptime(target_value_ptr));
+        TypeTableEntry *ptr_type = target_value_ptr->value.data.x_type;
+        assert(ptr_type->id == TypeTableEntryIdPointer);
+        ConstExprValue *out_val = ir_build_const_from(ira, &switch_target_instruction->base);
+        out_val->type = ira->codegen->builtin_types.entry_type;
+        out_val->data.x_type = ptr_type->data.pointer.child_type;
+        return out_val->type;
+    }
+
     assert(target_value_ptr->value.type->id == TypeTableEntryIdPointer);
+
     TypeTableEntry *target_type = target_value_ptr->value.type->data.pointer.child_type;
     ConstExprValue *pointee_val = nullptr;
-    if (target_value_ptr->value.special != ConstValSpecialRuntime) {
+    if (instr_is_comptime(target_value_ptr)) {
         pointee_val = const_ptr_pointee(&target_value_ptr->value);
         if (pointee_val->special == ConstValSpecialRuntime)
             pointee_val = nullptr;
src/main.cpp
@@ -167,9 +167,8 @@ int main(int argc, char **argv) {
         ZigList<const char *> args = {0};
         args.append(zig_exe_path);
         for (int i = 2; i < argc; i += 1) {
-            if (strcmp(argv[i], "--verbose") == 0) {
+            if (strcmp(argv[i], "--debug-build-verbose") == 0) {
                 verbose = true;
-                args.append(argv[i]);
             } else {
                 args.append(argv[i]);
             }
std/special/build_runner.zig
@@ -1,26 +1,106 @@
 const root = @import("@build");
 const std = @import("std");
 const io = std.io;
+const fmt = std.fmt;
 const os = std.os;
 const Builder = std.build.Builder;
 const mem = std.mem;
+const List = std.list.List;
 
 error InvalidArgs;
 
 pub fn main() -> %void {
-    if (os.args.count() < 2) {
-        %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
-        return error.InvalidArgs;
-    }
-    const zig_exe = os.args.at(1);
-    const leftover_arg_index = 2;
-
     // TODO use a more general purpose allocator here
     var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024);
     defer inc_allocator.deinit();
 
-    var builder = Builder.init(zig_exe, &inc_allocator.allocator);
+    const allocator = &inc_allocator.allocator;
+
+    var builder = Builder.init(allocator);
     defer builder.deinit();
+
+    var maybe_zig_exe: ?[]const u8 = null;
+    var targets = List([]const u8).init(allocator);
+
+    var arg_i: usize = 1;
+    while (arg_i < os.args.count(); arg_i += 1) {
+        const arg = os.args.at(arg_i);
+        if (mem.startsWith(u8, arg, "-O")) {
+            const option_contents = arg[2...];
+            if (option_contents.len == 0) {
+                %%io.stderr.printf("Expected option name after '-O'\n\n");
+                return usage(&builder, maybe_zig_exe, false, &io.stderr);
+            }
+            if (const name_end ?= mem.indexOfScalar(u8, option_contents, '=')) {
+                const option_name = option_contents[0...name_end];
+                const option_value = option_contents[name_end...];
+                if (builder.addUserInputOption(option_name, option_value))
+                    return usage(&builder, maybe_zig_exe, false, &io.stderr);
+            } else {
+                if (builder.addUserInputFlag(option_contents))
+                    return usage(&builder, maybe_zig_exe, false, &io.stderr);
+            }
+        } else if (mem.startsWith(u8, arg, "-")) {
+            if (mem.eql(u8, arg, "--verbose")) {
+                builder.verbose = true;
+            } else if (mem.eql(u8, arg, "--help")) {
+                 return usage(&builder, maybe_zig_exe, false, &io.stdout);
+            } else {
+                %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
+                return usage(&builder, maybe_zig_exe, false, &io.stderr);
+            }
+        } else if (maybe_zig_exe == null) {
+            maybe_zig_exe = arg;
+        } else {
+            %%targets.append(arg);
+        }
+    }
+
+    const zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
+
     root.build(&builder);
-    %return builder.make(leftover_arg_index);
+
+    if (builder.validateUserInputDidItFail())
+        return usage(&builder, maybe_zig_exe, true, &io.stderr);
+
+    %return builder.make(zig_exe, targets.toSliceConst());
+}
+
+fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, out_stream: &io.OutStream) -> %void {
+    const zig_exe = maybe_zig_exe ?? {
+        %%out_stream.printf("Expected first argument to be path to zig compiler\n");
+        return error.InvalidArgs;
+    };
+
+    // run the build script to collect the options
+    if (!already_ran_build) {
+        root.build(builder);
+    }
+
+    %%out_stream.printf(
+        \\Usage: {} build [options]
+        \\
+        \\General Options:
+        \\  --help                 Print this help and exit.
+        \\  --verbose              Print commands before executing them.
+        \\  --debug-build-verbose  Print verbose debugging information for the build system itself.
+        \\
+        \\Project-Specific Options:
+        \\
+    , zig_exe);
+
+    if (builder.available_options_list.len == 0) {
+        %%out_stream.printf("  (none)\n");
+    } else {
+        const allocator = builder.allocator;
+        for (builder.available_options_list.toSliceConst()) |option| {
+            const name = %%fmt.allocPrint(allocator,
+                "  -O{}=({})", option.name, Builder.typeIdName(option.type_id));
+            defer allocator.free(name);
+            %%out_stream.printf("{s24} {}\n", name, option.description);
+        }
+    }
+
+    if (out_stream == &io.stderr)
+        return error.InvalidArgs;
 }
std/buf_map.zig
@@ -11,14 +11,13 @@ pub const BufMap = struct {
 
     pub fn init(allocator: &Allocator) -> BufMap {
         var self = BufMap {
-            .hash_map = undefined,
+            .hash_map = BufMapHashMap.init(allocator),
         };
-        self.hash_map.init(allocator);
         return self;
     }
 
     pub fn deinit(self: &BufMap) {
-        var it = self.hash_map.entryIterator();
+        var it = self.hash_map.iterator();
         while (true) {
             const entry = it.next() ?? break; 
             self.free(entry.key);
@@ -54,7 +53,7 @@ pub const BufMap = struct {
     }
 
     pub fn iterator(self: &const BufMap) -> BufMapHashMap.Iterator {
-        return self.hash_map.entryIterator();
+        return self.hash_map.iterator();
     }
 
     fn free(self: &BufMap, value: []const u8) {
std/buf_set.zig
@@ -9,14 +9,13 @@ pub const BufSet = struct {
 
     pub fn init(allocator: &Allocator) -> BufSet {
         var self = BufSet {
-            .hash_map = undefined,
+            .hash_map = BufSetHashMap.init(allocator),
         };
-        self.hash_map.init(allocator);
         return self;
     }
 
     pub fn deinit(self: &BufSet) {
-        var it = self.hash_map.entryIterator();
+        var it = self.hash_map.iterator();
         while (true) {
             const entry = it.next() ?? break; 
             self.free(entry.key);
@@ -43,7 +42,7 @@ pub const BufSet = struct {
     }
 
     pub fn iterator(self: &const BufSet) -> BufSetHashMap.Iterator {
-        return self.hash_map.entryIterator();
+        return self.hash_map.iterator();
     }
 
     fn free(self: &BufSet, value: []const u8) {
std/build.zig
@@ -2,6 +2,7 @@ const io = @import("io.zig");
 const mem = @import("mem.zig");
 const debug = @import("debug.zig");
 const List = @import("list.zig").List;
+const HashMap = @import("hash_map.zig").HashMap;
 const Allocator = @import("mem.zig").Allocator;
 const os = @import("os/index.zig");
 const StdIo = os.ChildProcess.StdIo;
@@ -12,21 +13,58 @@ error ExtraArg;
 error UncleanExit;
 
 pub const Builder = struct {
-    zig_exe: []const u8,
     allocator: &Allocator,
     exe_list: List(&Exe),
     lib_paths: List([]const u8),
     include_paths: List([]const u8),
     rpaths: List([]const u8),
+    user_input_options: UserInputOptionsMap,
+    available_options_map: AvailableOptionsMap,
+    available_options_list: List(AvailableOption),
+    verbose: bool,
+    invalid_user_input: bool,
+
+    const UserInputOptionsMap = HashMap([]const u8, UserInputOption, mem.hash_slice_u8, mem.eql_slice_u8);
+    const AvailableOptionsMap = HashMap([]const u8, AvailableOption, mem.hash_slice_u8, mem.eql_slice_u8);
+
+    const AvailableOption = struct {
+        name: []const u8,
+        type_id: TypeId,
+        description: []const u8,
+    };
+
+    const UserInputOption = struct {
+        name: []const u8,
+        value: UserValue,
+        used: bool,
+    };
+
+    const UserValue = enum {
+        Flag,
+        Scalar: []const u8,
+        List: List([]const u8),
+    };
+
+    const TypeId = enum {
+        Bool,
+        Int,
+        Float,
+        String,
+        List,
+    };
 
-    pub fn init(zig_exe: []const u8, allocator: &Allocator) -> Builder {
+    pub fn init(allocator: &Allocator) -> Builder {
         var self = Builder {
-            .zig_exe = zig_exe,
+            .verbose = false,
+            .invalid_user_input = false,
             .allocator = allocator,
             .exe_list = List(&Exe).init(allocator),
             .lib_paths = List([]const u8).init(allocator),
             .include_paths = List([]const u8).init(allocator),
             .rpaths = List([]const u8).init(allocator),
+            .user_input_options = UserInputOptionsMap.init(allocator),
+            .available_options_map = AvailableOptionsMap.init(allocator),
+            .available_options_list = List(AvailableOption).init(allocator),
         };
         self.processNixOSEnvVars();
         return self;
@@ -46,6 +84,8 @@ pub const Builder = struct {
     pub fn addExeErr(self: &Builder, root_src: []const u8, name: []const u8) -> %&Exe {
         const exe = %return self.allocator.create(Exe);
         *exe = Exe {
+            .verbose = false,
+            .release = false,
             .root_src = root_src,
             .name = name,
             .target = Target.Native,
@@ -68,20 +108,13 @@ pub const Builder = struct {
         %%self.lib_paths.append(path);
     }
 
-    pub fn make(self: &Builder, leftover_arg_index: usize) -> %void {
+    pub fn make(self: &Builder, zig_exe: []const u8, targets: []const []const u8) -> %void {
+        if (targets.len != 0) {
+            debug.panic("TODO non default targets");
+        }
+
         var env_map = %return os.getEnvMap(self.allocator);
 
-        var verbose = false;
-        var arg_i: usize = leftover_arg_index;
-        while (arg_i < os.args.count(); arg_i += 1) {
-            const arg = os.args.at(arg_i);
-            if (mem.eql(u8, arg, "--verbose")) {
-                verbose = true;
-            } else {
-                %%io.stderr.printf("Unrecognized argument: '{}'\n", arg);
-                return error.ExtraArg;
-            }
-        }
         for (self.exe_list.toSlice()) |exe| {
             var zig_args = List([]const u8).init(self.allocator);
             defer zig_args.deinit();
@@ -89,10 +122,14 @@ pub const Builder = struct {
             %return zig_args.append("build_exe"[0...]); // TODO issue #296
             %return zig_args.append(exe.root_src);
 
-            if (verbose) {
+            if (exe.verbose) {
                 %return zig_args.append("--verbose"[0...]); // TODO issue #296
             }
 
+            if (exe.release) {
+                %return zig_args.append("--release"[0...]); // TODO issue #296
+            }
+
             %return zig_args.append("--name"[0...]); // TODO issue #296
             %return zig_args.append(exe.name);
 
@@ -149,22 +186,21 @@ pub const Builder = struct {
                 %return zig_args.append(lib_path);
             }
 
+            if (self.verbose) {
+                printInvocation(zig_exe, zig_args);
+            }
             // TODO issue #301
-            var child = os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), &env_map,
+            var child = os.ChildProcess.spawn(zig_exe, zig_args.toSliceConst(), &env_map,
                 StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator)
                 %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err));
             const term = %%child.wait();
-            const exe_result = switch (term) {
+            switch (term) {
                 Term.Clean => |code| {
                     if (code != 0) {
-                        %%io.stderr.printf("\nCompile failed with code {}. To reproduce:\n", code);
-                        printInvocation(self.zig_exe, zig_args);
                         return error.UncleanExit;
                     }
                 },
                 else => {
-                    %%io.stderr.printf("\nCompile crashed. To reproduce:\n");
-                    printInvocation(self.zig_exe, zig_args);
                     return error.UncleanExit;
                 },
             };
@@ -208,6 +244,144 @@ pub const Builder = struct {
             }
         }
     }
+
+    pub fn option(self: &Builder, comptime T: type, name: []const u8, description: []const u8) -> ?T {
+        const type_id = typeToEnum(T);
+        const available_option = AvailableOption {
+            .name = name,
+            .type_id = type_id,
+            .description = description,
+        };
+        if (const _ ?= %%self.available_options_map.put(name, available_option)) {
+            debug.panic("Option '{}' declared twice", name);
+        }
+        %%self.available_options_list.append(available_option);
+
+        const entry = self.user_input_options.get(name) ?? return null;
+        entry.value.used = true;
+        switch (type_id) {
+            TypeId.Bool => switch (entry.value.value) {
+                UserValue.Flag => return true,
+                UserValue.Scalar => |s| {
+                    if (mem.eql(u8, s, "true")) {
+                        return true;
+                    } else if (mem.eql(u8, s, "false")) {
+                        return false;
+                    } else {
+                        %%io.stderr.printf("Expected -O{} to be a boolean, but received '{}'\n", name, s);
+                        self.markInvalidUserInput();
+                        return null;
+                    }
+                },
+                UserValue.List => {
+                    %%io.stderr.printf("Expected -O{} to be a boolean, but received a list.\n", name);
+                    self.markInvalidUserInput();
+                    return null;
+                },
+            },
+            TypeId.Int => debug.panic("TODO integer options to build script"),
+            TypeId.Float => debug.panic("TODO float options to build script"),
+            TypeId.String => debug.panic("TODO string options to build script"),
+            TypeId.List => debug.panic("TODO list options to build script"),
+        }
+    }
+
+    pub fn addUserInputOption(self: &Builder, name: []const u8, value: []const u8) -> bool {
+        if (var prev_value ?= %%self.user_input_options.put(name, UserInputOption {
+            .name = name,
+            .value = UserValue.Scalar { value },
+            .used = false,
+        })) {
+            switch (prev_value.value) {
+                UserValue.Scalar => |s| {
+                    var list = List([]const u8).init(self.allocator);
+                    %%list.append(s);
+                    %%list.append(value);
+                    %%self.user_input_options.put(name, UserInputOption {
+                        .name = name,
+                        .value = UserValue.List { list },
+                        .used = false,
+                    });
+                },
+                UserValue.List => |*list| {
+                    %%list.append(value);
+                    %%self.user_input_options.put(name, UserInputOption {
+                        .name = name,
+                        .value = UserValue.List { *list },
+                        .used = false,
+                    });
+                },
+                UserValue.Flag => {
+                    %%io.stderr.printf("Option '-O{}={}' conflicts with flag '-O{}'.\n", name, value, name);
+                    return true;
+                },
+            }
+        }
+        return false;
+    }
+
+    pub fn addUserInputFlag(self: &Builder, name: []const u8) -> bool {
+        if (const prev_value ?= %%self.user_input_options.put(name, UserInputOption {
+            .name = name,
+            .value = UserValue.Flag,
+            .used = false,
+        })) {
+            switch (prev_value.value) {
+                UserValue.Scalar => |s| {
+                    %%io.stderr.printf("Flag '-O{}' conflicts with option '-O{}={}'.\n", name, name, s);
+                    return true;
+                },
+                UserValue.List => {
+                    %%io.stderr.printf("Flag '-O{}' conflicts with multiple options of the same name.\n", name);
+                    return true;
+                },
+                UserValue.Flag => {},
+            }
+        }
+        return false;
+    }
+
+    fn typeToEnum(comptime T: type) -> TypeId {
+        if (@isInteger(T)) {
+            TypeId.Int
+        } else if (@isFloat(T)) {
+            TypeId.Float
+        } else switch (T) {
+            bool => TypeId.Bool,
+            []const u8 => TypeId.String,
+            []const []const u8 => TypeId.List,
+            else => @compileError("Unsupported type: " ++ @typeName(T)),
+        }
+    }
+
+    fn markInvalidUserInput(self: &Builder) {
+        self.invalid_user_input = true;
+    }
+
+    pub fn typeIdName(id: TypeId) -> []const u8 {
+        return switch (id) {
+            TypeId.Bool => ([]const u8)("bool"), // TODO issue #125
+            TypeId.Int => ([]const u8)("int"), // TODO issue #125
+            TypeId.Float => ([]const u8)("float"), // TODO issue #125
+            TypeId.String => ([]const u8)("string"), // TODO issue #125
+            TypeId.List => ([]const u8)("list"), // TODO issue #125
+        };
+    }
+
+    pub fn validateUserInputDidItFail(self: &Builder) -> bool {
+        // make sure all args are used
+        var it = self.user_input_options.iterator();
+        while (true) {
+            const entry = it.next() ?? break;
+            if (!entry.value.used) {
+                %%io.stderr.printf("Invalid option: -O{}\n\n", entry.key);
+                self.markInvalidUserInput();
+            }
+        }
+
+        return self.invalid_user_input;
+    }
+
 };
 
 const CrossTarget = struct {
@@ -233,12 +407,14 @@ const Exe = struct {
     target: Target,
     linker_script: LinkerScript,
     link_libs: BufSet,
+    verbose: bool,
+    release: bool,
 
-    fn deinit(self: &Exe) {
+    pub fn deinit(self: &Exe) {
         self.link_libs.deinit();
     }
 
-    fn setTarget(self: &Exe, target_arch: Arch, target_os: Os, target_environ: Environ) {
+    pub fn setTarget(self: &Exe, target_arch: Arch, target_os: Os, target_environ: Environ) {
         self.target = Target.Cross {
             CrossTarget {
                 .arch = target_arch,
@@ -250,17 +426,25 @@ const Exe = struct {
 
     /// Exe keeps a reference to script for its lifetime or until this function
     /// is called again.
-    fn setLinkerScriptContents(self: &Exe, script: []const u8) {
+    pub fn setLinkerScriptContents(self: &Exe, script: []const u8) {
         self.linker_script = LinkerScript.Embed { script };
     }
 
-    fn setLinkerScriptPath(self: &Exe, path: []const u8) {
+    pub fn setLinkerScriptPath(self: &Exe, path: []const u8) {
         self.linker_script = LinkerScript.Path { path };
     }
 
-    fn linkLibrary(self: &Exe, name: []const u8) {
+    pub fn linkLibrary(self: &Exe, name: []const u8) {
         %%self.link_libs.put(name);
     }
+
+    pub fn setVerbose(self: &Exe, value: bool) {
+        self.verbose = value;
+    }
+
+    pub fn setRelease(self: &Exe, value: bool) {
+        self.release = value;
+    }
 };
 
 fn handleErr(err: error) -> noreturn {
@@ -401,3 +585,4 @@ fn targetEnvironName(target_environ: Environ) -> []const u8 {
         Environ.coreclr => ([]const u8)("coreclr"),
     };
 }
+
std/fmt.zig
@@ -13,6 +13,8 @@ const State = enum { // TODO put inside format function and make sure the name a
     Integer,
     IntegerWidth,
     Character,
+    Buf,
+    BufWidth,
 };
 
 /// Renders fmt string with args, calling output with slices of bytes.
@@ -82,8 +84,21 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
                 'c' => {
                     state = State.Character;
                 },
+                's' => {
+                    state = State.Buf;
+                },
                 else => @compileError("Unknown format character: " ++ []u8{c}),
             },
+            State.Buf => switch (c) {
+                '}' => {
+                    return output(context, args[next_arg]);
+                },
+                '0' ... '9' => {
+                    width_start = i;
+                    state = State.BufWidth;
+                },
+                else => @compileError("Unexpected character in format string: " ++ []u8{c}),
+            },
             State.CloseBrace => switch (c) {
                 '}' => {
                     state = State.Start;
@@ -117,6 +132,18 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
                 '0' ... '9' => {},
                 else => @compileError("Unexpected character in format string: " ++ []u8{c}),
             },
+            State.BufWidth => switch (c) {
+                '}' => {
+                    width = comptime %%parseUnsigned(usize, fmt[width_start...i], 10);
+                    if (!formatBuf(args[next_arg], width, context, output))
+                        return false;
+                    next_arg += 1;
+                    state = State.Start;
+                    start_index = i + 1;
+                },
+                '0' ... '9' => {},
+                else => @compileError("Unexpected character in format string: " ++ []u8{c}),
+            },
             State.Character => switch (c) {
                 '}' => {
                     if (!formatAsciiChar(args[next_arg], context, output))
@@ -165,6 +192,23 @@ pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const
     return output(context, (&c)[0...1]);
 }
 
+pub fn formatBuf(buf: []const u8, width: usize,
+    context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
+{
+    if (!output(context, buf))
+        return false;
+
+    var leftover_padding = if (width > buf.len) (width - buf.len) else return true;
+    const pad_byte: u8 = ' ';
+    while (leftover_padding > 0; leftover_padding -= 1) {
+        if (!output(context, (&pad_byte)[0...1]))
+            return false;
+    }
+
+    return true;
+}
+
+
 pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
     context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
 {
@@ -291,6 +335,34 @@ fn digitToChar(digit: u8, uppercase: bool) -> u8 {
     };
 }
 
+const BufPrintContext = struct {
+    remaining: []u8,
+};
+
+fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> bool {
+    mem.copy(u8, context.remaining, bytes);
+    context.remaining = context.remaining[bytes.len...];
+    return true;
+}
+
+pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) {
+    var context = BufPrintContext { .remaining = buf, };
+    _ = format(&context, bufPrintWrite, fmt, args);
+}
+
+pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) -> %[]u8 {
+    var size: usize = 0;
+    _ = format(&size, countSize, fmt, args);
+    const buf = %return allocator.alloc(u8, size);
+    bufPrint(buf, fmt, args);
+    return buf;
+}
+
+fn countSize(size: &usize, bytes: []const u8) -> bool {
+    *size += bytes.len;
+    return true;
+}
+
 test "testBufPrintInt" {
     var buffer: [max_int_digits]u8 = undefined;
     const buf = buffer[0...];
std/hash_map.zig
@@ -54,13 +54,15 @@ pub fn HashMap(comptime K: type, comptime V: type,
             }
         };
 
-        pub fn init(hm: &Self, allocator: &Allocator) {
-            hm.entries = []Entry{};
-            hm.allocator = allocator;
-            hm.size = 0;
-            hm.max_distance_from_start_index = 0;
-            // it doesn't actually matter what we set this to since we use wrapping integer arithmetic
-            hm.modification_count = undefined;
+        pub fn init(allocator: &Allocator) -> Self {
+            Self {
+                .entries = []Entry{},
+                .allocator = allocator,
+                .size = 0,
+                .max_distance_from_start_index = 0,
+                // it doesn't actually matter what we set this to since we use wrapping integer arithmetic
+                .modification_count = undefined,
+            }
         }
 
         pub fn deinit(hm: &Self) {
@@ -76,7 +78,8 @@ pub fn HashMap(comptime K: type, comptime V: type,
             hm.incrementModificationCount();
         }
 
-        pub fn put(hm: &Self, key: K, value: V) -> %void {
+        /// Returns the value that was already there.
+        pub fn put(hm: &Self, key: K, value: &const V) -> %?V {
             if (hm.entries.len == 0) {
                 %return hm.initCapacity(16);
             }
@@ -89,13 +92,13 @@ pub fn HashMap(comptime K: type, comptime V: type,
                 // dump all of the old elements into the new table
                 for (old_entries) |*old_entry| {
                     if (old_entry.used) {
-                        hm.internalPut(old_entry.key, old_entry.value);
+                        _ = hm.internalPut(old_entry.key, old_entry.value);
                     }
                 }
                 hm.allocator.free(old_entries);
             }
 
-            hm.internalPut(key, value);
+            return hm.internalPut(key, value);
         }
 
         pub fn get(hm: &Self, key: K) -> ?&Entry {
@@ -134,7 +137,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
             return null;
         }
 
-        pub fn entryIterator(hm: &const Self) -> Iterator {
+        pub fn iterator(hm: &const Self) -> Iterator {
             return Iterator {
                 .hm = hm,
                 .count = 0,
@@ -158,9 +161,10 @@ pub fn HashMap(comptime K: type, comptime V: type,
             }
         }
 
-        fn internalPut(hm: &Self, orig_key: K, orig_value: V) {
+        /// Returns the value that was already there.
+        fn internalPut(hm: &Self, orig_key: K, orig_value: &const V) -> ?V {
             var key = orig_key;
-            var value = orig_value;
+            var value = *orig_value;
             const start_index = hm.keyToIndex(key);
             var roll_over: usize = 0;
             var distance_from_start_index: usize = 0;
@@ -187,7 +191,10 @@ pub fn HashMap(comptime K: type, comptime V: type,
                     continue;
                 }
 
-                if (!entry.used) {
+                var result: ?V = null;
+                if (entry.used) {
+                    result = entry.value;
+                } else {
                     // adding an entry. otherwise overwriting old value with
                     // same key
                     hm.size += 1;
@@ -200,7 +207,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
                     .key = key,
                     .value = value,
                 };
-                return;
+                return result;
             }
             unreachable // put into a full map
         }
@@ -224,15 +231,18 @@ pub fn HashMap(comptime K: type, comptime V: type,
 }
 
 test "basicHashMapTest" {
-    var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;
-    map.init(&debug.global_allocator);
+    var map = HashMap(i32, i32, hash_i32, eql_i32).init(&debug.global_allocator);
     defer map.deinit();
 
-    %%map.put(1, 11);
-    %%map.put(2, 22);
-    %%map.put(3, 33);
-    %%map.put(4, 44);
-    %%map.put(5, 55);
+    // TODO issue #311
+    assert(%%map.put(1, i32(11)) == null);
+    assert(%%map.put(2, i32(22)) == null);
+    assert(%%map.put(3, i32(33)) == null);
+    assert(%%map.put(4, i32(44)) == null);
+    assert(%%map.put(5, i32(55)) == null);
+
+    assert(??%%map.put(5, i32(66)) == 55);
+    assert(??%%map.put(5, i32(55)) == 66);
 
     assert((??map.get(2)).value == 22);
     _ = map.remove(2);
@@ -243,6 +253,7 @@ test "basicHashMapTest" {
 fn hash_i32(x: i32) -> u32 {
     *@ptrcast(&u32, &x)
 }
+
 fn eql_i32(a: i32, b: i32) -> bool {
     a == b
 }
std/mem.zig
@@ -208,6 +208,10 @@ pub fn split(s: []const u8, c: u8) -> SplitIterator {
     }
 }
 
+pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> bool {
+    return if (needle.len > haystack.len) false else eql(T, haystack[0...needle.len], needle);
+}
+
 const SplitIterator = struct {
     s: []const u8,
     c: u8,
test/cases/switch.zig
@@ -138,3 +138,15 @@ fn returnsFalse() -> bool {
 test "switchOnConstEnumWithVar" {
     assert(!returnsFalse());
 }
+
+test "switch on type" {
+    assert(trueIfBoolFalseOtherwise(bool));
+    assert(!trueIfBoolFalseOtherwise(i32));
+}
+
+fn trueIfBoolFalseOtherwise(comptime T: type) -> bool {
+    switch (T) {
+        bool => true,
+        else => false,
+    }
+}