| 1 | module c |
| 2 | |
| 3 | import v3.types |
| 4 | import v3.flat |
| 5 | |
| 6 | // emit_sum_type emits emit sum type output for c. |
| 7 | fn (mut g FlatGen) emit_sum_type(name string) { |
| 8 | variants := g.tc.sum_types[name] |
| 9 | g.writeln('struct ${c_name(name)} {') |
| 10 | g.writeln('\tint typ;') |
| 11 | g.writeln('\tunion {') |
| 12 | for v in variants { |
| 13 | ct := g.tc.c_type(g.tc.parse_type(v)) |
| 14 | field := g.sum_field_name(v) |
| 15 | g.writeln('\t\t${ct}* ${field};') |
| 16 | } |
| 17 | g.writeln('\t};') |
| 18 | g.writeln('};') |
| 19 | g.writeln('') |
| 20 | } |
| 21 | |
| 22 | // sum_type_contains_struct reports whether sum type contains struct applies in c. |
| 23 | fn (g &FlatGen) sum_type_contains_struct(sum_name string, struct_name string) bool { |
| 24 | if sum_name in g.tc.sum_types { |
| 25 | for v in g.tc.sum_types[sum_name] { |
| 26 | if v == struct_name { |
| 27 | return true |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | return false |
| 32 | } |
| 33 | |
| 34 | // variant_references_sum supports variant references sum handling for FlatGen. |
| 35 | fn (g &FlatGen) variant_references_sum(variant string, sum_name string) bool { |
| 36 | _ = variant |
| 37 | _ = sum_name |
| 38 | return true |
| 39 | } |
| 40 | |
| 41 | // variant_refs_sum_inner supports variant refs sum inner handling for FlatGen. |
| 42 | fn (g &FlatGen) variant_refs_sum_inner(variant string, sum_name string, mut visited map[string]bool) bool { |
| 43 | normalized_variant := g.normalize_variant_name(variant) |
| 44 | if normalized_variant == sum_name |
| 45 | || normalized_variant.all_after_last('.') == sum_name.all_after_last('.') { |
| 46 | return true |
| 47 | } |
| 48 | if normalized_variant in visited { |
| 49 | return false |
| 50 | } |
| 51 | visited[normalized_variant] = true |
| 52 | mut lookup := normalized_variant |
| 53 | if lookup !in g.tc.structs && !lookup.contains('.') && sum_name.contains('.') { |
| 54 | qualified := '${sum_name.all_before_last('.')}.${lookup}' |
| 55 | if qualified in g.tc.structs { |
| 56 | lookup = qualified |
| 57 | } |
| 58 | } |
| 59 | if lookup !in g.tc.structs && !lookup.contains('.') { |
| 60 | for struct_name, _ in g.tc.structs { |
| 61 | if struct_name.all_after_last('.') == lookup { |
| 62 | lookup = struct_name |
| 63 | break |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | if lookup in g.tc.structs { |
| 68 | for f in g.tc.structs[lookup] { |
| 69 | if g.type_references_sum(f.typ, sum_name, mut visited) { |
| 70 | return true |
| 71 | } |
| 72 | } |
| 73 | } |
| 74 | return false |
| 75 | } |
| 76 | |
| 77 | // normalize_variant_name transforms normalize variant name data for c. |
| 78 | fn (g &FlatGen) normalize_variant_name(name string) string { |
| 79 | _ = g |
| 80 | mut res := name |
| 81 | if res.starts_with('&') { |
| 82 | res = res[1..] |
| 83 | } |
| 84 | if res.starts_with('ptr') && res.len > 3 { |
| 85 | res = res[3..] |
| 86 | } |
| 87 | if res.contains('__') && !res.contains('.') { |
| 88 | res = res.replace('__', '.') |
| 89 | } |
| 90 | return res |
| 91 | } |
| 92 | |
| 93 | // type_references_sum returns type references sum data for FlatGen. |
| 94 | fn (g &FlatGen) type_references_sum(typ types.Type, sum_name string, mut visited map[string]bool) bool { |
| 95 | resolved_sum := g.resolve_sum_name(sum_name) |
| 96 | clean := types.unwrap_pointer(typ) |
| 97 | if clean is types.Struct && g.resolve_sum_name(clean.name) == resolved_sum { |
| 98 | return true |
| 99 | } |
| 100 | if clean is types.SumType && g.resolve_sum_name(clean.name) == resolved_sum { |
| 101 | return true |
| 102 | } |
| 103 | if clean is types.SumType { |
| 104 | return true |
| 105 | } |
| 106 | if clean is types.Struct { |
| 107 | if g.variant_refs_sum_inner(clean.name, resolved_sum, mut visited) { |
| 108 | return true |
| 109 | } |
| 110 | } |
| 111 | if clean is types.Array { |
| 112 | return g.type_references_sum(clean.elem_type, resolved_sum, mut visited) |
| 113 | } |
| 114 | return false |
| 115 | } |
| 116 | |
| 117 | // resolve_sum_name resolves resolve sum name information for c. |
| 118 | fn (g &FlatGen) resolve_sum_name(sum_name string) string { |
| 119 | if sum_name in g.tc.sum_types { |
| 120 | return sum_name |
| 121 | } |
| 122 | for name, _ in g.tc.sum_types { |
| 123 | if name.all_after_last('.') == sum_name { |
| 124 | return name |
| 125 | } |
| 126 | } |
| 127 | return sum_name |
| 128 | } |
| 129 | |
| 130 | // resolve_variant resolves resolve variant information for c. |
| 131 | fn (g &FlatGen) resolve_variant(sum_name string, variant string) string { |
| 132 | resolved_sum := g.resolve_sum_name(sum_name) |
| 133 | normalized_variant := g.normalize_variant_name(variant) |
| 134 | if resolved_sum in g.tc.sum_types { |
| 135 | for v in g.tc.sum_types[resolved_sum] { |
| 136 | if v == normalized_variant { |
| 137 | return normalized_variant |
| 138 | } |
| 139 | } |
| 140 | for v in g.tc.sum_types[resolved_sum] { |
| 141 | if v.all_after_last('.') == normalized_variant { |
| 142 | return v |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | return normalized_variant |
| 147 | } |
| 148 | |
| 149 | // sum_field_name supports sum field name handling for FlatGen. |
| 150 | fn (g &FlatGen) sum_field_name(variant string) string { |
| 151 | if variant.starts_with('&') { |
| 152 | return g.sum_field_name(variant[1..]) |
| 153 | } |
| 154 | if variant.starts_with('ptr') && variant.len > 3 && variant[3..].contains('.') { |
| 155 | return g.sum_field_name(variant[3..]) |
| 156 | } |
| 157 | if variant.starts_with('ptr') && variant.len > 3 && variant[3..].contains('__') { |
| 158 | return g.sum_field_name(variant[3..].replace('__', '.')) |
| 159 | } |
| 160 | if variant.starts_with('[]') { |
| 161 | return '_Array_${c_name(variant[2..])}' |
| 162 | } |
| 163 | if variant.starts_with('map[') { |
| 164 | return '_Map_${c_name(variant[4..].replace(']', '_'))}' |
| 165 | } |
| 166 | return match variant { |
| 167 | 'int' { '_int' } |
| 168 | 'i8' { '_i8' } |
| 169 | 'i16' { '_i16' } |
| 170 | 'i64' { '_i64' } |
| 171 | 'u8', 'byte' { '_u8' } |
| 172 | 'u16' { '_u16' } |
| 173 | 'u32' { '_u32' } |
| 174 | 'u64' { '_u64' } |
| 175 | 'f32' { '_f32' } |
| 176 | 'f64' { '_f64' } |
| 177 | 'bool' { '_bool' } |
| 178 | 'string' { '_string' } |
| 179 | else { c_name(variant) } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // register_interface_strings updates register interface strings state for c. |
| 184 | fn (mut g FlatGen) register_interface_strings() { |
| 185 | for iface_name, methods in g.interfaces { |
| 186 | cn := c_name(iface_name) |
| 187 | for method in methods { |
| 188 | g.intern_string('interface method ${cn}.${method} not implemented') |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // collect_interface_impls discovers, for every interface, the concrete struct |
| 194 | // types that implement it (structural typing), and assigns each a stable 1-based |
| 195 | // type id. The id is stored in the boxed interface value's `_typ` field and is |
| 196 | // what the generated method-dispatch switch matches on. |
| 197 | fn (mut g FlatGen) collect_interface_impls() { |
| 198 | mut iface_names := []string{} |
| 199 | for name, _ in g.interfaces { |
| 200 | iface_names << name |
| 201 | } |
| 202 | iface_names.sort() |
| 203 | mut struct_names := []string{} |
| 204 | for name, _ in g.tc.structs { |
| 205 | struct_names << name |
| 206 | } |
| 207 | struct_names.sort() |
| 208 | for iface in iface_names { |
| 209 | if c_name(iface) == 'IError' { |
| 210 | continue |
| 211 | } |
| 212 | mut impls := []string{} |
| 213 | for concrete in struct_names { |
| 214 | if g.tc.named_type_implements_interface(concrete, iface) { |
| 215 | impls << concrete |
| 216 | } |
| 217 | } |
| 218 | g.iface_impls[iface] = impls |
| 219 | for idx, concrete in impls { |
| 220 | g.iface_type_ids['${iface}::${concrete}'] = idx + 1 |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // iface_type_id returns the 1-based dispatch id assigned to `concrete` for |
| 226 | // interface `iface`, or 0 if `concrete` does not implement `iface`. |
| 227 | fn (g &FlatGen) iface_type_id(iface string, concrete string) int { |
| 228 | return g.iface_type_ids['${iface}::${concrete}'] or { 0 } |
| 229 | } |
| 230 | |
| 231 | fn (mut g FlatGen) gen_interface_value_expr(id flat.NodeId, expected types.Type) bool { |
| 232 | iface_type := if expected is types.Alias { expected.base_type } else { expected } |
| 233 | if iface_type !is types.Interface { |
| 234 | return false |
| 235 | } |
| 236 | iface := iface_type as types.Interface |
| 237 | node := g.a.nodes[int(id)] |
| 238 | mut actual := g.usable_expr_type(id) |
| 239 | if node.kind == .ident { |
| 240 | if param_type := g.current_param_type(node.value) { |
| 241 | actual = param_type |
| 242 | } |
| 243 | } |
| 244 | actual_clean := if actual is types.Pointer { actual.base_type } else { actual } |
| 245 | actual_base := if actual_clean is types.Alias { actual_clean.base_type } else { actual_clean } |
| 246 | if actual_base is types.Interface { |
| 247 | return false |
| 248 | } |
| 249 | concrete_name := actual_base.name() |
| 250 | if concrete_name.len == 0 { |
| 251 | return false |
| 252 | } |
| 253 | type_id := g.iface_type_id(iface.name, concrete_name) |
| 254 | ct := g.tc.c_type(iface) |
| 255 | fields := g.tc.interface_fields[iface.name] or { []types.StructField{} } |
| 256 | concrete_ct := g.tc.c_type(actual_base) |
| 257 | if fields.len > 0 { |
| 258 | tmp := g.tmp_count |
| 259 | g.tmp_count++ |
| 260 | if actual is types.Pointer { |
| 261 | g.write('({ ${concrete_ct}* _iface${tmp} = ') |
| 262 | g.gen_expr(id) |
| 263 | g.write('; (${ct}){._typ = ${type_id}, ._object = _iface${tmp}') |
| 264 | for field in fields { |
| 265 | g.write(', .${c_name(field.name)} = _iface${tmp}->${c_name(field.name)}') |
| 266 | } |
| 267 | g.write('}; })') |
| 268 | } else { |
| 269 | g.write('({ ${concrete_ct} _iface${tmp} = ') |
| 270 | g.gen_expr(id) |
| 271 | g.write('; (${ct}){._typ = ${type_id}, ._object = memdup(&_iface${tmp}, sizeof(${concrete_ct}))') |
| 272 | for field in fields { |
| 273 | g.write(', .${c_name(field.name)} = _iface${tmp}.${c_name(field.name)}') |
| 274 | } |
| 275 | g.write('}; })') |
| 276 | } |
| 277 | return true |
| 278 | } |
| 279 | g.write('(${ct}){._typ = ${type_id}, ._object = ') |
| 280 | if actual is types.Pointer { |
| 281 | g.gen_expr(id) |
| 282 | } else if node.kind in [.ident, .selector, .index] { |
| 283 | g.write('memdup(&') |
| 284 | g.gen_expr(id) |
| 285 | g.write(', sizeof(${concrete_ct}))') |
| 286 | } else { |
| 287 | g.write('memdup((${concrete_ct}[]){') |
| 288 | g.gen_expr(id) |
| 289 | g.write('}, sizeof(${concrete_ct}))') |
| 290 | } |
| 291 | g.write('}') |
| 292 | return true |
| 293 | } |
| 294 | |
| 295 | // is_interface_type_name reports whether is interface type name applies in c. |
| 296 | fn (g &FlatGen) is_interface_type_name(name string) bool { |
| 297 | return name in g.interfaces || g.tc.qualify_name(name) in g.interfaces |
| 298 | } |
| 299 | |
| 300 | // has_ierror_interface reports whether has ierror interface applies in c. |
| 301 | fn (g &FlatGen) has_ierror_interface() bool { |
| 302 | for name, _ in g.interfaces { |
| 303 | if c_name(name) == 'IError' { |
| 304 | return true |
| 305 | } |
| 306 | } |
| 307 | return false |
| 308 | } |
| 309 | |
| 310 | // interface_init_typ_id computes the `_typ` dispatch id for a boxed interface |
| 311 | // literal by recovering the concrete type from its `_object` field. |
| 312 | fn (g &FlatGen) interface_init_typ_id(node flat.Node) ?int { |
| 313 | iface := if node.value in g.interfaces { |
| 314 | node.value |
| 315 | } else { |
| 316 | g.tc.qualify_name(node.value) |
| 317 | } |
| 318 | for i in 0 .. node.children_count { |
| 319 | field := g.a.child_node(&node, i) |
| 320 | if field.kind == .field_init && field.value == '_object' && field.children_count > 0 { |
| 321 | obj_type := g.tc.resolve_type(g.a.child(field, 0)) |
| 322 | concrete := types.unwrap_pointer(obj_type) |
| 323 | id := g.iface_type_id(iface, concrete.name()) |
| 324 | if id != 0 { |
| 325 | return id |
| 326 | } |
| 327 | return none |
| 328 | } |
| 329 | } |
| 330 | return none |
| 331 | } |
| 332 | |
| 333 | // interface_method_stubs emits a dispatch function for every abstract interface |
| 334 | // method: it switches on the boxed value's `_typ` and forwards to the concrete |
| 335 | // implementation, passing `_object` as the receiver. Interfaces with no known |
| 336 | // implementers (and the special builtin `IError`) fall back to a panic stub. |
| 337 | fn (mut g FlatGen) interface_method_stubs() { |
| 338 | for iface_name, methods in g.interfaces { |
| 339 | cn := c_name(iface_name) |
| 340 | for method in methods { |
| 341 | if !g.should_emit_interface_dispatch(iface_name, method) { |
| 342 | continue |
| 343 | } |
| 344 | g.gen_interface_dispatch(iface_name, cn, method) |
| 345 | } |
| 346 | } |
| 347 | if g.interfaces.len > 0 { |
| 348 | g.writeln('') |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | fn (g &FlatGen) should_emit_interface_dispatch(iface_name string, method string) bool { |
| 353 | if !g.has_used_fn_filter() { |
| 354 | return true |
| 355 | } |
| 356 | name := '${iface_name}.${method}' |
| 357 | if g.used_interface_dispatch_key(name) { |
| 358 | return true |
| 359 | } |
| 360 | if decl_key := g.interface_method_signature_key(iface_name, method) { |
| 361 | if decl_key != name && g.used_interface_dispatch_key(decl_key) { |
| 362 | return true |
| 363 | } |
| 364 | decl_short_name := '${decl_key.all_before_last('.').all_after_last('.')}.${method}' |
| 365 | if decl_short_name != decl_key && g.interface_dispatch_short_name_allowed(iface_name) |
| 366 | && g.used_interface_dispatch_key(decl_short_name) { |
| 367 | return true |
| 368 | } |
| 369 | } |
| 370 | short_name := '${iface_name.all_after_last('.')}.${method}' |
| 371 | return short_name != name && g.interface_dispatch_short_name_allowed(iface_name) |
| 372 | && g.used_interface_dispatch_key(short_name) |
| 373 | } |
| 374 | |
| 375 | fn (g &FlatGen) used_interface_dispatch_key(name string) bool { |
| 376 | return g.used_fn_contains(name) || g.used_fn_contains(c_name(name)) |
| 377 | } |
| 378 | |
| 379 | fn (g &FlatGen) interface_dispatch_short_name_allowed(iface_name string) bool { |
| 380 | return !iface_name.contains('.') |
| 381 | } |
| 382 | |
| 383 | // gen_interface_dispatch emits interface dispatch output for c. |
| 384 | fn (mut g FlatGen) gen_interface_dispatch(iface_name string, cn string, method string) { |
| 385 | sid := g.intern_string('interface method ${cn}.${method} not implemented') |
| 386 | mname := '${iface_name}.${method}' |
| 387 | decl_key := g.interface_method_signature_key(iface_name, method) or { mname } |
| 388 | impls := g.iface_impls[iface_name] or { []string{} } |
| 389 | if cn == 'IError' { |
| 390 | ret_ct := if method == 'code' { 'int' } else { 'string' } |
| 391 | g.writeln('${ret_ct} ${cn}__${method}(${cn}* i) {') |
| 392 | match method { |
| 393 | 'msg' { |
| 394 | g.writeln('\treturn i->message;') |
| 395 | } |
| 396 | 'code' { |
| 397 | g.writeln('\treturn i->code;') |
| 398 | } |
| 399 | else { |
| 400 | g.writeln('\tpanic(_str_${sid});') |
| 401 | g.writeln('\treturn (${ret_ct}){0};') |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | g.writeln('}') |
| 406 | return |
| 407 | } |
| 408 | // Interface-declared method signatures store named params unreliably (a named |
| 409 | // param like `node &ast.Node` can be split into two type-only params). The |
| 410 | // concrete implementer's method is a real fn_decl with a correctly parsed |
| 411 | // signature, so derive the dispatch parameter types from the first implementer |
| 412 | // that has the method. The receiver convention is resolved per implementer. |
| 413 | mut sig_key := '' |
| 414 | for concrete in impls { |
| 415 | ck := '${concrete}.${method}' |
| 416 | if ck in g.tc.fn_param_types { |
| 417 | sig_key = ck |
| 418 | break |
| 419 | } |
| 420 | } |
| 421 | ret_type := g.tc.fn_ret_types[decl_key] or { |
| 422 | if sig_key.len > 0 { |
| 423 | g.tc.fn_ret_types[sig_key] or { types.Type(types.void_) } |
| 424 | } else { |
| 425 | types.Type(types.void_) |
| 426 | } |
| 427 | } |
| 428 | // Use the ABI return type, not the bare value type: a fixed-array return is its `_v_ret_*` |
| 429 | // wrapper struct (a C function cannot return an array by value), matching what the concrete |
| 430 | // implementer's method returns and what the call site unwraps. |
| 431 | ret_ct := g.fn_return_type_name(ret_type) |
| 432 | mut sig_params := if sig_key.len > 0 { |
| 433 | g.tc.fn_param_types[sig_key] or { []types.Type{} } |
| 434 | } else { |
| 435 | g.tc.fn_param_types[decl_key] or { []types.Type{} } |
| 436 | } |
| 437 | mut arg_names := []string{} |
| 438 | g.write('${ret_ct} ${cn}__${method}(${cn}* i') |
| 439 | for pi := 1; pi < sig_params.len; pi++ { |
| 440 | pt := sig_params[pi] |
| 441 | pct := if pt is types.OptionType || pt is types.ResultType { |
| 442 | g.optional_type_name(pt) |
| 443 | } else { |
| 444 | g.tc.c_type(pt) |
| 445 | } |
| 446 | an := '_a${pi - 1}' |
| 447 | arg_names << an |
| 448 | g.write(', ${pct} ${an}') |
| 449 | } |
| 450 | g.writeln(') {') |
| 451 | if impls.len > 0 { |
| 452 | g.writeln('\tswitch (i->_typ) {') |
| 453 | for concrete in impls { |
| 454 | id := g.iface_type_id(iface_name, concrete) |
| 455 | concrete_key := '${concrete}.${method}' |
| 456 | if id == 0 || concrete_key !in g.tc.fn_param_types |
| 457 | || !g.interface_dispatch_target_is_emitted(concrete_key) { |
| 458 | continue |
| 459 | } |
| 460 | concrete_params := g.tc.fn_param_types[concrete_key] or { []types.Type{} } |
| 461 | recv_is_ptr := concrete_params.len > 0 && concrete_params[0] is types.Pointer |
| 462 | cct := g.tc.c_type(g.tc.parse_type(concrete)) |
| 463 | recv := if recv_is_ptr { |
| 464 | '(${cct}*)i->_object' |
| 465 | } else { |
| 466 | '*(${cct}*)i->_object' |
| 467 | } |
| 468 | g.write('\t\tcase ${id}: ') |
| 469 | mut call := '${c_name(concrete_key)}(${recv}' |
| 470 | for an in arg_names { |
| 471 | call += ', ${an}' |
| 472 | } |
| 473 | call += ')' |
| 474 | if ret_ct == 'void' { |
| 475 | g.writeln('${call}; return;') |
| 476 | } else { |
| 477 | g.writeln('return ${call};') |
| 478 | } |
| 479 | } |
| 480 | g.writeln('\t\tdefault: break;') |
| 481 | g.writeln('\t}') |
| 482 | } |
| 483 | g.writeln('\tpanic(_str_${sid});') |
| 484 | if ret_ct != 'void' { |
| 485 | g.writeln('\treturn (${ret_ct}){0};') |
| 486 | } |
| 487 | g.writeln('}') |
| 488 | } |
| 489 | |
| 490 | fn (g &FlatGen) interface_method_signature_key(iface_name string, method string) ?string { |
| 491 | key := '${iface_name}.${method}' |
| 492 | if key in g.tc.fn_ret_types || key in g.tc.fn_param_types { |
| 493 | return key |
| 494 | } |
| 495 | for embed in g.tc.interface_embeds[iface_name] or { []string{} } { |
| 496 | if found := g.interface_method_signature_key(embed, method) { |
| 497 | return found |
| 498 | } |
| 499 | } |
| 500 | return none |
| 501 | } |
| 502 | |
| 503 | fn (g &FlatGen) interface_dispatch_target_is_emitted(concrete_key string) bool { |
| 504 | if !g.has_used_fn_filter() { |
| 505 | return true |
| 506 | } |
| 507 | if g.used_interface_dispatch_key(concrete_key) { |
| 508 | return true |
| 509 | } |
| 510 | receiver_name := concrete_key.all_before_last('.') |
| 511 | if receiver_name.contains('.') { |
| 512 | return false |
| 513 | } |
| 514 | method := concrete_key.all_after_last('.') |
| 515 | short_key := '${receiver_name.all_after_last('.')}.${method}' |
| 516 | return short_key != concrete_key |
| 517 | && g.interface_dispatch_target_short_name_is_unambiguous(short_key, method) |
| 518 | && g.used_interface_dispatch_key(short_key) |
| 519 | } |
| 520 | |
| 521 | fn (g &FlatGen) interface_dispatch_target_short_name_is_unambiguous(short_name string, method string) bool { |
| 522 | mut seen := map[string]bool{} |
| 523 | mut matches := 0 |
| 524 | for _, impls in g.iface_impls { |
| 525 | for concrete in impls { |
| 526 | if seen[concrete] { |
| 527 | continue |
| 528 | } |
| 529 | seen[concrete] = true |
| 530 | if '${concrete.all_after_last('.')}.${method}' == short_name { |
| 531 | matches++ |
| 532 | if matches > 1 { |
| 533 | return false |
| 534 | } |
| 535 | } |
| 536 | } |
| 537 | } |
| 538 | return matches == 1 |
| 539 | } |
| 540 | |
| 541 | // sum_type_index supports sum type index handling for FlatGen. |
| 542 | fn (g &FlatGen) sum_type_index(sum_name string, variant string) int { |
| 543 | if sum_name in g.tc.sum_types { |
| 544 | for i, v in g.tc.sum_types[sum_name] { |
| 545 | if v == variant { |
| 546 | return i + 1 |
| 547 | } |
| 548 | } |
| 549 | for i, v in g.tc.sum_types[sum_name] { |
| 550 | if v.all_after_last('.') == variant { |
| 551 | return i + 1 |
| 552 | } |
| 553 | } |
| 554 | } |
| 555 | return 0 |
| 556 | } |
| 557 | |