Przeglądaj źródła

Add support for more complex object-like macros (#6338)

Uses `clang::Parser::ParseConstantExpression()` to parse the macro
replacement tokens, added as a token stream to the preprocessor. This
extends the support from simple object-like macros with a single
replacement token, to multiple tokens like unary operators, binary
operators, casting, nested macros etc.
The support is still limited to macros that are evaluated to an integer
constant. More types to be added as a follow-up.

Part of #6303
Ivana Ivanovska 5 miesięcy temu
rodzic
commit
b68b6ae1e7

+ 1 - 0
toolchain/check/BUILD

@@ -165,6 +165,7 @@ cc_library(
         "@llvm-project//clang:basic",
         "@llvm-project//clang:frontend",
         "@llvm-project//clang:lex",
+        "@llvm-project//clang:parse",
         "@llvm-project//clang:sema",
         "@llvm-project//clang:tooling",
         "@llvm-project//llvm:Support",

+ 40 - 14
toolchain/check/cpp/macros.cpp

@@ -5,6 +5,8 @@
 #include "toolchain/check/cpp/macros.h"
 
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/Parse/Parser.h"
 #include "clang/Sema/Sema.h"
 
 namespace Carbon::Check {
@@ -14,24 +16,41 @@ auto TryEvaluateMacroToConstant(Context& context, SemIR::LocId loc_id,
                                 clang::MacroInfo* macro_info) -> clang::Expr* {
   auto name_str_opt = context.names().GetAsStringIfIdentifier(name_id);
   CARBON_CHECK(macro_info, "macro info missing");
-  if (macro_info->getNumTokens() != 1) {
-    context.TODO(loc_id,
-                 llvm::formatv("Unsupported: macro with {0} replacement tokens",
-                               macro_info->getNumTokens()));
-    return nullptr;
-  }
-  const clang::Token& tok = macro_info->getReplacementToken(0);
-  if (!tok.is(clang::tok::numeric_constant)) {
-    context.TODO(loc_id,
-                 "Unsupported: macro replacement token kind: " +
-                     std::string(clang::tok::getTokenName(tok.getKind())));
+
+  if (macro_info->getNumTokens() == 0) {
+    context.TODO(loc_id, "Unsupported: macro with 0 replacement tokens");
     return nullptr;
   }
+
   clang::Sema& sema = context.clang_sema();
-  clang::ExprResult result = sema.ActOnNumericConstant(tok);
+  clang::Preprocessor& preprocessor = sema.getPreprocessor();
+  clang::Parser parser(preprocessor, sema, false);
+
+  llvm::SmallVector<clang::Token> tokens(macro_info->tokens().begin(),
+                                         macro_info->tokens().end());
+
+  clang::Token current_token = parser.getCurToken();
+
+  // Add eof token
+  clang::Token eof;
+  eof.startToken();
+  eof.setKind(clang::tok::eof);
+  eof.setLocation(current_token.getEndLoc());
+  tokens.push_back(eof);
+
+  tokens.push_back(current_token);
+
+  preprocessor.EnterTokenStream(tokens, false, false);
+  parser.ConsumeAnyToken(true);
+
+  clang::ExprResult result = parser.ParseConstantExpression();
   clang::Expr* result_expr = result.get();
 
-  if (!result_expr || result.isInvalid()) {
+  bool success =
+      !result.isInvalid() && parser.getCurToken().is(clang::tok::eof);
+
+  if (!success) {
+    parser.SkipUntil(clang::tok::eof);
     CARBON_DIAGNOSTIC(
         InCppMacroEvaluation, Error,
         "failed to evaluate macro Cpp.{0} to a valid constant expression",
@@ -40,7 +59,14 @@ auto TryEvaluateMacroToConstant(Context& context, SemIR::LocId loc_id,
     return nullptr;
   }
 
-  return result_expr;
+  clang::Expr::EvalResult evaluated_result;
+  if (!result_expr->EvaluateAsInt(evaluated_result, sema.getASTContext())) {
+    context.TODO(loc_id, "non-integer constant expression in macro.");
+    return nullptr;
+  }
+  return clang::IntegerLiteral::Create(
+      sema.getASTContext(), evaluated_result.Val.getInt(),
+      result_expr->getType(), result_expr->getExprLoc());
 }
 
 }  // namespace Carbon::Check

+ 2 - 3
toolchain/check/cpp/macros.h

@@ -11,9 +11,8 @@ namespace Carbon::Check {
 
 // Tries to evaluate the given macro to a constant expression. Returns the
 // evaluated expression on success or nullptr otherwise. Currently supports only
-// simple object-like macros with a single replacement token that is a numeric
-// literal token.
-// TODO: Add support for multiple tokens and other literal types.
+// object-like macros that evaluate to an integer constant.
+// TODO: Add support for other literal types.
 auto TryEvaluateMacroToConstant(Context& context, SemIR::LocId loc_id,
                                 SemIR::NameId name_id,
                                 clang::MacroInfo* macro_info) -> clang::Expr*;

+ 322 - 34
toolchain/check/testdata/interop/cpp/macros.carbon

@@ -186,29 +186,71 @@ fn F() {
   Cpp.MACRO_NAME;
 }
 
-// --- multiple_replacement_tokens_object_like_macro.h
-#define CONFIG_VALUE 1+2
+// --- unary_operator.h
+#define NEGATIVE -1
 
-// --- fail_todo_import_multiple_replacement_tokens_object_like_macro.carbon
+// --- import_unary_operator.carbon
 
 library "[[@TEST_NAME]]";
 
-import Cpp library "multiple_replacement_tokens_object_like_macro.h";
+import Cpp library "unary_operator.h";
 
 fn F() {
-  // TODO: Get rid of the second error.
-  // CHECK:STDERR: fail_todo_import_multiple_replacement_tokens_object_like_macro.carbon:[[@LINE+11]]:16: error: semantics TODO: `Unsupported: macro with 3 replacement tokens` [SemanticsTodo]
-  // CHECK:STDERR:   let a: i32 = Cpp.CONFIG_VALUE;
-  // CHECK:STDERR:                ^~~~~~~~~~~~~~~~
-  // CHECK:STDERR: fail_todo_import_multiple_replacement_tokens_object_like_macro.carbon:[[@LINE+8]]:16: note: in `Cpp` name lookup for `CONFIG_VALUE` [InCppNameLookup]
-  // CHECK:STDERR:   let a: i32 = Cpp.CONFIG_VALUE;
-  // CHECK:STDERR:                ^~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
-  // CHECK:STDERR: fail_todo_import_multiple_replacement_tokens_object_like_macro.carbon:[[@LINE+4]]:16: error: member name `CONFIG_VALUE` not found in `Cpp` [MemberNameNotFoundInInstScope]
-  // CHECK:STDERR:   let a: i32 = Cpp.CONFIG_VALUE;
-  // CHECK:STDERR:                ^~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
-  let a: i32 = Cpp.CONFIG_VALUE;
+  //@dump-sem-ir-begin
+  let a: i32 = Cpp.NEGATIVE;
+  //@dump-sem-ir-end
+}
+
+// --- binary_operator.h
+#define ADDITION 1+2
+
+// --- import_binary_operator.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "binary_operator.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  let a: i32 = Cpp.ADDITION;
+  //@dump-sem-ir-end
+}
+
+// --- casting.h
+#define CAST_UNSIGNED (unsigned int)1
+#define CAST_STATIC static_cast<int>(100.5)
+#define CAST_FUNCTIONAL int(99.9)
+#define CAST_BOOL_TO_INT (int)true
+
+// --- import_casting.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "casting.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  let a: u32 = Cpp.CAST_UNSIGNED;
+  let b: i32 = Cpp.CAST_STATIC;
+  let c: i32 = Cpp.CAST_FUNCTIONAL;
+  let d: i32 = Cpp.CAST_BOOL_TO_INT;
+  //@dump-sem-ir-end
+}
+
+// --- nested_macros.h
+#define ONE 1
+#define INDIRECT_ONE ONE
+
+// --- import_nested_macros.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "nested_macros.h";
+
+fn F() {
+  //@dump-sem-ir-begin
+  let a: i32 = Cpp.INDIRECT_ONE;
+  //@dump-sem-ir-end
 }
 
 // --- string_literal_object_like_macro.h
@@ -222,7 +264,7 @@ import Cpp library "string_literal_object_like_macro.h";
 
 fn F() {
   // TODO: Get rid of the second error.
-  // CHECK:STDERR: fail_todo_import_string_literal_object_like_macro.carbon:[[@LINE+11]]:3: error: semantics TODO: `Unsupported: macro replacement token kind: string_literal` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_import_string_literal_object_like_macro.carbon:[[@LINE+11]]:3: error: semantics TODO: `non-integer constant expression in macro.` [SemanticsTodo]
   // CHECK:STDERR:   Cpp.CONFIG_VALUE;
   // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
   // CHECK:STDERR: fail_todo_import_string_literal_object_like_macro.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `CONFIG_VALUE` [InCppNameLookup]
@@ -247,7 +289,7 @@ import Cpp library "floating_point_literal_macro.h";
 
 fn F() {
   // TODO: Get rid of the second error.
-  // CHECK:STDERR: fail_todo_floating_point_literal_macro.carbon:[[@LINE+11]]:16: error: semantics TODO: `Unsupported: constant type: double` [SemanticsTodo]
+  // CHECK:STDERR: fail_todo_floating_point_literal_macro.carbon:[[@LINE+11]]:16: error: semantics TODO: `non-integer constant expression in macro.` [SemanticsTodo]
   // CHECK:STDERR:   let a: f64 = Cpp.PI;
   // CHECK:STDERR:                ^~~~~~
   // CHECK:STDERR: fail_todo_floating_point_literal_macro.carbon:[[@LINE+8]]:16: note: in `Cpp` name lookup for `PI` [InCppNameLookup]
@@ -273,19 +315,19 @@ library "[[@TEST_NAME]]";
 import Cpp library "macro_undefined.h";
 
 fn F() {
- // CHECK:STDERR: fail_macro_undefined.carbon:[[@LINE+4]]:15: error: member name `CONFIG_VALUE` not found in `Cpp` [MemberNameNotFoundInInstScope]
- // CHECK:STDERR:  let a: i32 = Cpp.CONFIG_VALUE;
- // CHECK:STDERR:               ^~~~~~~~~~~~~~~~
- // CHECK:STDERR:
- let a: i32 = Cpp.CONFIG_VALUE;
+  // CHECK:STDERR: fail_macro_undefined.carbon:[[@LINE+4]]:16: error: member name `CONFIG_VALUE` not found in `Cpp` [MemberNameNotFoundInInstScope]
+  // CHECK:STDERR:   let a: i32 = Cpp.CONFIG_VALUE;
+  // CHECK:STDERR:                ^~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  let a: i32 = Cpp.CONFIG_VALUE;
 }
 
 // --- macro_redefined.h
 
-#define CONFIG_VALUE 1
-#undef CONFIG_VALUE
+#define REDEF_NAME 1
+#undef REDEF_NAME
 
-#define CONFIG_VALUE 2
+#define REDEF_NAME 2
 
 // --- import_macro_redefined.carbon
 
@@ -294,31 +336,35 @@ library "[[@TEST_NAME]]";
 import Cpp library "macro_redefined.h";
 
 fn F() {
- let a: i32 = Cpp.CONFIG_VALUE;
+  //@dump-sem-ir-begin
+  let a: i32 = Cpp.REDEF_NAME;
+  //@dump-sem-ir-end
 }
 
 // --- macro_defined_twice.h
 
-#define CONFIG_VALUE 1
-#define CONFIG_VALUE 2
+#define TWICE_DEF 1
+#define TWICE_DEF 2
 
 // --- import_macro_defined_twice.carbon
 
 library "[[@TEST_NAME]]";
 
 // CHECK:STDERR: import_macro_defined_twice.carbon:[[@LINE+9]]:10: in file included here [InCppInclude]
-// CHECK:STDERR: ./macro_defined_twice.h:3:9: warning: 'CONFIG_VALUE' macro redefined [CppInteropParseWarning]
-// CHECK:STDERR:     3 | #define CONFIG_VALUE 2
+// CHECK:STDERR: ./macro_defined_twice.h:3:9: warning: 'TWICE_DEF' macro redefined [CppInteropParseWarning]
+// CHECK:STDERR:     3 | #define TWICE_DEF 2
 // CHECK:STDERR:       |         ^
 // CHECK:STDERR: import_macro_defined_twice.carbon:[[@LINE+5]]:10: in file included here [InCppInclude]
 // CHECK:STDERR: ./macro_defined_twice.h:2:9: note: previous definition is here [CppInteropParseNote]
-// CHECK:STDERR:     2 | #define CONFIG_VALUE 1
+// CHECK:STDERR:     2 | #define TWICE_DEF 1
 // CHECK:STDERR:       |         ^
 // CHECK:STDERR:
 import Cpp library "macro_defined_twice.h";
 
 fn F() {
- let a: i32 = Cpp.CONFIG_VALUE;
+  //@dump-sem-ir-begin
+  let a: i32 = Cpp.TWICE_DEF;
+  //@dump-sem-ir-end
 }
 
 // ============================================================================
@@ -500,3 +546,245 @@ fn F() {
 // CHECK:STDOUT:   <elided>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- import_unary_operator.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_-1: %i32 = int_value -1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .NEGATIVE = %int_-1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_-1: %i32 = int_value -1 [concrete = constants.%int_-1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %NEGATIVE.ref: %i32 = name_ref NEGATIVE, imports.%int_-1 [concrete = constants.%int_-1]
+// CHECK:STDOUT:   %.loc8: type = splice_block %i32.loc8 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc8: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc8: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i32 = value_binding a, %NEGATIVE.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_binary_operator.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_3: %i32 = int_value 3 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .ADDITION = %int_3
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_3: %i32 = int_value 3 [concrete = constants.%int_3]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %ADDITION.ref: %i32 = name_ref ADDITION, imports.%int_3 [concrete = constants.%int_3]
+// CHECK:STDOUT:   %.loc8: type = splice_block %i32.loc8 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc8: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc8: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i32 = value_binding a, %ADDITION.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_casting.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %u32: type = class_type @UInt, @UInt(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.4a9: type = pattern_type %u32 [concrete]
+// CHECK:STDOUT:   %int_1.c1d: %u32 = int_value 1 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_100: %i32 = int_value 100 [concrete]
+// CHECK:STDOUT:   %int_99: %i32 = int_value 99 [concrete]
+// CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .CAST_UNSIGNED = %int_1.c1d
+// CHECK:STDOUT:     .CAST_STATIC = %int_100
+// CHECK:STDOUT:     .CAST_FUNCTIONAL = %int_99
+// CHECK:STDOUT:     .CAST_BOOL_TO_INT = %int_1.5d2
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_1.c1d: %u32 = int_value 1 [concrete = constants.%int_1.c1d]
+// CHECK:STDOUT:   %int_100: %i32 = int_value 100 [concrete = constants.%int_100]
+// CHECK:STDOUT:   %int_99: %i32 = int_value 99 [concrete = constants.%int_99]
+// CHECK:STDOUT:   %int_1.5d2: %i32 = int_value 1 [concrete = constants.%int_1.5d2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.4a9 = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc8: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %CAST_UNSIGNED.ref: %u32 = name_ref CAST_UNSIGNED, imports.%int_1.c1d [concrete = constants.%int_1.c1d]
+// CHECK:STDOUT:   %.loc8: type = splice_block %u32.loc8 [concrete = constants.%u32] {
+// CHECK:STDOUT:     %int_32.loc8: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %u32.loc8: type = class_type @UInt, @UInt(constants.%int_32) [concrete = constants.%u32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %u32 = value_binding a, %CAST_UNSIGNED.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %pattern_type.7ce = value_binding_pattern b [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc9: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %CAST_STATIC.ref: %i32 = name_ref CAST_STATIC, imports.%int_100 [concrete = constants.%int_100]
+// CHECK:STDOUT:   %.loc9: type = splice_block %i32.loc9 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc9: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc9: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b: %i32 = value_binding b, %CAST_STATIC.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %c.patt: %pattern_type.7ce = value_binding_pattern c [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc10: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %CAST_FUNCTIONAL.ref: %i32 = name_ref CAST_FUNCTIONAL, imports.%int_99 [concrete = constants.%int_99]
+// CHECK:STDOUT:   %.loc10: type = splice_block %i32.loc10 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc10: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc10: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %c: %i32 = value_binding c, %CAST_FUNCTIONAL.ref
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %d.patt: %pattern_type.7ce = value_binding_pattern d [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref.loc11: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %CAST_BOOL_TO_INT.ref: %i32 = name_ref CAST_BOOL_TO_INT, imports.%int_1.5d2 [concrete = constants.%int_1.5d2]
+// CHECK:STDOUT:   %.loc11: type = splice_block %i32.loc11 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc11: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc11: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %d: %i32 = value_binding d, %CAST_BOOL_TO_INT.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_nested_macros.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_1: %i32 = int_value 1 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .INDIRECT_ONE = %int_1
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_1: %i32 = int_value 1 [concrete = constants.%int_1]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %INDIRECT_ONE.ref: %i32 = name_ref INDIRECT_ONE, imports.%int_1 [concrete = constants.%int_1]
+// CHECK:STDOUT:   %.loc8: type = splice_block %i32.loc8 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc8: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc8: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i32 = value_binding a, %INDIRECT_ONE.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_macro_redefined.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_2: %i32 = int_value 2 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .REDEF_NAME = %int_2
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_2: %i32 = int_value 2 [concrete = constants.%int_2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %REDEF_NAME.ref: %i32 = name_ref REDEF_NAME, imports.%int_2 [concrete = constants.%int_2]
+// CHECK:STDOUT:   %.loc8: type = splice_block %i32.loc8 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc8: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc8: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i32 = value_binding a, %REDEF_NAME.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_macro_defined_twice.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %int_32: Core.IntLiteral = int_value 32 [concrete]
+// CHECK:STDOUT:   %i32: type = class_type @Int, @Int(%int_32) [concrete]
+// CHECK:STDOUT:   %pattern_type.7ce: type = pattern_type %i32 [concrete]
+// CHECK:STDOUT:   %int_2: %i32 = int_value 2 [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .TWICE_DEF = %int_2
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %int_2: %i32 = int_value 2 [concrete = constants.%int_2]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %pattern_type.7ce = value_binding_pattern a [concrete]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT:   %TWICE_DEF.ref: %i32 = name_ref TWICE_DEF, imports.%int_2 [concrete = constants.%int_2]
+// CHECK:STDOUT:   %.loc17: type = splice_block %i32.loc17 [concrete = constants.%i32] {
+// CHECK:STDOUT:     %int_32.loc17: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:     %i32.loc17: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a: %i32 = value_binding a, %TWICE_DEF.ref
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT: