From 5aefabe045ca9c6f961bb5354961fe483085f145 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jan 2018 00:22:33 -0500 Subject: [PATCH] docgen: validate See Also sections See #465 --- doc/docgen.zig | 156 +++++++++++--------- doc/langref.html.in | 338 +++++++------------------------------------- std/hash_map.zig | 4 + 3 files changed, 147 insertions(+), 351 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index d21ed78cb0..80ffeddb1c 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -35,9 +35,10 @@ pub fn main() -> %void { var file_out_stream = io.FileOutStream.init(&out_file); var buffered_out_stream = io.BufferedOutStream.init(&file_out_stream.stream); - const toc = try genToc(allocator, in_file_name, input_file_bytes); + var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); + var toc = try genToc(allocator, &tokenizer); - try genHtml(allocator, toc, &buffered_out_stream.stream); + try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream); try buffered_out_stream.flush(); } @@ -240,21 +241,22 @@ const HeaderOpen = struct { n: usize, }; -const Tag = enum { - Nav, - HeaderOpen, - HeaderClose, +const SeeAlsoItem = struct { + name: []const u8, + token: Token, }; const Node = union(enum) { Content: []const u8, Nav, HeaderOpen: HeaderOpen, + SeeAlso: []const SeeAlsoItem, }; const Toc = struct { nodes: []Node, toc: []u8, + urls: std.HashMap([]const u8, Token, mem.hash_slice_u8, mem.eql_slice_u8), }; const Action = enum { @@ -262,11 +264,9 @@ const Action = enum { Close, }; -fn genToc(allocator: &mem.Allocator, source_file_name: []const u8, input_file_bytes: []const u8) -> %Toc { - var tokenizer = Tokenizer.init(source_file_name, input_file_bytes); - +fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { var urls = std.HashMap([]const u8, Token, mem.hash_slice_u8, mem.eql_slice_u8).init(allocator); - defer urls.deinit(); + %defer urls.deinit(); var header_stack_size: usize = 0; var last_action = Action.Open; @@ -287,89 +287,98 @@ fn genToc(allocator: &mem.Allocator, source_file_name: []const u8, input_file_by switch (token.id) { Token.Id.Eof => { if (header_stack_size != 0) { - return parseError(&tokenizer, token, "unbalanced headers"); + return parseError(tokenizer, token, "unbalanced headers"); } try toc.write(" \n"); break; }, Token.Id.Content => { - try nodes.append(Node {.Content = input_file_bytes[token.start..token.end] }); + try nodes.append(Node {.Content = tokenizer.buffer[token.start..token.end] }); }, Token.Id.BracketOpen => { - const tag_token = try eatToken(&tokenizer, Token.Id.TagContent); - const tag_name = input_file_bytes[tag_token.start..tag_token.end]; + const tag_token = try eatToken(tokenizer, Token.Id.TagContent); + const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; - var tag: Tag = undefined; if (mem.eql(u8, tag_name, "nav")) { - tag = Tag.Nav; + _ = eatToken(tokenizer, Token.Id.BracketClose); + + try nodes.append(Node.Nav); } else if (mem.eql(u8, tag_name, "header_open")) { - tag = Tag.HeaderOpen; + _ = eatToken(tokenizer, Token.Id.Separator); + const content_token = try eatToken(tokenizer, Token.Id.TagContent); + const content = tokenizer.buffer[content_token.start..content_token.end]; + _ = eatToken(tokenizer, Token.Id.BracketClose); + header_stack_size += 1; + + const urlized = try urlize(allocator, content); + try nodes.append(Node{.HeaderOpen = HeaderOpen { + .name = content, + .url = urlized, + .n = header_stack_size, + }}); + if (try urls.put(urlized, tag_token)) |other_tag_token| { + parseError(tokenizer, tag_token, "duplicate header url: #{}", urlized) catch {}; + parseError(tokenizer, other_tag_token, "other tag here") catch {}; + return error.ParseError; + } + if (last_action == Action.Open) { + try toc.writeByte('\n'); + try toc.writeByteNTimes(' ', header_stack_size * 4); + try toc.write("\n"); + } else { + try toc.write("\n"); + last_action = Action.Close; + } + } else if (mem.eql(u8, tag_name, "see_also")) { + var list = std.ArrayList(SeeAlsoItem).init(allocator); + %defer list.deinit(); - switch (tag) { - Tag.HeaderOpen => { - const content = tag_content ?? return parseError(&tokenizer, tag_token, "expected header content"); - const urlized = try urlize(allocator, content); - try nodes.append(Node{.HeaderOpen = HeaderOpen { - .name = content, - .url = urlized, - .n = header_stack_size, - }}); - if (try urls.put(urlized, tag_token)) |other_tag_token| { - parseError(&tokenizer, tag_token, "duplicate header url: #{}", urlized) catch {}; - parseError(&tokenizer, other_tag_token, "other tag here") catch {}; - return error.ParseError; + while (true) { + const see_also_tok = tokenizer.next(); + switch (see_also_tok.id) { + Token.Id.TagContent => { + const content = tokenizer.buffer[see_also_tok.start..see_also_tok.end]; + try list.append(SeeAlsoItem { + .name = content, + .token = see_also_tok, + }); + }, + Token.Id.Separator => {}, + Token.Id.BracketClose => { + try nodes.append(Node {.SeeAlso = list.toOwnedSlice() } ); + break; + }, + else => return parseError(tokenizer, see_also_tok, "invalid see_also token"), } - if (last_action == Action.Open) { - try toc.writeByte('\n'); - try toc.writeByteNTimes(' ', header_stack_size * 4); - try toc.write("\n"); - } else { - try toc.write("\n"); - last_action = Action.Close; - } - }, - Tag.Nav => { - try nodes.append(Node.Nav); - }, + } + } else { + return parseError(tokenizer, tag_token, "unrecognized tag name: {}", tag_name); } }, - else => return parseError(&tokenizer, token, "invalid token"), + else => return parseError(tokenizer, token, "invalid token"), } } return Toc { .nodes = nodes.toOwnedSlice(), .toc = toc_buf.toOwnedSlice(), + .urls = urls, }; } @@ -393,7 +402,7 @@ fn urlize(allocator: &mem.Allocator, input: []const u8) -> %[]u8 { return buf.toOwnedSlice(); } -fn genHtml(allocator: &mem.Allocator, toc: &const Toc, out: &io.OutStream) -> %void { +fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io.OutStream) -> %void { for (toc.nodes) |node| { switch (node) { Node.Content => |data| { @@ -405,6 +414,17 @@ fn genHtml(allocator: &mem.Allocator, toc: &const Toc, out: &io.OutStream) -> %v Node.HeaderOpen => |info| { try out.print("{}\n", info.n, info.url, info.name, info.n); }, + Node.SeeAlso => |items| { + try out.write("

