Skip to content

Commit

Permalink
Support implicit conversion from a type value to a facet value (carbo…
Browse files Browse the repository at this point in the history
…n-language#4863)

If a type satisfies the requirements of a FacetType, then that type as a
value can be converted to a FacetValue, which binds the type value to
the FacetType.

For instance, if the class A implements an interface B, then
```
  fn F(b:! B) {}
```
can be called with the type `A`
```
  F(A);
```
This does not handle yet receiving non-type values matching a FacetType,
as that requires deducing the required FacetValue for the caller's
argument. Follow-up work will do this step.

---------

Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
  • Loading branch information
danakj and josh11b authored Feb 6, 2025
1 parent 40f3de2 commit f3a898b
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 13 deletions.
38 changes: 38 additions & 0 deletions toolchain/check/convert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/check/impl_lookup.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/diagnostics/format_providers.h"
Expand Down Expand Up @@ -988,6 +989,43 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
}

if (sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
if (sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
// Conversion from a facet value (which has type `FacetType`) to a
// different facet value (which has type `FacetType`), if the value's
// `FacetType` satisfies the requirements of the target `FacetType`. The
// underlying type in the facet value will be preserved, just the
// `FacetType` will change.

// TODO: We need to do impl lookup for the FacetType, here, not for the
// FacetValue (so not using `context.constant_values().Get(value_id)` like
// we do for `TypeType`). The FacetType erased the type in the FacetValue,
// so using that here would be like an implicit cast back to the concrete
// type.
context.TODO(loc_id, "Facet value converting to facet value");
} else if (sem_ir.types().Is<SemIR::TypeType>(value_type_id)) {
// Conversion from a type value (which has type `type`) to a facet value
// (which has type `FacetType`), if the type satisfies the requirements of
// the target `FacetType`, as determined by finding an impl witness. This
// binds the value to the `FacetType` with a `FacetValue`.
auto witness_inst_id = LookupImplWitness(
context, loc_id,
// The value instruction evaluates to a type value (which has type
// `type`). This gets that type value if it's available at compile
// time, as a constant value.
context.constant_values().Get(value_id),
context.types().GetConstantId(target.type_id));
if (witness_inst_id != SemIR::InstId::None) {
return context.AddInst<SemIR::FacetValue>(
loc_id, {
.type_id = target.type_id,
.type_inst_id = value_id,
.witness_inst_id = witness_inst_id,
});
}
}
}

// No builtin conversion applies.
return value_id;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtin_conversions/no_prelude/convert_class_type_to_facet_type.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtin_conversions/no_prelude/convert_class_type_to_facet_type.carbon

interface Animal {}

class Goat {}
impl Goat as Animal {}

fn WalkAnimal(A:! Animal) {}

fn F() {
WalkAnimal(Goat);
}

