Quellcode durchsuchen

Expose code completion API (#2222)

Initial code completion API implementation
Tim Jones vor 6 Jahren
Ursprung
Commit
9d31464b57

+ 64 - 0
include/dxc/dxcisense.h

@@ -564,6 +564,39 @@ enum DxcCursorKindFlags
   DxcCursorKind_Unexposed = 0x100,
 };
 
+enum DxcCodeCompleteFlags
+{
+  DxcCodeCompleteFlags_None = 0,
+  DxcCodeCompleteFlags_IncludeMacros = 0x1,
+  DxcCodeCompleteFlags_IncludeCodePatterns = 0x2,
+  DxcCodeCompleteFlags_IncludeBriefComments = 0x4,
+};
+
+enum DxcCompletionChunkKind
+{
+  DxcCompletionChunk_Optional = 0,
+  DxcCompletionChunk_TypedText = 1,
+  DxcCompletionChunk_Text = 2,
+  DxcCompletionChunk_Placeholder = 3,
+  DxcCompletionChunk_Informative = 4,
+  DxcCompletionChunk_CurrentParameter = 5,
+  DxcCompletionChunk_LeftParen = 6,
+  DxcCompletionChunk_RightParen = 7,
+  DxcCompletionChunk_LeftBracket = 8,
+  DxcCompletionChunk_RightBracket = 9,
+  DxcCompletionChunk_LeftBrace = 10,
+  DxcCompletionChunk_RightBrace = 11,
+  DxcCompletionChunk_LeftAngle = 12,
+  DxcCompletionChunk_RightAngle = 13,
+  DxcCompletionChunk_Comma = 14,
+  DxcCompletionChunk_ResultType = 15,
+  DxcCompletionChunk_Colon = 16,
+  DxcCompletionChunk_SemiColon = 17,
+  DxcCompletionChunk_Equal = 18,
+  DxcCompletionChunk_HorizontalSpace = 19,
+  DxcCompletionChunk_VerticalSpace = 20,
+};
+
 struct IDxcCursor;
 struct IDxcDiagnostic;
 struct IDxcFile;
@@ -576,6 +609,9 @@ struct IDxcToken;
 struct IDxcTranslationUnit;
 struct IDxcType;
 struct IDxcUnsavedFile;
+struct IDxcCodeCompleteResults;
+struct IDxcCompletionResult;
+struct IDxcCompletionString;
 
 struct __declspec(uuid("1467b985-288d-4d2a-80c1-ef89c42c40bc"))
 IDxcCursor : public IUnknown
@@ -744,6 +780,11 @@ IDxcTranslationUnit : public IUnknown
     _Out_ unsigned* errorLength,
     _Out_ BSTR* errorMessage) = 0;
   virtual HRESULT STDMETHODCALLTYPE GetInclusionList(_Out_ unsigned* pResultCount, _Outptr_result_buffer_(*pResultCount) IDxcInclusion*** pResult) = 0;
+  virtual HRESULT STDMETHODCALLTYPE CodeCompleteAt(
+      _In_ char *fileName, unsigned line, unsigned column,
+      _In_ IDxcUnsavedFile** pUnsavedFiles, unsigned numUnsavedFiles,
+      _In_ DxcCodeCompleteFlags options,
+      _Outptr_result_nullonfailure_ IDxcCodeCompleteResults **pResult) = 0;
 };
 
 struct __declspec(uuid("2ec912fd-b144-4a15-ad0d-1c5439c81e46"))
@@ -762,6 +803,29 @@ IDxcUnsavedFile : public IUnknown
   virtual HRESULT STDMETHODCALLTYPE GetLength(_Out_ unsigned* pLength) = 0;
 };
 
