소스 검색

Allow no-op functions to have unused arguments (#5318)

For example, we might want `[self: Self]` to be ignored.
Jon Ross-Perkins 1 년 전
부모
커밋
401c72a5c3

+ 58 - 0
toolchain/check/testdata/builtins/no_prelude/no_op.carbon

@@ -28,6 +28,16 @@ fn F() {
   NoOp();
 }
 
+// --- ignore_args.carbon
+
+library "[[@TEST_NAME]]";
+
+fn NoOp(x: ()) -> () = "no_op";
+
+fn F() {
+  NoOp(());
+}
+
 // --- assign.carbon
 
 library "[[@TEST_NAME]]";
@@ -114,6 +124,54 @@ fn NoOp() -> {} = "no_op";
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- ignore_args.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %NoOp.type: type = fn_type @NoOp [concrete]
+// CHECK:STDOUT:   %NoOp: %NoOp.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoOp = %NoOp.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %NoOp.decl: %NoOp.type = fn_decl @NoOp [concrete = constants.%NoOp] {
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %empty_tuple.type = value_param_pattern %x.patt, call_param0
+// CHECK:STDOUT:     %return.patt: %empty_tuple.type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %empty_tuple.type = out_param_pattern %return.patt, call_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc4_20.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc4_20.2: type = converted %.loc4_20.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     %x.param: %empty_tuple.type = value_param call_param0
+// CHECK:STDOUT:     %.loc4_13.1: type = splice_block %.loc4_13.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:       %.loc4_13.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:       %.loc4_13.3: type = converted %.loc4_13.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %x: %empty_tuple.type = bind_name x, %x.param
+// CHECK:STDOUT:     %return.param: ref %empty_tuple.type = out_param call_param1
+// CHECK:STDOUT:     %return: ref %empty_tuple.type = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @NoOp(%x.param_patt: %empty_tuple.type) -> %empty_tuple.type = "no_op";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %NoOp.ref: %NoOp.type = name_ref NoOp, file.%NoOp.decl [concrete = constants.%NoOp]
+// CHECK:STDOUT:   %.loc7_9.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc7_9.2: %empty_tuple.type = converted %.loc7_9.1, %empty_tuple [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %no_op: init %empty_tuple.type = call %NoOp.ref(%.loc7_9.2) [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- assign.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 38 - 5
toolchain/lower/testdata/builtins/no_prelude/no_op.carbon

@@ -10,6 +10,8 @@
 
 fn NoOp() = "no_op";
 
+fn NoOp2(x: ()) -> () = "no_op";
+
 fn F() {
   NoOp();
 }
@@ -19,6 +21,16 @@ fn G() -> () {
   return a;
 }
 
+fn H() -> () {
+  return NoOp2(());
+}
+
+fn I();
+
+fn J() {
+  NoOp2(I());
+}
+
 // CHECK:STDOUT: ; ModuleID = 'no_op.carbon'
 // CHECK:STDOUT: source_filename = "no_op.carbon"
 // CHECK:STDOUT:
@@ -34,6 +46,21 @@ fn G() -> () {
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CH.Main() !dbg !11 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc25_18.1.temp = alloca {}, align 8, !dbg !12
+// CHECK:STDOUT:   ret void, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CI.Main()
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CJ.Main() !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   call void @_CI.Main(), !dbg !15
+// CHECK:STDOUT:   %.loc31_11.1.temp = alloca {}, align 8, !dbg !15
+// CHECK:STDOUT:   ret void, !dbg !16
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
 // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
 // CHECK:STDOUT:
@@ -46,10 +73,16 @@ fn G() -> () {
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !3 = !DIFile(filename: "no_op.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 15, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
-// CHECK:STDOUT: !7 = !DILocation(line: 13, column: 1, scope: !4)
-// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
-// CHECK:STDOUT: !9 = !DILocation(line: 18, column: 3, scope: !8)
-// CHECK:STDOUT: !10 = !DILocation(line: 19, column: 3, scope: !8)
+// CHECK:STDOUT: !7 = !DILocation(line: 15, column: 1, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 19, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 20, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 21, column: 3, scope: !8)
+// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !12 = !DILocation(line: 25, column: 10, scope: !11)
+// CHECK:STDOUT: !13 = !DILocation(line: 25, column: 3, scope: !11)
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "J", linkageName: "_CJ.Main", scope: null, file: !3, line: 30, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !15 = !DILocation(line: 31, column: 9, scope: !14)
+// CHECK:STDOUT: !16 = !DILocation(line: 30, column: 1, scope: !14)

+ 10 - 1
toolchain/sem_ir/builtin_function_kind.cpp

@@ -192,6 +192,15 @@ static auto ValidateSignature(const File& sem_ir,
   return true;
 }
 
+// Validates the signature for NoOp. This ignores all arguments, only validating
+// that the return type is compatible.
+static auto ValidateNoOpSignature(const File& sem_ir,
+                                  llvm::ArrayRef<TypeId> /*arg_types*/,
+                                  TypeId return_type) -> bool {
+  ValidateState state;
+  return Check<NoReturn>(sem_ir, state, return_type);
+}
+
 // Descriptions of builtin functions follow. For each builtin, a corresponding
 // `BuiltinInfo` constant is declared describing properties of that builtin.
 namespace BuiltinFunctionInfo {
@@ -215,7 +224,7 @@ using FloatT = TypeParam<0, AnyFloat>;
 // Not a builtin function.
 constexpr BuiltinInfo None = {"", nullptr};
 
-constexpr BuiltinInfo NoOp = {"no_op", ValidateSignature<auto()->NoReturn>};
+constexpr BuiltinInfo NoOp = {"no_op", ValidateNoOpSignature};
 
 // Prints a single character.
 constexpr BuiltinInfo PrintChar = {