// CHECK:STDOUT: --- convert_class_type_to_facet_type.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %Animal.type: type = facet_type <@Animal> [template]
// CHECK:STDOUT: %Self: %Animal.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Goat: type = class_type @Goat [template]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [template]
// CHECK:STDOUT: %impl_witness: <witness> = impl_witness () [template]
// CHECK:STDOUT: %A: %Animal.type = bind_symbolic_name A, 0 [symbolic]
// CHECK:STDOUT: %A.patt: %Animal.type = symbolic_binding_pattern A, 0 [symbolic]
// CHECK:STDOUT: %WalkAnimal.type: type = fn_type @WalkAnimal [template]
// CHECK:STDOUT: %WalkAnimal: %WalkAnimal.type = struct_value () [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %Animal.facet: %Animal.type = facet_value %Goat, %impl_witness [template]
// CHECK:STDOUT: %WalkAnimal.specific_fn: <specific function> = specific_function %WalkAnimal, @WalkAnimal(%Animal.facet) [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Animal = %Animal.decl
// CHECK:STDOUT: .Goat = %Goat.decl
// CHECK:STDOUT: .WalkAnimal = %WalkAnimal.decl
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Animal.decl: type = interface_decl @Animal [template = constants.%Animal.type] {} {}
// CHECK:STDOUT: %Goat.decl: type = class_decl @Goat [template = constants.%Goat] {} {}
// CHECK:STDOUT: impl_decl @impl [template] {} {
// CHECK:STDOUT: %Goat.ref: type = name_ref Goat, file.%Goat.decl [template = constants.%Goat]
// CHECK:STDOUT: %Animal.ref: type = name_ref Animal, file.%Animal.decl [template = constants.%Animal.type]
// CHECK:STDOUT: }
// CHECK:STDOUT: %impl_witness: <witness> = impl_witness () [template = constants.%impl_witness]
// CHECK:STDOUT: %WalkAnimal.decl: %WalkAnimal.type = fn_decl @WalkAnimal [template = constants.%WalkAnimal] {
// CHECK:STDOUT: %A.patt.loc16_15.1: %Animal.type = symbolic_binding_pattern A, 0 [symbolic = %A.patt.loc16_15.2 (constants.%A.patt)]
// CHECK:STDOUT: %A.param_patt: %Animal.type = value_param_pattern %A.patt.loc16_15.1, runtime_param<none> [symbolic = %A.patt.loc16_15.2 (constants.%A.patt)]
// CHECK:STDOUT: } {
// CHECK:STDOUT: %A.param: %Animal.type = value_param runtime_param<none>
// CHECK:STDOUT: %Animal.ref: type = name_ref Animal, file.%Animal.decl [template = constants.%Animal.type]
// CHECK:STDOUT: %A.loc16_15.1: %Animal.type = bind_symbolic_name A, 0, %A.param [symbolic = %A.loc16_15.2 (constants.%A)]
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Animal {
// CHECK:STDOUT: %Self: %Animal.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: witness = ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: %Goat.ref as %Animal.ref {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = file.%impl_witness
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Goat {
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%Goat
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: generic fn @WalkAnimal(%A.loc16_15.1: %Animal.type) {
// CHECK:STDOUT: %A.loc16_15.2: %Animal.type = bind_symbolic_name A, 0 [symbolic = %A.loc16_15.2 (constants.%A)]
// CHECK:STDOUT: %A.patt.loc16_15.2: %Animal.type = symbolic_binding_pattern A, 0 [symbolic = %A.patt.loc16_15.2 (constants.%A.patt)]
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT:
// CHECK:STDOUT: fn(%A.param_patt: %Animal.type) {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %WalkAnimal.ref: %WalkAnimal.type = name_ref WalkAnimal, file.%WalkAnimal.decl [template = constants.%WalkAnimal]
// CHECK:STDOUT: %Goat.ref: type = name_ref Goat, file.%Goat.decl [template = constants.%Goat]
// CHECK:STDOUT: %Animal.facet: %Animal.type = facet_value %Goat.ref, constants.%impl_witness [template = constants.%Animal.facet]
// CHECK:STDOUT: %.loc19: %Animal.type = converted %Goat.ref, %Animal.facet [template = constants.%Animal.facet]
// CHECK:STDOUT: %WalkAnimal.specific_fn: <specific function> = specific_function %WalkAnimal.ref, @WalkAnimal(constants.%Animal.facet) [template = constants.%WalkAnimal.specific_fn]
// CHECK:STDOUT: %WalkAnimal.call: init %empty_tuple.type = call %WalkAnimal.specific_fn()
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @WalkAnimal(constants.%A) {
// CHECK:STDOUT: %A.loc16_15.2 => constants.%A
// CHECK:STDOUT: %A.patt.loc16_15.2 => constants.%A
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @WalkAnimal(constants.%Animal.facet) {
// CHECK:STDOUT: %A.loc16_15.2 => constants.%Animal.facet
// CHECK:STDOUT: %A.patt.loc16_15.2 => constants.%Animal.facet
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT: }
// CHECK:STDOUT:
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/builtin_conversions/no_prelude/fail_todo_convert_facet_value_to_facet_value.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/builtin_conversions/no_prelude/fail_todo_convert_facet_value_to_facet_value.carbon

interface Eats {}
interface Goat {}

class Ginger {}
impl Ginger as Goat {}

impl Goat as Eats {}

fn Feed(e:! Eats) {}

fn F() {
// CHECK:STDERR: fail_todo_convert_facet_value_to_facet_value.carbon:[[@LINE+14]]:3: error: semantics TODO: `Facet value converting to facet value` [SemanticsTodo]
// CHECK:STDERR: Feed(Ginger as Goat);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_convert_facet_value_to_facet_value.carbon:[[@LINE-6]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Feed(e:! Eats) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_todo_convert_facet_value_to_facet_value.carbon:[[@LINE+7]]:3: error: `Core.ImplicitAs` implicitly referenced here, but package `Core` not found [CoreNotFound]
// CHECK:STDERR: Feed(Ginger as Goat);
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR: fail_todo_convert_facet_value_to_facet_value.carbon:[[@LINE-13]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn Feed(e:! Eats) {}
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
Feed(Ginger as Goat);
}

// CHECK:STDOUT: --- fail_todo_convert_facet_value_to_facet_value.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %Eats.type: type = facet_type <@Eats> [template]
// CHECK:STDOUT: %Self.1b5: %Eats.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Goat.type: type = facet_type <@Goat> [template]
// CHECK:STDOUT: %Self.092: %Goat.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Ginger: type = class_type @Ginger [template]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [template]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template]
// CHECK:STDOUT: %impl_witness: <witness> = impl_witness () [template]
// CHECK:STDOUT: %e: %Eats.type = bind_symbolic_name e, 0 [symbolic]
// CHECK:STDOUT: %e.patt: %Eats.type = symbolic_binding_pattern e, 0 [symbolic]
// CHECK:STDOUT: %Feed.type: type = fn_type @Feed [template]
// CHECK:STDOUT: %Feed: %Feed.type = struct_value () [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %Goat.facet: %Goat.type = facet_value %Ginger, %impl_witness [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Eats = %Eats.decl
// CHECK:STDOUT: .Goat = %Goat.decl
// CHECK:STDOUT: .Ginger = %Ginger.decl
// CHECK:STDOUT: .Feed = %Feed.decl
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Eats.decl: type = interface_decl @Eats [template = constants.%Eats.type] {} {}
// CHECK:STDOUT: %Goat.decl: type = interface_decl @Goat [template = constants.%Goat.type] {} {}
// CHECK:STDOUT: %Ginger.decl: type = class_decl @Ginger [template = constants.%Ginger] {} {}
// CHECK:STDOUT: impl_decl @impl.1 [template] {} {
// CHECK:STDOUT: %Ginger.ref: type = name_ref Ginger, file.%Ginger.decl [template = constants.%Ginger]
// CHECK:STDOUT: %Goat.ref: type = name_ref Goat, file.%Goat.decl [template = constants.%Goat.type]
// CHECK:STDOUT: }
// CHECK:STDOUT: %impl_witness.loc15: <witness> = impl_witness () [template = constants.%impl_witness]
// CHECK:STDOUT: impl_decl @impl.2 [template] {} {
// CHECK:STDOUT: %Goat.ref: type = name_ref Goat, file.%Goat.decl [template = constants.%Goat.type]
// CHECK:STDOUT: %Eats.ref: type = name_ref Eats, file.%Eats.decl [template = constants.%Eats.type]
// CHECK:STDOUT: }
// CHECK:STDOUT: %impl_witness.loc17: <witness> = impl_witness () [template = constants.%impl_witness]
// CHECK:STDOUT: %Feed.decl: %Feed.type = fn_decl @Feed [template = constants.%Feed] {
// CHECK:STDOUT: %e.patt.loc19_9.1: %Eats.type = symbolic_binding_pattern e, 0 [symbolic = %e.patt.loc19_9.2 (constants.%e.patt)]
// CHECK:STDOUT: %e.param_patt: %Eats.type = value_param_pattern %e.patt.loc19_9.1, runtime_param<none> [symbolic = %e.patt.loc19_9.2 (constants.%e.patt)]
// CHECK:STDOUT: } {
// CHECK:STDOUT: %e.param: %Eats.type = value_param runtime_param<none>
// CHECK:STDOUT: %Eats.ref: type = name_ref Eats, file.%Eats.decl [template = constants.%Eats.type]
// CHECK:STDOUT: %e.loc19_9.1: %Eats.type = bind_symbolic_name e, 0, %e.param [symbolic = %e.loc19_9.2 (constants.%e)]
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Eats {
// CHECK:STDOUT: %Self: %Eats.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.1b5]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: witness = ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Goat {
// CHECK:STDOUT: %Self: %Goat.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self.092]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: witness = ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl.1: %Ginger.ref as %Goat.ref {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = file.%impl_witness.loc15
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl.2: %Goat.ref as %Eats.ref {
// CHECK:STDOUT: !members:
// CHECK:STDOUT: witness = file.%impl_witness.loc17
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @Ginger {
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness %empty_struct_type [template = constants.%complete_type]
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%Ginger
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: generic fn @Feed(%e.loc19_9.1: %Eats.type) {
// CHECK:STDOUT: %e.loc19_9.2: %Eats.type = bind_symbolic_name e, 0 [symbolic = %e.loc19_9.2 (constants.%e)]
// CHECK:STDOUT: %e.patt.loc19_9.2: %Eats.type = symbolic_binding_pattern e, 0 [symbolic = %e.patt.loc19_9.2 (constants.%e.patt)]
// CHECK:STDOUT:
// CHECK:STDOUT: !definition:
// CHECK:STDOUT:
// CHECK:STDOUT: fn(%e.param_patt: %Eats.type) {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %Feed.ref: %Feed.type = name_ref Feed, file.%Feed.decl [template = constants.%Feed]
// CHECK:STDOUT: %Ginger.ref: type = name_ref Ginger, file.%Ginger.decl [template = constants.%Ginger]
// CHECK:STDOUT: %Goat.ref: type = name_ref Goat, file.%Goat.decl [template = constants.%Goat.type]
// CHECK:STDOUT: %Goat.facet: %Goat.type = facet_value %Ginger.ref, constants.%impl_witness [template = constants.%Goat.facet]
// CHECK:STDOUT: %.loc36_15: %Goat.type = converted %Ginger.ref, %Goat.facet [template = constants.%Goat.facet]
// CHECK:STDOUT: %.loc36_22: %Eats.type = converted %.loc36_15, <error> [template = <error>]
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: specific @Feed(constants.%e) {
// CHECK:STDOUT: %e.loc19_9.2 => constants.%e
// CHECK:STDOUT: %e.patt.loc19_9.2 => constants.%e
// CHECK:STDOUT: }
// CHECK:STDOUT:
11 changes: 9 additions & 2 deletions toolchain/check/testdata/interface/no_prelude/generic.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,20 @@ class B {}
fn F(T:! Generic(A));
fn G(T:! Generic(B)) {
// TODO: Include generic arguments in the type name.
// CHECK:STDERR: fail_mismatched_args.carbon:[[@LINE+7]]:3: error: `Core.ImplicitAs` implicitly referenced here, but package `Core` not found [CoreNotFound]
// CHECK:STDERR: fail_mismatched_args.carbon:[[@LINE+14]]:3: error: semantics TODO: `Facet value converting to facet value` [SemanticsTodo]
// CHECK:STDERR: F(T);
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_mismatched_args.carbon:[[@LINE-6]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn F(T:! Generic(A));
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
// CHECK:STDERR: fail_mismatched_args.carbon:[[@LINE+7]]:3: error: `Core.ImplicitAs` implicitly referenced here, but package `Core` not found [CoreNotFound]
// CHECK:STDERR: F(T);
// CHECK:STDERR: ^~~~
// CHECK:STDERR: fail_mismatched_args.carbon:[[@LINE-13]]:1: note: while deducing parameters of generic declared here [DeductionGenericHere]
// CHECK:STDERR: fn F(T:! Generic(A));
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~
// CHECK:STDERR:
F(T);
}

Expand Down Expand Up @@ -497,7 +504,7 @@ fn G(T:! Generic(B)) {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [template = constants.%F]
// CHECK:STDOUT: %T.ref: %Generic.type.4ce = name_ref T, %T.loc10_6.1 [symbolic = %T.loc10_6.2 (constants.%T.bae)]
// CHECK:STDOUT: %.loc19: %Generic.type.c7c = converted %T.ref, <error> [template = <error>]
// CHECK:STDOUT: %.loc26: %Generic.type.c7c = converted %T.ref, <error> [template = <error>]
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT: }
Expand Down
Loading

0 comments on commit f3a898b

Please sign in to comment.