From 6439f9065d1f1323d95a3321c7804fa09980a380 Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Mon, 7 Oct 2024 15:53:22 -0700 Subject: [PATCH] Handle block scopes in compile-time binding check (#4379) This fixes a fuzzer-found crash when a compile-time binding occurs inside a block (which is represented by an invalid inst ID). --- toolchain/check/handle_binding_pattern.cpp | 20 ++- .../testdata/let/compile_time_bindings.carbon | 157 +++++++++++++++++- 2 files changed, 170 insertions(+), 7 deletions(-) diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index e4db10fa53c63..38bb069239338 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -219,12 +219,20 @@ auto HandleParseNode(Context& context, bool is_generic = true; if (context.decl_introducer_state_stack().innermost().kind == Lex::TokenKind::Let) { - auto scope_inst = context.insts().Get(context.scope_stack().PeekInstId()); - if (!scope_inst.Is() && - !scope_inst.Is()) { - context.TODO(node_id, - "`let` compile time binding outside function or interface"); - is_generic = false; + // Disallow `let` outside of function and interface definitions. + // TODO: find a less brittle way of doing this. An invalid scope_inst_id + // can represent a block scope, but is also used for other kinds of scopes + // that aren't necessarily part of an interface or function decl. + auto scope_inst_id = context.scope_stack().PeekInstId(); + if (scope_inst_id.is_valid()) { + auto scope_inst = context.insts().Get(scope_inst_id); + if (!scope_inst.Is() && + !scope_inst.Is()) { + context.TODO( + node_id, + "`let` compile time binding outside function or interface"); + is_generic = false; + } } } diff --git a/toolchain/check/testdata/let/compile_time_bindings.carbon b/toolchain/check/testdata/let/compile_time_bindings.carbon index e4c53e7af855d..5e5ca98a3065e 100644 --- a/toolchain/check/testdata/let/compile_time_bindings.carbon +++ b/toolchain/check/testdata/let/compile_time_bindings.carbon @@ -89,6 +89,18 @@ fn F() -> i32 { return Zero; } +// --- use_in_block.carbon + +library "[[@TEST_NAME]]"; + +fn F() -> i32 { + if (true) { + let Zero:! i32 = 0; + return Zero; + } + return 1; +} + // --- fail_return_in_interface.carbon library "[[@TEST_NAME]]"; @@ -131,11 +143,25 @@ library "[[@TEST_NAME]]"; // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: let T:! type = i32; -// CHECK:STDERR: fail_return_in_package_scope.carbon:[[@LINE+3]]:11: error: cannot evaluate type expression +// CHECK:STDERR: fail_return_in_package_scope.carbon:[[@LINE+4]]:11: error: cannot evaluate type expression // CHECK:STDERR: fn F() -> T; // CHECK:STDERR: ^ +// CHECK:STDERR: fn F() -> T; +// --- fail_use_in_impl.carbon + +library "[[@TEST_NAME]]"; + +interface Empty {} + +impl i32 as Empty { + // CHECK:STDERR: fail_use_in_impl.carbon:[[@LINE+3]]:7: error: semantics TODO: ``let` compile time binding outside function or interface` + // CHECK:STDERR: let Zero:! i32 = 0; + // CHECK:STDERR: ^~~~~~~~~~ + let Zero:! i32 = 0; +} + // CHECK:STDOUT: --- fail_let_after.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -560,6 +586,70 @@ fn F() -> T; // CHECK:STDOUT: return %Zero.ref // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: --- use_in_block.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Int32.type: type = fn_type @Int32 [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %.2: bool = bool_literal true [template] +// CHECK:STDOUT: %.3: i32 = int_literal 0 [template] +// CHECK:STDOUT: %Zero: i32 = bind_symbolic_name Zero, 0 [symbolic] +// CHECK:STDOUT: %.4: i32 = int_literal 1 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: .Int32 = %import_ref +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/operators +// CHECK:STDOUT: import Core//prelude/types +// CHECK:STDOUT: import Core//prelude/operators/arithmetic +// CHECK:STDOUT: import Core//prelude/operators/as +// CHECK:STDOUT: import Core//prelude/operators/bitwise +// CHECK:STDOUT: import Core//prelude/operators/comparison +// CHECK:STDOUT: import Core//prelude/types/bool +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} { +// CHECK:STDOUT: %int.make_type_32.loc4: init type = call constants.%Int32() [template = i32] +// CHECK:STDOUT: %.loc4_11.1: type = value_of_initializer %int.make_type_32.loc4 [template = i32] +// CHECK:STDOUT: %.loc4_11.2: type = converted %int.make_type_32.loc4, %.loc4_11.1 [template = i32] +// CHECK:STDOUT: %return: ref i32 = var +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc5: bool = bool_literal true [template = constants.%.2] +// CHECK:STDOUT: if %.loc5 br !if.then else br !if.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then: +// CHECK:STDOUT: %int.make_type_32.loc6: init type = call constants.%Int32() [template = i32] +// CHECK:STDOUT: %.loc6_16.1: type = value_of_initializer %int.make_type_32.loc6 [template = i32] +// CHECK:STDOUT: %.loc6_16.2: type = converted %int.make_type_32.loc6, %.loc6_16.1 [template = i32] +// CHECK:STDOUT: %.loc6_22: i32 = int_literal 0 [template = constants.%.3] +// CHECK:STDOUT: %Zero: i32 = bind_symbolic_name Zero, 0, %.loc6_22 [symbolic = constants.%Zero] +// CHECK:STDOUT: %Zero.ref: i32 = name_ref Zero, %Zero [symbolic = constants.%Zero] +// CHECK:STDOUT: return %Zero.ref +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else: +// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.4] +// CHECK:STDOUT: return %.loc9 +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: --- fail_return_in_interface.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { @@ -822,3 +912,68 @@ fn F() -> T; // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: --- fail_use_in_impl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Empty.type: type = interface_type @Empty [template] +// CHECK:STDOUT: %Self: %Empty.type = bind_symbolic_name Self, 0 [symbolic] +// CHECK:STDOUT: %Int32.type: type = fn_type @Int32 [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template] +// CHECK:STDOUT: %.2: i32 = int_literal 0 [template] +// CHECK:STDOUT: %.3: = interface_witness () [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: .Int32 = %import_ref +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/operators +// CHECK:STDOUT: import Core//prelude/types +// CHECK:STDOUT: import Core//prelude/operators/arithmetic +// CHECK:STDOUT: import Core//prelude/operators/as +// CHECK:STDOUT: import Core//prelude/operators/bitwise +// CHECK:STDOUT: import Core//prelude/operators/comparison +// CHECK:STDOUT: import Core//prelude/types/bool +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Empty = %Empty.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Empty.decl: type = interface_decl @Empty [template = constants.%Empty.type] {} {} +// CHECK:STDOUT: impl_decl @impl [template] {} { +// CHECK:STDOUT: %int.make_type_32.loc6: init type = call constants.%Int32() [template = i32] +// CHECK:STDOUT: %.loc6_6.1: type = value_of_initializer %int.make_type_32.loc6 [template = i32] +// CHECK:STDOUT: %.loc6_6.2: type = converted %int.make_type_32.loc6, %.loc6_6.1 [template = i32] +// CHECK:STDOUT: %Empty.ref: type = name_ref Empty, file.%Empty.decl [template = constants.%Empty.type] +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: interface @Empty { +// CHECK:STDOUT: %Self: %Empty.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: %.loc6_6.2 as %Empty.ref { +// CHECK:STDOUT: %int.make_type_32.loc10: init type = call constants.%Int32() [template = i32] +// CHECK:STDOUT: %.loc10_14.1: type = value_of_initializer %int.make_type_32.loc10 [template = i32] +// CHECK:STDOUT: %.loc10_14.2: type = converted %int.make_type_32.loc10, %.loc10_14.1 [template = i32] +// CHECK:STDOUT: %.loc10_20: i32 = int_literal 0 [template = constants.%.2] +// CHECK:STDOUT: %Zero: i32 = bind_name Zero, %.loc10_20 +// CHECK:STDOUT: %.loc6_19: = interface_witness () [template = constants.%.3] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Zero = %Zero +// CHECK:STDOUT: witness = %.loc6_19 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32"; +// CHECK:STDOUT: