Commit ec1b6f6673

Andrew Kelley <superjoe30@gmail.com>
2018-06-10 05:42:14
breaking syntax change: ??x to x.? (#1095)
See #1023 This also renames Nullable/Maybe to Optional
1 parent d464b25
doc/codegen.md
@@ -6,7 +6,7 @@ Every type has a "handle". If a type is a simple primitive type such as i32 or
 f64, the handle is "by value", meaning that we pass around the value itself when
 we refer to a value of that type.
 
-If a type is a container, error union, maybe type, slice, or array, then its
+If a type is a container, error union, optional type, slice, or array, then its
 handle is a pointer, and everywhere we refer to a value of this type we refer to
 a pointer.
 
@@ -19,7 +19,7 @@ Error union types are represented as:
         payload: T,
     }
 
-Maybe types are represented as:
+Optional types are represented as:
 
     struct {
         payload: T,
@@ -28,6 +28,6 @@ Maybe types are represented as:
 
 ## Data Optimizations
 
-Maybe pointer types are special: the 0x0 pointer value is used to represent a
-null pointer. Thus, instead of the struct above, maybe pointer types are
+Optional pointer types are special: the 0x0 pointer value is used to represent a
+null pointer. Thus, instead of the struct above, optional pointer types are
 represented as a `usize` in codegen and the handle is by value.
doc/langref.html.in
@@ -156,18 +156,18 @@ pub fn main() void {
         true or false,
         !true);
     
-    // nullable
-    var nullable_value: ?[]const u8 = null;
-    assert(nullable_value == null);
+    // optional
+    var optional_value: ?[]const u8 = null;
+    assert(optional_value == null);
 
-    warn("\nnullable 1\ntype: {}\nvalue: {}\n",
-        @typeName(@typeOf(nullable_value)), nullable_value);
+    warn("\noptional 1\ntype: {}\nvalue: {}\n",
+        @typeName(@typeOf(optional_value)), optional_value);
 
-    nullable_value = "hi";
-    assert(nullable_value != null);
+    optional_value = "hi";
+    assert(optional_value != null);
 
-    warn("\nnullable 2\ntype: {}\nvalue: {}\n",
-        @typeName(@typeOf(nullable_value)), nullable_value);
+    warn("\noptional 2\ntype: {}\nvalue: {}\n",
+        @typeName(@typeOf(optional_value)), optional_value);
 
     // error union
     var number_or_error: error!i32 = error.ArgNotFound;
@@ -428,7 +428,7 @@ pub fn main() void {
         </tr>
         <tr>
           <td><code>null</code></td>
-          <td>used to set a nullable type to <code>null</code></td>
+          <td>used to set an optional type to <code>null</code></td>
         </tr>
         <tr>
           <td><code>undefined</code></td>
@@ -440,7 +440,7 @@ pub fn main() void {
         </tr>
       </table>
       </div>
-      {#see_also|Nullables|this#}
+      {#see_also|Optionals|this#}
       {#header_close#}
       {#header_open|String Literals#}
       {#code_begin|test#}
@@ -988,7 +988,7 @@ a ^= b</code></pre></td>
           <td><pre><code class="zig">a ?? b</code></pre></td>
           <td>
             <ul>
-              <li>{#link|Nullables#}</li>
+              <li>{#link|Optionals#}</li>
             </ul>
           </td>
           <td>If <code>a</code> is <code>null</code>,
@@ -1003,10 +1003,10 @@ unwrapped == 1234</code></pre>
           </td>
         </tr>
         <tr>
-          <td><pre><code class="zig">??a</code></pre></td>
+          <td><pre><code class="zig">a.?</code></pre></td>
           <td>
             <ul>
-              <li>{#link|Nullables#}</li>
+              <li>{#link|Optionals#}</li>
             </ul>
           </td>
           <td>
@@ -1015,7 +1015,7 @@ unwrapped == 1234</code></pre>
           </td>
           <td>
             <pre><code class="zig">const value: ?u32 = 5678;
-??value == 5678</code></pre>
+value.? == 5678</code></pre>
           </td>
         </tr>
         <tr>
@@ -1103,7 +1103,7 @@ unwrapped == 1234</code></pre>
           <td><pre><code class="zig">a == null<code></pre></td>
           <td>
             <ul>
-              <li>{#link|Nullables#}</li>
+              <li>{#link|Optionals#}</li>
             </ul>
           </td>
           <td>
@@ -1267,8 +1267,8 @@ x.* == 1234</code></pre>
       {#header_open|Precedence#}
       <pre><code>x() x[] x.y
 a!b
-!x -x -%x ~x &amp;x ?x ??x
-x{} x.*
+!x -x -%x ~x &amp;x ?x
+x{} x.* x.?
 ! * / % ** *%
 + - ++ +% -%
 &lt;&lt; &gt;&gt;
@@ -1483,17 +1483,17 @@ test "volatile" {
     assert(@typeOf(mmio_ptr) == *volatile u8);
 }
 
-test "nullable pointers" {
-    // Pointers cannot be null. If you want a null pointer, use the nullable
-    // prefix `?` to make the pointer type nullable.
+test "optional pointers" {
+    // Pointers cannot be null. If you want a null pointer, use the optional
+    // prefix `?` to make the pointer type optional.
     var ptr: ?*i32 = null;
 
     var x: i32 = 1;
     ptr = &x;
 
-    assert((??ptr).* == 1);
+    assert(ptr.?.* == 1);
 
-    // Nullable pointers are the same size as normal pointers, because pointer
+    // Optional pointers are the same size as normal pointers, because pointer
     // value 0 is used as the null value.
     assert(@sizeOf(?*i32) == @sizeOf(*i32));
 }
@@ -1832,7 +1832,7 @@ test "linked list" {
         .last = &node,
         .len = 1,
     };
-    assert((??list2.first).data == 1234);
+    assert(list2.first.?.data == 1234);
 }
       {#code_end#}
       {#see_also|comptime|@fieldParentPtr#}
@@ -2270,7 +2270,7 @@ fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
 }
 
 test "while null capture" {
-    // Just like if expressions, while loops can take a nullable as the
+    // Just like if expressions, while loops can take an optional as the
     // condition and capture the payload. When null is encountered the loop
     // exits.
     var sum1: u32 = 0;
@@ -2280,7 +2280,7 @@ test "while null capture" {
     }
     assert(sum1 == 3);
 
-    // The else branch is allowed on nullable iteration. In this case, it will
+    // The else branch is allowed on optional iteration. In this case, it will
     // be executed on the first null value encountered.
     var sum2: u32 = 0;
     numbers_left = 3;
@@ -2340,7 +2340,7 @@ fn typeNameLength(comptime T: type) usize {
     return @typeName(T).len;
 }
       {#code_end#}
-      {#see_also|if|Nullables|Errors|comptime|unreachable#}
+      {#see_also|if|Optionals|Errors|comptime|unreachable#}
       {#header_close#}
       {#header_open|for#}
       {#code_begin|test|for#}
@@ -2400,7 +2400,7 @@ test "for else" {
         if (value == null) {
             break 9;
         } else {
-            sum += ??value;
+            sum += value.?;
         }
     } else blk: {
         assert(sum == 7);
@@ -2461,7 +2461,7 @@ test "if boolean" {
     assert(result == 47);
 }
 
-test "if nullable" {
+test "if optional" {
     // If expressions test for null.
 
     const a: ?u32 = 0;
@@ -2544,7 +2544,7 @@ test "if error union" {
     }
 }
       {#code_end#}
-      {#see_also|Nullables|Errors#}
+      {#see_also|Optionals|Errors#}
       {#header_close#}
       {#header_open|defer#}
       {#code_begin|test|defer#}
@@ -3167,24 +3167,24 @@ test "inferred error set" {
       <p>TODO</p>
       {#header_close#}
       {#header_close#}
-      {#header_open|Nullables#}
+      {#header_open|Optionals#}
       <p>
       One area that Zig provides safety without compromising efficiency or
-      readability is with the nullable type.
+      readability is with the optional type.
       </p>
       <p>
-      The question mark symbolizes the nullable type. You can convert a type to a nullable
+      The question mark symbolizes the optional type. You can convert a type to an optional
       type by putting a question mark in front of it, like this:
       </p>
       {#code_begin|syntax#}
 // normal integer
 const normal_int: i32 = 1234;
 
-// nullable integer
-const nullable_int: ?i32 = 5678;
+// optional integer
+const optional_int: ?i32 = 5678;
       {#code_end#}
       <p>
-      Now the variable <code>nullable_int</code> could be an <code>i32</code>, or <code>null</code>.
+      Now the variable <code>optional_int</code> could be an <code>i32</code>, or <code>null</code>.
       </p>
       <p>
       Instead of integers, let's talk about pointers. Null references are the source of many runtime
@@ -3193,8 +3193,8 @@ const nullable_int: ?i32 = 5678;
       </p>
       <p>Zig does not have them.</p>
       <p>
-      Instead, you can use a nullable pointer. This secretly compiles down to a normal pointer,
-      since we know we can use 0 as the null value for the nullable type. But the compiler
+      Instead, you can use an optional pointer. This secretly compiles down to a normal pointer,
+      since we know we can use 0 as the null value for the optional type. But the compiler
       can check your work and make sure you don't assign null to something that can't be null.
       </p>
       <p>
@@ -3226,7 +3226,7 @@ fn doAThing() ?*Foo {
       <p>
         Here, Zig is at least as convenient, if not more, than C. And, the type of "ptr"
         is <code>*u8</code> <em>not</em> <code>?*u8</code>. The <code>??</code> operator
-        unwrapped the nullable type and therefore <code>ptr</code> is guaranteed to be non-null everywhere
+        unwrapped the optional type and therefore <code>ptr</code> is guaranteed to be non-null everywhere
         it is used in the function.
       </p>
       <p>
@@ -3245,10 +3245,10 @@ fn doAThing() ?*Foo {
         In Zig you can accomplish the same thing:
       </p>
       {#code_begin|syntax#}
-fn doAThing(nullable_foo: ?*Foo) void {
+fn doAThing(optional_foo: ?*Foo) void {
     // do some stuff
 
-    if (nullable_foo) |foo| {
+    if (optional_foo) |foo| {
       doSomethingWithFoo(foo);
     }
 
@@ -3257,7 +3257,7 @@ fn doAThing(nullable_foo: ?*Foo) void {
       {#code_end#}
       <p>
       Once again, the notable thing here is that inside the if block,
-      <code>foo</code> is no longer a nullable pointer, it is a pointer, which
+      <code>foo</code> is no longer an optional pointer, it is a pointer, which
       cannot be null.
       </p>
       <p>
@@ -3267,20 +3267,20 @@ fn doAThing(nullable_foo: ?*Foo) void {
       The optimizer can sometimes make better decisions knowing that pointer arguments
       cannot be null.
       </p>
-      {#header_open|Nullable Type#}
-      <p>A nullable is created by putting <code>?</code> in front of a type. You can use compile-time
-      reflection to access the child type of a nullable:</p>
+      {#header_open|Optional Type#}
+      <p>An optional is created by putting <code>?</code> in front of a type. You can use compile-time
+      reflection to access the child type of an optional:</p>
       {#code_begin|test#}
 const assert = @import("std").debug.assert;
 
-test "nullable type" {
-    // Declare a nullable and implicitly cast from null:
+test "optional type" {
+    // Declare an optional and implicitly cast from null:
     var foo: ?i32 = null;
 
-    // Implicitly cast from child type of a nullable
+    // Implicitly cast from child type of an optional
     foo = 1234;
 
-    // Use compile-time reflection to access the child type of the nullable:
+    // Use compile-time reflection to access the child type of the optional:
     comptime assert(@typeOf(foo).Child == i32);
 }
       {#code_end#}
@@ -4888,7 +4888,7 @@ pub const TypeId = enum {
     ComptimeInt,
     Undefined,
     Null,
-    Nullable,
+    Optional,
     ErrorUnion,
     Error,
     Enum,
@@ -4922,7 +4922,7 @@ pub const TypeInfo = union(TypeId) {
     ComptimeInt: void,
     Undefined: void,
     Null: void,
-    Nullable: Nullable,
+    Optional: Optional,
     ErrorUnion: ErrorUnion,
     ErrorSet: ErrorSet,
     Enum: Enum,
@@ -4975,7 +4975,7 @@ pub const TypeInfo = union(TypeId) {
         defs: []Definition,
     };
 
-    pub const Nullable = struct {
+    pub const Optional = struct {
         child: type,
     };
 
@@ -5366,8 +5366,8 @@ comptime {
       <p>At compile-time:</p>
       {#code_begin|test_err|unable to unwrap null#}
 comptime {
-    const nullable_number: ?i32 = null;
-    const number = ??nullable_number;
+    const optional_number: ?i32 = null;
+    const number = optional_number.?;
 }
       {#code_end#}
       <p>At runtime crashes with the message <code>attempt to unwrap null</code> and a stack trace.</p>
@@ -5376,9 +5376,9 @@ comptime {
       {#code_begin|exe|test#}
 const warn = @import("std").debug.warn;
 pub fn main() void {
-    const nullable_number: ?i32 = null;
+    const optional_number: ?i32 = null;
 
-    if (nullable_number) |number| {
+    if (optional_number) |number| {
         warn("got number: {}\n", number);
     } else {
         warn("it's null\n");
@@ -5939,9 +5939,9 @@ AsmInputItem = "[" Symbol "]" String "(" Expression ")"
 
 AsmClobbers= ":" list(String, ",")
 
-UnwrapExpression = BoolOrExpression (UnwrapNullable | UnwrapError) | BoolOrExpression
+UnwrapExpression = BoolOrExpression (UnwrapOptional | UnwrapError) | BoolOrExpression
 
-UnwrapNullable = "??" Expression
+UnwrapOptional = "??" Expression
 
 UnwrapError = "catch" option("|" Symbol "|") Expression
 
@@ -6015,12 +6015,10 @@ MultiplyOperator = "||" | "*" | "/" | "%" | "**" | "*%"
 
 PrefixOpExpression = PrefixOp TypeExpr | SuffixOpExpression
 
-SuffixOpExpression = ("async" option("&lt;" SuffixOpExpression "&gt;") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression | PtrDerefExpression)
+SuffixOpExpression = ("async" option("&lt;" SuffixOpExpression "&gt;") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression | ".*" | ".?")
 
 FieldAccessExpression = "." Symbol
 
-PtrDerefExpression = ".*"
-
 FnCallExpression = "(" list(Expression, ",") ")"
 
 ArrayAccessExpression = "[" Expression "]"
@@ -6033,7 +6031,7 @@ ContainerInitBody = list(StructLiteralField, ",") | list(Expression, ",")
 
 StructLiteralField = "." Symbol "=" Expression
 
-PrefixOp = "!" | "-" | "~" | (("*" | "[*]") option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "??" | "-%" | "try" | "await"
+PrefixOp = "!" | "-" | "~" | (("*" | "[*]") option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "-%" | "try" | "await"
 
 PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ContainerDecl | ("continue" option(":" Symbol)) | ErrorSetDecl | PromiseType
 
example/cat/main.zig
@@ -7,7 +7,7 @@ const allocator = std.debug.global_allocator;
 
 pub fn main() !void {
     var args_it = os.args();
-    const exe = try unwrapArg(??args_it.next(allocator));
+    const exe = try unwrapArg(args_it.next(allocator).?);
     var catted_anything = false;
     var stdout_file = try io.getStdOut();
 
src/all_types.hpp
@@ -145,8 +145,8 @@ enum ConstPtrSpecial {
     // emit a binary with a compile time known address.
     // In this case index is the numeric address value.
     // We also use this for null pointer. We need the data layout for ConstCastOnly == true
-    // types to be the same, so all nullables of pointer types use x_ptr
-    // instead of x_nullable
+    // types to be the same, so all optionals of pointer types use x_ptr
+    // instead of x_optional
     ConstPtrSpecialHardCodedAddr,
     // This means that the pointer represents memory of assigning to _.
     // That is, storing discards the data, and loading is invalid.
@@ -222,10 +222,10 @@ enum RuntimeHintErrorUnion {
     RuntimeHintErrorUnionNonError,
 };
 
-enum RuntimeHintMaybe {
-    RuntimeHintMaybeUnknown,
-    RuntimeHintMaybeNull, // TODO is this value even possible? if this is the case it might mean the const value is compile time known.
-    RuntimeHintMaybeNonNull,
+enum RuntimeHintOptional {
+    RuntimeHintOptionalUnknown,
+    RuntimeHintOptionalNull, // TODO is this value even possible? if this is the case it might mean the const value is compile time known.
+    RuntimeHintOptionalNonNull,
 };
 
 enum RuntimeHintPtr {
@@ -254,7 +254,7 @@ struct ConstExprValue {
         bool x_bool;
         ConstBoundFnValue x_bound_fn;
         TypeTableEntry *x_type;
-        ConstExprValue *x_nullable;
+        ConstExprValue *x_optional;
         ConstErrValue x_err_union;
         ErrorTableEntry *x_err_set;
         BigInt x_enum_tag;
@@ -268,7 +268,7 @@ struct ConstExprValue {
 
         // populated if special == ConstValSpecialRuntime
         RuntimeHintErrorUnion rh_error_union;
-        RuntimeHintMaybe rh_maybe;
+        RuntimeHintOptional rh_maybe;
         RuntimeHintPtr rh_ptr;
     } data;
 };
@@ -556,7 +556,7 @@ enum BinOpType {
     BinOpTypeMultWrap,
     BinOpTypeDiv,
     BinOpTypeMod,
-    BinOpTypeUnwrapMaybe,
+    BinOpTypeUnwrapOptional,
     BinOpTypeArrayCat,
     BinOpTypeArrayMult,
     BinOpTypeErrorUnion,
@@ -623,8 +623,8 @@ enum PrefixOp {
     PrefixOpBinNot,
     PrefixOpNegation,
     PrefixOpNegationWrap,
-    PrefixOpMaybe,
-    PrefixOpUnwrapMaybe,
+    PrefixOpOptional,
+    PrefixOpUnwrapOptional,
     PrefixOpAddrOf,
 };
 
@@ -1052,7 +1052,7 @@ struct TypeTableEntryStruct {
     HashMap<Buf *, TypeStructField *, buf_hash, buf_eql_buf> fields_by_name;
 };
 
-struct TypeTableEntryMaybe {
+struct TypeTableEntryOptional {
     TypeTableEntry *child_type;
 };
 
@@ -1175,7 +1175,7 @@ enum TypeTableEntryId {
     TypeTableEntryIdComptimeInt,
     TypeTableEntryIdUndefined,
     TypeTableEntryIdNull,
-    TypeTableEntryIdMaybe,
+    TypeTableEntryIdOptional,
     TypeTableEntryIdErrorUnion,
     TypeTableEntryIdErrorSet,
     TypeTableEntryIdEnum,
@@ -1206,7 +1206,7 @@ struct TypeTableEntry {
         TypeTableEntryFloat floating;
         TypeTableEntryArray array;
         TypeTableEntryStruct structure;
-        TypeTableEntryMaybe maybe;
+        TypeTableEntryOptional maybe;
         TypeTableEntryErrorUnion error_union;
         TypeTableEntryErrorSet error_set;
         TypeTableEntryEnum enumeration;
@@ -1402,7 +1402,7 @@ enum PanicMsgId {
     PanicMsgIdRemainderDivisionByZero,
     PanicMsgIdExactDivisionRemainder,
     PanicMsgIdSliceWidenRemainder,
-    PanicMsgIdUnwrapMaybeFail,
+    PanicMsgIdUnwrapOptionalFail,
     PanicMsgIdInvalidErrorCode,
     PanicMsgIdIncorrectAlignment,
     PanicMsgIdBadUnionField,
@@ -2016,8 +2016,8 @@ enum IrInstructionId {
     IrInstructionIdAsm,
     IrInstructionIdSizeOf,
     IrInstructionIdTestNonNull,
-    IrInstructionIdUnwrapMaybe,
-    IrInstructionIdMaybeWrap,
+    IrInstructionIdUnwrapOptional,
+    IrInstructionIdOptionalWrap,
     IrInstructionIdUnionTag,
     IrInstructionIdClz,
     IrInstructionIdCtz,
@@ -2184,7 +2184,7 @@ enum IrUnOp {
     IrUnOpNegation,
     IrUnOpNegationWrap,
     IrUnOpDereference,
-    IrUnOpMaybe,
+    IrUnOpOptional,
 };
 
 struct IrInstructionUnOp {
@@ -2487,7 +2487,7 @@ struct IrInstructionTestNonNull {
     IrInstruction *value;
 };
 
-struct IrInstructionUnwrapMaybe {
+struct IrInstructionUnwrapOptional {
     IrInstruction base;
 
     IrInstruction *value;
@@ -2745,7 +2745,7 @@ struct IrInstructionUnwrapErrPayload {
     bool safety_check_on;
 };
 
-struct IrInstructionMaybeWrap {
+struct IrInstructionOptionalWrap {
     IrInstruction base;
 
     IrInstruction *value;
@@ -2954,10 +2954,10 @@ struct IrInstructionExport {
 struct IrInstructionErrorReturnTrace {
     IrInstruction base;
 
-    enum Nullable {
+    enum Optional {
         Null,
         NonNull,
-    } nullable;
+    } optional;
 };
 
 struct IrInstructionErrorUnion {
src/analyze.cpp
@@ -236,7 +236,7 @@ bool type_is_complete(TypeTableEntry *type_entry) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdFn:
@@ -272,7 +272,7 @@ bool type_has_zero_bits_known(TypeTableEntry *type_entry) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdFn:
@@ -520,7 +520,7 @@ TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) {
     } else {
         ensure_complete_type(g, child_type);
 
-        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdMaybe);
+        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdOptional);
         assert(child_type->type_ref || child_type->zero_bits);
         assert(child_type->di_type);
         entry->is_copyable = type_is_copyable(g, child_type);
@@ -1361,7 +1361,7 @@ static bool type_allowed_in_packed_struct(TypeTableEntry *type_entry) {
             return type_entry->data.structure.layout == ContainerLayoutPacked;
         case TypeTableEntryIdUnion:
             return type_entry->data.unionation.layout == ContainerLayoutPacked;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 TypeTableEntry *child_type = type_entry->data.maybe.child_type;
                 return type_is_codegen_pointer(child_type);
@@ -1415,7 +1415,7 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) {
             return type_allowed_in_extern(g, type_entry->data.pointer.child_type);
         case TypeTableEntryIdStruct:
             return type_entry->data.structure.layout == ContainerLayoutExtern || type_entry->data.structure.layout == ContainerLayoutPacked;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 TypeTableEntry *child_type = type_entry->data.maybe.child_type;
                 return child_type->id == TypeTableEntryIdPointer || child_type->id == TypeTableEntryIdFn;
@@ -1538,7 +1538,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c
             case TypeTableEntryIdPointer:
             case TypeTableEntryIdArray:
             case TypeTableEntryIdStruct:
-            case TypeTableEntryIdMaybe:
+            case TypeTableEntryIdOptional:
             case TypeTableEntryIdErrorUnion:
             case TypeTableEntryIdErrorSet:
             case TypeTableEntryIdEnum:
@@ -1632,7 +1632,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -2985,8 +2985,8 @@ static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) {
         return wrong_panic_prototype(g, proto_node, fn_type);
     }
 
-    TypeTableEntry *nullable_ptr_to_stack_trace_type = get_maybe_type(g, get_ptr_to_stack_trace_type(g));
-    if (fn_type_id->param_info[1].type != nullable_ptr_to_stack_trace_type) {
+    TypeTableEntry *optional_ptr_to_stack_trace_type = get_maybe_type(g, get_ptr_to_stack_trace_type(g));
+    if (fn_type_id->param_info[1].type != optional_ptr_to_stack_trace_type) {
         return wrong_panic_prototype(g, proto_node, fn_type);
     }
 
@@ -3368,7 +3368,7 @@ TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEnt
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -3746,7 +3746,7 @@ static bool is_container(TypeTableEntry *type_entry) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdFn:
@@ -3805,7 +3805,7 @@ void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdFn:
@@ -3824,7 +3824,7 @@ TypeTableEntry *get_codegen_ptr_type(TypeTableEntry *type) {
     if (type->id == TypeTableEntryIdPointer) return type;
     if (type->id == TypeTableEntryIdFn) return type;
     if (type->id == TypeTableEntryIdPromise) return type;
-    if (type->id == TypeTableEntryIdMaybe) {
+    if (type->id == TypeTableEntryIdOptional) {
         if (type->data.maybe.child_type->id == TypeTableEntryIdPointer) return type->data.maybe.child_type;
         if (type->data.maybe.child_type->id == TypeTableEntryIdFn) return type->data.maybe.child_type;
         if (type->data.maybe.child_type->id == TypeTableEntryIdPromise) return type->data.maybe.child_type;
@@ -4331,7 +4331,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) {
              return type_has_bits(type_entry);
         case TypeTableEntryIdErrorUnion:
              return type_has_bits(type_entry->data.error_union.payload_type);
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
              return type_has_bits(type_entry->data.maybe.child_type) &&
                     !type_is_codegen_pointer(type_entry->data.maybe.child_type);
         case TypeTableEntryIdUnion:
@@ -4709,12 +4709,12 @@ static uint32_t hash_const_val(ConstExprValue *const_val) {
         case TypeTableEntryIdUnion:
             // TODO better hashing algorithm
             return 2709806591;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             if (get_codegen_ptr_type(const_val->type) != nullptr) {
                 return hash_const_val(const_val) * 1992916303;
             } else {
-                if (const_val->data.x_nullable) {
-                    return hash_const_val(const_val->data.x_nullable) * 1992916303;
+                if (const_val->data.x_optional) {
+                    return hash_const_val(const_val->data.x_optional) * 1992916303;
                 } else {
                     return 4016830364;
                 }
@@ -4817,12 +4817,12 @@ static bool can_mutate_comptime_var_state(ConstExprValue *value) {
             }
             return false;
 
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             if (get_codegen_ptr_type(value->type) != nullptr)
                 return value->data.x_ptr.mut == ConstPtrMutComptimeVar;
-            if (value->data.x_nullable == nullptr)
+            if (value->data.x_optional == nullptr)
                 return false;
-            return can_mutate_comptime_var_state(value->data.x_nullable);
+            return can_mutate_comptime_var_state(value->data.x_optional);
 
         case TypeTableEntryIdErrorUnion:
             if (value->data.x_err_union.err != nullptr)
@@ -4869,7 +4869,7 @@ static bool return_type_is_cacheable(TypeTableEntry *return_type) {
         case TypeTableEntryIdUnion:
             return false;
 
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             return return_type_is_cacheable(return_type->data.maybe.child_type);
 
         case TypeTableEntryIdErrorUnion:
@@ -4978,7 +4978,7 @@ bool type_requires_comptime(TypeTableEntry *type_entry) {
         case TypeTableEntryIdUnion:
             assert(type_has_zero_bits_known(type_entry));
             return type_entry->data.unionation.requires_comptime;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             return type_requires_comptime(type_entry->data.maybe.child_type);
         case TypeTableEntryIdErrorUnion:
             return type_requires_comptime(type_entry->data.error_union.payload_type);
@@ -5460,13 +5460,13 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b) {
             zig_panic("TODO");
         case TypeTableEntryIdNull:
             zig_panic("TODO");
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             if (get_codegen_ptr_type(a->type) != nullptr)
                 return const_values_equal_ptr(a, b);
-            if (a->data.x_nullable == nullptr || b->data.x_nullable == nullptr) {
-                return (a->data.x_nullable == nullptr && b->data.x_nullable == nullptr);
+            if (a->data.x_optional == nullptr || b->data.x_optional == nullptr) {
+                return (a->data.x_optional == nullptr && b->data.x_optional == nullptr);
             } else {
-                return const_values_equal(a->data.x_nullable, b->data.x_nullable);
+                return const_values_equal(a->data.x_optional, b->data.x_optional);
             }
         case TypeTableEntryIdErrorUnion:
             zig_panic("TODO");
@@ -5708,12 +5708,12 @@ void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val) {
                 buf_appendf(buf, "undefined");
                 return;
             }
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 if (get_codegen_ptr_type(const_val->type) != nullptr)
                     return render_const_val_ptr(g, buf, const_val, type_entry->data.maybe.child_type);
-                if (const_val->data.x_nullable) {
-                    render_const_value(g, buf, const_val->data.x_nullable);
+                if (const_val->data.x_optional) {
+                    render_const_value(g, buf, const_val->data.x_optional);
                 } else {
                     buf_appendf(buf, "null");
                 }
@@ -5819,7 +5819,7 @@ uint32_t type_id_hash(TypeId x) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
         case TypeTableEntryIdUnion:
@@ -5865,7 +5865,7 @@ bool type_id_eql(TypeId a, TypeId b) {
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdPromise:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -5987,7 +5987,7 @@ static const TypeTableEntryId all_type_ids[] = {
     TypeTableEntryIdComptimeInt,
     TypeTableEntryIdUndefined,
     TypeTableEntryIdNull,
-    TypeTableEntryIdMaybe,
+    TypeTableEntryIdOptional,
     TypeTableEntryIdErrorUnion,
     TypeTableEntryIdErrorSet,
     TypeTableEntryIdEnum,
@@ -6042,7 +6042,7 @@ size_t type_id_index(TypeTableEntry *entry) {
             return 11;
         case TypeTableEntryIdNull:
             return 12;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             return 13;
         case TypeTableEntryIdErrorUnion:
             return 14;
@@ -6100,8 +6100,8 @@ const char *type_id_name(TypeTableEntryId id) {
             return "Undefined";
         case TypeTableEntryIdNull:
             return "Null";
-        case TypeTableEntryIdMaybe:
-            return "Nullable";
+        case TypeTableEntryIdOptional:
+            return "Optional";
         case TypeTableEntryIdErrorUnion:
             return "ErrorUnion";
         case TypeTableEntryIdErrorSet:
src/ast_render.cpp
@@ -50,7 +50,7 @@ static const char *bin_op_str(BinOpType bin_op) {
         case BinOpTypeAssignBitXor:           return "^=";
         case BinOpTypeAssignBitOr:            return "|=";
         case BinOpTypeAssignMergeErrorSets:   return "||=";
-        case BinOpTypeUnwrapMaybe:            return "??";
+        case BinOpTypeUnwrapOptional:         return "??";
         case BinOpTypeArrayCat:               return "++";
         case BinOpTypeArrayMult:              return "**";
         case BinOpTypeErrorUnion:             return "!";
@@ -66,8 +66,8 @@ static const char *prefix_op_str(PrefixOp prefix_op) {
         case PrefixOpNegationWrap: return "-%";
         case PrefixOpBoolNot: return "!";
         case PrefixOpBinNot: return "~";
-        case PrefixOpMaybe: return "?";
-        case PrefixOpUnwrapMaybe: return "??";
+        case PrefixOpOptional: return "?";
+        case PrefixOpUnwrapOptional: return "??";
         case PrefixOpAddrOf: return "&";
     }
     zig_unreachable();
src/codegen.cpp
@@ -865,7 +865,7 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) {
             return buf_create_from_str("exact division produced remainder");
         case PanicMsgIdSliceWidenRemainder:
             return buf_create_from_str("slice widening size mismatch");
-        case PanicMsgIdUnwrapMaybeFail:
+        case PanicMsgIdUnwrapOptionalFail:
             return buf_create_from_str("attempt to unwrap null");
         case PanicMsgIdUnreachable:
             return buf_create_from_str("reached unreachable code");
@@ -2734,7 +2734,7 @@ static LLVMValueRef ir_render_un_op(CodeGen *g, IrExecutable *executable, IrInst
 
     switch (op_id) {
         case IrUnOpInvalid:
-        case IrUnOpMaybe:
+        case IrUnOpOptional:
         case IrUnOpDereference:
             zig_unreachable();
         case IrUnOpNegation:
@@ -3333,7 +3333,7 @@ static LLVMValueRef ir_render_asm(CodeGen *g, IrExecutable *executable, IrInstru
 }
 
 static LLVMValueRef gen_non_null_bit(CodeGen *g, TypeTableEntry *maybe_type, LLVMValueRef maybe_handle) {
-    assert(maybe_type->id == TypeTableEntryIdMaybe);
+    assert(maybe_type->id == TypeTableEntryIdOptional);
     TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
     if (child_type->zero_bits) {
         return maybe_handle;
@@ -3355,23 +3355,23 @@ static LLVMValueRef ir_render_test_non_null(CodeGen *g, IrExecutable *executable
 }
 
 static LLVMValueRef ir_render_unwrap_maybe(CodeGen *g, IrExecutable *executable,
-        IrInstructionUnwrapMaybe *instruction)
+        IrInstructionUnwrapOptional *instruction)
 {
     TypeTableEntry *ptr_type = instruction->value->value.type;
     assert(ptr_type->id == TypeTableEntryIdPointer);
     TypeTableEntry *maybe_type = ptr_type->data.pointer.child_type;
-    assert(maybe_type->id == TypeTableEntryIdMaybe);
+    assert(maybe_type->id == TypeTableEntryIdOptional);
     TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
     LLVMValueRef maybe_ptr = ir_llvm_value(g, instruction->value);
     LLVMValueRef maybe_handle = get_handle_value(g, maybe_ptr, maybe_type, ptr_type);
     if (ir_want_runtime_safety(g, &instruction->base) && instruction->safety_check_on) {
         LLVMValueRef non_null_bit = gen_non_null_bit(g, maybe_type, maybe_handle);
-        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapMaybeOk");
-        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapMaybeFail");
+        LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapOptionalOk");
+        LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapOptionalFail");
         LLVMBuildCondBr(g->builder, non_null_bit, ok_block, fail_block);
 
         LLVMPositionBuilderAtEnd(g->builder, fail_block);
-        gen_safety_crash(g, PanicMsgIdUnwrapMaybeFail);
+        gen_safety_crash(g, PanicMsgIdUnwrapOptionalFail);
 
         LLVMPositionBuilderAtEnd(g->builder, ok_block);
     }
@@ -3593,17 +3593,17 @@ static LLVMValueRef ir_render_align_cast(CodeGen *g, IrExecutable *executable, I
     } else if (target_type->id == TypeTableEntryIdFn) {
         align_bytes = target_type->data.fn.fn_type_id.alignment;
         ptr_val = target_val;
-    } else if (target_type->id == TypeTableEntryIdMaybe &&
+    } else if (target_type->id == TypeTableEntryIdOptional &&
             target_type->data.maybe.child_type->id == TypeTableEntryIdPointer)
     {
         align_bytes = target_type->data.maybe.child_type->data.pointer.alignment;
         ptr_val = target_val;
-    } else if (target_type->id == TypeTableEntryIdMaybe &&
+    } else if (target_type->id == TypeTableEntryIdOptional &&
             target_type->data.maybe.child_type->id == TypeTableEntryIdFn)
     {
         align_bytes = target_type->data.maybe.child_type->data.fn.fn_type_id.alignment;
         ptr_val = target_val;
-    } else if (target_type->id == TypeTableEntryIdMaybe &&
+    } else if (target_type->id == TypeTableEntryIdOptional &&
             target_type->data.maybe.child_type->id == TypeTableEntryIdPromise)
     {
         zig_panic("TODO audit this function");
@@ -3705,7 +3705,7 @@ static LLVMValueRef ir_render_cmpxchg(CodeGen *g, IrExecutable *executable, IrIn
             success_order, failure_order, instruction->is_weak);
 
     TypeTableEntry *maybe_type = instruction->base.value.type;
-    assert(maybe_type->id == TypeTableEntryIdMaybe);
+    assert(maybe_type->id == TypeTableEntryIdOptional);
     TypeTableEntry *child_type = maybe_type->data.maybe.child_type;
 
     if (type_is_codegen_pointer(child_type)) {
@@ -4115,10 +4115,10 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutable *execu
     }
 }
 
-static LLVMValueRef ir_render_maybe_wrap(CodeGen *g, IrExecutable *executable, IrInstructionMaybeWrap *instruction) {
+static LLVMValueRef ir_render_maybe_wrap(CodeGen *g, IrExecutable *executable, IrInstructionOptionalWrap *instruction) {
     TypeTableEntry *wanted_type = instruction->base.value.type;
 
-    assert(wanted_type->id == TypeTableEntryIdMaybe);
+    assert(wanted_type->id == TypeTableEntryIdOptional);
 
     TypeTableEntry *child_type = wanted_type->data.maybe.child_type;
 
@@ -4699,8 +4699,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_asm(g, executable, (IrInstructionAsm *)instruction);
         case IrInstructionIdTestNonNull:
             return ir_render_test_non_null(g, executable, (IrInstructionTestNonNull *)instruction);
-        case IrInstructionIdUnwrapMaybe:
-            return ir_render_unwrap_maybe(g, executable, (IrInstructionUnwrapMaybe *)instruction);
+        case IrInstructionIdUnwrapOptional:
+            return ir_render_unwrap_maybe(g, executable, (IrInstructionUnwrapOptional *)instruction);
         case IrInstructionIdClz:
             return ir_render_clz(g, executable, (IrInstructionClz *)instruction);
         case IrInstructionIdCtz:
@@ -4741,8 +4741,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_unwrap_err_code(g, executable, (IrInstructionUnwrapErrCode *)instruction);
         case IrInstructionIdUnwrapErrPayload:
             return ir_render_unwrap_err_payload(g, executable, (IrInstructionUnwrapErrPayload *)instruction);
-        case IrInstructionIdMaybeWrap:
-            return ir_render_maybe_wrap(g, executable, (IrInstructionMaybeWrap *)instruction);
+        case IrInstructionIdOptionalWrap:
+            return ir_render_maybe_wrap(g, executable, (IrInstructionOptionalWrap *)instruction);
         case IrInstructionIdErrWrapCode:
             return ir_render_err_wrap_code(g, executable, (IrInstructionErrWrapCode *)instruction);
         case IrInstructionIdErrWrapPayload:
@@ -4972,7 +4972,7 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con
             }
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdFn:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdPromise:
             {
                 LLVMValueRef ptr_val = gen_const_val(g, const_val, "");
@@ -5137,19 +5137,19 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c
             } else {
                 return LLVMConstNull(LLVMInt1Type());
             }
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 TypeTableEntry *child_type = type_entry->data.maybe.child_type;
                 if (child_type->zero_bits) {
-                    return LLVMConstInt(LLVMInt1Type(), const_val->data.x_nullable ? 1 : 0, false);
+                    return LLVMConstInt(LLVMInt1Type(), const_val->data.x_optional ? 1 : 0, false);
                 } else if (type_is_codegen_pointer(child_type)) {
                     return gen_const_val_ptr(g, const_val, name);
                 } else {
                     LLVMValueRef child_val;
                     LLVMValueRef maybe_val;
                     bool make_unnamed_struct;
-                    if (const_val->data.x_nullable) {
-                        child_val = gen_const_val(g, const_val->data.x_nullable, "");
+                    if (const_val->data.x_optional) {
+                        child_val = gen_const_val(g, const_val->data.x_optional, "");
                         maybe_val = LLVMConstAllOnes(LLVMInt1Type());
 
                         make_unnamed_struct = is_llvm_value_unnamed_type(const_val->type, child_val);
@@ -5755,8 +5755,8 @@ static void do_code_gen(CodeGen *g) {
             } else if (instruction->id == IrInstructionIdSlice) {
                 IrInstructionSlice *slice_instruction = (IrInstructionSlice *)instruction;
                 slot = &slice_instruction->tmp_ptr;
-            } else if (instruction->id == IrInstructionIdMaybeWrap) {
-                IrInstructionMaybeWrap *maybe_wrap_instruction = (IrInstructionMaybeWrap *)instruction;
+            } else if (instruction->id == IrInstructionIdOptionalWrap) {
+                IrInstructionOptionalWrap *maybe_wrap_instruction = (IrInstructionOptionalWrap *)instruction;
                 slot = &maybe_wrap_instruction->tmp_ptr;
             } else if (instruction->id == IrInstructionIdErrWrapPayload) {
                 IrInstructionErrWrapPayload *err_wrap_payload_instruction = (IrInstructionErrWrapPayload *)instruction;
@@ -6511,7 +6511,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             "    ComptimeInt: void,\n"
             "    Undefined: void,\n"
             "    Null: void,\n"
-            "    Nullable: Nullable,\n"
+            "    Optional: Optional,\n"
             "    ErrorUnion: ErrorUnion,\n"
             "    ErrorSet: ErrorSet,\n"
             "    Enum: Enum,\n"
@@ -6570,7 +6570,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             "        defs: []Definition,\n"
             "    };\n"
             "\n"
-            "    pub const Nullable = struct {\n"
+            "    pub const Optional = struct {\n"
             "        child: type,\n"
             "    };\n"
             "\n"
@@ -7145,7 +7145,7 @@ static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, TypeTableEntry
         case TypeTableEntryIdArray:
             prepend_c_type_to_decl_list(g, gen_h, type_entry->data.array.child_type);
             return;
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             prepend_c_type_to_decl_list(g, gen_h, type_entry->data.maybe.child_type);
             return;
         case TypeTableEntryIdFn:
@@ -7234,7 +7234,7 @@ static void get_c_type(CodeGen *g, GenH *gen_h, TypeTableEntry *type_entry, Buf
                 buf_appendf(out_buf, "%s%s *", const_str, buf_ptr(&child_buf));
                 break;
             }
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 TypeTableEntry *child_type = type_entry->data.maybe.child_type;
                 if (child_type->zero_bits) {
@@ -7448,7 +7448,7 @@ static void gen_h_file(CodeGen *g) {
             case TypeTableEntryIdBlock:
             case TypeTableEntryIdBoundFn:
             case TypeTableEntryIdArgTuple:
-            case TypeTableEntryIdMaybe:
+            case TypeTableEntryIdOptional:
             case TypeTableEntryIdFn:
             case TypeTableEntryIdPromise:
                 zig_unreachable();
src/ir.cpp
@@ -47,7 +47,7 @@ enum ConstCastResultId {
     ConstCastResultIdErrSetGlobal,
     ConstCastResultIdPointerChild,
     ConstCastResultIdSliceChild,
-    ConstCastResultIdNullableChild,
+    ConstCastResultIdOptionalChild,
     ConstCastResultIdErrorUnionPayload,
     ConstCastResultIdErrorUnionErrorSet,
     ConstCastResultIdFnAlign,
@@ -86,7 +86,7 @@ struct ConstCastOnly {
         ConstCastErrSetMismatch error_set;
         ConstCastOnly *pointer_child;
         ConstCastOnly *slice_child;
-        ConstCastOnly *nullable_child;
+        ConstCastOnly *optional_child;
         ConstCastOnly *error_union_payload;
         ConstCastOnly *error_union_error_set;
         ConstCastOnly *return_type;
@@ -372,8 +372,8 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionTestNonNull *) {
     return IrInstructionIdTestNonNull;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapMaybe *) {
-    return IrInstructionIdUnwrapMaybe;
+static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapOptional *) {
+    return IrInstructionIdUnwrapOptional;
 }
 
 static constexpr IrInstructionId ir_instruction_id(IrInstructionClz *) {
@@ -524,8 +524,8 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapErrPayload
     return IrInstructionIdUnwrapErrPayload;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionMaybeWrap *) {
-    return IrInstructionIdMaybeWrap;
+static constexpr IrInstructionId ir_instruction_id(IrInstructionOptionalWrap *) {
+    return IrInstructionIdOptionalWrap;
 }
 
 static constexpr IrInstructionId ir_instruction_id(IrInstructionErrWrapPayload *) {
@@ -1571,7 +1571,7 @@ static IrInstruction *ir_build_test_nonnull_from(IrBuilder *irb, IrInstruction *
 static IrInstruction *ir_build_unwrap_maybe(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value,
         bool safety_check_on)
 {
-    IrInstructionUnwrapMaybe *instruction = ir_build_instruction<IrInstructionUnwrapMaybe>(irb, scope, source_node);
+    IrInstructionUnwrapOptional *instruction = ir_build_instruction<IrInstructionUnwrapOptional>(irb, scope, source_node);
     instruction->value = value;
     instruction->safety_check_on = safety_check_on;
 
@@ -1590,7 +1590,7 @@ static IrInstruction *ir_build_unwrap_maybe_from(IrBuilder *irb, IrInstruction *
 }
 
 static IrInstruction *ir_build_maybe_wrap(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
-    IrInstructionMaybeWrap *instruction = ir_build_instruction<IrInstructionMaybeWrap>(irb, scope, source_node);
+    IrInstructionOptionalWrap *instruction = ir_build_instruction<IrInstructionOptionalWrap>(irb, scope, source_node);
     instruction->value = value;
 
     ir_ref_instruction(value, irb->current_basic_block);
@@ -2496,9 +2496,9 @@ static IrInstruction *ir_build_arg_type(IrBuilder *irb, Scope *scope, AstNode *s
     return &instruction->base;
 }
 
-static IrInstruction *ir_build_error_return_trace(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstructionErrorReturnTrace::Nullable nullable) {
+static IrInstruction *ir_build_error_return_trace(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstructionErrorReturnTrace::Optional optional) {
     IrInstructionErrorReturnTrace *instruction = ir_build_instruction<IrInstructionErrorReturnTrace>(irb, scope, source_node);
-    instruction->nullable = nullable;
+    instruction->optional = optional;
 
     return &instruction->base;
 }
@@ -3295,9 +3295,9 @@ static IrInstruction *ir_gen_maybe_ok_or(IrBuilder *irb, Scope *parent_scope, As
         is_comptime = ir_build_test_comptime(irb, parent_scope, node, is_non_null);
     }
 
-    IrBasicBlock *ok_block = ir_create_basic_block(irb, parent_scope, "MaybeNonNull");
-    IrBasicBlock *null_block = ir_create_basic_block(irb, parent_scope, "MaybeNull");
-    IrBasicBlock *end_block = ir_create_basic_block(irb, parent_scope, "MaybeEnd");
+    IrBasicBlock *ok_block = ir_create_basic_block(irb, parent_scope, "OptionalNonNull");
+    IrBasicBlock *null_block = ir_create_basic_block(irb, parent_scope, "OptionalNull");
+    IrBasicBlock *end_block = ir_create_basic_block(irb, parent_scope, "OptionalEnd");
     ir_build_cond_br(irb, parent_scope, node, is_non_null, ok_block, null_block, is_comptime);
 
     ir_set_cursor_at_end_and_append_block(irb, null_block);
@@ -3426,7 +3426,7 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, Scope *scope, AstNode *node)
             return ir_gen_bin_op_id(irb, scope, node, IrBinOpArrayMult);
         case BinOpTypeMergeErrorSets:
             return ir_gen_bin_op_id(irb, scope, node, IrBinOpMergeErrorSets);
-        case BinOpTypeUnwrapMaybe:
+        case BinOpTypeUnwrapOptional:
             return ir_gen_maybe_ok_or(irb, scope, node);
         case BinOpTypeErrorUnion:
             return ir_gen_error_union(irb, scope, node);
@@ -4703,9 +4703,9 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNod
             return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpNegation), lval);
         case PrefixOpNegationWrap:
             return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpNegationWrap), lval);
-        case PrefixOpMaybe:
-            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpMaybe), lval);
-        case PrefixOpUnwrapMaybe:
+        case PrefixOpOptional:
+            return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpOptional), lval);
+        case PrefixOpUnwrapOptional:
             return ir_gen_maybe_assert_ok(irb, scope, node, lval);
         case PrefixOpAddrOf: {
             AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
@@ -5370,9 +5370,9 @@ static IrInstruction *ir_gen_test_expr(IrBuilder *irb, Scope *scope, AstNode *no
     IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node, maybe_val_ptr);
     IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_val);
 
-    IrBasicBlock *then_block = ir_create_basic_block(irb, scope, "MaybeThen");
-    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "MaybeElse");
-    IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "MaybeEndIf");
+    IrBasicBlock *then_block = ir_create_basic_block(irb, scope, "OptionalThen");
+    IrBasicBlock *else_block = ir_create_basic_block(irb, scope, "OptionalElse");
+    IrBasicBlock *endif_block = ir_create_basic_block(irb, scope, "OptionalEndIf");
 
     IrInstruction *is_comptime;
     if (ir_should_inline(irb->exec, scope)) {
@@ -7519,7 +7519,7 @@ static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstruction *instruc
         }
     } else if (const_val_fits_in_num_lit(const_val, other_type)) {
         return true;
-    } else if (other_type->id == TypeTableEntryIdMaybe) {
+    } else if (other_type->id == TypeTableEntryIdOptional) {
         TypeTableEntry *child_type = other_type->data.maybe.child_type;
         if (const_val_fits_in_num_lit(const_val, child_type)) {
             return true;
@@ -7663,7 +7663,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry
         return result;
 
     // * and [*] can do a const-cast-only to ?* and ?[*], respectively
-    if (expected_type->id == TypeTableEntryIdMaybe &&
+    if (expected_type->id == TypeTableEntryIdOptional &&
         expected_type->data.maybe.child_type->id == TypeTableEntryIdPointer &&
         actual_type->id == TypeTableEntryIdPointer)
     {
@@ -7718,12 +7718,12 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry
     }
 
     // maybe
-    if (expected_type->id == TypeTableEntryIdMaybe && actual_type->id == TypeTableEntryIdMaybe) {
+    if (expected_type->id == TypeTableEntryIdOptional && actual_type->id == TypeTableEntryIdOptional) {
         ConstCastOnly child = types_match_const_cast_only(ira, expected_type->data.maybe.child_type, actual_type->data.maybe.child_type, source_node);
         if (child.id != ConstCastResultIdOk) {
-            result.id = ConstCastResultIdNullableChild;
-            result.data.nullable_child = allocate_nonzero<ConstCastOnly>(1);
-            *result.data.nullable_child = child;
+            result.id = ConstCastResultIdOptionalChild;
+            result.data.optional_child = allocate_nonzero<ConstCastOnly>(1);
+            *result.data.optional_child = child;
         }
         return result;
     }
@@ -7925,7 +7925,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira,
     }
 
     // implicit conversion from ?T to ?U
-    if (expected_type->id == TypeTableEntryIdMaybe && actual_type->id == TypeTableEntryIdMaybe) {
+    if (expected_type->id == TypeTableEntryIdOptional && actual_type->id == TypeTableEntryIdOptional) {
         ImplicitCastMatchResult res = ir_types_match_with_implicit_cast(ira, expected_type->data.maybe.child_type,
                 actual_type->data.maybe.child_type, value);
         if (res != ImplicitCastMatchResultNo)
@@ -7933,7 +7933,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira,
     }
 
     // implicit conversion from non maybe type to maybe type
-    if (expected_type->id == TypeTableEntryIdMaybe) {
+    if (expected_type->id == TypeTableEntryIdOptional) {
         ImplicitCastMatchResult res = ir_types_match_with_implicit_cast(ira, expected_type->data.maybe.child_type,
                 actual_type, value);
         if (res != ImplicitCastMatchResultNo)
@@ -7941,7 +7941,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira,
     }
 
     // implicit conversion from null literal to maybe type
-    if (expected_type->id == TypeTableEntryIdMaybe &&
+    if (expected_type->id == TypeTableEntryIdOptional &&
         actual_type->id == TypeTableEntryIdNull)
     {
         return ImplicitCastMatchResultYes;
@@ -7963,7 +7963,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira,
 
     // implicit conversion from T to U!?T
     if (expected_type->id == TypeTableEntryIdErrorUnion &&
-        expected_type->data.error_union.payload_type->id == TypeTableEntryIdMaybe &&
+        expected_type->data.error_union.payload_type->id == TypeTableEntryIdOptional &&
         ir_types_match_with_implicit_cast(ira,
             expected_type->data.error_union.payload_type->data.maybe.child_type,
             actual_type, value))
@@ -8072,7 +8072,7 @@ static ImplicitCastMatchResult ir_types_match_with_implicit_cast(IrAnalyze *ira,
     }
 
     // implicit [N]T to ?[]const T
-    if (expected_type->id == TypeTableEntryIdMaybe &&
+    if (expected_type->id == TypeTableEntryIdOptional &&
         is_slice(expected_type->data.maybe.child_type) &&
         actual_type->id == TypeTableEntryIdArray)
     {
@@ -8552,13 +8552,13 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
             continue;
         }
 
-        if (prev_type->id == TypeTableEntryIdMaybe &&
+        if (prev_type->id == TypeTableEntryIdOptional &&
             types_match_const_cast_only(ira, prev_type->data.maybe.child_type, cur_type, source_node).id == ConstCastResultIdOk)
         {
             continue;
         }
 
-        if (cur_type->id == TypeTableEntryIdMaybe &&
+        if (cur_type->id == TypeTableEntryIdOptional &&
                    types_match_const_cast_only(ira, cur_type->data.maybe.child_type, prev_type, source_node).id == ConstCastResultIdOk)
         {
             prev_inst = cur_inst;
@@ -8711,7 +8711,7 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
             ir_add_error_node(ira, source_node,
                 buf_sprintf("unable to make maybe out of number literal"));
             return ira->codegen->builtin_types.entry_invalid;
-        } else if (prev_inst->value.type->id == TypeTableEntryIdMaybe) {
+        } else if (prev_inst->value.type->id == TypeTableEntryIdOptional) {
             return prev_inst->value.type;
         } else {
             return get_maybe_type(ira->codegen, prev_inst->value.type);
@@ -9193,7 +9193,7 @@ static FnTableEntry *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value) {
 }
 
 static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) {
-    assert(wanted_type->id == TypeTableEntryIdMaybe);
+    assert(wanted_type->id == TypeTableEntryIdOptional);
 
     if (instr_is_comptime(value)) {
         TypeTableEntry *payload_type = wanted_type->data.maybe.child_type;
@@ -9211,7 +9211,7 @@ static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *sourc
         if (get_codegen_ptr_type(wanted_type) != nullptr) {
             copy_const_val(&const_instruction->base.value, val, val->data.x_ptr.mut == ConstPtrMutComptimeConst);
         } else {
-            const_instruction->base.value.data.x_nullable = val;
+            const_instruction->base.value.data.x_optional = val;
         }
         const_instruction->base.value.type = wanted_type;
         return &const_instruction->base;
@@ -9219,7 +9219,7 @@ static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *sourc
 
     IrInstruction *result = ir_build_maybe_wrap(&ira->new_irb, source_instr->scope, source_instr->source_node, value);
     result->value.type = wanted_type;
-    result->value.data.rh_maybe = RuntimeHintMaybeNonNull;
+    result->value.data.rh_maybe = RuntimeHintOptionalNonNull;
     ir_add_alloca(ira, result, wanted_type);
     return result;
 }
@@ -9361,7 +9361,7 @@ static IrInstruction *ir_analyze_cast_ref(IrAnalyze *ira, IrInstruction *source_
 }
 
 static IrInstruction *ir_analyze_null_to_maybe(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) {
-    assert(wanted_type->id == TypeTableEntryIdMaybe);
+    assert(wanted_type->id == TypeTableEntryIdOptional);
     assert(instr_is_comptime(value));
 
     ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
@@ -9373,7 +9373,7 @@ static IrInstruction *ir_analyze_null_to_maybe(IrAnalyze *ira, IrInstruction *so
         const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
         const_instruction->base.value.data.x_ptr.data.hard_coded_addr.addr = 0;
     } else {
-        const_instruction->base.value.data.x_nullable = nullptr;
+        const_instruction->base.value.data.x_optional = nullptr;
     }
     const_instruction->base.value.type = wanted_type;
     return &const_instruction->base;
@@ -9992,7 +9992,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
     }
 
     // explicit cast from [N]T to ?[]const N
-    if (wanted_type->id == TypeTableEntryIdMaybe &&
+    if (wanted_type->id == TypeTableEntryIdOptional &&
         is_slice(wanted_type->data.maybe.child_type) &&
         actual_type->id == TypeTableEntryIdArray)
     {
@@ -10091,7 +10091,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
 
     // explicit cast from T to ?T
     // note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism
-    if (wanted_type->id == TypeTableEntryIdMaybe) {
+    if (wanted_type->id == TypeTableEntryIdOptional) {
         TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type;
         if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk) {
             return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
@@ -10120,7 +10120,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
     }
 
     // explicit cast from null literal to maybe type
-    if (wanted_type->id == TypeTableEntryIdMaybe &&
+    if (wanted_type->id == TypeTableEntryIdOptional &&
         actual_type->id == TypeTableEntryIdNull)
     {
         return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type);
@@ -10173,8 +10173,8 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
 
     // explicit cast from T to E!?T
     if (wanted_type->id == TypeTableEntryIdErrorUnion &&
-        wanted_type->data.error_union.payload_type->id == TypeTableEntryIdMaybe &&
-        actual_type->id != TypeTableEntryIdMaybe)
+        wanted_type->data.error_union.payload_type->id == TypeTableEntryIdOptional &&
+        actual_type->id != TypeTableEntryIdOptional)
     {
         TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type;
         if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node).id == ConstCastResultIdOk ||
@@ -10737,13 +10737,13 @@ static bool resolve_cmp_op_id(IrBinOp op_id, Cmp cmp) {
     }
 }
 
-static bool nullable_value_is_null(ConstExprValue *val) {
+static bool optional_value_is_null(ConstExprValue *val) {
     assert(val->special == ConstValSpecialStatic);
     if (get_codegen_ptr_type(val->type) != nullptr) {
         return val->data.x_ptr.special == ConstPtrSpecialHardCodedAddr &&
             val->data.x_ptr.data.hard_coded_addr.addr == 0;
     } else {
-        return val->data.x_nullable == nullptr;
+        return val->data.x_optional == nullptr;
     }
 }
 
@@ -10755,8 +10755,8 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
     IrBinOp op_id = bin_op_instruction->op_id;
     bool is_equality_cmp = (op_id == IrBinOpCmpEq || op_id == IrBinOpCmpNotEq);
     if (is_equality_cmp &&
-        ((op1->value.type->id == TypeTableEntryIdNull && op2->value.type->id == TypeTableEntryIdMaybe) ||
-        (op2->value.type->id == TypeTableEntryIdNull && op1->value.type->id == TypeTableEntryIdMaybe) ||
+        ((op1->value.type->id == TypeTableEntryIdNull && op2->value.type->id == TypeTableEntryIdOptional) ||
+        (op2->value.type->id == TypeTableEntryIdNull && op1->value.type->id == TypeTableEntryIdOptional) ||
         (op1->value.type->id == TypeTableEntryIdNull && op2->value.type->id == TypeTableEntryIdNull)))
     {
         if (op1->value.type->id == TypeTableEntryIdNull && op2->value.type->id == TypeTableEntryIdNull) {
@@ -10776,7 +10776,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
             ConstExprValue *maybe_val = ir_resolve_const(ira, maybe_op, UndefBad);
             if (!maybe_val)
                 return ira->codegen->builtin_types.entry_invalid;
-            bool is_null = nullable_value_is_null(maybe_val);
+            bool is_null = optional_value_is_null(maybe_val);
             ConstExprValue *out_val = ir_build_const_from(ira, &bin_op_instruction->base);
             out_val->data.x_bool = (op_id == IrBinOpCmpEq) ? is_null : !is_null;
             return ira->codegen->builtin_types.entry_bool;
@@ -10925,7 +10925,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
         case TypeTableEntryIdStruct:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdUnion:
             ir_add_error_node(ira, source_node,
@@ -11998,7 +11998,7 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi
                 case TypeTableEntryIdComptimeInt:
                 case TypeTableEntryIdUndefined:
                 case TypeTableEntryIdNull:
-                case TypeTableEntryIdMaybe:
+                case TypeTableEntryIdOptional:
                 case TypeTableEntryIdErrorUnion:
                 case TypeTableEntryIdErrorSet:
                 case TypeTableEntryIdNamespace:
@@ -12022,7 +12022,7 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
             zig_panic("TODO export const value of type %s", buf_ptr(&target->value.type->name));
@@ -12049,24 +12049,24 @@ static bool exec_has_err_ret_trace(CodeGen *g, IrExecutable *exec) {
 static TypeTableEntry *ir_analyze_instruction_error_return_trace(IrAnalyze *ira,
         IrInstructionErrorReturnTrace *instruction)
 {
-    if (instruction->nullable == IrInstructionErrorReturnTrace::Null) {
+    if (instruction->optional == IrInstructionErrorReturnTrace::Null) {
         TypeTableEntry *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(ira->codegen);
-        TypeTableEntry *nullable_type = get_maybe_type(ira->codegen, ptr_to_stack_trace_type);
+        TypeTableEntry *optional_type = get_maybe_type(ira->codegen, ptr_to_stack_trace_type);
         if (!exec_has_err_ret_trace(ira->codegen, ira->new_irb.exec)) {
             ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
-            assert(get_codegen_ptr_type(nullable_type) != nullptr);
+            assert(get_codegen_ptr_type(optional_type) != nullptr);
             out_val->data.x_ptr.special = ConstPtrSpecialHardCodedAddr;
             out_val->data.x_ptr.data.hard_coded_addr.addr = 0;
-            return nullable_type;
+            return optional_type;
         }
         IrInstruction *new_instruction = ir_build_error_return_trace(&ira->new_irb, instruction->base.scope,
-                instruction->base.source_node, instruction->nullable);
+                instruction->base.source_node, instruction->optional);
         ir_link_new_instruction(new_instruction, &instruction->base);
-        return nullable_type;
+        return optional_type;
     } else {
         assert(ira->codegen->have_err_ret_tracing);
         IrInstruction *new_instruction = ir_build_error_return_trace(&ira->new_irb, instruction->base.scope,
-                instruction->base.source_node, instruction->nullable);
+                instruction->base.source_node, instruction->optional);
         ir_link_new_instruction(new_instruction, &instruction->base);
         return get_ptr_to_stack_trace_type(ira->codegen);
     }
@@ -12998,7 +12998,7 @@ static TypeTableEntry *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -13017,7 +13017,7 @@ static TypeTableEntry *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op
         case TypeTableEntryIdUnreachable:
         case TypeTableEntryIdOpaque:
             ir_add_error_node(ira, un_op_instruction->base.source_node,
-                    buf_sprintf("type '%s' not nullable", buf_ptr(&type_entry->name)));
+                    buf_sprintf("type '%s' not optional", buf_ptr(&type_entry->name)));
             return ira->codegen->builtin_types.entry_invalid;
     }
     zig_unreachable();
@@ -13109,7 +13109,7 @@ static TypeTableEntry *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructio
             return ir_analyze_negation(ira, un_op_instruction);
         case IrUnOpDereference:
             return ir_analyze_dereference(ira, un_op_instruction);
-        case IrUnOpMaybe:
+        case IrUnOpOptional:
             return ir_analyze_maybe(ira, un_op_instruction);
     }
     zig_unreachable();
@@ -14155,7 +14155,7 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
                         buf_ptr(&child_type->name), buf_ptr(field_name)));
                 return ira->codegen->builtin_types.entry_invalid;
             }
-        } else if (child_type->id == TypeTableEntryIdMaybe) {
+        } else if (child_type->id == TypeTableEntryIdOptional) {
             if (buf_eql_str(field_name, "Child")) {
                 bool ptr_is_const = true;
                 bool ptr_is_volatile = false;
@@ -14339,7 +14339,7 @@ static TypeTableEntry *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructi
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -14607,7 +14607,7 @@ static TypeTableEntry *ir_analyze_instruction_slice_type(IrAnalyze *ira,
         case TypeTableEntryIdStruct:
         case TypeTableEntryIdComptimeFloat:
         case TypeTableEntryIdComptimeInt:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -14715,7 +14715,7 @@ static TypeTableEntry *ir_analyze_instruction_array_type(IrAnalyze *ira,
         case TypeTableEntryIdStruct:
         case TypeTableEntryIdComptimeFloat:
         case TypeTableEntryIdComptimeInt:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -14786,7 +14786,7 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira,
         case TypeTableEntryIdPointer:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -14810,14 +14810,14 @@ static TypeTableEntry *ir_analyze_instruction_test_non_null(IrAnalyze *ira, IrIn
 
     TypeTableEntry *type_entry = value->value.type;
 
-    if (type_entry->id == TypeTableEntryIdMaybe) {
+    if (type_entry->id == TypeTableEntryIdOptional) {
         if (instr_is_comptime(value)) {
             ConstExprValue *maybe_val = ir_resolve_const(ira, value, UndefBad);
             if (!maybe_val)
                 return ira->codegen->builtin_types.entry_invalid;
 
             ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
-            out_val->data.x_bool = !nullable_value_is_null(maybe_val);
+            out_val->data.x_bool = !optional_value_is_null(maybe_val);
             return ira->codegen->builtin_types.entry_bool;
         }
 
@@ -14835,7 +14835,7 @@ static TypeTableEntry *ir_analyze_instruction_test_non_null(IrAnalyze *ira, IrIn
 }
 
 static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
-        IrInstructionUnwrapMaybe *unwrap_maybe_instruction)
+        IrInstructionUnwrapOptional *unwrap_maybe_instruction)
 {
     IrInstruction *value = unwrap_maybe_instruction->value->other;
     if (type_is_invalid(value->value.type))
@@ -14863,9 +14863,9 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
                 ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile);
         ir_link_new_instruction(result_instr, &unwrap_maybe_instruction->base);
         return result_instr->value.type;
-    } else if (type_entry->id != TypeTableEntryIdMaybe) {
+    } else if (type_entry->id != TypeTableEntryIdOptional) {
         ir_add_error_node(ira, unwrap_maybe_instruction->value->source_node,
-                buf_sprintf("expected nullable type, found '%s'", buf_ptr(&type_entry->name)));
+                buf_sprintf("expected optional type, found '%s'", buf_ptr(&type_entry->name)));
         return ira->codegen->builtin_types.entry_invalid;
     }
     TypeTableEntry *child_type = type_entry->data.maybe.child_type;
@@ -14881,7 +14881,7 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
         ConstExprValue *maybe_val = const_ptr_pointee(ira->codegen, val);
 
         if (val->data.x_ptr.mut != ConstPtrMutRuntimeVar) {
-            if (nullable_value_is_null(maybe_val)) {
+            if (optional_value_is_null(maybe_val)) {
                 ir_add_error(ira, &unwrap_maybe_instruction->base, buf_sprintf("unable to unwrap null"));
                 return ira->codegen->builtin_types.entry_invalid;
             }
@@ -14891,7 +14891,7 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
             if (type_is_codegen_pointer(child_type)) {
                 out_val->data.x_ptr.data.ref.pointee = maybe_val;
             } else {
-                out_val->data.x_ptr.data.ref.pointee = maybe_val->data.x_nullable;
+                out_val->data.x_ptr.data.ref.pointee = maybe_val->data.x_optional;
             }
             return result_type;
         }
@@ -15216,7 +15216,7 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira,
         case TypeTableEntryIdStruct:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdBlock:
         case TypeTableEntryIdBoundFn:
         case TypeTableEntryIdArgTuple:
@@ -15737,7 +15737,7 @@ static TypeTableEntry *ir_analyze_min_max(IrAnalyze *ira, IrInstruction *source_
         case TypeTableEntryIdComptimeInt:
         case TypeTableEntryIdUndefined:
         case TypeTableEntryIdNull:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdUnion:
@@ -16255,11 +16255,11 @@ static bool ir_make_type_info_defs(IrAnalyze *ira, ConstExprValue *out_val, Scop
                         0, 0);
                     fn_def_fields[6].type = get_maybe_type(ira->codegen, get_slice_type(ira->codegen, u8_ptr));
                     if (fn_node->is_extern && buf_len(fn_node->lib_name) > 0) {
-                        fn_def_fields[6].data.x_nullable = create_const_vals(1);
+                        fn_def_fields[6].data.x_optional = create_const_vals(1);
                         ConstExprValue *lib_name = create_const_str_lit(ira->codegen, fn_node->lib_name);
-                        init_const_slice(ira->codegen, fn_def_fields[6].data.x_nullable, lib_name, 0, buf_len(fn_node->lib_name), true);
+                        init_const_slice(ira->codegen, fn_def_fields[6].data.x_optional, lib_name, 0, buf_len(fn_node->lib_name), true);
                     } else {
-                        fn_def_fields[6].data.x_nullable = nullptr;
+                        fn_def_fields[6].data.x_optional = nullptr;
                     }
                     // return_type: type
                     ensure_field_index(fn_def_val->type, "return_type", 7);
@@ -16507,11 +16507,11 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t
 
                 break;
             }
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             {
                 result = create_const_vals(1);
                 result->special = ConstValSpecialStatic;
-                result->type = ir_type_info_get_type(ira, "Nullable");
+                result->type = ir_type_info_get_type(ira, "Optional");
 
                 ConstExprValue *fields = create_const_vals(1);
                 result->data.x_struct.fields = fields;
@@ -16725,10 +16725,10 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t
                     inner_fields[1].type = get_maybe_type(ira->codegen, type_info_enum_field_type);
 
                     if (fields[1].data.x_type == ira->codegen->builtin_types.entry_undef) {
-                        inner_fields[1].data.x_nullable = nullptr;
+                        inner_fields[1].data.x_optional = nullptr;
                     } else {
-                        inner_fields[1].data.x_nullable = create_const_vals(1);
-                        make_enum_field_val(inner_fields[1].data.x_nullable, union_field->enum_field, type_info_enum_field_type);
+                        inner_fields[1].data.x_optional = create_const_vals(1);
+                        make_enum_field_val(inner_fields[1].data.x_optional, union_field->enum_field, type_info_enum_field_type);
                     }
 
                     inner_fields[2].special = ConstValSpecialStatic;
@@ -16796,13 +16796,13 @@ static ConstExprValue *ir_make_type_info_value(IrAnalyze *ira, TypeTableEntry *t
                     inner_fields[1].type = get_maybe_type(ira->codegen, ira->codegen->builtin_types.entry_usize);
 
                     if (!type_has_bits(struct_field->type_entry)) {
-                        inner_fields[1].data.x_nullable = nullptr;
+                        inner_fields[1].data.x_optional = nullptr;
                     } else {
                         size_t byte_offset = LLVMOffsetOfElement(ira->codegen->target_data_ref, type_entry->type_ref, struct_field->gen_index);
-                        inner_fields[1].data.x_nullable = create_const_vals(1);
-                        inner_fields[1].data.x_nullable->special = ConstValSpecialStatic;
-                        inner_fields[1].data.x_nullable->type = ira->codegen->builtin_types.entry_usize;
-                        bigint_init_unsigned(&inner_fields[1].data.x_nullable->data.x_bigint, byte_offset);
+                        inner_fields[1].data.x_optional = create_const_vals(1);
+                        inner_fields[1].data.x_optional->special = ConstValSpecialStatic;
+                        inner_fields[1].data.x_optional->type = ira->codegen->builtin_types.entry_usize;
+                        bigint_init_unsigned(&inner_fields[1].data.x_optional->data.x_bigint, byte_offset);
                     }
 
                     inner_fields[2].special = ConstValSpecialStatic;
@@ -18027,7 +18027,7 @@ static TypeTableEntry *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstruc
         case TypeTableEntryIdPromise:
         case TypeTableEntryIdArray:
         case TypeTableEntryIdStruct:
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
         case TypeTableEntryIdErrorUnion:
         case TypeTableEntryIdErrorSet:
         case TypeTableEntryIdEnum:
@@ -18591,7 +18591,7 @@ static IrInstruction *ir_align_cast(IrAnalyze *ira, IrInstruction *target, uint3
         old_align_bytes = fn_type_id.alignment;
         fn_type_id.alignment = align_bytes;
         result_type = get_fn_type(ira->codegen, &fn_type_id);
-    } else if (target_type->id == TypeTableEntryIdMaybe &&
+    } else if (target_type->id == TypeTableEntryIdOptional &&
             target_type->data.maybe.child_type->id == TypeTableEntryIdPointer)
     {
         TypeTableEntry *ptr_type = target_type->data.maybe.child_type;
@@ -18599,7 +18599,7 @@ static IrInstruction *ir_align_cast(IrAnalyze *ira, IrInstruction *target, uint3
         TypeTableEntry *better_ptr_type = adjust_ptr_align(ira->codegen, ptr_type, align_bytes);
 
         result_type = get_maybe_type(ira->codegen, better_ptr_type);
-    } else if (target_type->id == TypeTableEntryIdMaybe &&
+    } else if (target_type->id == TypeTableEntryIdOptional &&
             target_type->data.maybe.child_type->id == TypeTableEntryIdFn)
     {
         FnTypeId fn_type_id = target_type->data.maybe.child_type->data.fn.fn_type_id;
@@ -18757,7 +18757,7 @@ static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue
             return;
         case TypeTableEntryIdStruct:
             zig_panic("TODO buf_write_value_bytes struct type");
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             zig_panic("TODO buf_write_value_bytes maybe type");
         case TypeTableEntryIdErrorUnion:
             zig_panic("TODO buf_write_value_bytes error union");
@@ -18815,7 +18815,7 @@ static void buf_read_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue
             zig_panic("TODO buf_read_value_bytes array type");
         case TypeTableEntryIdStruct:
             zig_panic("TODO buf_read_value_bytes struct type");
-        case TypeTableEntryIdMaybe:
+        case TypeTableEntryIdOptional:
             zig_panic("TODO buf_read_value_bytes maybe type");
         case TypeTableEntryIdErrorUnion:
             zig_panic("TODO buf_read_value_bytes error union");
@@ -19731,7 +19731,7 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
         case IrInstructionIdUnionInit:
         case IrInstructionIdStructFieldPtr:
         case IrInstructionIdUnionFieldPtr:
-        case IrInstructionIdMaybeWrap:
+        case IrInstructionIdOptionalWrap:
         case IrInstructionIdErrWrapCode:
         case IrInstructionIdErrWrapPayload:
         case IrInstructionIdCast:
@@ -19791,8 +19791,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_size_of(ira, (IrInstructionSizeOf *)instruction);
         case IrInstructionIdTestNonNull:
             return ir_analyze_instruction_test_non_null(ira, (IrInstructionTestNonNull *)instruction);
-        case IrInstructionIdUnwrapMaybe:
-            return ir_analyze_instruction_unwrap_maybe(ira, (IrInstructionUnwrapMaybe *)instruction);
+        case IrInstructionIdUnwrapOptional:
+            return ir_analyze_instruction_unwrap_maybe(ira, (IrInstructionUnwrapOptional *)instruction);
         case IrInstructionIdClz:
             return ir_analyze_instruction_clz(ira, (IrInstructionClz *)instruction);
         case IrInstructionIdCtz:
@@ -20128,7 +20128,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdSliceType:
         case IrInstructionIdSizeOf:
         case IrInstructionIdTestNonNull:
-        case IrInstructionIdUnwrapMaybe:
+        case IrInstructionIdUnwrapOptional:
         case IrInstructionIdClz:
         case IrInstructionIdCtz:
         case IrInstructionIdSwitchVar:
@@ -20150,7 +20150,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdFrameAddress:
         case IrInstructionIdTestErr:
         case IrInstructionIdUnwrapErrCode:
-        case IrInstructionIdMaybeWrap:
+        case IrInstructionIdOptionalWrap:
         case IrInstructionIdErrWrapCode:
         case IrInstructionIdErrWrapPayload:
         case IrInstructionIdFnProto:
src/ir_print.cpp
@@ -148,7 +148,7 @@ static const char *ir_un_op_id_str(IrUnOp op_id) {
             return "-%";
         case IrUnOpDereference:
             return "*";
-        case IrUnOpMaybe:
+        case IrUnOpOptional:
             return "?";
     }
     zig_unreachable();
@@ -481,7 +481,7 @@ static void ir_print_test_null(IrPrint *irp, IrInstructionTestNonNull *instructi
     fprintf(irp->f, " != null");
 }
 
-static void ir_print_unwrap_maybe(IrPrint *irp, IrInstructionUnwrapMaybe *instruction) {
+static void ir_print_unwrap_maybe(IrPrint *irp, IrInstructionUnwrapOptional *instruction) {
     fprintf(irp->f, "&??*");
     ir_print_other_instruction(irp, instruction->value);
     if (!instruction->safety_check_on) {
@@ -777,7 +777,7 @@ static void ir_print_unwrap_err_payload(IrPrint *irp, IrInstructionUnwrapErrPayl
     }
 }
 
-static void ir_print_maybe_wrap(IrPrint *irp, IrInstructionMaybeWrap *instruction) {
+static void ir_print_maybe_wrap(IrPrint *irp, IrInstructionOptionalWrap *instruction) {
     fprintf(irp->f, "@maybeWrap(");
     ir_print_other_instruction(irp, instruction->value);
     fprintf(irp->f, ")");
@@ -1032,7 +1032,7 @@ static void ir_print_export(IrPrint *irp, IrInstructionExport *instruction) {
 
 static void ir_print_error_return_trace(IrPrint *irp, IrInstructionErrorReturnTrace *instruction) {
     fprintf(irp->f, "@errorReturnTrace(");
-    switch (instruction->nullable) {
+    switch (instruction->optional) {
         case IrInstructionErrorReturnTrace::Null:
             fprintf(irp->f, "Null");
             break;
@@ -1348,8 +1348,8 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdTestNonNull:
             ir_print_test_null(irp, (IrInstructionTestNonNull *)instruction);
             break;
-        case IrInstructionIdUnwrapMaybe:
-            ir_print_unwrap_maybe(irp, (IrInstructionUnwrapMaybe *)instruction);
+        case IrInstructionIdUnwrapOptional:
+            ir_print_unwrap_maybe(irp, (IrInstructionUnwrapOptional *)instruction);
             break;
         case IrInstructionIdCtz:
             ir_print_ctz(irp, (IrInstructionCtz *)instruction);
@@ -1465,8 +1465,8 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdUnwrapErrPayload:
             ir_print_unwrap_err_payload(irp, (IrInstructionUnwrapErrPayload *)instruction);
             break;
-        case IrInstructionIdMaybeWrap:
-            ir_print_maybe_wrap(irp, (IrInstructionMaybeWrap *)instruction);
+        case IrInstructionIdOptionalWrap:
+            ir_print_maybe_wrap(irp, (IrInstructionOptionalWrap *)instruction);
             break;
         case IrInstructionIdErrWrapCode:
             ir_print_err_wrap_code(irp, (IrInstructionErrWrapCode *)instruction);
src/parser.cpp
@@ -1046,12 +1046,11 @@ static AstNode *ast_parse_fn_proto_partial(ParseContext *pc, size_t *token_index
 }
 
 /*
-SuffixOpExpression = ("async" option("<" SuffixOpExpression ">") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | PtrDerefExpression | SliceExpression)
+SuffixOpExpression = ("async" option("&lt;" SuffixOpExpression "&gt;") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression | ".*" | ".?")
 FnCallExpression : token(LParen) list(Expression, token(Comma)) token(RParen)
 ArrayAccessExpression : token(LBracket) Expression token(RBracket)
 SliceExpression = "[" Expression ".." option(Expression) "]"
 FieldAccessExpression : token(Dot) token(Symbol)
-PtrDerefExpression = ".*"
 StructLiteralField : token(Dot) token(Symbol) token(Eq) Expression
 */
 static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
@@ -1148,6 +1147,14 @@ static AstNode *ast_parse_suffix_op_expr(ParseContext *pc, size_t *token_index,
                 AstNode *node = ast_create_node(pc, NodeTypePtrDeref, first_token);
                 node->data.ptr_deref_expr.target = primary_expr;
 
+                primary_expr = node;
+            } else if (token->id == TokenIdQuestion) {
+                *token_index += 1;
+
+                AstNode *node = ast_create_node(pc, NodeTypePrefixOpExpr, first_token);
+                node->data.prefix_op_expr.prefix_op = PrefixOpUnwrapOptional;
+                node->data.prefix_op_expr.primary_expr = primary_expr;
+
                 primary_expr = node;
             } else {
                 ast_invalid_token_error(pc, token);
@@ -1165,8 +1172,8 @@ static PrefixOp tok_to_prefix_op(Token *token) {
         case TokenIdDash: return PrefixOpNegation;
         case TokenIdMinusPercent: return PrefixOpNegationWrap;
         case TokenIdTilde: return PrefixOpBinNot;
-        case TokenIdMaybe: return PrefixOpMaybe;
-        case TokenIdDoubleQuestion: return PrefixOpUnwrapMaybe;
+        case TokenIdQuestion: return PrefixOpOptional;
+        case TokenIdDoubleQuestion: return PrefixOpUnwrapOptional;
         case TokenIdAmpersand: return PrefixOpAddrOf;
         default: return PrefixOpInvalid;
     }
@@ -2304,8 +2311,8 @@ static BinOpType ast_parse_ass_op(ParseContext *pc, size_t *token_index, bool ma
 }
 
 /*
-UnwrapExpression : BoolOrExpression (UnwrapMaybe | UnwrapError) | BoolOrExpression
-UnwrapMaybe : "??" BoolOrExpression
+UnwrapExpression : BoolOrExpression (UnwrapOptional | UnwrapError) | BoolOrExpression
+UnwrapOptional : "??" BoolOrExpression
 UnwrapError = "catch" option("|" Symbol "|") Expression
 */
 static AstNode *ast_parse_unwrap_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
@@ -2322,7 +2329,7 @@ static AstNode *ast_parse_unwrap_expr(ParseContext *pc, size_t *token_index, boo
 
         AstNode *node = ast_create_node(pc, NodeTypeBinOpExpr, token);
         node->data.bin_op_expr.op1 = lhs;
-        node->data.bin_op_expr.bin_op = BinOpTypeUnwrapMaybe;
+        node->data.bin_op_expr.bin_op = BinOpTypeUnwrapOptional;
         node->data.bin_op_expr.op2 = rhs;
 
         return node;
src/tokenizer.cpp
@@ -625,7 +625,7 @@ void tokenize(Buf *buf, Tokenization *out) {
                         t.state = TokenizeStateSawDot;
                         break;
                     case '?':
-                        begin_token(&t, TokenIdMaybe);
+                        begin_token(&t, TokenIdQuestion);
                         t.state = TokenizeStateSawQuestionMark;
                         break;
                     default:
@@ -639,11 +639,6 @@ void tokenize(Buf *buf, Tokenization *out) {
                         end_token(&t);
                         t.state = TokenizeStateStart;
                         break;
-                    case '=':
-                        set_token_id(&t, t.cur_tok, TokenIdMaybeAssign);
-                        end_token(&t);
-                        t.state = TokenizeStateStart;
-                        break;
                     default:
                         t.pos -= 1;
                         end_token(&t);
@@ -1609,8 +1604,7 @@ const char * token_name(TokenId id) {
         case TokenIdLBrace: return "{";
         case TokenIdLBracket: return "[";
         case TokenIdLParen: return "(";
-        case TokenIdMaybe: return "?";
-        case TokenIdMaybeAssign: return "?=";
+        case TokenIdQuestion: return "?";
         case TokenIdMinusEq: return "-=";
         case TokenIdMinusPercent: return "-%";
         case TokenIdMinusPercentEq: return "-%=";
src/tokenizer.hpp
@@ -100,8 +100,7 @@ enum TokenId {
     TokenIdLBrace,
     TokenIdLBracket,
     TokenIdLParen,
-    TokenIdMaybe,
-    TokenIdMaybeAssign,
+    TokenIdQuestion,
     TokenIdMinusEq,
     TokenIdMinusPercent,
     TokenIdMinusPercentEq,
src/translate_c.cpp
@@ -382,7 +382,7 @@ static AstNode *trans_create_node_inline_fn(Context *c, Buf *fn_name, AstNode *r
     fn_def->data.fn_def.fn_proto = fn_proto;
     fn_proto->data.fn_proto.fn_def_node = fn_def;
 
-    AstNode *unwrap_node = trans_create_node_prefix_op(c, PrefixOpUnwrapMaybe, ref_node);
+    AstNode *unwrap_node = trans_create_node_prefix_op(c, PrefixOpUnwrapOptional, ref_node);
     AstNode *fn_call_node = trans_create_node(c, NodeTypeFnCallExpr);
     fn_call_node->data.fn_call_expr.fn_ref_expr = unwrap_node;
 
@@ -410,7 +410,7 @@ static AstNode *trans_create_node_inline_fn(Context *c, Buf *fn_name, AstNode *r
 }
 
 static AstNode *trans_create_node_unwrap_null(Context *c, AstNode *child) {
-    return trans_create_node_prefix_op(c, PrefixOpUnwrapMaybe, child);
+    return trans_create_node_prefix_op(c, PrefixOpUnwrapOptional, child);
 }
 
 static AstNode *get_global(Context *c, Buf *name) {
@@ -879,14 +879,14 @@ static AstNode *trans_type(Context *c, const Type *ty, const SourceLocation &sou
                 }
 
                 if (qual_type_child_is_fn_proto(child_qt)) {
-                    return trans_create_node_prefix_op(c, PrefixOpMaybe, child_node);
+                    return trans_create_node_prefix_op(c, PrefixOpOptional, child_node);
                 }
 
                 PtrLen ptr_len = type_is_opaque(c, child_qt.getTypePtr(), source_loc) ? PtrLenSingle : PtrLenUnknown;
 
                 AstNode *pointer_node = trans_create_node_ptr_type(c, child_qt.isConstQualified(),
                         child_qt.isVolatileQualified(), child_node, ptr_len);
-                return trans_create_node_prefix_op(c, PrefixOpMaybe, pointer_node);
+                return trans_create_node_prefix_op(c, PrefixOpOptional, pointer_node);
             }
         case Type::Typedef:
             {
@@ -1963,7 +1963,7 @@ static AstNode *trans_unary_operator(Context *c, ResultUsed result_used, TransSc
                 bool is_fn_ptr = qual_type_is_fn_ptr(stmt->getSubExpr()->getType());
                 if (is_fn_ptr)
                     return value_node;
-                AstNode *unwrapped = trans_create_node_prefix_op(c, PrefixOpUnwrapMaybe, value_node);
+                AstNode *unwrapped = trans_create_node_prefix_op(c, PrefixOpUnwrapOptional, value_node);
                 return trans_create_node_ptr_deref(c, unwrapped);
             }
         case UO_Plus:
@@ -2587,7 +2587,7 @@ static AstNode *trans_call_expr(Context *c, ResultUsed result_used, TransScope *
             }
         }
         if (callee_node == nullptr) {
-            callee_node = trans_create_node_prefix_op(c, PrefixOpUnwrapMaybe, callee_raw_node);
+            callee_node = trans_create_node_prefix_op(c, PrefixOpUnwrapOptional, callee_raw_node);
         }
     } else {
         callee_node = callee_raw_node;
@@ -4301,7 +4301,7 @@ static AstNode *trans_lookup_ast_maybe_fn(Context *c, AstNode *ref_node) {
         return nullptr;
     if (prefix_node->type != NodeTypePrefixOpExpr)
         return nullptr;
-    if (prefix_node->data.prefix_op_expr.prefix_op != PrefixOpMaybe)
+    if (prefix_node->data.prefix_op_expr.prefix_op != PrefixOpOptional)
         return nullptr;
 
     AstNode *fn_proto_node = prefix_node->data.prefix_op_expr.primary_expr;
src-self-hosted/arg.zig
@@ -99,7 +99,7 @@ pub const Args = struct {
                                 error.ArgumentNotInAllowedSet => {
                                     std.debug.warn("argument '{}' is invalid for flag '{}'\n", args[i], arg);
                                     std.debug.warn("allowed options are ");
-                                    for (??flag.allowed_set) |possible| {
+                                    for (flag.allowed_set.?) |possible| {
                                         std.debug.warn("'{}' ", possible);
                                     }
                                     std.debug.warn("\n");
@@ -276,14 +276,14 @@ test "parse arguments" {
     debug.assert(!args.present("help2"));
     debug.assert(!args.present("init"));
 
-    debug.assert(mem.eql(u8, ??args.single("build-file"), "build.zig"));
-    debug.assert(mem.eql(u8, ??args.single("color"), "on"));
+    debug.assert(mem.eql(u8, args.single("build-file").?, "build.zig"));
+    debug.assert(mem.eql(u8, args.single("color").?, "on"));
 
-    const objects = ??args.many("object");
+    const objects = args.many("object").?;
     debug.assert(mem.eql(u8, objects[0], "obj1"));
     debug.assert(mem.eql(u8, objects[1], "obj2"));
 
-    debug.assert(mem.eql(u8, ??args.single("library"), "lib2"));
+    debug.assert(mem.eql(u8, args.single("library").?, "lib2"));
 
     const pos = args.positionals.toSliceConst();
     debug.assert(mem.eql(u8, pos[0], "build"));
src-self-hosted/llvm.zig
@@ -8,6 +8,6 @@ pub const ContextRef = removeNullability(c.LLVMContextRef);
 pub const BuilderRef = removeNullability(c.LLVMBuilderRef);
 
 fn removeNullability(comptime T: type) type {
-    comptime assert(@typeId(T) == builtin.TypeId.Nullable);
+    comptime assert(@typeId(T) == builtin.TypeId.Optional);
     return T.Child;
 }
src-self-hosted/main.zig
@@ -490,7 +490,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
                 try stderr.print("encountered --pkg-end with no matching --pkg-begin\n");
                 os.exit(1);
             }
-            cur_pkg = ??cur_pkg.parent;
+            cur_pkg = cur_pkg.parent.?;
         }
     }
 
@@ -514,7 +514,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
         },
     }
 
-    const basename = os.path.basename(??in_file);
+    const basename = os.path.basename(in_file.?);
     var it = mem.split(basename, ".");
     const root_name = it.next() ?? {
         try stderr.write("file name cannot be empty\n");
@@ -523,12 +523,12 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
 
     const asm_a = flags.many("assembly");
     const obj_a = flags.many("object");
-    if (in_file == null and (obj_a == null or (??obj_a).len == 0) and (asm_a == null or (??asm_a).len == 0)) {
+    if (in_file == null and (obj_a == null or obj_a.?.len == 0) and (asm_a == null or asm_a.?.len == 0)) {
         try stderr.write("Expected source file argument or at least one --object or --assembly argument\n");
         os.exit(1);
     }
 
-    if (out_type == Module.Kind.Obj and (obj_a != null and (??obj_a).len != 0)) {
+    if (out_type == Module.Kind.Obj and (obj_a != null and obj_a.?.len != 0)) {
         try stderr.write("When building an object file, --object arguments are invalid\n");
         os.exit(1);
     }
std/fmt/index.zig
@@ -111,7 +111,7 @@ pub fn formatType(
         builtin.TypeId.Bool => {
             return output(context, if (value) "true" else "false");
         },
-        builtin.TypeId.Nullable => {
+        builtin.TypeId.Optional => {
             if (value) |payload| {
                 return formatType(payload, fmt, context, Errors, output);
             } else {
@@ -819,11 +819,11 @@ test "parse unsigned comptime" {
 test "fmt.format" {
     {
         const value: ?i32 = 1234;
-        try testFmt("nullable: 1234\n", "nullable: {}\n", value);
+        try testFmt("optional: 1234\n", "optional: {}\n", value);
     }
     {
         const value: ?i32 = null;
-        try testFmt("nullable: null\n", "nullable: {}\n", value);
+        try testFmt("optional: null\n", "optional: {}\n", value);
     }
     {
         const value: error!i32 = 1234;
std/os/linux/vdso.zig
@@ -67,7 +67,7 @@ pub fn lookup(vername: []const u8, name: []const u8) usize {
         if (0 == syms[i].st_shndx) continue;
         if (!mem.eql(u8, name, cstr.toSliceConst(strings + syms[i].st_name))) continue;
         if (maybe_versym) |versym| {
-            if (!checkver(??maybe_verdef, versym[i], vername, strings))
+            if (!checkver(maybe_verdef.?, versym[i], vername, strings))
                 continue;
         }
         return base + syms[i].st_value;
std/os/child_process.zig
@@ -156,7 +156,7 @@ pub const ChildProcess = struct {
             };
         }
         try self.waitUnwrappedWindows();
-        return ??self.term;
+        return self.term.?;
     }
 
     pub fn killPosix(self: *ChildProcess) !Term {
@@ -175,7 +175,7 @@ pub const ChildProcess = struct {
             };
         }
         self.waitUnwrapped();
-        return ??self.term;
+        return self.term.?;
     }
 
     /// Blocks until child process terminates and then cleans up all resources.
@@ -212,8 +212,8 @@ pub const ChildProcess = struct {
         defer Buffer.deinit(&stdout);
         defer Buffer.deinit(&stderr);
 
-        var stdout_file_in_stream = io.FileInStream.init(&??child.stdout);
-        var stderr_file_in_stream = io.FileInStream.init(&??child.stderr);
+        var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?);
+        var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?);
 
         try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size);
         try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size);
@@ -232,7 +232,7 @@ pub const ChildProcess = struct {
         }
 
         try self.waitUnwrappedWindows();
-        return ??self.term;
+        return self.term.?;
     }
 
     fn waitPosix(self: *ChildProcess) !Term {
@@ -242,7 +242,7 @@ pub const ChildProcess = struct {
         }
 
         self.waitUnwrapped();
-        return ??self.term;
+        return self.term.?;
     }
 
     pub fn deinit(self: *ChildProcess) void {
@@ -619,13 +619,13 @@ pub const ChildProcess = struct {
         self.term = null;
 
         if (self.stdin_behavior == StdIo.Pipe) {
-            os.close(??g_hChildStd_IN_Rd);
+            os.close(g_hChildStd_IN_Rd.?);
         }
         if (self.stderr_behavior == StdIo.Pipe) {
-            os.close(??g_hChildStd_ERR_Wr);
+            os.close(g_hChildStd_ERR_Wr.?);
         }
         if (self.stdout_behavior == StdIo.Pipe) {
-            os.close(??g_hChildStd_OUT_Wr);
+            os.close(g_hChildStd_OUT_Wr.?);
         }
     }
 
std/os/index.zig
@@ -422,7 +422,7 @@ pub fn posixExecve(argv: []const []const u8, env_map: *const BufMap, allocator:
 
     const exe_path = argv[0];
     if (mem.indexOfScalar(u8, exe_path, '/') != null) {
-        return posixExecveErrnoToErr(posix.getErrno(posix.execve(??argv_buf[0], argv_buf.ptr, envp_buf.ptr)));
+        return posixExecveErrnoToErr(posix.getErrno(posix.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
     }
 
     const PATH = getEnvPosix("PATH") ?? "/usr/local/bin:/bin/:/usr/bin";
@@ -1729,7 +1729,7 @@ test "windows arg parsing" {
 fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void {
     var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line);
     for (expected_args) |expected_arg| {
-        const arg = ??it.next(debug.global_allocator) catch unreachable;
+        const arg = it.next(debug.global_allocator).? catch unreachable;
         assert(mem.eql(u8, arg, expected_arg));
     }
     assert(it.next(debug.global_allocator) == null);
std/os/path.zig
@@ -265,7 +265,7 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
     var it2 = mem.split(ns2, []u8{sep2});
 
     // TODO ASCII is wrong, we actually need full unicode support to compare paths.
-    return asciiEqlIgnoreCase(??it1.next(), ??it2.next());
+    return asciiEqlIgnoreCase(it1.next().?, it2.next().?);
 }
 
 fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
@@ -286,7 +286,7 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8
             var it2 = mem.split(p2, []u8{sep2});
 
             // TODO ASCII is wrong, we actually need full unicode support to compare paths.
-            return asciiEqlIgnoreCase(??it1.next(), ??it2.next()) and asciiEqlIgnoreCase(??it1.next(), ??it2.next());
+            return asciiEqlIgnoreCase(it1.next().?, it2.next().?) and asciiEqlIgnoreCase(it1.next().?, it2.next().?);
         },
     }
 }
@@ -414,8 +414,8 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
             WindowsPath.Kind.NetworkShare => {
                 result = try allocator.alloc(u8, max_size);
                 var it = mem.split(paths[first_index], "/\\");
-                const server_name = ??it.next();
-                const other_name = ??it.next();
+                const server_name = it.next().?;
+                const other_name = it.next().?;
 
                 result[result_index] = '\\';
                 result_index += 1;
std/special/bootstrap.zig
@@ -54,10 +54,10 @@ fn posixCallMainAndExit() noreturn {
     const argc = argc_ptr[0];
     const argv = @ptrCast([*][*]u8, argc_ptr + 1);
 
-    const envp_nullable = @ptrCast([*]?[*]u8, argv + argc + 1);
+    const envp_optional = @ptrCast([*]?[*]u8, argv + argc + 1);
     var envp_count: usize = 0;
-    while (envp_nullable[envp_count]) |_| : (envp_count += 1) {}
-    const envp = @ptrCast([*][*]u8, envp_nullable)[0..envp_count];
+    while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
+    const envp = @ptrCast([*][*]u8, envp_optional)[0..envp_count];
     if (builtin.os == builtin.Os.linux) {
         const auxv = @ptrCast([*]usize, envp.ptr + envp_count + 1);
         var i: usize = 0;
std/special/builtin.zig
@@ -19,7 +19,7 @@ export fn memset(dest: ?[*]u8, c: u8, n: usize) ?[*]u8 {
 
     var index: usize = 0;
     while (index != n) : (index += 1)
-        (??dest)[index] = c;
+        dest.?[index] = c;
 
     return dest;
 }
@@ -29,7 +29,7 @@ export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) ?[*]
 
     var index: usize = 0;
     while (index != n) : (index += 1)
-        (??dest)[index] = (??src)[index];
+        dest.?[index] = src.?[index];
 
     return dest;
 }
@@ -40,13 +40,13 @@ export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) ?[*]u8 {
     if (@ptrToInt(dest) < @ptrToInt(src)) {
         var index: usize = 0;
         while (index != n) : (index += 1) {
-            (??dest)[index] = (??src)[index];
+            dest.?[index] = src.?[index];
         }
     } else {
         var index = n;
         while (index != 0) {
             index -= 1;
-            (??dest)[index] = (??src)[index];
+            dest.?[index] = src.?[index];
         }
     }
 
std/zig/ast.zig
@@ -1417,7 +1417,7 @@ pub const Node = struct {
             Range,
             Sub,
             SubWrap,
-            UnwrapMaybe,
+            UnwrapOptional,
         };
 
         pub fn iterate(self: *InfixOp, index: usize) ?*Node {
@@ -1475,7 +1475,7 @@ pub const Node = struct {
                 Op.Range,
                 Op.Sub,
                 Op.SubWrap,
-                Op.UnwrapMaybe,
+                Op.UnwrapOptional,
                 => {},
             }
 
@@ -1507,14 +1507,13 @@ pub const Node = struct {
             BitNot,
             BoolNot,
             Cancel,
-            MaybeType,
+            OptionalType,
             Negation,
             NegationWrap,
             Resume,
             PtrType: PtrInfo,
             SliceType: PtrInfo,
             Try,
-            UnwrapMaybe,
         };
 
         pub const PtrInfo = struct {
@@ -1557,12 +1556,12 @@ pub const Node = struct {
                 Op.BitNot,
                 Op.BoolNot,
                 Op.Cancel,
-                Op.MaybeType,
+                Op.OptionalType,
                 Op.Negation,
                 Op.NegationWrap,
                 Op.Try,
                 Op.Resume,
-                Op.UnwrapMaybe,
+                Op.UnwrapOptional,
                 Op.PointerType,
                 => {},
             }
@@ -1619,6 +1618,7 @@ pub const Node = struct {
             ArrayInitializer: InitList,
             StructInitializer: InitList,
             Deref,
+            UnwrapOptional,
 
             pub const InitList = SegmentedList(*Node, 2);
 
std/zig/parse.zig
@@ -711,7 +711,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                     else => {
                         // TODO: this is a special case. Remove this when #760 is fixed
                         if (token_ptr.id == Token.Id.Keyword_error) {
-                            if ((??tok_it.peek()).id == Token.Id.LBrace) {
+                            if (tok_it.peek().?.id == Token.Id.LBrace) {
                                 const error_type_node = try arena.construct(ast.Node.ErrorType{
                                     .base = ast.Node{ .id = ast.Node.Id.ErrorType },
                                     .token = token_index,
@@ -1434,8 +1434,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                 try stack.append(State{
                     .ExpectTokenSave = ExpectTokenSave{
                         .id = Token.Id.AngleBracketRight,
-                        .ptr = &??async_node.rangle_bracket,
-                    },
+                        .ptr = &async_node.rangle_bracket.?                    },
                 });
                 try stack.append(State{ .TypeExprBegin = OptionalCtx{ .RequiredNull = &async_node.allocator_type } });
                 continue;
@@ -1567,7 +1566,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                             .bit_range = null,
                         };
                         // TODO https://github.com/ziglang/zig/issues/1022
-                        const align_info = &??addr_of_info.align_info;
+                        const align_info = &addr_of_info.align_info.?;
 
                         try stack.append(State{ .AlignBitRange = align_info });
                         try stack.append(State{ .Expression = OptionalCtx{ .Required = &align_info.node } });
@@ -1604,7 +1603,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                 switch (token.ptr.id) {
                     Token.Id.Colon => {
                         align_info.bit_range = ast.Node.PrefixOp.PtrInfo.Align.BitRange(undefined);
-                        const bit_range = &??align_info.bit_range;
+                        const bit_range = &align_info.bit_range.?;
 
                         try stack.append(State{ .ExpectToken = Token.Id.RParen });
                         try stack.append(State{ .Expression = OptionalCtx{ .Required = &bit_range.end } });
@@ -2144,7 +2143,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
             State.CurlySuffixExpressionEnd => |opt_ctx| {
                 const lhs = opt_ctx.get() ?? continue;
 
-                if ((??tok_it.peek()).id == Token.Id.Period) {
+                if (tok_it.peek().?.id == Token.Id.Period) {
                     const node = try arena.construct(ast.Node.SuffixOp{
                         .base = ast.Node{ .id = ast.Node.Id.SuffixOp },
                         .lhs = lhs,
@@ -2326,6 +2325,17 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                             stack.append(State{ .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
                             continue;
                         }
+                        if (eatToken(&tok_it, &tree, Token.Id.QuestionMark)) |question_token| {
+                            const node = try arena.construct(ast.Node.SuffixOp{
+                                .base = ast.Node{ .id = ast.Node.Id.SuffixOp },
+                                .lhs = lhs,
+                                .op = ast.Node.SuffixOp.Op.UnwrapOptional,
+                                .rtoken = question_token,
+                            });
+                            opt_ctx.store(&node.base);
+                            stack.append(State{ .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable;
+                            continue;
+                        }
                         const node = try arena.construct(ast.Node.InfixOp{
                             .base = ast.Node{ .id = ast.Node.Id.InfixOp },
                             .lhs = lhs,
@@ -2403,7 +2413,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
                             .arrow_token = next_token_index,
                             .return_type = undefined,
                         };
-                        const return_type_ptr = &((??node.result).return_type);
+                        const return_type_ptr = &node.result.?.return_type;
                         try stack.append(State{ .Expression = OptionalCtx{ .Required = return_type_ptr } });
                         continue;
                     },
@@ -2875,7 +2885,7 @@ const OptionalCtx = union(enum) {
     pub fn get(self: *const OptionalCtx) ?*ast.Node {
         switch (self.*) {
             OptionalCtx.Optional => |ptr| return ptr.*,
-            OptionalCtx.RequiredNull => |ptr| return ??ptr.*,
+            OptionalCtx.RequiredNull => |ptr| return ptr.*.?,
             OptionalCtx.Required => |ptr| return ptr.*,
         }
     }
@@ -3237,7 +3247,7 @@ fn tokenIdToAssignment(id: *const Token.Id) ?ast.Node.InfixOp.Op {
 fn tokenIdToUnwrapExpr(id: @TagType(Token.Id)) ?ast.Node.InfixOp.Op {
     return switch (id) {
         Token.Id.Keyword_catch => ast.Node.InfixOp.Op{ .Catch = null },
-        Token.Id.QuestionMarkQuestionMark => ast.Node.InfixOp.Op{ .UnwrapMaybe = void{} },
+        Token.Id.QuestionMarkQuestionMark => ast.Node.InfixOp.Op{ .UnwrapOptional = void{} },
         else => null,
     };
 }
@@ -3299,8 +3309,7 @@ fn tokenIdToPrefixOp(id: @TagType(Token.Id)) ?ast.Node.PrefixOp.Op {
                 .volatile_token = null,
             },
         },
-        Token.Id.QuestionMark => ast.Node.PrefixOp.Op{ .MaybeType = void{} },
-        Token.Id.QuestionMarkQuestionMark => ast.Node.PrefixOp.Op{ .UnwrapMaybe = void{} },
+        Token.Id.QuestionMark => ast.Node.PrefixOp.Op{ .OptionalType = void{} },
         Token.Id.Keyword_await => ast.Node.PrefixOp.Op{ .Await = void{} },
         Token.Id.Keyword_try => ast.Node.PrefixOp.Op{ .Try = void{} },
         else => null,
@@ -3322,7 +3331,7 @@ fn createToCtxLiteral(arena: *mem.Allocator, opt_ctx: *const OptionalCtx, compti
 }
 
 fn eatToken(tok_it: *ast.Tree.TokenList.Iterator, tree: *ast.Tree, id: @TagType(Token.Id)) ?TokenIndex {
-    const token = ??tok_it.peek();
+    const token = tok_it.peek().?;
 
     if (token.id == id) {
         return nextToken(tok_it, tree).index;
@@ -3334,7 +3343,7 @@ fn eatToken(tok_it: *ast.Tree.TokenList.Iterator, tree: *ast.Tree, id: @TagType(
 fn nextToken(tok_it: *ast.Tree.TokenList.Iterator, tree: *ast.Tree) AnnotatedToken {
     const result = AnnotatedToken{
         .index = tok_it.index,
-        .ptr = ??tok_it.next(),
+        .ptr = tok_it.next().?,
     };
     assert(result.ptr.id != Token.Id.LineComment);
 
std/zig/parser_test.zig
@@ -650,9 +650,10 @@ test "zig fmt: statements with empty line between" {
     );
 }
 
-test "zig fmt: ptr deref operator" {
+test "zig fmt: ptr deref operator and unwrap optional operator" {
     try testCanonical(
         \\const a = b.*;
+        \\const a = b.?;
         \\
     );
 }
@@ -1209,7 +1210,7 @@ test "zig fmt: precedence" {
 test "zig fmt: prefix operators" {
     try testCanonical(
         \\test "prefix operators" {
-        \\    try return --%~??!*&0;
+        \\    try return --%~!*&0;
         \\}
         \\
     );
std/zig/render.zig
@@ -222,7 +222,7 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
                 }
             }
 
-            const value_expr = ??tag.value_expr;
+            const value_expr = tag.value_expr.?;
             try renderToken(tree, stream, tree.prevToken(value_expr.firstToken()), indent, start_col, Space.Space); // =
             try renderExpression(allocator, stream, tree, indent, start_col, value_expr, Space.Comma); // value,
         },
@@ -465,8 +465,7 @@ fn renderExpression(
                 ast.Node.PrefixOp.Op.BoolNot,
                 ast.Node.PrefixOp.Op.Negation,
                 ast.Node.PrefixOp.Op.NegationWrap,
-                ast.Node.PrefixOp.Op.UnwrapMaybe,
-                ast.Node.PrefixOp.Op.MaybeType,
+                ast.Node.PrefixOp.Op.OptionalType,
                 ast.Node.PrefixOp.Op.AddressOf,
                 => {
                     try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None);
@@ -513,7 +512,7 @@ fn renderExpression(
 
                         var it = call_info.params.iterator(0);
                         while (true) {
-                            const param_node = ??it.next();
+                            const param_node = it.next().?;
 
                             const param_node_new_indent = if (param_node.*.id == ast.Node.Id.MultilineStringLiteral) blk: {
                                 break :blk indent;
@@ -559,10 +558,10 @@ fn renderExpression(
                     return renderToken(tree, stream, rbracket, indent, start_col, space); // ]
                 },
 
-                ast.Node.SuffixOp.Op.Deref => {
+                ast.Node.SuffixOp.Op.Deref, ast.Node.SuffixOp.Op.UnwrapOptional => {
                     try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
                     try renderToken(tree, stream, tree.prevToken(suffix_op.rtoken), indent, start_col, Space.None); // .
-                    return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // *
+                    return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // * or ?
                 },
 
                 @TagType(ast.Node.SuffixOp.Op).Slice => |range| {
@@ -595,7 +594,7 @@ fn renderExpression(
                     }
 
                     if (field_inits.len == 1) blk: {
-                        const field_init = ??field_inits.at(0).*.cast(ast.Node.FieldInitializer);
+                        const field_init = field_inits.at(0).*.cast(ast.Node.FieldInitializer).?;
 
                         if (field_init.expr.cast(ast.Node.SuffixOp)) |nested_suffix_op| {
                             if (nested_suffix_op.op == ast.Node.SuffixOp.Op.StructInitializer) {
@@ -688,7 +687,7 @@ fn renderExpression(
                         var count: usize = 1;
                         var it = exprs.iterator(0);
                         while (true) {
-                            const expr = (??it.next()).*;
+                            const expr = it.next().?.*;
                             if (it.peek()) |next_expr| {
                                 const expr_last_token = expr.*.lastToken() + 1;
                                 const loc = tree.tokenLocation(tree.tokens.at(expr_last_token).end, next_expr.*.firstToken());
@@ -806,7 +805,7 @@ fn renderExpression(
                 },
             }
 
-            return renderExpression(allocator, stream, tree, indent, start_col, ??flow_expr.rhs, space);
+            return renderExpression(allocator, stream, tree, indent, start_col, flow_expr.rhs.?, space);
         },
 
         ast.Node.Id.Payload => {
@@ -1245,7 +1244,7 @@ fn renderExpression(
             } else {
                 var it = switch_case.items.iterator(0);
                 while (true) {
-                    const node = ??it.next();
+                    const node = it.next().?;
                     if (it.peek()) |next_node| {
                         try renderExpression(allocator, stream, tree, indent, start_col, node.*, Space.None);
 
@@ -1550,7 +1549,7 @@ fn renderExpression(
 
                 var it = asm_node.outputs.iterator(0);
                 while (true) {
-                    const asm_output = ??it.next();
+                    const asm_output = it.next().?;
                     const node = &(asm_output.*).base;
 
                     if (it.peek()) |next_asm_output| {
@@ -1588,7 +1587,7 @@ fn renderExpression(
 
                 var it = asm_node.inputs.iterator(0);
                 while (true) {
-                    const asm_input = ??it.next();
+                    const asm_input = it.next().?;
                     const node = &(asm_input.*).base;
 
                     if (it.peek()) |next_asm_input| {
@@ -1620,7 +1619,7 @@ fn renderExpression(
 
             var it = asm_node.clobbers.iterator(0);
             while (true) {
-                const clobber_token = ??it.next();
+                const clobber_token = it.next().?;
 
                 if (it.peek() == null) {
                     try renderToken(tree, stream, clobber_token.*, indent_once, start_col, Space.Newline);
std/array_list.zig
@@ -258,7 +258,7 @@ test "iterator ArrayList test" {
     }
 
     it.reset();
-    assert(??it.next() == 1);
+    assert(it.next().? == 1);
 }
 
 test "insert ArrayList test" {
std/buf_map.zig
@@ -72,15 +72,15 @@ test "BufMap" {
     defer bufmap.deinit();
 
     try bufmap.set("x", "1");
-    assert(mem.eql(u8, ??bufmap.get("x"), "1"));
+    assert(mem.eql(u8, bufmap.get("x").?, "1"));
     assert(1 == bufmap.count());
 
     try bufmap.set("x", "2");
-    assert(mem.eql(u8, ??bufmap.get("x"), "2"));
+    assert(mem.eql(u8, bufmap.get("x").?, "2"));
     assert(1 == bufmap.count());
 
     try bufmap.set("x", "3");
-    assert(mem.eql(u8, ??bufmap.get("x"), "3"));
+    assert(mem.eql(u8, bufmap.get("x").?, "3"));
     assert(1 == bufmap.count());
 
     bufmap.delete("x");
std/event.zig
@@ -40,9 +40,9 @@ pub const TcpServer = struct {
         self.listen_address = std.net.Address.initPosix(try std.os.posixGetSockName(self.sockfd));
 
         self.accept_coro = try async<self.loop.allocator> TcpServer.handler(self);
-        errdefer cancel ??self.accept_coro;
+        errdefer cancel self.accept_coro.?;
 
-        try self.loop.addFd(self.sockfd, ??self.accept_coro);
+        try self.loop.addFd(self.sockfd, self.accept_coro.?);
         errdefer self.loop.removeFd(self.sockfd);
     }
 
std/hash_map.zig
@@ -265,11 +265,11 @@ test "basic hash map usage" {
     assert((map.put(4, 44) catch unreachable) == null);
     assert((map.put(5, 55) catch unreachable) == null);
 
-    assert(??(map.put(5, 66) catch unreachable) == 55);
-    assert(??(map.put(5, 55) catch unreachable) == 66);
+    assert((map.put(5, 66) catch unreachable).? == 55);
+    assert((map.put(5, 55) catch unreachable).? == 66);
 
     assert(map.contains(2));
-    assert((??map.get(2)).value == 22);
+    assert(map.get(2).?.value == 22);
     _ = map.remove(2);
     assert(map.remove(2) == null);
     assert(map.get(2) == null);
@@ -317,7 +317,7 @@ test "iterator hash map" {
     }
 
     it.reset();
-    var entry = ??it.next();
+    var entry = it.next().?;
     assert(entry.key == keys[0]);
     assert(entry.value == values[0]);
 }
std/heap.zig
@@ -142,7 +142,7 @@ pub const DirectAllocator = struct {
                 const root_addr = @intToPtr(*align(1) usize, old_record_addr).*;
                 const old_ptr = @intToPtr(*c_void, root_addr);
                 const amt = new_size + alignment + @sizeOf(usize);
-                const new_ptr = os.windows.HeapReAlloc(??self.heap_handle, 0, old_ptr, amt) ?? blk: {
+                const new_ptr = os.windows.HeapReAlloc(self.heap_handle.?, 0, old_ptr, amt) ?? blk: {
                     if (new_size > old_mem.len) return error.OutOfMemory;
                     const new_record_addr = old_record_addr - new_size + old_mem.len;
                     @intToPtr(*align(1) usize, new_record_addr).* = root_addr;
@@ -171,7 +171,7 @@ pub const DirectAllocator = struct {
                 const record_addr = @ptrToInt(bytes.ptr) + bytes.len;
                 const root_addr = @intToPtr(*align(1) usize, record_addr).*;
                 const ptr = @intToPtr(*c_void, root_addr);
-                _ = os.windows.HeapFree(??self.heap_handle, 0, ptr);
+                _ = os.windows.HeapFree(self.heap_handle.?, 0, ptr);
             },
             else => @compileError("Unsupported OS"),
         }
std/json.zig
@@ -908,7 +908,7 @@ pub const TokenStream = struct {
 };
 
 fn checkNext(p: *TokenStream, id: Token.Id) void {
-    const token = ??(p.next() catch unreachable);
+    const token = (p.next() catch unreachable).?;
     debug.assert(token.id == id);
 }
 
@@ -1376,17 +1376,17 @@ test "json parser dynamic" {
 
     var root = tree.root;
 
-    var image = (??root.Object.get("Image")).value;
+    var image = root.Object.get("Image").?.value;
 
-    const width = (??image.Object.get("Width")).value;
+    const width = image.Object.get("Width").?.value;
     debug.assert(width.Integer == 800);
 
-    const height = (??image.Object.get("Height")).value;
+    const height = image.Object.get("Height").?.value;
     debug.assert(height.Integer == 600);
 
-    const title = (??image.Object.get("Title")).value;
+    const title = image.Object.get("Title").?.value;
     debug.assert(mem.eql(u8, title.String, "View from 15th Floor"));
 
-    const animated = (??image.Object.get("Animated")).value;
+    const animated = image.Object.get("Animated").?.value;
     debug.assert(animated.Bool == false);
 }
std/linked_list.zig
@@ -270,8 +270,8 @@ test "basic linked list test" {
     var last = list.pop(); // {2, 3, 4}
     list.remove(three); // {2, 4}
 
-    assert((??list.first).data == 2);
-    assert((??list.last).data == 4);
+    assert(list.first.?.data == 2);
+    assert(list.last.?.data == 4);
     assert(list.len == 2);
 }
 
@@ -336,7 +336,7 @@ test "basic intrusive linked list test" {
     var last = list.pop(); // {2, 3, 4}
     list.remove(&three.link); // {2, 4}
 
-    assert((??list.first).toData().value == 2);
-    assert((??list.last).toData().value == 4);
+    assert(list.first.?.toData().value == 2);
+    assert(list.last.?.toData().value == 4);
     assert(list.len == 2);
 }
std/macho.zig
@@ -130,7 +130,7 @@ pub fn loadSymbols(allocator: *mem.Allocator, in: *io.FileInStream) !SymbolTable
     for (syms) |sym| {
         if (!isSymbol(sym)) continue;
         const start = sym.n_strx;
-        const end = ??mem.indexOfScalarPos(u8, strings, start, 0);
+        const end = mem.indexOfScalarPos(u8, strings, start, 0).?;
         const name = strings[start..end];
         const address = sym.n_value;
         symbols[nsym] = Symbol{ .name = name, .address = address };
std/mem.zig
@@ -304,20 +304,20 @@ pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, nee
 }
 
 test "mem.indexOf" {
-    assert(??indexOf(u8, "one two three four", "four") == 14);
-    assert(??lastIndexOf(u8, "one two three two four", "two") == 14);
+    assert(indexOf(u8, "one two three four", "four").? == 14);
+    assert(lastIndexOf(u8, "one two three two four", "two").? == 14);
     assert(indexOf(u8, "one two three four", "gour") == null);
     assert(lastIndexOf(u8, "one two three four", "gour") == null);
-    assert(??indexOf(u8, "foo", "foo") == 0);
-    assert(??lastIndexOf(u8, "foo", "foo") == 0);
+    assert(indexOf(u8, "foo", "foo").? == 0);
+    assert(lastIndexOf(u8, "foo", "foo").? == 0);
     assert(indexOf(u8, "foo", "fool") == null);
     assert(lastIndexOf(u8, "foo", "lfoo") == null);
     assert(lastIndexOf(u8, "foo", "fool") == null);
 
-    assert(??indexOf(u8, "foo foo", "foo") == 0);
-    assert(??lastIndexOf(u8, "foo foo", "foo") == 4);
-    assert(??lastIndexOfAny(u8, "boo, cat", "abo") == 6);
-    assert(??lastIndexOfScalar(u8, "boo", 'o') == 2);
+    assert(indexOf(u8, "foo foo", "foo").? == 0);
+    assert(lastIndexOf(u8, "foo foo", "foo").? == 4);
+    assert(lastIndexOfAny(u8, "boo, cat", "abo").? == 6);
+    assert(lastIndexOfScalar(u8, "boo", 'o').? == 2);
 }
 
 /// Reads an integer from memory with size equal to bytes.len.
@@ -432,9 +432,9 @@ pub fn split(buffer: []const u8, split_bytes: []const u8) SplitIterator {
 
 test "mem.split" {
     var it = split("   abc def   ghi  ", " ");
-    assert(eql(u8, ??it.next(), "abc"));
-    assert(eql(u8, ??it.next(), "def"));
-    assert(eql(u8, ??it.next(), "ghi"));
+    assert(eql(u8, it.next().?, "abc"));
+    assert(eql(u8, it.next().?, "def"));
+    assert(eql(u8, it.next().?, "ghi"));
     assert(it.next() == null);
 }
 
std/segmented_list.zig
@@ -364,7 +364,7 @@ fn testSegmentedList(comptime prealloc: usize, allocator: *Allocator) !void {
         assert(x == 0);
     }
 
-    assert(??list.pop() == 100);
+    assert(list.pop().? == 100);
     assert(list.len == 99);
 
     try list.pushMany([]i32{
@@ -373,9 +373,9 @@ fn testSegmentedList(comptime prealloc: usize, allocator: *Allocator) !void {
         3,
     });
     assert(list.len == 102);
-    assert(??list.pop() == 3);
-    assert(??list.pop() == 2);
-    assert(??list.pop() == 1);
+    assert(list.pop().? == 3);
+    assert(list.pop().? == 2);
+    assert(list.pop().? == 1);
     assert(list.len == 99);
 
     try list.pushMany([]const i32{});
std/unicode.zig
@@ -286,15 +286,15 @@ fn testUtf8IteratorOnAscii() void {
     const s = Utf8View.initComptime("abc");
 
     var it1 = s.iterator();
-    debug.assert(std.mem.eql(u8, "a", ??it1.nextCodepointSlice()));
-    debug.assert(std.mem.eql(u8, "b", ??it1.nextCodepointSlice()));
-    debug.assert(std.mem.eql(u8, "c", ??it1.nextCodepointSlice()));
+    debug.assert(std.mem.eql(u8, "a", it1.nextCodepointSlice().?));
+    debug.assert(std.mem.eql(u8, "b", it1.nextCodepointSlice().?));
+    debug.assert(std.mem.eql(u8, "c", it1.nextCodepointSlice().?));
     debug.assert(it1.nextCodepointSlice() == null);
 
     var it2 = s.iterator();
-    debug.assert(??it2.nextCodepoint() == 'a');
-    debug.assert(??it2.nextCodepoint() == 'b');
-    debug.assert(??it2.nextCodepoint() == 'c');
+    debug.assert(it2.nextCodepoint().? == 'a');
+    debug.assert(it2.nextCodepoint().? == 'b');
+    debug.assert(it2.nextCodepoint().? == 'c');
     debug.assert(it2.nextCodepoint() == null);
 }
 
@@ -321,15 +321,15 @@ fn testUtf8ViewOk() void {
     const s = Utf8View.initComptime("東京市");
 
     var it1 = s.iterator();
-    debug.assert(std.mem.eql(u8, "東", ??it1.nextCodepointSlice()));
-    debug.assert(std.mem.eql(u8, "京", ??it1.nextCodepointSlice()));
-    debug.assert(std.mem.eql(u8, "市", ??it1.nextCodepointSlice()));
+    debug.assert(std.mem.eql(u8, "東", it1.nextCodepointSlice().?));
+    debug.assert(std.mem.eql(u8, "京", it1.nextCodepointSlice().?));
+    debug.assert(std.mem.eql(u8, "市", it1.nextCodepointSlice().?));
     debug.assert(it1.nextCodepointSlice() == null);
 
     var it2 = s.iterator();
-    debug.assert(??it2.nextCodepoint() == 0x6771);
-    debug.assert(??it2.nextCodepoint() == 0x4eac);
-    debug.assert(??it2.nextCodepoint() == 0x5e02);
+    debug.assert(it2.nextCodepoint().? == 0x6771);
+    debug.assert(it2.nextCodepoint().? == 0x4eac);
+    debug.assert(it2.nextCodepoint().? == 0x5e02);
     debug.assert(it2.nextCodepoint() == null);
 }
 
test/cases/bugs/656.zig
@@ -9,7 +9,7 @@ const Value = struct {
     align_expr: ?u32,
 };
 
-test "nullable if after an if in a switch prong of a switch with 2 prongs in an else" {
+test "optional if after an if in a switch prong of a switch with 2 prongs in an else" {
     foo(false, true);
 }
 
test/cases/cast.zig
@@ -109,16 +109,16 @@ test "implicitly cast indirect pointer to maybe-indirect pointer" {
         const Self = this;
         x: u8,
         fn constConst(p: *const *const Self) u8 {
-            return (p.*).x;
+            return p.*.x;
         }
         fn maybeConstConst(p: ?*const *const Self) u8 {
-            return ((??p).*).x;
+            return p.?.*.x;
         }
         fn constConstConst(p: *const *const *const Self) u8 {
-            return (p.*.*).x;
+            return p.*.*.x;
         }
         fn maybeConstConstConst(p: ?*const *const *const Self) u8 {
-            return ((??p).*.*).x;
+            return p.?.*.*.x;
         }
     };
     const s = S{ .x = 42 };
@@ -177,56 +177,56 @@ test "string literal to &const []const u8" {
 }
 
 test "implicitly cast from T to error!?T" {
-    castToMaybeTypeError(1);
-    comptime castToMaybeTypeError(1);
+    castToOptionalTypeError(1);
+    comptime castToOptionalTypeError(1);
 }
 const A = struct {
     a: i32,
 };
-fn castToMaybeTypeError(z: i32) void {
+fn castToOptionalTypeError(z: i32) void {
     const x = i32(1);
     const y: error!?i32 = x;
-    assert(??(try y) == 1);
+    assert((try y).? == 1);
 
     const f = z;
     const g: error!?i32 = f;
 
     const a = A{ .a = z };
     const b: error!?A = a;
-    assert((??(b catch unreachable)).a == 1);
+    assert((b catch unreachable).?.a == 1);
 }
 
 test "implicitly cast from int to error!?T" {
-    implicitIntLitToMaybe();
-    comptime implicitIntLitToMaybe();
+    implicitIntLitToOptional();
+    comptime implicitIntLitToOptional();
 }
-fn implicitIntLitToMaybe() void {
+fn implicitIntLitToOptional() void {
     const f: ?i32 = 1;
     const g: error!?i32 = 1;
 }
 
 test "return null from fn() error!?&T" {
-    const a = returnNullFromMaybeTypeErrorRef();
-    const b = returnNullLitFromMaybeTypeErrorRef();
+    const a = returnNullFromOptionalTypeErrorRef();
+    const b = returnNullLitFromOptionalTypeErrorRef();
     assert((try a) == null and (try b) == null);
 }
-fn returnNullFromMaybeTypeErrorRef() error!?*A {
+fn returnNullFromOptionalTypeErrorRef() error!?*A {
     const a: ?*A = null;
     return a;
 }
-fn returnNullLitFromMaybeTypeErrorRef() error!?*A {
+fn returnNullLitFromOptionalTypeErrorRef() error!?*A {
     return null;
 }
 
 test "peer type resolution: ?T and T" {
-    assert(??peerTypeTAndMaybeT(true, false) == 0);
-    assert(??peerTypeTAndMaybeT(false, false) == 3);
+    assert(peerTypeTAndOptionalT(true, false).? == 0);
+    assert(peerTypeTAndOptionalT(false, false).? == 3);
     comptime {
-        assert(??peerTypeTAndMaybeT(true, false) == 0);
-        assert(??peerTypeTAndMaybeT(false, false) == 3);
+        assert(peerTypeTAndOptionalT(true, false).? == 0);
+        assert(peerTypeTAndOptionalT(false, false).? == 3);
     }
 }
-fn peerTypeTAndMaybeT(c: bool, b: bool) ?usize {
+fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
     if (c) {
         return if (b) null else usize(0);
     }
@@ -251,11 +251,11 @@ fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 {
 }
 
 test "implicitly cast from [N]T to ?[]const T" {
-    assert(mem.eql(u8, ??castToMaybeSlice(), "hi"));
-    comptime assert(mem.eql(u8, ??castToMaybeSlice(), "hi"));
+    assert(mem.eql(u8, castToOptionalSlice().?, "hi"));
+    comptime assert(mem.eql(u8, castToOptionalSlice().?, "hi"));
 }
 
-fn castToMaybeSlice() ?[]const u8 {
+fn castToOptionalSlice() ?[]const u8 {
     return "hi";
 }
 
@@ -404,5 +404,5 @@ fn testCastPtrOfArrayToSliceAndPtr() void {
 test "cast *[1][*]const u8 to [*]const ?[*]const u8" {
     const window_name = [1][*]const u8{c"window name"};
     const x: [*]const ?[*]const u8 = &window_name;
-    assert(mem.eql(u8, std.cstr.toSliceConst(??x[0]), "window name"));
+    assert(mem.eql(u8, std.cstr.toSliceConst(x[0].?), "window name"));
 }
test/cases/error.zig
@@ -140,7 +140,7 @@ fn testComptimeTestErrorEmptySet(x: EmptyErrorSet!i32) void {
     if (x) |v| assert(v == 1234) else |err| @compileError("bad");
 }
 
-test "syntax: nullable operator in front of error union operator" {
+test "syntax: optional operator in front of error union operator" {
     comptime {
         assert(?error!i32 == ?(error!i32));
     }
test/cases/eval.zig
@@ -12,7 +12,7 @@ fn fibonacci(x: i32) i32 {
 }
 
 fn unwrapAndAddOne(blah: ?i32) i32 {
-    return ??blah + 1;
+    return blah.? + 1;
 }
 const should_be_1235 = unwrapAndAddOne(1234);
 test "static add one" {
test/cases/generics.zig
@@ -127,7 +127,7 @@ test "generic fn with implicit cast" {
     }) == 0);
 }
 fn getByte(ptr: ?*const u8) u8 {
-    return (??ptr).*;
+    return ptr.?.*;
 }
 fn getFirstByte(comptime T: type, mem: []const T) u8 {
     return getByte(@ptrCast(*const u8, &mem[0]));
test/cases/misc.zig
@@ -505,7 +505,7 @@ test "@typeId" {
         assert(@typeId(@typeOf(1.0)) == Tid.ComptimeFloat);
         assert(@typeId(@typeOf(undefined)) == Tid.Undefined);
         assert(@typeId(@typeOf(null)) == Tid.Null);
-        assert(@typeId(?i32) == Tid.Nullable);
+        assert(@typeId(?i32) == Tid.Optional);
         assert(@typeId(error!i32) == Tid.ErrorUnion);
         assert(@typeId(error) == Tid.ErrorSet);
         assert(@typeId(AnEnum) == Tid.Enum);
test/cases/null.zig
@@ -1,6 +1,6 @@
 const assert = @import("std").debug.assert;
 
-test "nullable type" {
+test "optional type" {
     const x: ?bool = true;
 
     if (x) |y| {
@@ -33,7 +33,7 @@ test "test maybe object and get a pointer to the inner value" {
         b.* = false;
     }
 
-    assert(??maybe_bool == false);
+    assert(maybe_bool.? == false);
 }
 
 test "rhs maybe unwrap return" {
@@ -47,9 +47,9 @@ test "maybe return" {
 }
 
 fn maybeReturnImpl() void {
-    assert(??foo(1235));
+    assert(foo(1235).?);
     if (foo(null) != null) unreachable;
-    assert(!??foo(1234));
+    assert(!foo(1234).?);
 }
 
 fn foo(x: ?i32) ?bool {
@@ -102,12 +102,12 @@ fn testTestNullRuntime(x: ?i32) void {
     assert(!(x != null));
 }
 
-test "nullable void" {
-    nullableVoidImpl();
-    comptime nullableVoidImpl();
+test "optional void" {
+    optionalVoidImpl();
+    comptime optionalVoidImpl();
 }
 
-fn nullableVoidImpl() void {
+fn optionalVoidImpl() void {
     assert(bar(null) == null);
     assert(bar({}) != null);
 }
@@ -120,19 +120,19 @@ fn bar(x: ?void) ?void {
     }
 }
 
-const StructWithNullable = struct {
+const StructWithOptional = struct {
     field: ?i32,
 };
 
-var struct_with_nullable: StructWithNullable = undefined;
+var struct_with_optional: StructWithOptional = undefined;
 
-test "unwrap nullable which is field of global var" {
-    struct_with_nullable.field = null;
-    if (struct_with_nullable.field) |payload| {
+test "unwrap optional which is field of global var" {
+    struct_with_optional.field = null;
+    if (struct_with_optional.field) |payload| {
         unreachable;
     }
-    struct_with_nullable.field = 1234;
-    if (struct_with_nullable.field) |payload| {
+    struct_with_optional.field = 1234;
+    if (struct_with_optional.field) |payload| {
         assert(payload == 1234);
     } else {
         unreachable;
test/cases/reflection.zig
@@ -2,7 +2,7 @@ const assert = @import("std").debug.assert;
 const mem = @import("std").mem;
 const reflection = this;
 
-test "reflection: array, pointer, nullable, error union type child" {
+test "reflection: array, pointer, optional, error union type child" {
     comptime {
         assert(([10]u8).Child == u8);
         assert((*u8).Child == u8);
test/cases/type_info.zig
@@ -88,15 +88,15 @@ fn testArray() void {
     assert(arr_info.Array.child == bool);
 }
 
-test "type info: nullable type info" {
-    testNullable();
-    comptime testNullable();
+test "type info: optional type info" {
+    testOptional();
+    comptime testOptional();
 }
 
-fn testNullable() void {
+fn testOptional() void {
     const null_info = @typeInfo(?void);
-    assert(TypeId(null_info) == TypeId.Nullable);
-    assert(null_info.Nullable.child == void);
+    assert(TypeId(null_info) == TypeId.Optional);
+    assert(null_info.Optional.child == void);
 }
 
 test "type info: promise info" {
@@ -168,7 +168,7 @@ fn testUnion() void {
     assert(typeinfo_info.Union.tag_type == TypeId);
     assert(typeinfo_info.Union.fields.len == 25);
     assert(typeinfo_info.Union.fields[4].enum_field != null);
-    assert((??typeinfo_info.Union.fields[4].enum_field).value == 4);
+    assert(typeinfo_info.Union.fields[4].enum_field.?.value == 4);
     assert(typeinfo_info.Union.fields[4].field_type == @typeOf(@typeInfo(u8).Int));
     assert(typeinfo_info.Union.defs.len == 20);
 
test/cases/while.zig
@@ -81,7 +81,7 @@ test "while with else" {
     assert(got_else == 1);
 }
 
-test "while with nullable as condition" {
+test "while with optional as condition" {
     numbers_left = 10;
     var sum: i32 = 0;
     while (getNumberOrNull()) |value| {
@@ -90,7 +90,7 @@ test "while with nullable as condition" {
     assert(sum == 45);
 }
 
-test "while with nullable as condition with else" {
+test "while with optional as condition with else" {
     numbers_left = 10;
     var sum: i32 = 0;
     var got_else: i32 = 0;
@@ -132,7 +132,7 @@ fn getNumberOrNull() ?i32 {
     };
 }
 
-test "while on nullable with else result follow else prong" {
+test "while on optional with else result follow else prong" {
     const result = while (returnNull()) |value| {
         break value;
     } else
@@ -140,8 +140,8 @@ test "while on nullable with else result follow else prong" {
     assert(result == 2);
 }
 
-test "while on nullable with else result follow break prong" {
-    const result = while (returnMaybe(10)) |value| {
+test "while on optional with else result follow break prong" {
+    const result = while (returnOptional(10)) |value| {
         break value;
     } else
         i32(2);
@@ -210,7 +210,7 @@ fn testContinueOuter() void {
 fn returnNull() ?i32 {
     return null;
 }
-fn returnMaybe(x: i32) ?i32 {
+fn returnOptional(x: i32) ?i32 {
     return x;
 }
 fn returnError() error!i32 {
test/compile_errors.zig
@@ -1341,7 +1341,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         \\    if (true) |x| { }
         \\}
     ,
-        ".tmp_source.zig:2:9: error: expected nullable type, found 'bool'",
+        ".tmp_source.zig:2:9: error: expected optional type, found 'bool'",
     );
 
     cases.add(
@@ -1780,7 +1780,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
     );
 
     cases.add(
-        "assign null to non-nullable pointer",
+        "assign null to non-optional pointer",
         \\const a: *u8 = null;
         \\
         \\export fn entry() usize { return @sizeOf(@typeOf(a)); }
@@ -2817,7 +2817,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
     );
 
     cases.add(
-        "while expected bool, got nullable",
+        "while expected bool, got optional",
         \\export fn foo() void {
         \\    while (bar()) {}
         \\}
@@ -2837,23 +2837,23 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
     );
 
     cases.add(
-        "while expected nullable, got bool",
+        "while expected optional, got bool",
         \\export fn foo() void {
         \\    while (bar()) |x| {}
         \\}
         \\fn bar() bool { return true; }
     ,
-        ".tmp_source.zig:2:15: error: expected nullable type, found 'bool'",
+        ".tmp_source.zig:2:15: error: expected optional type, found 'bool'",
     );
 
     cases.add(
-        "while expected nullable, got error union",
+        "while expected optional, got error union",
         \\export fn foo() void {
         \\    while (bar()) |x| {}
         \\}
         \\fn bar() error!i32 { return 1; }
     ,
-        ".tmp_source.zig:2:15: error: expected nullable type, found 'error!i32'",
+        ".tmp_source.zig:2:15: error: expected optional type, found 'error!i32'",
     );
 
     cases.add(
@@ -2867,7 +2867,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
     );
 
     cases.add(
-        "while expected error union, got nullable",
+        "while expected error union, got optional",
         \\export fn foo() void {
         \\    while (bar()) |x| {} else |err| {}
         \\}
test/tests.zig
@@ -282,8 +282,8 @@ pub const CompareOutputContext = struct {
             var stdout = Buffer.initNull(b.allocator);
             var stderr = Buffer.initNull(b.allocator);
 
-            var stdout_file_in_stream = io.FileInStream.init(&??child.stdout);
-            var stderr_file_in_stream = io.FileInStream.init(&??child.stderr);
+            var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?);
+            var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?);
 
             stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable;
             stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable;
@@ -601,8 +601,8 @@ pub const CompileErrorContext = struct {
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
 
-            var stdout_file_in_stream = io.FileInStream.init(&??child.stdout);
-            var stderr_file_in_stream = io.FileInStream.init(&??child.stderr);
+            var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?);
+            var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?);
 
             stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable;
             stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;
@@ -872,8 +872,8 @@ pub const TranslateCContext = struct {
             var stdout_buf = Buffer.initNull(b.allocator);
             var stderr_buf = Buffer.initNull(b.allocator);
 
-            var stdout_file_in_stream = io.FileInStream.init(&??child.stdout);
-            var stderr_file_in_stream = io.FileInStream.init(&??child.stderr);
+            var stdout_file_in_stream = io.FileInStream.init(&child.stdout.?);
+            var stderr_file_in_stream = io.FileInStream.init(&child.stderr.?);
 
             stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable;
             stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;
build.zig
@@ -75,7 +75,7 @@ pub fn build(b: *Builder) !void {
             cxx_compiler,
             "-print-file-name=libstdc++.a",
         });
-        const libstdcxx_path = ??mem.split(libstdcxx_path_padded, "\r\n").next();
+        const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
         if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
             warn(
                 \\Unable to determine path to libstdc++.a