+
+struct __declspec(uuid("1E06466A-FD8B-45F3-A78F-8A3F76EBB552"))
+IDxcCodeCompleteResults : public IUnknown
+{
+  virtual HRESULT STDMETHODCALLTYPE GetNumResults(_Out_ unsigned* pResult) = 0;
+  virtual HRESULT STDMETHODCALLTYPE GetResultAt(unsigned index, _Outptr_result_nullonfailure_ IDxcCompletionResult** pResult) = 0;
+};
+
+struct __declspec(uuid("943C0588-22D0-4784-86FC-701F802AC2B6"))
+IDxcCompletionResult : public IUnknown
+{
+  virtual HRESULT STDMETHODCALLTYPE GetCursorKind(_Out_ DxcCursorKind* pResult) = 0;
+  virtual HRESULT STDMETHODCALLTYPE GetCompletionString(_Outptr_result_nullonfailure_ IDxcCompletionString** pResult) = 0;
+};
+
+struct __declspec(uuid("06B51E0F-A605-4C69-A110-CD6E14B58EEC"))
+IDxcCompletionString : public IUnknown
+{
+  virtual HRESULT STDMETHODCALLTYPE GetNumCompletionChunks(_Out_ unsigned* pResult) = 0;
+  virtual HRESULT STDMETHODCALLTYPE GetCompletionChunkKind(unsigned chunkNumber, _Out_ DxcCompletionChunkKind* pResult) = 0;
+  virtual HRESULT STDMETHODCALLTYPE GetCompletionChunkText(unsigned chunkNumber, _Out_ LPSTR* pResult) = 0;
+};
+
 // Fun fact: 'extern' is required because const is by default static in C++, so
 // CLSID_DxcIntelliSense is not visible externally (this is OK in C, since const is
 // not by default static in C)

+ 4 - 19
tools/clang/lib/Frontend/ASTUnit.cpp

@@ -48,6 +48,7 @@
 #include <cstdio>
 #include <cstdlib>
 #include "clang/Frontend/VerifyDiagnosticConsumer.h"  // HLSL Change
+#include "llvm/Support/ManagedStatic.h" // HLSL Change
 using namespace clang;
 
 using llvm::TimeRecord;
@@ -104,28 +105,12 @@ static llvm::sys::SmartMutex<false> &getOnDiskMutex() {
   return M;
 }
 
