authorgravatar for jhc@dismail.deJimmi Holst Christensen <jhc@dismail.de> 2022-01-01 17:36:53+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-01-01 15:40:24-05:00
loga41ad639a85218130f80956ce0c2e59ff322a1af
tree7d1da86468fe9257e6c2ea1c7c752a418fce7f52
parent0662f1d52204b9c3bce22b3bd72bf4faf2df3559

fmt: Refactor parsing of placeholders into its own function

This saves on comptime format string parsing, as the compiler caches comptime calls. The catch here, is that parsePlaceHolder cannot take the placeholder string as a slice. It must take it as an array by value for the caching to occure. There is also some logic in here that ensures that the specifier_arg is always them same slice when the items they contain are the same. This makes the compiler stamp out less copies of formatType.

2 files changed, 271 insertions(+), 264 deletions(-)

lib/std/fmt.zig+271-222
...@@ -75,111 +75,20 @@ pub fn format(...@@ -75,111 +75,20 @@ pub fn format(
75 comptime fmt: []const u8,75 comptime fmt: []const u8,
76 args: anytype,76 args: anytype,
77) !void {77) !void {
78 const ArgSetType = u32;
79
80 const ArgsType = @TypeOf(args);78 const ArgsType = @TypeOf(args);
79 const args_type_info = @typeInfo(ArgsType);
81 // XXX: meta.trait.is(.Struct)(ArgsType) doesn't seem to work...80 // XXX: meta.trait.is(.Struct)(ArgsType) doesn't seem to work...
82 if (@typeInfo(ArgsType) != .Struct) {81 if (args_type_info != .Struct) {
83 @compileError("Expected tuple or struct argument, found " ++ @typeName(ArgsType));82 @compileError("Expected tuple or struct argument, found " ++ @typeName(ArgsType));
84 }83 }
8584
86 const fields_info = meta.fields(ArgsType);85 const fields_info = args_type_info.Struct.fields;
87 if (fields_info.len > @typeInfo(ArgSetType).Int.bits) {86 if (fields_info.len > max_format_args) {
88 @compileError("32 arguments max are supported per format call");87 @compileError("32 arguments max are supported per format call");
89 }88 }
9089
91 comptime var arg_state: struct {
92 next_arg: usize = 0,
93 used_args: usize = 0,
94 args_len: usize = fields_info.len,
95
96 fn hasUnusedArgs(comptime self: *@This()) bool {
97 return @popCount(ArgSetType, self.used_args) != self.args_len;
98 }
99
100 fn nextArg(comptime self: *@This(), comptime arg_index: ?usize) comptime_int {
101 const next_index = arg_index orelse init: {
102 const arg = self.next_arg;
103 self.next_arg += 1;
104 break :init arg;
105 };
106
107 if (next_index >= self.args_len) {
108 @compileError("Too few arguments");
109 }
110
111 // Mark this argument as used
112 self.used_args |= 1 << next_index;
113
114 return next_index;
115 }
116 } = .{};
117
118 comptime var parser: struct {
119 buf: []const u8 = undefined,
120 pos: comptime_int = 0,
121
122 // Returns a decimal number or null if the current character is not a
123 // digit
124 fn number(comptime self: *@This()) ?usize {
125 var r: ?usize = null;
126
127 while (self.pos < self.buf.len) : (self.pos += 1) {
128 switch (self.buf[self.pos]) {
129 '0'...'9' => {
130 if (r == null) r = 0;
131 r.? *= 10;
132 r.? += self.buf[self.pos] - '0';
133 },
134 else => break,
135 }
136 }
137
138 return r;
139 }
140
141 // Returns a substring of the input starting from the current position
142 // and ending where `ch` is found or until the end if not found
143 fn until(comptime self: *@This(), comptime ch: u8) []const u8 {
144 const start = self.pos;
145
146 if (start >= self.buf.len)
147 return &[_]u8{};
148
149 while (self.pos < self.buf.len) : (self.pos += 1) {
150 if (self.buf[self.pos] == ch) break;
151 }
152 return self.buf[start..self.pos];
153 }
154
155 // Returns one character, if available
156 fn char(comptime self: *@This()) ?u8 {
157 if (self.pos < self.buf.len) {
158 const ch = self.buf[self.pos];
159 self.pos += 1;
160 return ch;
161 }
162 return null;
163 }
164
165 fn maybe(comptime self: *@This(), comptime val: u8) bool {
166 if (self.pos < self.buf.len and self.buf[self.pos] == val) {
167 self.pos += 1;
168 return true;
169 }
170 return false;
171 }
172
173 // Returns the n-th next character or null if that's past the end
174 fn peek(comptime self: *@This(), comptime n: usize) ?u8 {
175 return if (self.pos + n < self.buf.len) self.buf[self.pos + n] else null;
176 }
177 } = .{};
178
179 var options: FormatOptions = .{};
180
181 @setEvalBranchQuota(2000000);90 @setEvalBranchQuota(2000000);
18291 comptime var arg_state: ArgState = .{ .args_len = fields_info.len };
183 comptime var i = 0;92 comptime var i = 0;
184 inline while (i < fmt.len) {93 inline while (i < fmt.len) {
185 const start_index = i;94 const start_index = i;
...@@ -234,134 +143,258 @@ pub fn format(...@@ -234,134 +143,258 @@ pub fn format(
234 comptime assert(fmt[i] == '}');143 comptime assert(fmt[i] == '}');
235 i += 1;144 i += 1;
236145
237 options = .{};146 const placeholder = comptime parsePlaceholder(fmt[fmt_begin..fmt_end].*);
238147 const arg_pos = comptime switch (placeholder.arg) {
239 // Parse the format fragment between braces148 .none => null,
240 parser.buf = fmt[fmt_begin..fmt_end];149 .number => |pos| pos,
241 parser.pos = 0;150 .named => |arg_name| meta.fieldIndex(ArgsType, arg_name) orelse
242151 @compileError("No argument with name '" ++ arg_name ++ "'"),
243 // Parse the positional argument number152 };
244 const opt_pos_arg = comptime init: {
245 if (parser.maybe('[')) {
246 const arg_name = parser.until(']');
247153
248 if (!parser.maybe(']')) {154 const width = switch (placeholder.width) {
249 @compileError("Expected closing ]");155 .none => null,
250 }156 .number => |v| v,
157 .named => |arg_name| blk: {
158 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
159 @compileError("No argument with name '" ++ arg_name ++ "'");
160 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("Too few arguments");
161 break :blk @field(args, arg_name);
162 },
163 };
251164
252 break :init meta.fieldIndex(ArgsType, arg_name) orelse165 const precision = switch (placeholder.precision) {
166 .none => null,
167 .number => |v| v,
168 .named => |arg_name| blk: {
169 const arg_i = comptime meta.fieldIndex(ArgsType, arg_name) orelse
253 @compileError("No argument with name '" ++ arg_name ++ "'");170 @compileError("No argument with name '" ++ arg_name ++ "'");
254 } else {171 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("Too few arguments");
255 break :init parser.number();172 break :blk @field(args, arg_name);
256 }173 },
257 };174 };
258175
259 // Parse the format specifier176 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
260 const specifier_arg = comptime parser.until(':');177 @compileError("Too few arguments");
261178
262 // Skip the colon, if present179 try formatType(
263 if (comptime parser.char()) |ch| {180 @field(args, fields_info[arg_to_print].name),
264 if (ch != ':') {181 placeholder.specifier_arg,
265 @compileError("Expected : or }, found '" ++ [1]u8{ch} ++ "'");182 FormatOptions{
266 }183 .fill = placeholder.fill,
184 .alignment = placeholder.alignment,
185 .width = width,
186 .precision = precision,
187 },
188 writer,
189 default_max_depth,
190 );
191 }
192
193 if (comptime arg_state.hasUnusedArgs()) {
194 const missing_count = arg_state.args_len - @popCount(ArgSetType, arg_state.used_args);
195 switch (missing_count) {
196 0 => unreachable,
197 1 => @compileError("Unused argument in '" ++ fmt ++ "'"),
198 else => @compileError((comptime comptimePrint("{d}", .{missing_count})) ++ " unused arguments in '" ++ fmt ++ "'"),
267 }199 }
200 }
201}
268202
269 // Parse the fill character203fn parsePlaceholder(comptime str: anytype) Placeholder {
270 // The fill parameter requires the alignment parameter to be specified204 comptime var parser = Parser{ .buf = &str };
271 // too205
272 if (comptime parser.peek(1)) |ch| {206 // Parse the positional argument number
273 if (comptime mem.indexOfScalar(u8, "<^>", ch) != null) {207 const arg = comptime parser.specifier() catch |err|
274 options.fill = comptime parser.char().?;208 @compileError(@errorName(err));
275 }209
210 // Parse the format specifier
211 const specifier_arg = comptime parser.until(':');
212
213 // Skip the colon, if present
214 if (comptime parser.char()) |ch| {
215 if (ch != ':') {
216 @compileError("Expected : or }, found '" ++ [1]u8{ch} ++ "'");
276 }217 }
218 }
277219
278 // Parse the alignment parameter220 // Parse the fill character
279 if (comptime parser.peek(0)) |ch| {221 // The fill parameter requires the alignment parameter to be specified
280 switch (ch) {222 // too
281 '<' => {223 const fill = comptime if (parser.peek(1)) |ch|
282 options.alignment = .Left;224 switch (ch) {
283 _ = comptime parser.char();225 '<', '^', '>' => parser.char().?,
284 },226 else => ' ',
285 '^' => {227 }
286 options.alignment = .Center;228 else
287 _ = comptime parser.char();229 ' ';
288 },230
289 '>' => {231 // Parse the alignment parameter
290 options.alignment = .Right;232 const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: {
291 _ = comptime parser.char();233 switch (ch) {
292 },234 '<', '^', '>' => _ = parser.char(),
293 else => {},235 else => {},
294 }
295 }236 }
237 break :init switch (ch) {
238 '<' => .Left,
239 '^' => .Center,
240 else => .Right,
241 };
242 } else .Right;
296243
297 // Parse the width parameter244 // Parse the width parameter
298 options.width = comptime init: {245 const width = comptime parser.specifier() catch |err|
299 if (parser.maybe('[')) {246 @compileError(@errorName(err));
300 const arg_name = parser.until(']');
301247
302 if (!parser.maybe(']')) {248 // Skip the dot, if present
303 @compileError("Expected closing ]");249 if (comptime parser.char()) |ch| {
304 }250 if (ch != '.') {
251 @compileError("Expected . or }, found '" ++ [1]u8{ch} ++ "'");
252 }
253 }
305254
306 const index = meta.fieldIndex(ArgsType, arg_name) orelse255 // Parse the precision parameter
307 @compileError("No argument with name '" ++ arg_name ++ "'");256 const precision = comptime parser.specifier() catch |err|
308 const arg_index = arg_state.nextArg(index);257 @compileError(@errorName(err));
309258
310 break :init @field(args, fields_info[arg_index].name);259 if (comptime parser.char()) |ch| {
311 } else {260 @compileError("Extraneous trailing character '" ++ [1]u8{ch} ++ "'");
312 break :init parser.number();261 }
313 }262
314 };263 return Placeholder{
264 .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),
265 .fill = fill,
266 .alignment = alignment,
267 .arg = arg,
268 .width = width,
269 .precision = precision,
270 };
271}
272
273fn cacheString(str: anytype) []const u8 {
274 return &str;
275}
276
277const Placeholder = struct {
278 specifier_arg: []const u8,
279 fill: u8,
280 alignment: Alignment,
281 arg: Specifier,
282 width: Specifier,
283 precision: Specifier,
284};
285
286const Specifier = union(enum) {
287 none,
288 number: usize,
289 named: []const u8,
290};
315291
316 // Skip the dot, if present292const Parser = struct {
317 if (comptime parser.char()) |ch| {293 buf: []const u8,
318 if (ch != '.') {294 pos: usize = 0,
319 @compileError("Expected . or }, found '" ++ [1]u8{ch} ++ "'");295
296 // Returns a decimal number or null if the current character is not a
297 // digit
298 fn number(self: *@This()) ?usize {
299 var r: ?usize = null;
300
301 while (self.pos < self.buf.len) : (self.pos += 1) {
302 switch (self.buf[self.pos]) {
303 '0'...'9' => {
304 if (r == null) r = 0;
305 r.? *= 10;
306 r.? += self.buf[self.pos] - '0';
307 },
308 else => break,
320 }309 }
321 }310 }
322311
323 // Parse the precision parameter312 return r;
324 options.precision = comptime init: {313 }
325 if (parser.maybe('[')) {
326 const arg_name = parser.until(']');
327314
328 if (!parser.maybe(']')) {315 // Returns a substring of the input starting from the current position
329 @compileError("Expected closing ]");316 // and ending where `ch` is found or until the end if not found
330 }317 fn until(self: *@This(), ch: u8) []const u8 {
318 const start = self.pos;
331319
332 const arg_i = meta.fieldIndex(ArgsType, arg_name) orelse320 if (start >= self.buf.len)
333 @compileError("No argument with name '" ++ arg_name ++ "'");321 return &[_]u8{};
334 const arg_to_use = arg_state.nextArg(arg_i);
335322
336 break :init @field(args, fields_info[arg_to_use].name);323 while (self.pos < self.buf.len) : (self.pos += 1) {
337 } else {324 if (self.buf[self.pos] == ch) break;
338 break :init parser.number();325 }
339 }326 return self.buf[start..self.pos];
340 };327 }
341328
342 if (comptime parser.char()) |ch| {329 // Returns one character, if available
343 @compileError("Extraneous trailing character '" ++ [1]u8{ch} ++ "'");330 fn char(self: *@This()) ?u8 {
331 if (self.pos < self.buf.len) {
332 const ch = self.buf[self.pos];
333 self.pos += 1;
334 return ch;
344 }335 }
336 return null;
337 }
345338
346 const arg_to_print = comptime arg_state.nextArg(opt_pos_arg);339 fn maybe(self: *@This(), val: u8) bool {
347 try formatType(340 if (self.pos < self.buf.len and self.buf[self.pos] == val) {
348 @field(args, fields_info[arg_to_print].name),341 self.pos += 1;
349 specifier_arg,342 return true;
350 options,343 }
351 writer,344 return false;
352 default_max_depth,
353 );
354 }345 }
355346
356 if (comptime arg_state.hasUnusedArgs()) {347 // Returns a decimal number or null if the current character is not a
357 const missing_count = arg_state.args_len - @popCount(ArgSetType, arg_state.used_args);348 // digit
358 switch (missing_count) {349 fn specifier(self: *@This()) !Specifier {
359 0 => unreachable,350 if (self.maybe('[')) {
360 1 => @compileError("Unused argument in '" ++ fmt ++ "'"),351 const arg_name = self.until(']');
361 else => @compileError((comptime comptimePrint("{d}", .{missing_count})) ++ " unused arguments in '" ++ fmt ++ "'"),352
353 if (!self.maybe(']'))
354 return @field(anyerror, "Expected closing ]");
355
356 return Specifier{ .named = arg_name };
362 }357 }
358 if (self.number()) |i|
359 return Specifier{ .number = i };
360
361 return Specifier{ .none = {} };
362 }
363
364 // Returns the n-th next character or null if that's past the end
365 fn peek(self: *@This(), n: usize) ?u8 {
366 return if (self.pos + n < self.buf.len) self.buf[self.pos + n] else null;
367 }
368};
369
370const ArgSetType = u32;
371const max_format_args = @typeInfo(ArgSetType).Int.bits;
372
373const ArgState = struct {
374 next_arg: usize = 0,
375 used_args: ArgSetType = 0,
376 args_len: usize,
377
378 fn hasUnusedArgs(self: *@This()) bool {
379 return @popCount(ArgSetType, self.used_args) != self.args_len;
363 }380 }
364}381
382 fn nextArg(self: *@This(), arg_index: ?usize) ?usize {
383 const next_index = arg_index orelse init: {
384 const arg = self.next_arg;
385 self.next_arg += 1;
386 break :init arg;
387 };
388
389 if (next_index >= self.args_len) {
390 return null;
391 }
392
393 // Mark this argument as used
394 self.used_args |= @as(ArgSetType, 1) << @intCast(u5, next_index);
395 return next_index;
396 }
397};
365398
366pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void {399pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @TypeOf(writer).Error!void {
367 _ = options;400 _ = options;
...@@ -535,14 +568,19 @@ pub fn formatType(...@@ -535,14 +568,19 @@ pub fn formatType(
535 if (actual_fmt.len == 0)568 if (actual_fmt.len == 0)
536 @compileError("cannot format array ref without a specifier (i.e. {s} or {*})");569 @compileError("cannot format array ref without a specifier (i.e. {s} or {*})");
537 if (info.child == u8) {570 if (info.child == u8) {
538 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {571 switch (actual_fmt[0]) {
539 return formatText(value, actual_fmt, options, writer);572 's', 'x', 'X', 'e', 'E' => {
573 comptime checkTextFmt(actual_fmt);
574 return formatBuf(value, options, writer);
575 },
576 else => {},
540 }577 }
541 }578 }
542 if (comptime std.meta.trait.isZigString(info.child)) {579 if (comptime std.meta.trait.isZigString(info.child)) {
543 for (value) |item, i| {580 for (value) |item, i| {
544 if (i != 0) try formatText(", ", actual_fmt, options, writer);581 comptime checkTextFmt(actual_fmt);
545 try formatText(item, actual_fmt, options, writer);582 if (i != 0) try formatBuf(", ", options, writer);
583 try formatBuf(item, options, writer);
546 }584 }
547 return;585 return;
548 }586 }
...@@ -560,8 +598,12 @@ pub fn formatType(...@@ -560,8 +598,12 @@ pub fn formatType(
560 return formatType(mem.span(value), actual_fmt, options, writer, max_depth);598 return formatType(mem.span(value), actual_fmt, options, writer, max_depth);
561 }599 }
562 if (ptr_info.child == u8) {600 if (ptr_info.child == u8) {
563 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {601 switch (actual_fmt[0]) {
564 return formatText(mem.span(value), actual_fmt, options, writer);602 's', 'x', 'X', 'e', 'E' => {
603 comptime checkTextFmt(actual_fmt);
604 return formatBuf(mem.span(value), options, writer);
605 },
606 else => {},
565 }607 }
566 }608 }
567 @compileError("Unknown format string: '" ++ actual_fmt ++ "' for type '" ++ @typeName(T) ++ "'");609 @compileError("Unknown format string: '" ++ actual_fmt ++ "' for type '" ++ @typeName(T) ++ "'");
...@@ -573,8 +615,12 @@ pub fn formatType(...@@ -573,8 +615,12 @@ pub fn formatType(
573 return writer.writeAll("{ ... }");615 return writer.writeAll("{ ... }");
574 }616 }
575 if (ptr_info.child == u8) {617 if (ptr_info.child == u8) {
576 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {618 switch (actual_fmt[0]) {
577 return formatText(value, actual_fmt, options, writer);619 's', 'x', 'X', 'e', 'E' => {
620 comptime checkTextFmt(actual_fmt);
621 return formatBuf(value, options, writer);
622 },
623 else => {},
578 }624 }
579 }625 }
580 try writer.writeAll("{ ");626 try writer.writeAll("{ ");
...@@ -594,8 +640,12 @@ pub fn formatType(...@@ -594,8 +640,12 @@ pub fn formatType(
594 return writer.writeAll("{ ... }");640 return writer.writeAll("{ ... }");
595 }641 }
596 if (info.child == u8) {642 if (info.child == u8) {
597 if (comptime mem.indexOfScalar(u8, "sxXeE", actual_fmt[0]) != null) {643 switch (actual_fmt[0]) {
598 return formatText(&value, actual_fmt, options, writer);644 's', 'x', 'X', 'e', 'E' => {
645 comptime checkTextFmt(actual_fmt);
646 return formatBuf(&value, options, writer);
647 },
648 else => {},
599 }649 }
600 }650 }
601 try writer.writeAll("{ ");651 try writer.writeAll("{ ");
...@@ -881,29 +931,28 @@ pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) {...@@ -881,29 +931,28 @@ pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) {
881 return .{ .data = value };931 return .{ .data = value };
882}932}
883933
934fn checkTextFmt(comptime fmt: []const u8) void {
935 if (fmt.len != 1)
936 @compileError("Unsupported format string '" ++ fmt ++ "' when formatting text");
937 switch (fmt[0]) {
938 'x' => @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead"),
939 'X' => @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead"),
940 'e' => @compileError("specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead"),
941 'E' => @compileError("specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead"),
942 'z' => @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead"),
943 'Z' => @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead"),
944 else => {},
945 }
946}
947
884pub fn formatText(948pub fn formatText(
885 bytes: []const u8,949 bytes: []const u8,
886 comptime fmt: []const u8,950 comptime fmt: []const u8,
887 options: FormatOptions,951 options: FormatOptions,
888 writer: anytype,952 writer: anytype,
889) !void {953) !void {
890 if (comptime std.mem.eql(u8, fmt, "s")) {954 comptime checkTextFmt(fmt);
891 return formatBuf(bytes, options, writer);955 return formatBuf(bytes, options, writer);
892 } else if (comptime (std.mem.eql(u8, fmt, "x"))) {
893 @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead");
894 } else if (comptime (std.mem.eql(u8, fmt, "X"))) {
895 @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead");
896 } else if (comptime (std.mem.eql(u8, fmt, "e"))) {
897 @compileError("specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead");
898 } else if (comptime (std.mem.eql(u8, fmt, "E"))) {
899 @compileError("specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead");
900 } else if (comptime std.mem.eql(u8, fmt, "z")) {
901 @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead");
902 } else if (comptime std.mem.eql(u8, fmt, "Z")) {
903 @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead");
904 } else {
905 @compileError("Unsupported format string '" ++ fmt ++ "' when formatting text");
906 }
907}956}
908957
909pub fn formatAsciiChar(958pub fn formatAsciiChar(
src/stage1/analyze.cpp-42
...@@ -5876,49 +5876,7 @@ static bool can_mutate_comptime_var_state(ZigValue *value) {...@@ -5876,49 +5876,7 @@ static bool can_mutate_comptime_var_state(ZigValue *value) {
5876 zig_unreachable();5876 zig_unreachable();
5877}5877}
58785878
5879static bool return_type_is_cacheable(ZigType *return_type) {
5880 switch (return_type->id) {
5881 case ZigTypeIdInvalid:
5882 zig_unreachable();
5883 case ZigTypeIdMetaType:
5884 case ZigTypeIdVoid:
5885 case ZigTypeIdBool:
5886 case ZigTypeIdUnreachable:
5887 case ZigTypeIdInt:
5888 case ZigTypeIdFloat:
5889 case ZigTypeIdComptimeFloat:
5890 case ZigTypeIdComptimeInt:
5891 case ZigTypeIdEnumLiteral:
5892 case ZigTypeIdUndefined:
5893 case ZigTypeIdNull:
5894 case ZigTypeIdBoundFn:
5895 case ZigTypeIdFn:
5896 case ZigTypeIdOpaque:
5897 case ZigTypeIdErrorSet:
5898 case ZigTypeIdEnum:
5899 case ZigTypeIdPointer:
5900 case ZigTypeIdVector:
5901 case ZigTypeIdFnFrame:
5902 case ZigTypeIdAnyFrame:
5903 return true;
5904
5905 case ZigTypeIdArray:
5906 case ZigTypeIdStruct:
5907 case ZigTypeIdUnion:
5908 return false;
5909
5910 case ZigTypeIdOptional:
5911 return return_type_is_cacheable(return_type->data.maybe.child_type);
5912
5913 case ZigTypeIdErrorUnion:
5914 return return_type_is_cacheable(return_type->data.error_union.payload_type);
5915 }
5916 zig_unreachable();
5917}
5918
5919bool fn_eval_cacheable(Scope *scope, ZigType *return_type) {5879bool fn_eval_cacheable(Scope *scope, ZigType *return_type) {
5920 if (!return_type_is_cacheable(return_type))
5921 return false;
5922 while (scope) {5880 while (scope) {
5923 if (scope->id == ScopeIdVarDecl) {5881 if (scope->id == ScopeIdVarDecl) {
5924 ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;5882 ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;