See also:

\n"); + }, } } diff --git a/doc/langref.html.in b/doc/langref.html.in index 1ef979b14b..cdbc371511 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -77,13 +77,7 @@ Hello, world! pub fn main() -> %void { warn("Hello, world!\n"); } -

See also:

- + {#see_also|Values|@import|Errors|Root Source File#} {#header_close#} {#header_open|Source Encoding#}

Zig source code is encoded in UTF-8. An invalid UTF-8 byte sequence results in a compile error.

@@ -391,13 +385,7 @@ value: 1234 an error code -

See also:

- + {#see_also|Integers|Floats|void|Errors#} {#header_close#} {#header_open|Primitive Values#} @@ -426,11 +414,7 @@ value: 1234
refers to the thing in immediate scope
-

See also:

- + {#see_also|Nullables|this#} {#header_close#} {#header_open|String Literals#}
const assert = @import("std").debug.assert;
@@ -452,11 +436,7 @@ test "string literals" {
 }
$ zig test string_literals.zig
 Test 1/1 string literals...OK
-

See also:

- + {#see_also|Arrays|Zig Test#} {#header_open|Escape Sequences#} @@ -538,10 +518,7 @@ Test 1/1 string literals...OK In this example the variable c_string_literal has type &const char and has a terminating null byte.

-

See also:

- + {#see_also|@embedFile#} {#header_close#} {#header_close#} {#header_open|Assignment#} @@ -627,12 +604,7 @@ const binary_int = 0b11110000; integer overflow. Also available are operations such as +% and -% which are defined to have wrapping arithmetic on all targets.

-

See also:

- + {#see_also|Integer Overflow|Division by Zero|Wrapping Operations#} {#header_close#} {#header_close#} {#header_open|Floats#} @@ -680,11 +652,7 @@ $ zig build-exe test.zig --object foo.o $ ./test optimized = 1.0e-2 strict = 9.765625e-3 -

See also:

- + {#see_also|@setFloatMode|Division by Zero#} {#header_close#} {#header_open|Operators#} {#header_open|Table of Operators#} @@ -1402,11 +1370,7 @@ Test 1/4 iterate over an array...OK Test 2/4 modify an array...OK Test 3/4 compile-time array initalization...OK Test 4/4 array initialization with function calls...OK -

See also:

- + {#see_also|for|Slices#} {#header_close#} {#header_open|Pointers#}
const assert = @import("std").debug.assert;
@@ -1659,11 +1623,7 @@ Tests failed. Use the following command to reproduce the failure:
       

Instead, use @bitCast:

@bitCast(u32, f32(12.34))

As an added benefit, the @bitcast version works at compile-time.

-

See also:

- + {#see_also|Slices|Memory#} {#header_close#} {#header_close#} {#header_open|Slices#} @@ -1760,12 +1720,7 @@ test "slice widening" { Test 1/3 using slices for strings...OK Test 2/3 slice pointer...OK Test 3/3 slice widening...OK
-

See also:

- + {#see_also|Pointers|for|Arrays#} {#header_close#} {#header_open|struct#}
// Declare a struct.
@@ -1907,11 +1862,7 @@ Test 1/4 dot product...OK
 Test 2/4 struct namespaced variable...OK
 Test 3/4 field parent pointer...OK
 Test 4/4 linked list...OK
-

See also:

- + {#see_also|comptime|@fieldParentPtr#} {#header_close#} {#header_open|enum#}
const assert = @import("std").debug.assert;
@@ -2024,12 +1975,7 @@ Test 5/8 @TagType...OK
 Test 6/8 @memberCount...OK
 Test 7/8 @memberName...OK
 Test 8/8 @tagName...OK
-

See also:

- + {#see_also|@memberName|@memberCount|@tagName#} {#header_close#} {#header_open|union#}
const assert = @import("std").debug.assert;
@@ -2235,13 +2181,7 @@ test "switch inside function" {
 Test 1/2 switch simple...OK
 Test 2/2 switch enum...OK
 Test 3/3 switch inside function...OK
-

See also:

- + {#see_also|comptime|enum|@compileError|Compile Variables#} {#header_close#} {#header_open|while#}
const assert = @import("std").debug.assert;
@@ -2404,14 +2344,7 @@ Test 5/8 while loop continuation expression, more complicated...OK
 Test 6/8 while else...OK
 Test 7/8 while null capture...OK
 Test 8/8 inline while loop...OK
-

See also:

- + {#see_also|if|Nullables|Errors|comptime|unreachable#} {#header_close#} {#header_open|for#}
const assert = @import("std").debug.assert;
@@ -2507,13 +2440,7 @@ Test 1/4 for basics...OK
 Test 2/4 for reference...OK
 Test 3/4 for else...OK
 Test 4/4 inline for loop...OK
-

See also:

- + {#see_also|while|comptime|Arrays|Slices#} {#header_close#} {#header_open|if#}
// If expressions have three uses, corresponding to the three types:
@@ -2628,29 +2555,9 @@ test "if error union" {
 Test 1/3 if boolean...OK
 Test 2/3 if nullable...OK
 Test 3/3 if error union...OK
-

See also:

- + {#see_also|Nullables|Errors#} {#header_close#} - {#header_open|goto#} -
const assert = @import("std").debug.assert;
-
-test "goto" {
-    var value = false;
-    goto label;
-    value = true;
-
-label:
-    assert(value == false);
-}
-
-
$ zig test goto.zig
-Test 1/1 goto...OK
-
-

Note that there are plans to remove goto

-{{deheader_open:fer}} + {#header_open|defer#}
const assert = @import("std").debug.assert;
 const printf = @import("std").io.stdout.printf;
 
@@ -2736,10 +2643,7 @@ encountered an error!
 end of function
 OK
 
-

See also:

- + {#see_also|Errors#} {#header_close#} {#header_open|unreachable#}

@@ -2811,12 +2715,7 @@ comptime { test.zig:9:12: error: unreachable code assert(@typeOf(unreachable) == noreturn); ^ -

See also:

- + {#see_also|Zig Test|Build Mode|comptime#} {#header_close#} {#header_close#} {#header_open|noreturn#} @@ -3142,12 +3041,7 @@ pub fn parseU64(buf: []const u8, radix: u8) -> %u64 { in other languages. -

See also:

- + {#see_also|defer|if|switch#} {#header_close#} {#header_open|Nullables#}

@@ -3976,11 +3870,7 @@ comptime { The result is a target-specific compile time constant. It is guaranteed to be less than or equal to @sizeOf(T).

-

See also:

- - + {#see_also|Alignment#} {#header_close#} {#header_open|@cDefine#}
@cDefine(comptime name: []u8, value)
@@ -3999,14 +3889,7 @@ comptime { Use the void value, like this:

@cDefine("_GNU_SOURCE", {})
-

See also:

- + {#see_also|Import from C Header File|@cInclude|@cImport|@cUndef|void#} {#header_close#} {#header_open|@cImport#}
@cImport(expression) -> (namespace)
@@ -4019,13 +3902,7 @@ comptime { @cInclude, @cDefine, and @cUndef work within this expression, appending to a temporary buffer which is then parsed as C code.

-

See also:

- + {#see_also|Import from C Header File|@cInclude|@cDefine|@cUndef#} {#header_close#} {#header_open|@cInclude#}
@cInclude(comptime path: []u8)
@@ -4036,13 +3913,7 @@ comptime { This appends #include <$path>\n to the c_import temporary buffer.

-

See also:

- + {#see_also|Import from C Header File|@cImport|@cDefine|@cUndef#} {#header_close#} {#header_open|@cUndef#}
@cUndef(comptime name: []u8)
@@ -4053,13 +3924,7 @@ comptime { This appends #undef $name to the @cImport temporary buffer.

-

See also:

- + {#see_also|Import from C Header File|@cImport|@cDefine|@cInclude#} {#header_close#} {#header_open|@canImplicitCast#}
@canImplicitCast(comptime T: type, value) -> bool
@@ -4091,11 +3956,7 @@ comptime { AtomicOrder can be found with @import("builtin").AtomicOrder.

@typeOf(ptr).alignment must be >= @sizeOf(T).

-

See also:

- - + {#see_also|Compile Variables#} {#header_close#} {#header_open|@compileError#}
@compileError(comptime msg: []u8)
@@ -4183,12 +4044,8 @@ test.zig:6:2: error: found compile log statement
  • @divExact(6, 3) == 2
  • @divExact(a, b) * b == a
  • -

    See also:

    - +

    For a function that returns a possible error code, use @import("std").math.divExact.

    + {#see_also|@divTrunc|@divFloor#} {#header_close#} {#header_open|@divFloor#}
    @divFloor(numerator: T, denominator: T) -> T
    @@ -4201,12 +4058,8 @@ test.zig:6:2: error: found compile log statement
  • @divFloor(-5, 3) == -2
  • @divFloor(a, b) + @mod(a, b) == a
  • -

    See also:

    - +

    For a function that returns a possible error code, use @import("std").math.divFloor.

    + {#see_also|@divTrunc|@divExact#} {#header_close#} {#header_open|@divTrunc#}
    @divTrunc(numerator: T, denominator: T) -> T
    @@ -4219,12 +4072,8 @@ test.zig:6:2: error: found compile log statement
  • @divTrunc(-5, 3) == -1
  • @divTrunc(a, b) + @rem(a, b) == a
  • -

    See also:

    - +

    For a function that returns a possible error code, use @import("std").math.divTrunc.

    + {#see_also|@divFloor|@divExact#} {#header_close#} {#header_open|@embedFile#}
    @embedFile(comptime path: []const u8) -> [X]u8
    @@ -4236,10 +4085,7 @@ test.zig:6:2: error: found compile log statement

    path is absolute or relative to the current file, just like @import.

    -

    See also:

    - + {#see_also|@import#} {#header_close#} {#header_open|@export#}
    @export(comptime name: []const u8, target: var, linkage: builtin.GlobalLinkage) -> []const u8
    @@ -4294,10 +4140,7 @@ test.zig:6:2: error: found compile log statement

    AtomicOrder can be found with @import("builtin").AtomicOrder.

    -

    See also:

    - + {#see_also|Compile Variables#} {#header_close#} {#header_open|@fieldParentPtr#}
    @fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8,
    @@ -4338,11 +4181,7 @@ test.zig:6:2: error: found compile log statement
             
  • @import("std") - Zig Standard Library
  • @import("builtin") - Compiler-provided types and variables
  • -

    See also:

    - + {#see_also|Compile Variables|@embedFile#} {#header_close#} {#header_open|@inlineCall#}
    @inlineCall(function: X, args: ...) -> Y
    @@ -4359,10 +4198,7 @@ fn add(a: i32, b: i32) -> i32 { a + b }
    Unlike a normal function call, however, @inlineCall guarantees that the call will be inlined. If the call cannot be inlined, a compile error is emitted.

    -

    See also:

    - + {#see_also|@noInlineCall#} {#header_close#} {#header_open|@intToPtr#}
    @intToPtr(comptime DestType: type, int: usize) -> DestType
    @@ -4454,11 +4290,8 @@ mem.set(u8, dest, c);
  • @mod(-5, 3) == 1
  • @divFloor(a, b) + @mod(a, b) == a
  • -

    See also:

    - +

    For a function that returns an error code, see @import("std").math.mod.

    + {#see_also|@rem#} {#header_close#} {#header_open|@mulWithOverflow#}
    @mulWithOverflow(comptime T: type, a: T, b: T, result: &T) -> bool
    @@ -4483,10 +4316,7 @@ fn add(a: i32, b: i32) -> i32 { a + b } Unlike a normal function call, however, @noInlineCall guarantees that the call will not be inlined. If the call must be inlined, a compile error is emitted.

    -

    See also:

    - + {#see_also|@inlineCall#} {#header_close#} {#header_open|@offsetOf#}
    @offsetOf(comptime T: type, comptime field_name: [] const u8) -> (number literal)
    @@ -4529,11 +4359,7 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat'
  • From library code, calling the programmer's panic function if they exposed one in the root source file.
  • When mixing C and Zig code, calling the canonical panic implementation across multiple .o files.
  • -

    See also:

    - - + {#see_also|Root Source File#} {#header_close#} {#header_open|@ptrCast#}
    @ptrCast(comptime DestType: type, value: var) -> DestType
    @@ -4565,11 +4391,8 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat'
  • @rem(-5, 3) == -2
  • @divTrunc(a, b) + @rem(a, b) == a
  • -

    See also:

    - +

    For a function that returns an error code, see @import("std").math.rem.

    + {#see_also|@mod#} {#header_close#} {#header_open|@returnAddress#}
    @returnAddress()
    @@ -4584,7 +4407,6 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat'

    This function is only valid within function scope.

    - {#header_close#} {#header_open|@setDebugSafety#}
    @setDebugSafety(scope, safety_on: bool)
    @@ -4623,11 +4445,7 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat'
    $ ./zig build-obj test.zig

    (no output because it worked fine)

    -

    See also:

    - - + {#see_also|comptime#} {#header_close#} {#header_open|@setFloatMode#}
    @setFloatMode(scope, mode: @import("builtin").FloatMode)
    @@ -4655,21 +4473,14 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat' Strict - Floating point operations follow strict IEEE compliance. -

    See also:

    - - + {#see_also|Floating Point Operations#} {#header_close#} {#header_open|@setGlobalLinkage#}
    @setGlobalLinkage(global_variable_name, comptime linkage: GlobalLinkage)

    GlobalLinkage can be found with @import("builtin").GlobalLinkage.

    -

    See also:

    - + {#see_also|Compile Variables#} {#header_close#} {#header_open|@setGlobalSection#}
    @setGlobalSection(global_variable_name, comptime section_name: []const u8) -> bool
    @@ -4687,11 +4498,7 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat' The type of shift_amt is an unsigned integer with log2(T.bit_count) bits. This is because shift_amt >= T.bit_count is undefined behavior.

    -

    See also:

    - + {#see_also|@shrExact|@shlWithOverflow#} {#header_close#} {#header_open|@shlWithOverflow#}
    @shlWithOverflow(comptime T: type, a: T, shift_amt: Log2T, result: &T) -> bool
    @@ -4704,11 +4511,7 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat' The type of shift_amt is an unsigned integer with log2(T.bit_count) bits. This is because shift_amt >= T.bit_count is undefined behavior.

    -

    See also:

    - + {#see_also|@shlExact|@shrExact#} {#header_close#} {#header_open|@shrExact#}
    @shrExact(value: T, shift_amt: Log2T) -> T
    @@ -4720,10 +4523,7 @@ test.zig:5:9: error: expected type '&Derp', found '&Wat' The type of shift_amt is an unsigned integer with log2(T.bit_count) bits. This is because shift_amt >= T.bit_count is undefined behavior.

    -

    See also:

    - + {#see_also|@shlExact|@shlWithOverflow#} {#header_close#} {#header_open|@sizeOf#}
    @sizeOf(comptime T: type) -> (number literal)
    @@ -4863,12 +4663,7 @@ pub fn build(b: &Builder) {
  • Safety checks enabled
  • Slow compilation speed
  • -

    See also:

    - + {#see_also|Compile Variables|Zig Build System|Undefined Behavior#} {#header_close#} {#header_close#} {#header_open|Undefined Behavior#} @@ -5234,10 +5029,7 @@ comptime {

    TODO: importance of checking for allocation failure

    TODO: mention overcommit and the OOM Killer

    TODO: mention recursion

    -

    See also:

    - + {#see_also|Pointers#} {#header_close#} {#header_open|Compile Variables#} @@ -5406,10 +5198,7 @@ pub const object_format = ObjectFormat.elf; pub const mode = Mode.ReleaseFast; pub const link_libs = [][]const u8 { }; -

    See also:

    - + {#see_also|Build Mode#} {#header_close#} {#header_open|Root Source File#}

    TODO: explain how root source file finds other files

    @@ -5456,10 +5245,7 @@ pub const link_libs = [][]const u8 {
  • c_longdouble
  • c_void
  • -

    See also:

    - + {#see_also|Primitive Types#} {#header_close#} {#header_open|C String Literals#}
    extern fn puts(&const u8);
    @@ -5472,10 +5258,7 @@ pub fn main() -> %void {
             c\\multiline C string literal
         );
     }
    -

    See also:

    - + {#see_also|String Literals#} {#header_close#} {#header_open|Import from C Header File#}

    @@ -5504,14 +5287,7 @@ const c = @cImport({ } @cInclude("soundio.h"); }); -

    See also:

    - + {#see_also|@cImport|@cInclude|@cDefine|@cUndef|@import#} {#header_close#} {#header_open|Mixing Object Files#}

    @@ -5571,11 +5347,7 @@ pub fn build(b: &Builder) {

    $ zig build
     $ ./test
     all your base are belong to us
    -

    See also:

    - + {#see_also|Targets|Zig Build System#} {#header_close#} {#header_close#} {#header_open|Targets#} diff --git a/std/hash_map.zig b/std/hash_map.zig index e3c968146e..2cd08d1280 100644 --- a/std/hash_map.zig +++ b/std/hash_map.zig @@ -109,6 +109,10 @@ pub fn HashMap(comptime K: type, comptime V: type, return hm.internalGet(key); } + pub fn contains(hm: &Self, key: K) -> bool { + return hm.get(key) != null; + } + pub fn remove(hm: &Self, key: K) -> ?&Entry { hm.incrementModificationCount(); const start_index = hm.keyToIndex(key);