-static void __cdecl cleanupOnDiskMapAtExit(); // HLSL Change - __cdecl
-
+// HLSL Change: use ManagedStatic
 typedef llvm::DenseMap<const ASTUnit *,
                        std::unique_ptr<OnDiskData>> OnDiskDataMap;
 static OnDiskDataMap &getOnDiskDataMap() {
-  static OnDiskDataMap M;
-  static bool hasRegisteredAtExit = false;
-  if (!hasRegisteredAtExit) {
-    hasRegisteredAtExit = true;
-    atexit(cleanupOnDiskMapAtExit);
-  }
-  return M;
-}
-
-static void __cdecl cleanupOnDiskMapAtExit() {  // HLSL Change - __cdecl
-  // Use the mutex because there can be an alive thread destroying an ASTUnit.
-  llvm::MutexGuard Guard(getOnDiskMutex());
-  for (const auto &I : getOnDiskDataMap()) {
-    // We don't worry about freeing the memory associated with OnDiskDataMap.
-    // All we care about is erasing stale files.
-    I.second->Cleanup();
-  }
+  static llvm::ManagedStatic<OnDiskDataMap> M;
+  return *M;
 }
 
 static OnDiskData &getOnDiskData(const ASTUnit *AU) {

+ 2 - 1
tools/clang/tools/libclang/CIndexCodeCompletion.cpp

@@ -825,7 +825,8 @@ CXCodeCompleteResults *clang_codeCompleteAt(CXTranslationUnit TU,
     complete_column, llvm::makeArrayRef(unsaved_files, num_unsaved_files),
     options, nullptr};
 
-  if (getenv("LIBCLANG_NOTHREADS")) {
+  // HLSL Change - Force code completion to run on current thread.
+  if (true || getenv("LIBCLANG_NOTHREADS")) {
     clang_codeCompleteAt_Impl(&CCAI);
     return CCAI.result;
   }

+ 193 - 0
tools/clang/tools/libclang/dxcisenseimpl.cpp

@@ -1823,6 +1823,41 @@ HRESULT DxcTranslationUnit::GetInclusionList(unsigned *pResultCount,
   return S_OK;
 }
 
+_Use_decl_annotations_
+HRESULT DxcTranslationUnit::CodeCompleteAt(
+	char *fileName, unsigned line, unsigned column,
+	IDxcUnsavedFile **pUnsavedFiles, unsigned numUnsavedFiles,
+	DxcCodeCompleteFlags options, IDxcCodeCompleteResults **pResult)
+{
+  if (pResult == nullptr) return E_POINTER;
+
+  DxcThreadMalloc TM(m_pMalloc);
+
+  CXUnsavedFile *files;
+  HRESULT hr = SetupUnsavedFiles(pUnsavedFiles, numUnsavedFiles, &files);
+  if (FAILED(hr))
+    return hr;
+
+  CXCodeCompleteResults *results = clang_codeCompleteAt(
+      m_tu, fileName, line, column, files, numUnsavedFiles, options);
+
+  CleanupUnsavedFiles(files, numUnsavedFiles);
+
+  if (results == nullptr) return E_FAIL;
+  *pResult = nullptr;
+  DxcCodeCompleteResults *newValue =
+      new (std::nothrow) DxcCodeCompleteResults();
+  if (newValue == nullptr)
+  {
+	  clang_disposeCodeCompleteResults(results);
+	  return E_OUTOFMEMORY;
+  }
+  newValue->Initialize(results);
+  newValue->AddRef();
+  *pResult = newValue;
+  return S_OK;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 DxcType::DxcType()
@@ -1883,6 +1918,140 @@ HRESULT DxcType::GetKind(DxcTypeKind* pResult)
 
 ///////////////////////////////////////////////////////////////////////////////
 
+DxcCodeCompleteResults::DxcCodeCompleteResults()
+{
+  m_pMalloc = DxcGetThreadMallocNoRef();
+}
+
+DxcCodeCompleteResults::~DxcCodeCompleteResults()
+{
+	clang_disposeCodeCompleteResults(m_ccr);
+}
+
+void DxcCodeCompleteResults::Initialize(CXCodeCompleteResults* ccr)
+{
+  m_ccr = ccr;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCodeCompleteResults::GetNumResults(unsigned *pResult)
+{
+  if (pResult == nullptr)
+    return E_POINTER;
+
+  DxcThreadMalloc TM(m_pMalloc);
+
+  *pResult = m_ccr->NumResults;
+  return S_OK;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCodeCompleteResults::GetResultAt(
+  unsigned index,
+  IDxcCompletionResult **pResult)
+{
+  if (pResult == nullptr)
+    return E_POINTER;
+
+  DxcThreadMalloc TM(m_pMalloc);
+
+  CXCompletionResult result = m_ccr->Results[index];
+
+  *pResult = nullptr;
+  DxcCompletionResult *newValue = new (std::nothrow) DxcCompletionResult();
+  if (newValue == nullptr)
+    return E_OUTOFMEMORY;
+  newValue->Initialize(result);
+  newValue->AddRef();
+  *pResult = newValue;
+
+  return S_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DxcCompletionResult::DxcCompletionResult()
+{
+  m_pMalloc = DxcGetThreadMallocNoRef();
+}
+
+DxcCompletionResult::~DxcCompletionResult()
+{
+}
+
+void DxcCompletionResult::Initialize(const CXCompletionResult &cr)
+{
+  m_cr = cr;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCompletionResult::GetCursorKind(DxcCursorKind *pResult)
+{
+  if (pResult == nullptr) return E_POINTER;
+  *pResult = (DxcCursorKind)m_cr.CursorKind;
+  return S_OK;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCompletionResult::GetCompletionString(IDxcCompletionString **pResult)
+{
+  if (pResult == nullptr) return E_POINTER;
+
+  DxcThreadMalloc TM(m_pMalloc);
+
+  *pResult = nullptr;
+  DxcCompletionString *newValue = new (std::nothrow) DxcCompletionString();
+  if (newValue == nullptr)
+    return E_OUTOFMEMORY;
+  newValue->Initialize(m_cr.CompletionString);
+  newValue->AddRef();
+  *pResult = newValue;
+
+  return S_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+DxcCompletionString::DxcCompletionString()
+{
+  m_pMalloc = DxcGetThreadMallocNoRef();
+}
+
+DxcCompletionString::~DxcCompletionString()
+{
+}
+
+void DxcCompletionString::Initialize(const CXCompletionString &cs)
+{
+  m_cs = cs;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCompletionString::GetNumCompletionChunks(unsigned *pResult)
+{
+  if (pResult == nullptr) return E_POINTER;
+  *pResult = clang_getNumCompletionChunks(m_cs);
+  return S_OK;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCompletionString::GetCompletionChunkKind(unsigned chunkNumber, DxcCompletionChunkKind *pResult)
+{
+  if (pResult == nullptr) return E_POINTER;
+  *pResult = (DxcCompletionChunkKind)clang_getCompletionChunkKind(m_cs, chunkNumber);
+  return S_OK;
+}
+
+_Use_decl_annotations_
+HRESULT DxcCompletionString::GetCompletionChunkText(unsigned chunkNumber, LPSTR* pResult)
+{
+	if (pResult == nullptr) return E_POINTER;
+	DxcThreadMalloc TM(m_pMalloc);
+	return CXStringToAnsiAndDispose(clang_getCompletionChunkText(m_cs, chunkNumber), pResult);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 C_ASSERT((int)DxcCursor_UnexposedDecl == (int)CXCursor_UnexposedDecl);
 C_ASSERT((int)DxcCursor_StructDecl == (int)CXCursor_StructDecl);
 C_ASSERT((int)DxcCursor_UnionDecl == (int)CXCursor_UnionDecl);
@@ -2052,3 +2221,27 @@ C_ASSERT((int)DxcCursor_FirstExtraDecl == (int)CXCursor_FirstExtraDecl);
 C_ASSERT((int)DxcCursor_LastExtraDecl == (int)CXCursor_LastExtraDecl);
 
 C_ASSERT((int)DxcTranslationUnitFlags_UseCallerThread == (int)CXTranslationUnit_UseCallerThread);
+
+C_ASSERT((int)DxcCodeCompleteFlags_IncludeMacros == (int)CXCodeComplete_IncludeMacros);
+C_ASSERT((int)DxcCodeCompleteFlags_IncludeCodePatterns == (int)CXCodeComplete_IncludeCodePatterns);
+C_ASSERT((int)DxcCodeCompleteFlags_IncludeBriefComments == (int)CXCodeComplete_IncludeBriefComments);
+
+C_ASSERT((int)DxcCompletionChunk_Optional == (int)CXCompletionChunk_Optional);
+C_ASSERT((int)DxcCompletionChunk_TypedText == (int)CXCompletionChunk_TypedText);
+C_ASSERT((int)DxcCompletionChunk_Text == (int)CXCompletionChunk_Text);
+C_ASSERT((int)DxcCompletionChunk_Placeholder == (int)CXCompletionChunk_Placeholder);
+C_ASSERT((int)DxcCompletionChunk_Informative == (int)CXCompletionChunk_Informative);
+C_ASSERT((int)DxcCompletionChunk_CurrentParameter == (int)CXCompletionChunk_CurrentParameter);
+C_ASSERT((int)DxcCompletionChunk_LeftParen == (int)CXCompletionChunk_LeftParen);
+C_ASSERT((int)DxcCompletionChunk_RightParen == (int)CXCompletionChunk_RightParen);
+C_ASSERT((int)DxcCompletionChunk_LeftBracket == (int)CXCompletionChunk_LeftBracket);
+C_ASSERT((int)DxcCompletionChunk_RightBracket == (int)CXCompletionChunk_RightBracket);
+C_ASSERT((int)DxcCompletionChunk_LeftBrace == (int)CXCompletionChunk_LeftBrace);
+C_ASSERT((int)DxcCompletionChunk_RightBrace == (int)CXCompletionChunk_RightBrace);
+C_ASSERT((int)DxcCompletionChunk_Comma == (int)CXCompletionChunk_Comma);
+C_ASSERT((int)DxcCompletionChunk_ResultType == (int)CXCompletionChunk_ResultType);
+C_ASSERT((int)DxcCompletionChunk_Colon == (int)CXCompletionChunk_Colon);
+C_ASSERT((int)DxcCompletionChunk_SemiColon == (int)CXCompletionChunk_SemiColon);
+C_ASSERT((int)DxcCompletionChunk_Equal == (int)CXCompletionChunk_Equal);
+C_ASSERT((int)DxcCompletionChunk_HorizontalSpace == (int)CXCompletionChunk_HorizontalSpace);
+C_ASSERT((int)DxcCompletionChunk_VerticalSpace == (int)CXCompletionChunk_VerticalSpace);

+ 67 - 0
tools/clang/tools/libclang/dxcisenseimpl.h

@@ -338,6 +338,12 @@ public:
       _Out_ unsigned* errorLength,
       _Out_ BSTR* errorMessage) override;
     HRESULT STDMETHODCALLTYPE GetInclusionList(_Out_ unsigned* pResultCount, _Outptr_result_buffer_(*pResultCount) IDxcInclusion*** pResult) override;
+    HRESULT STDMETHODCALLTYPE CodeCompleteAt(
+      _In_ char *fileName, unsigned line, unsigned column,
+      _In_ IDxcUnsavedFile** pUnsavedFiles, unsigned numUnsavedFiles,
+      _In_ DxcCodeCompleteFlags options,
+      _Outptr_result_nullonfailure_ IDxcCodeCompleteResults **pResult)
+      override;
 };
 
 class DxcType : public IDxcType
@@ -362,6 +368,67 @@ public:
   HRESULT STDMETHODCALLTYPE GetKind(_Out_ DxcTypeKind* pResult) override;
 };
 
+class DxcCodeCompleteResults : public IDxcCodeCompleteResults
+{
+private:
+  DXC_MICROCOM_TM_REF_FIELDS()
+  CXCodeCompleteResults* m_ccr;
+public:
+  DXC_MICROCOM_TM_ADDREF_RELEASE_IMPL()
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
+                                           void **ppvObject) override {
+    return DoBasicQueryInterface<IDxcCodeCompleteResults>(this, iid, ppvObject);
+  }
+
+  DxcCodeCompleteResults();
+  ~DxcCodeCompleteResults();
+  void Initialize(CXCodeCompleteResults* ccr);
+
+  HRESULT STDMETHODCALLTYPE GetNumResults(_Out_ unsigned *pResult) override;
+  HRESULT STDMETHODCALLTYPE GetResultAt(unsigned index, _Outptr_result_nullonfailure_ IDxcCompletionResult **pResult) override;
+};
+
+class DxcCompletionResult : public IDxcCompletionResult
+{
+private:
+  DXC_MICROCOM_TM_REF_FIELDS()
+  CXCompletionResult m_cr;
+public:
+  DXC_MICROCOM_TM_ADDREF_RELEASE_IMPL()
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
+                                           void **ppvObject) override {
+    return DoBasicQueryInterface<IDxcCompletionResult>(this, iid, ppvObject);
+  }
+
+  DxcCompletionResult();
+  ~DxcCompletionResult();
+  void Initialize(const CXCompletionResult& cr);
+
+  HRESULT STDMETHODCALLTYPE GetCursorKind(_Out_ DxcCursorKind *pResult) override;
+  HRESULT STDMETHODCALLTYPE GetCompletionString(_Outptr_result_nullonfailure_ IDxcCompletionString **pResult) override;
+};
+
+class DxcCompletionString : public IDxcCompletionString
+{
+private:
+  DXC_MICROCOM_TM_REF_FIELDS()
+  CXCompletionString m_cs;
+public:
+  DXC_MICROCOM_TM_ADDREF_RELEASE_IMPL()
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
+                                           void **ppvObject) override {
+    return DoBasicQueryInterface<IDxcCompletionString>(this, iid, ppvObject);
+  }
+
+  DxcCompletionString();
+  ~DxcCompletionString();
+  void Initialize(const CXCompletionString& cs);
+
+  HRESULT STDMETHODCALLTYPE GetNumCompletionChunks(_Out_ unsigned *pResult) override;
+  HRESULT STDMETHODCALLTYPE GetCompletionChunkKind(unsigned chunkNumber, _Out_ DxcCompletionChunkKind *pResult) override;
+  HRESULT STDMETHODCALLTYPE GetCompletionChunkText(unsigned chunkNumber, _Out_ LPSTR* pResult) override;
+};
+
 HRESULT CreateDxcIntelliSense(_In_ REFIID riid, _Out_ LPVOID* ppv) throw();
 
 #endif

+ 35 - 0
tools/clang/unittests/HLSL/DXIsenseTest.cpp

@@ -120,6 +120,8 @@ protected:
   TEST_METHOD(QualifiedNameVariable)
 
   TEST_METHOD(TypeWhenICEThenEval)
+
+  TEST_METHOD(CompletionWhenResultsAvailable)
 };
 
 bool DXIntellisenseTest::DXIntellisenseTestClassSetup() {
@@ -788,3 +790,36 @@ TEST_F(DXIntellisenseTest, TypeWhenICEThenEval)
   VERIFY_SUCCEEDED(typeCursor->GetSpelling(&name));
   VERIFY_ARE_EQUAL_STR("const float [2]", name); // global variables converted to const by default
 }
+
+TEST_F(DXIntellisenseTest, CompletionWhenResultsAvailable)
+{
+  char program[] =
+	"struct MyStruct {};"
+	"MyStr";
+  CompilationResult result(CompilationResult::CreateForProgram(program, _countof(program)));
+  VERIFY_ARE_EQUAL(false, result.ParseSucceeded());
+  char* fileName = "filename.hlsl";
+  CComPtr<IDxcUnsavedFile> unsavedFile;
+  VERIFY_SUCCEEDED(TrivialDxcUnsavedFile::Create(fileName, program, &unsavedFile));
+  CComPtr<IDxcCodeCompleteResults> codeCompleteResults;
+  VERIFY_SUCCEEDED(result.TU->CodeCompleteAt(fileName, 2, 1, &unsavedFile.p, 1, DxcCodeCompleteFlags_None, &codeCompleteResults));
+  unsigned numResults;
+  VERIFY_SUCCEEDED(codeCompleteResults->GetNumResults(&numResults));
+  VERIFY_IS_GREATER_THAN_OR_EQUAL(numResults, 1u);
+  CComPtr<IDxcCompletionResult> completionResult;
+  VERIFY_SUCCEEDED(codeCompleteResults->GetResultAt(0, &completionResult));
+  DxcCursorKind completionResultCursorKind;
+  VERIFY_SUCCEEDED(completionResult->GetCursorKind(&completionResultCursorKind));
+  VERIFY_ARE_EQUAL(DxcCursor_StructDecl, completionResultCursorKind);
+  CComPtr<IDxcCompletionString> completionString;
+  VERIFY_SUCCEEDED(completionResult->GetCompletionString(&completionString));
+  unsigned numCompletionChunks;
+  VERIFY_SUCCEEDED(completionString->GetNumCompletionChunks(&numCompletionChunks));
+  VERIFY_ARE_EQUAL(1, numCompletionChunks);
+  DxcCompletionChunkKind completionChunkKind;
+  VERIFY_SUCCEEDED(completionString->GetCompletionChunkKind(0, &completionChunkKind));
+  VERIFY_ARE_EQUAL(DxcCompletionChunk_TypedText, completionChunkKind);
+  CComHeapPtr<char> completionChunkText;
+  VERIFY_SUCCEEDED(completionString->GetCompletionChunkText(0, &completionChunkText));
+  VERIFY_ARE_EQUAL_STR("MyStruct", completionChunkText);
+}

+ 2 - 0
tools/clang/unittests/HLSL/WEXAdapter.h

@@ -59,6 +59,8 @@
 #define VERIFY_IS_NOT_NULL_2(expr, msg) EXPECT_NE(nullptr, (expr)) << msg
 #define VERIFY_IS_NOT_NULL(...) MACRO_N(VERIFY_IS_NOT_NULL_, __VA_ARGS__)
 
+#define VERIFY_IS_GREATER_THAN_OR_EQUAL(greater, less) EXPECT_GE(greater, less)
+
 #define VERIFY_WIN32_BOOL_SUCCEEDED_1(expr) EXPECT_TRUE(expr)
 #define VERIFY_WIN32_BOOL_SUCCEEDED_2(expr, msg) EXPECT_TRUE(expr) << msg
 #define VERIFY_WIN32_BOOL_SUCCEEDED(...) MACRO_N(VERIFY_WIN32_BOOL_SUCCEEDED_, __VA_ARGS__)