15 Commits

Author SHA1 Message Date
ykiko
c727c12d62 fix(semantic): prevent cross-template depth collisions in resolver
Fix template argument resolution for complex typedef chains (e.g.
vector<T>::reference through allocator_traits) by:
- Adding ownership checking in find_argument to skip frames from
  unrelated templates at the same depth
- Tracking entry stack size in TransformDependentNameType to prevent
  frame leaks from nested NNS qualifier resolutions
- Extracting template info directly from concrete TSTs in lookup to
  avoid stack corruption
- Adding push_nns_qualifier_frames for multi-level dependent chains

Also removes debug traces from TemplateResolver.Standard test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 13:08:45 +08:00
ykiko
7f85125818 fix: remove unused sema variable in resolver test diagnostics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 23:08:57 +08:00
ykiko
16a4844b22 fix: add typedef chain diagnostics to TemplateResolver test
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 22:45:38 +08:00
ykiko
90bfebb122 fix: add debug logging to TemplateResolver.Standard test
Enable resolver trace output to diagnose allocator recursion bug.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 22:18:20 +08:00
ykiko
9873e26cd1 fix: update selection test ranges and add resolver diagnostics
Update CXXFeatures/UsingEnum annotation ranges for LLVM 22.1 AST changes.
Add temporary diagnostic output to TemplateResolver.Standard test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 21:42:47 +08:00
ykiko
fd100ecd60 ci: remove skip_upload parameter from build-llvm workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 19:17:50 +08:00
ykiko
71723d89f1 ci: checkout latest branch code when validating from artifact
When rerunning failed validate jobs after pushing fixes, the checkout
now fetches the branch HEAD instead of the original trigger commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 19:11:23 +08:00
ykiko
f9a3bd8526 fix: update cmake libs and test expectations for LLVM 22.1
Add clangOptions to Debug library list (split from clangDriver in 22.1).
Update test expectations for ElaboratedType removal and NNS changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 18:55:15 +08:00
ykiko
4115351834 ci: split LLVM build and clice validation into separate jobs
Restructure build-llvm.yml so that LLVM build and clice build/test
run in separate jobs. This allows "rerun failed jobs" to skip the
expensive LLVM rebuild when only clice code needs fixing.

- Rename build → build-llvm (LLVM only, no clice build/test/prune)
- Add validate job that reuses test-cmake.yml with llvm_from_artifact
- Upload raw LLVM install directory as artifact for validation
- Keep .tar.xz packaging for clice-llvm release
- Fix tar to use flat directory structure (no build-install wrapper)
- Filter download-llvm.sh to only fetch .tar.xz artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 18:42:24 +08:00
ykiko
7050fc5d20 fix: update test to use new clang/Options path for LLVM 22.1 2026-04-23 08:32:45 +08:00
ykiko
0133f5db23 fix: pin kotatsu to known-good commit before breaking refactor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 07:35:58 +08:00
ykiko
207d6af089 fix: revert AnonymousTagNameStyle to AnonymousTagLocations for 22.1.4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 06:11:11 +08:00
ykiko
5468a6efb1 fix: use variadic DIAG macro for LLVM version compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-23 05:01:59 +08:00
ykiko
2d193d80cd fix: adapt clice source code for LLVM 22.1 API changes
Major LLVM 22.1 breaking changes addressed:
- NestedNameSpecifier changed from pointer to value type
- ElaboratedType removed (merged into individual type nodes)
- DependentTemplateSpecializationType merged into TemplateSpecializationType
- TreeTransform overrides now require additional parameters
- TagDecl::getTypeForDecl() removed, use ASTContext::getTagType()
- InjectedClassNameType::getInjectedSpecializationType() removed
- TypedefTypeLoc::getTypedefNameDecl() renamed to getDecl()
- AnonymousTagLocations renamed to AnonymousTagNameStyle
- DIAG macro expanded from 11 to 13 parameters
- Header moves: clang/Driver/Options.h → clang/Options/Options.h
- ClangTidyModuleRegistry.h renamed to ClangTidyModule.h
- createDiagnostics/createFileManager API changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 23:37:42 +08:00
ykiko
3e73150569 feat(ci): add LLVM 22.1 distribution components
LLVM 22.1 introduced new CMake targets (clangAnalysisLifetimeSafety,
clangOptions, clangDependencyScanning, LLVMPlugins) that existing
components depend on. Add them to the distribution component list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-22 21:19:54 +08:00
24 changed files with 321 additions and 395 deletions

View File

@@ -7,11 +7,6 @@ on:
description: "LLVM version to build (e.g., 21.1.8)"
required: true
type: string
skip_upload:
description: "Skip upload and PR creation (build-only mode)"
required: false
type: boolean
default: false
skip_pr:
description: "Skip PR creation (upload only, no PR)"
required: false
@@ -23,7 +18,7 @@ on:
branches: [main-turn-off]
jobs:
build:
build-llvm:
strategy:
fail-fast: false
matrix:
@@ -150,117 +145,17 @@ jobs:
--build-dir=build \
${EXTRA_ARGS}
- name: Build clice using installed LLVM
if: ${{ !matrix.target_triple }}
shell: bash
run: |
pixi run cmake-config ${{ matrix.llvm_mode }} ON -- \
"-DCLICE_ENABLE_LTO=${{ matrix.lto }}" \
"-DLLVM_INSTALL_PATH=.llvm/build-install"
pixi run cmake-build ${{ matrix.llvm_mode }}
- name: Build clice using installed LLVM (cross-compile)
if: ${{ matrix.target_triple }}
shell: bash
run: |
ENV="${{ matrix.pixi_env || 'package' }}"
pixi run -e "$ENV" cmake-config ${{ matrix.llvm_mode }} ON -- \
"-DCLICE_ENABLE_LTO=${{ matrix.lto }}" \
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}" \
"-DLLVM_INSTALL_PATH=.llvm/build-install"
pixi run -e "$ENV" cmake-build ${{ matrix.llvm_mode }}
- name: Verify cross-compiled binary architecture
if: ${{ matrix.target_triple && runner.os != 'Windows' }}
shell: bash
run: |
BINARY="build/${{ matrix.llvm_mode }}/bin/clice"
echo "Binary info:"
file "$BINARY"
case "${{ matrix.target_triple }}" in
aarch64-linux-gnu) file "$BINARY" | grep -q "aarch64" ;;
x86_64-apple-darwin) file "$BINARY" | grep -q "x86_64" ;;
esac
- name: Upload cross-compiled clice for functional test
if: ${{ matrix.target_triple && matrix.lto == 'OFF' }}
# Upload raw install directory for validation (non-LTO only, one per platform/config).
# Artifact name matches the key used by test-cmake.yml: llvm-install-{target_triple|os}-{mode}
- name: Upload LLVM install for validation
if: ${{ matrix.lto == 'OFF' }}
uses: actions/upload-artifact@v4
with:
name: cross-clice-${{ matrix.target_triple }}-${{ matrix.llvm_mode }}
path: |
build/${{ matrix.llvm_mode }}/bin/
build/${{ matrix.llvm_mode }}/lib/
if-no-files-found: error
name: llvm-install-${{ matrix.target_triple || matrix.os }}-${{ matrix.llvm_mode }}
path: .llvm/build-install/
retention-days: 1
- name: Run tests
if: ${{ !matrix.target_triple }}
shell: bash
run: pixi run test ${{ matrix.llvm_mode }}
# Prune is only supported for native builds (requires linking clice to test).
# Cross-compiled targets reuse the native prune manifest of the same OS.
- name: Prune LLVM static libraries (Debug/RelWithDebInfo no LTO)
if: (!matrix.target_triple) && (matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'))
shell: bash
run: |
MANIFEST="pruned-libs-${{ matrix.os }}.json"
echo "LLVM_PRUNED_MANIFEST=${MANIFEST}" >> "${GITHUB_ENV}"
python3 scripts/prune-llvm-bin.py \
--action discover \
--install-dir ".llvm/build-install/lib" \
--build-dir "build/${{ matrix.llvm_mode }}" \
--max-attempts 60 \
--sleep-seconds 60 \
--manifest "${MANIFEST}"
- name: Upload pruned-libs manifest
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
uses: actions/upload-artifact@v4
with:
name: llvm-pruned-libs-${{ matrix.os }}
path: ${{ env.LLVM_PRUNED_MANIFEST }}
if-no-files-found: error
compression-level: 0
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO, native only)
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
MANIFEST="pruned-libs-${{ matrix.os }}.json"
python3 scripts/prune-llvm-bin.py \
--action apply \
--manifest "${MANIFEST}" \
--install-dir ".llvm/build-install/lib" \
--build-dir "build/${{ matrix.llvm_mode }}" \
--gh-run-id "${{ github.run_id }}" \
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
--gh-download-dir "artifacts" \
--max-attempts 60 \
--sleep-seconds 60
# For cross-compiled LTO builds, apply the native prune manifest.
# The unused library set is arch-independent (same API surface).
- name: Apply pruned-libs manifest (cross-compile + LTO)
if: matrix.target_triple && matrix.lto == 'ON'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
MANIFEST="pruned-libs-${{ matrix.os }}.json"
python3 scripts/prune-llvm-bin.py \
--action apply \
--manifest "${MANIFEST}" \
--install-dir ".llvm/build-install/lib" \
--build-dir "build/${{ matrix.llvm_mode }}" \
--gh-run-id "${{ github.run_id }}" \
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
--gh-download-dir "artifacts" \
--max-attempts 60 \
--sleep-seconds 60
# Package as .tar.xz for the final clice-llvm release (all entries).
- name: Package LLVM install directory
shell: bash
run: |
@@ -269,7 +164,6 @@ jobs:
MODE_TAG="debug"
fi
# Determine arch/platform/toolchain from target triple or runner OS
if [[ -n "${{ matrix.target_triple }}" ]]; then
case "${{ matrix.target_triple }}" in
x86_64-apple-darwin)
@@ -304,56 +198,25 @@ jobs:
ARCHIVE="${ARCH}-${PLATFORM}-${TOOLCHAIN}-${MODE_TAG}${SUFFIX}.tar.xz"
set -eo pipefail
tar -C .llvm -cf - build-install | xz -T0 -9 -c > "${ARCHIVE}"
tar -C .llvm/build-install -cf - . | xz -T0 -9 -c > "${ARCHIVE}"
echo "LLVM_INSTALL_ARCHIVE=${ARCHIVE}" >> "${GITHUB_ENV}"
- name: Upload LLVM install artifact
- name: Upload LLVM release archive
uses: actions/upload-artifact@v4
with:
name: ${{ env.LLVM_INSTALL_ARCHIVE }}
path: ${{ env.LLVM_INSTALL_ARCHIVE }}
if-no-files-found: error
test-cross:
needs: build
strategy:
fail-fast: false
matrix:
include:
- os: macos-15-intel
llvm_mode: RelWithDebInfo
target_triple: x86_64-apple-darwin
- os: ubuntu-24.04-arm
llvm_mode: RelWithDebInfo
target_triple: aarch64-linux-gnu
- os: windows-11-arm
llvm_mode: RelWithDebInfo
target_triple: aarch64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
with:
environments: test-run
- name: Download cross-compiled clice
uses: actions/download-artifact@v4
with:
name: cross-clice-${{ matrix.target_triple }}-${{ matrix.llvm_mode }}
path: build/${{ matrix.llvm_mode }}/
- name: Make binaries executable
if: runner.os != 'Windows'
run: chmod +x build/${{ matrix.llvm_mode }}/bin/*
- name: Run tests
run: pixi run -e test-run test ${{ matrix.llvm_mode }}
validate:
needs: build-llvm
uses: ./.github/workflows/test-cmake.yml
with:
llvm_from_artifact: true
upload:
needs: build
if: ${{ !cancelled() && inputs.llvm_version && !inputs.skip_upload }}
needs: [build-llvm, validate]
if: ${{ inputs.llvm_version }}
runs-on: ubuntu-24.04
permissions:
contents: read

View File

@@ -2,6 +2,11 @@ name: cmake
on:
workflow_call:
inputs:
llvm_from_artifact:
description: "Download LLVM from workflow artifacts instead of release"
type: boolean
default: false
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/ccache
@@ -48,12 +53,22 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.llvm_from_artifact && github.ref_name || '' }}
- uses: ./.github/actions/setup-pixi
with:
environments: ${{ matrix.pixi_env || 'default' }}
- name: Download LLVM install
if: ${{ inputs.llvm_from_artifact }}
uses: actions/download-artifact@v4
with:
name: llvm-install-${{ matrix.target_triple || matrix.os }}-${{ matrix.build_type }}
path: .llvm-install/
- name: Restore compiler cache
if: ${{ !inputs.llvm_from_artifact }}
uses: actions/cache@v4
with:
path: ${{ runner.os == 'Windows' && '.cache/sccache' || '.cache/ccache' }}
@@ -62,6 +77,7 @@ jobs:
${{ runner.os }}-${{ matrix.build_type }}-${{ matrix.target_triple || 'native' }}-ccache-
- name: Zero cache stats
if: ${{ !inputs.llvm_from_artifact }}
run: |
ENV="${{ matrix.pixi_env || 'default' }}"
if [ "$RUNNER_OS" = "Windows" ]; then
@@ -74,15 +90,28 @@ jobs:
- name: Build (native)
if: ${{ !matrix.target_triple }}
run: pixi run build ${{ matrix.build_type }} ON
shell: bash
run: |
if [ "${{ inputs.llvm_from_artifact }}" = "true" ]; then
pixi run cmake-config ${{ matrix.build_type }} ON -- \
"-DLLVM_INSTALL_PATH=.llvm-install"
pixi run cmake-build ${{ matrix.build_type }}
else
pixi run build ${{ matrix.build_type }} ON
fi
- name: Build (cross-compile)
if: ${{ matrix.target_triple }}
shell: bash
run: |
ENV="${{ matrix.pixi_env || 'default' }}"
EXTRA_ARGS=""
if [ "${{ inputs.llvm_from_artifact }}" = "true" ]; then
EXTRA_ARGS="-DLLVM_INSTALL_PATH=.llvm-install"
fi
pixi run -e "$ENV" cmake-config ${{ matrix.build_type }} OFF -- \
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}"
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}" \
$EXTRA_ARGS
pixi run -e "$ENV" cmake-build ${{ matrix.build_type }}
- name: Upload cross-compiled binaries
@@ -101,7 +130,7 @@ jobs:
run: pixi run test ${{ matrix.build_type }}
- name: Print cache stats and stop server
if: always()
if: ${{ always() && !inputs.llvm_from_artifact }}
run: |
ENV="${{ matrix.pixi_env || 'default' }}"
if [ "$RUNNER_OS" = "Windows" ]; then

View File

@@ -81,6 +81,7 @@ function(setup_llvm LLVM_VERSION)
clangBasic
clangDriver
clangFormat
clangOptions
clangFrontend
clangLex
clangSema

View File

@@ -41,8 +41,7 @@ set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
kotatsu
GIT_REPOSITORY https://github.com/clice-io/kotatsu
GIT_TAG main
GIT_SHALLOW TRUE
GIT_TAG 048f23f0d786
)
set(KOTA_ENABLE_ZEST ON)

View File

@@ -10,7 +10,7 @@ fi
WORKFLOW_ID="$1"
mkdir -p artifacts
gh run download "${WORKFLOW_ID}" --dir artifacts
gh run download "${WORKFLOW_ID}" --dir artifacts --pattern "*.tar.xz"
echo "Downloaded artifacts:"
find artifacts -maxdepth 4 -type f -print

View File

@@ -35,16 +35,19 @@
"LLVMFrontendOffloading",
"LLVMFrontendAtomic",
"LLVMFrontendDirective",
"LLVMPlugins",
"LLVMWindowsDriver",
"clangIndex",
"clangAPINotes",
"clangAST",
"clangASTMatchers",
"clangBasic",
"clangDependencyScanning",
"clangDriver",
"clangFormat",
"clangFrontend",
"clangLex",
"clangOptions",
"clangParse",
"clangSema",
"clangSerialization",
@@ -87,6 +90,7 @@
"clangToolingRefactoring",
"clangTransformer",
"clangCrossTU",
"clangAnalysisLifetimeSafety",
"clangAnalysisFlowSensitive",
"clangAnalysisFlowSensitiveModels",
"clangStaticAnalyzerCheckers",

View File

@@ -7,9 +7,9 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Options.h"
#include "clang/Driver/Types.h"
#include "clang/Options/OptionUtils.h"
#include "clang/Options/Options.h"
namespace clice {
@@ -35,7 +35,7 @@ struct Thief {
template struct Thief<&opt::OptTable::DashDashParsing, &opt::OptTable::GroupedShortOptions>;
auto& option_table = driver::getDriverOptTable();
auto& option_table = clang::getDriverOptTable();
} // namespace
@@ -45,7 +45,7 @@ std::unique_ptr<llvm::opt::Arg> ArgumentParser::parse_one(unsigned& index) {
return option_table.ParseOneArg(*this, index, opt::Visibility(visibility_mask));
}
using ID = clang::driver::options::ID;
using ID = clang::options::ID;
bool is_discarded_option(unsigned id) {
switch(id) {
@@ -165,7 +165,7 @@ llvm::StringRef resource_dir() {
if(exe.empty()) {
return std::string{};
}
return clang::driver::Driver::GetResourcesPath(exe);
return clang::GetResourcesPath(exe);
}();
return dir;
}
@@ -246,7 +246,7 @@ std::string print_argv(llvm::ArrayRef<const char*> args) {
}
unsigned default_visibility(llvm::StringRef driver) {
namespace options = clang::driver::options;
namespace options = clang::options;
auto name = llvm::sys::path::filename(driver);
name.consume_back(".exe");

View File

@@ -6,11 +6,11 @@
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "clang/Driver/Options.h"
#include "clang/Options/Options.h"
namespace clice {
using ID = clang::driver::options::ID;
using ID = clang::options::ID;
SearchConfig extract_search_config(llvm::ArrayRef<const char*> arguments,
llvm::StringRef directory) {
@@ -24,9 +24,12 @@ SearchConfig extract_search_config(llvm::ArrayRef<const char*> arguments,
std::vector<SearchDir> after;
auto make_absolute = [&](llvm::StringRef path) -> std::string {
llvm::SmallString<256> abs_path(path);
if(!llvm::sys::path::is_absolute(abs_path)) {
llvm::sys::fs::make_absolute(directory, abs_path);
llvm::SmallString<256> abs_path;
if(llvm::sys::path::is_absolute(path)) {
abs_path = path;
} else {
abs_path = directory;
llvm::sys::path::append(abs_path, path);
}
llvm::sys::path::remove_dots(abs_path, true);
return abs_path.str().str();

View File

@@ -7,6 +7,7 @@
#include "support/logging.h"
#include "llvm/Support/Error.h"
#include "clang/Driver/CreateInvocationFromArgs.h"
#include "clang/Frontend/MultiplexConsumer.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Lex/PreprocessorOptions.h"
@@ -246,13 +247,14 @@ CompilationStatus CompilationUnitRef::Self::run_clang(
self.instance = std::make_unique<clang::CompilerInstance>(std::move(invocation));
auto& instance = *self.instance;
instance.createDiagnostics(*params.vfs, diagnostic_consumer.release(), true);
instance.createDiagnostics(diagnostic_consumer.release(), true);
if(auto remapping = clang::createVFSFromCompilerInvocation(instance.getInvocation(),
instance.getDiagnostics(),
params.vfs)) {
instance.createFileManager(std::move(remapping));
instance.setVirtualFileSystem(std::move(remapping));
}
instance.createFileManager();
if(!instance.createTarget()) {
return CompilationStatus::SetupFail;

View File

@@ -16,17 +16,7 @@ namespace clice {
llvm::StringRef DiagnosticID::diagnostic_code() const {
switch(value) {
#define DIAG(ENUM, \
CLASS, \
DEFAULT_MAPPING, \
DESC, \
GROPU, \
SFINAE, \
NOWERROR, \
SHOWINSYSHEADER, \
SHOWINSYSMACRO, \
DEFERRABLE, \
CATEGORY) \
#define DIAG(ENUM, ...) \
case clang::diag::ENUM: return #ENUM;
#include "clang/Basic/DiagnosticASTKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"

View File

@@ -6,7 +6,7 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang-tidy/ClangTidyCheck.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyModule.h"
#include "clang-tidy/ClangTidyOptions.h"
namespace clice::tidy {

View File

@@ -10,7 +10,7 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang-tidy/ClangTidyCheck.h"
#include "clang-tidy/ClangTidyDiagnosticConsumer.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyModule.h"
#include "clang-tidy/ClangTidyOptions.h"
namespace clice::tidy {

View File

@@ -131,8 +131,8 @@ public:
policy(unit.context().getPrintingPolicy()) {
// The sugared type is more useful in some cases, and the canonical
// type in other cases.
policy.SuppressScope = true; // keep type names short
policy.AnonymousTagLocations = false; // do not print lambda locations
policy.SuppressScope = true; // keep type names short
policy.AnonymousTagLocations = false;
// Not setting PrintCanonicalTypes for "auto" allows
// SuppressDefaultTemplateArgs (set by default) to have an effect.
}

View File

@@ -504,14 +504,14 @@ bool USRGenerator::GenLoc(const Decl* D, bool IncludeOffset) {
static void printQualifier(llvm::raw_ostream& Out,
const LangOptions& LangOpts,
NestedNameSpecifier* NNS) {
NestedNameSpecifier NNS) {
// FIXME: Encode the qualifier, don't just print it.
PrintingPolicy PO(LangOpts);
PO.SuppressTagKeyword = true;
PO.SuppressUnwrittenScope = true;
PO.ConstantArraySizeAsWritten = false;
PO.AnonymousTagLocations = false;
NNS->print(Out, PO);
NNS.print(Out, PO);
}
void USRGenerator::VisitType(QualType T) {
@@ -740,7 +740,7 @@ void USRGenerator::VisitType(QualType T) {
return;
}
if(const InjectedClassNameType* InjT = T->getAs<InjectedClassNameType>()) {
T = InjT->getInjectedSpecializationType();
T = InjT->desugar();
continue;
}
if(const auto* VT = T->getAs<VectorType>()) {

View File

@@ -200,10 +200,6 @@ llvm::StringRef identifier_of(const clang::NamedDecl& D) {
}
llvm::StringRef identifier_of(clang::QualType type) {
if(const auto* ET = llvm::dyn_cast<clang::ElaboratedType>(type)) {
return identifier_of(ET->getNamedType());
}
if(const auto* BT = llvm::dyn_cast<clang::BuiltinType>(type)) {
clang::PrintingPolicy PP(clang::LangOptions{});
PP.adjustForCPlusPlus();
@@ -308,12 +304,6 @@ const clang::NamedDecl* decl_of_impl(const void* T) {
}
auto decl_of(clang::QualType type) -> const clang::NamedDecl* {
// Strip type-sugar that wraps the underlying type without adding a decl
// (e.g. ElaboratedType for "struct Foo" vs plain "Foo").
if(auto ET = type->getAs<clang::ElaboratedType>()) {
type = ET->getNamedType();
}
if(auto TST = type->getAs<clang::TemplateSpecializationType>()) {
auto decl = TST->getTemplateName().getAsTemplateDecl();
if(type->isDependentType()) {
@@ -405,8 +395,8 @@ std::string display_name_of(const clang::NamedDecl* decl) {
// Handle 'using namespace'. They all have the same name - <using-directive>.
if(auto* UD = llvm::dyn_cast<clang::UsingDirectiveDecl>(decl)) {
out << "using namespace ";
if(auto* Qual = UD->getQualifier())
Qual->print(out, policy);
if(auto Qual = UD->getQualifier())
Qual.print(out, policy);
UD->getNominatedNamespaceAsWritten()->printName(out);
return out.str();
}
@@ -433,8 +423,8 @@ std::string display_name_of(const clang::NamedDecl* decl) {
}
// Print nested name qualifier if it was written in the source code.
if(auto* qualifier = get_qualifier_loc(decl).getNestedNameSpecifier()) {
qualifier->print(out, policy);
if(auto qualifier = get_qualifier_loc(decl).getNestedNameSpecifier()) {
qualifier.print(out, policy);
}
// Print the name itself.

View File

@@ -107,7 +107,7 @@ public:
return true;
}
bool TraverseTypeLoc(clang::TypeLoc loc) {
bool TraverseTypeLoc(clang::TypeLoc loc, bool TraverseQualifier = true) {
CHECK_DERIVED_IMPL(TraverseTypeLoc);
if(!loc) {
@@ -116,10 +116,10 @@ public:
/// FIXME: Workaround for `QualifiedTypeLoc`.
if(auto QL = loc.getAs<clang::QualifiedTypeLoc>()) {
return Base::TraverseTypeLoc(QL.getUnqualifiedLoc());
return Base::TraverseTypeLoc(QL.getUnqualifiedLoc(), TraverseQualifier);
}
return Base::TraverseTypeLoc(loc);
return Base::TraverseTypeLoc(loc, TraverseQualifier);
}
bool TraverseAttr(clang::Attr* attr) {
@@ -132,9 +132,8 @@ public:
return Base::TraverseAttr(attr);
}
/// We don't want to node withou location information.
constexpr bool TraverseNestedNameSpecifier
[[gnu::always_inline]] (clang::NestedNameSpecifier*) {
/// We don't want to node without location information.
constexpr bool TraverseNestedNameSpecifier [[gnu::always_inline]] (clang::NestedNameSpecifier) {
CHECK_DERIVED_IMPL(TraverseNestedNameSpecifier);
return true;
}

View File

@@ -126,17 +126,18 @@ struct InstantiationStack {
return data;
}
/// Look up a template type parameter in the stack by matching its depth against
/// each frame's template parameter list depth. Searches from innermost (top) to
/// outermost (bottom). Returns nullptr if no matching frame or index out of range.
/// Look up a template type parameter in the stack by matching its depth and
/// ownership against each frame. Searches from innermost (top) to outermost
/// (bottom). Returns nullptr if no matching frame or index out of range.
///
/// IMPORTANT: depth alone identifies the template "level", not the specific template.
/// Different templates at the same depth (e.g. vector and test both at depth 0) will
/// match the FIRST frame found. Callers must ensure the stack only contains relevant
/// frames when calling this.
/// When the TTP has a declaration, ownership is verified: the frame's template
/// parameter at the same index must be the exact same TemplateTypeParmDecl.
/// This prevents depth collisions between unrelated templates at the same
/// nesting level (e.g. __alloc_traits and vector both at depth 0).
const clang::TemplateArgument* find_argument(const clang::TemplateTypeParmType* T) const {
auto depth = T->getDepth();
auto index = T->getIndex();
auto* decl = T->getDecl();
for(auto it = data.rbegin(); it != data.rend(); ++it) {
clang::TemplateParameterList* params = nullptr;
if(auto* CTD = llvm::dyn_cast<clang::ClassTemplateDecl>(it->first)) {
@@ -150,10 +151,16 @@ struct InstantiationStack {
params = FTD->getTemplateParameters();
}
if(params && params->getDepth() == depth) {
if(index < it->second.size()) {
return &it->second[index];
if(index >= it->second.size()) {
return nullptr;
}
return nullptr;
// If the TTP has a declaration, verify it actually belongs to this
// frame's template by checking the parameter list. Skip frames from
// unrelated templates that happen to share the same depth.
if(decl && index < params->size() && params->getParam(index) != decl) {
continue;
}
return &it->second[index];
}
}
return nullptr;
@@ -167,7 +174,10 @@ static clang::QualType get_decl_type(clang::Decl* decl) {
if(auto* TND = llvm::dyn_cast<clang::TypedefNameDecl>(decl))
return TND->getUnderlyingType();
if(auto* RD = llvm::dyn_cast<clang::RecordDecl>(decl))
return clang::QualType(RD->getTypeForDecl(), 0);
return decl->getASTContext().getTagType(clang::ElaboratedTypeKeyword::None,
std::nullopt,
RD,
false);
return clang::QualType();
}
@@ -205,14 +215,11 @@ public:
/// Desugar dependent typedefs to expose template parameters for substitution.
clang::QualType TransformTypedefType(clang::TypeLocBuilder& TLB, clang::TypedefTypeLoc TL) {
if(auto* TND = TL.getTypedefNameDecl()) {
if(auto* TND = TL.getDecl()) {
auto underlying = TND->getUnderlyingType();
if(underlying->isDependentType()) {
auto type = TransformType(underlying);
if(!type.isNull()) {
if(auto ET = llvm::dyn_cast<clang::ElaboratedType>(type)) {
type = ET->getNamedType();
}
TLB.pushTrivial(context, type, {});
return type;
}
@@ -221,20 +228,10 @@ public:
return Base::TransformTypedefType(TLB, TL);
}
clang::QualType TransformElaboratedType(clang::TypeLocBuilder& TLB,
clang::ElaboratedTypeLoc TL) {
clang::QualType type = TransformType(TL.getNamedTypeLoc().getType());
if(type.isNull()) {
return Base::TransformElaboratedType(TLB, TL);
}
TLB.pushTrivial(context, type, {});
return type;
}
clang::QualType TransformInjectedClassNameType(clang::TypeLocBuilder& TLB,
clang::InjectedClassNameTypeLoc TL) {
auto ICT = TL.getTypePtr();
clang::QualType type = TransformType(ICT->getInjectedSpecializationType());
clang::QualType type = TransformType(ICT->desugar());
if(type.isNull()) {
return Base::TransformInjectedClassNameType(TLB, TL);
}
@@ -427,7 +424,8 @@ public:
visit_template_decl_contexts(
llvm::dyn_cast<clang::Decl>(decl->getDeclContext()),
[&](clang::Decl* decl, clang::TemplateParameterList* params) {
stack.push(decl, params->getInjectedTemplateArgs(context));
auto args = params->getInjectedTemplateArgs(context);
stack.push(decl, args);
});
std::ranges::reverse(stack.frames());
}
@@ -489,31 +487,46 @@ public:
lookup_result lookup(clang::QualType type, clang::DeclarationName name) {
clang::Decl* TD = nullptr;
llvm::ArrayRef<clang::TemplateArgument> args;
type = TransformType(type);
if(type.isNull()) {
return lookup_result();
// For concrete TSTs (non-dependent template name), extract template info directly
// without calling TransformType. This avoids corrupting template arguments when
// unrelated stack frames match by depth (e.g. __alloc_traits at depth 0 would
// incorrectly substitute test's T which is also at depth 0).
if(auto TST = type->getAs<clang::TemplateSpecializationType>()) {
if(!TST->getTemplateName().getAsDependentTemplateName()) {
TD = TST->getTemplateName().getAsTemplateDecl();
args = TST->template_arguments();
}
}
if(auto TST = type->getAs<clang::TemplateSpecializationType>()) {
TD = TST->getTemplateName().getAsTemplateDecl();
args = TST->template_arguments();
} else if(auto DTST = type->getAs<clang::DependentTemplateSpecializationType>()) {
// If this DTST was already resolved (possibly to itself when unresolvable),
// skip the redundant lookup.
if(resolved.count(DTST)) {
if(!TD) {
type = TransformType(type);
if(type.isNull()) {
return lookup_result();
}
auto& template_name = DTST->getDependentTemplateName();
auto name = template_name.getName().getIdentifier();
if(!name) {
return {};
}
if(auto TST = type->getAs<clang::TemplateSpecializationType>()) {
if(auto* DTN = TST->getTemplateName().getAsDependentTemplateName()) {
// If this dependent TST was already resolved (possibly to itself when
// unresolvable), skip the redundant lookup.
if(resolved.count(TST)) {
return lookup_result();
}
if(auto decl = preferred(lookup(template_name.getQualifier(), name))) {
TD = decl;
args = DTST->template_arguments();
auto name = DTN->getName().getIdentifier();
if(!name) {
return {};
}
if(auto decl = preferred(lookup(DTN->getQualifier(), name))) {
TD = decl;
args = TST->template_arguments();
}
} else {
TD = TST->getTemplateName().getAsTemplateDecl();
args = TST->template_arguments();
}
}
}
@@ -536,45 +549,23 @@ public:
return lookup_result();
}
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) {
lookup_result lookup(clang::NestedNameSpecifier NNS, clang::DeclarationName name) {
if(!NNS) {
return lookup_result();
}
if(auto iter = resolved.find(NNS); iter != resolved.end()) {
return lookup(iter->second, name);
}
// Handle each NestedNameSpecifier kind:
// - Identifier: dependent name in NNS chain (e.g. `base::type::inner`), resolve recursively
// - TypeSpec: concrete or dependent type used as qualifier (e.g. `vector<T>::`)
// - Global/Namespace/NamespaceAlias/Super: not dependent, cannot resolve further
switch(NNS->getKind()) {
case clang::NestedNameSpecifier::Identifier: {
auto stack_size = stack.data.size();
auto* decl = preferred(lookup(NNS->getPrefix(), NNS->getAsIdentifier()));
auto type = get_decl_type(decl);
if(!type.isNull()) {
type = substitute(type);
}
while(stack.data.size() > stack_size) {
stack.pop();
}
if(!type.isNull()) {
resolved.try_emplace(NNS, type);
return lookup(type, name);
}
return {};
// In LLVM 22+, the old Identifier NNS kind is represented as a Type
// (DependentNameType), so the Type case handles both old TypeSpec and
// Identifier cases via lookup(QualType, name) → TransformDependentNameType.
switch(NNS.getKind()) {
case clang::NestedNameSpecifier::Kind::Type: {
return lookup(clang::QualType(NNS.getAsType(), 0), name);
}
case clang::NestedNameSpecifier::TypeSpec: {
return lookup(clang::QualType(NNS->getAsType(), 0), name);
}
case clang::NestedNameSpecifier::Global:
case clang::NestedNameSpecifier::Namespace:
case clang::NestedNameSpecifier::NamespaceAlias:
case clang::NestedNameSpecifier::Super: {
case clang::NestedNameSpecifier::Kind::Global:
case clang::NestedNameSpecifier::Kind::Namespace:
case clang::NestedNameSpecifier::Kind::MicrosoftSuper:
case clang::NestedNameSpecifier::Kind::Null: {
return {};
}
}
@@ -708,14 +699,14 @@ public:
///
/// TODO: Replace with a general mechanism for resolving well-known standard
/// library patterns, or improve the resolver to handle these chains naturally.
clang::QualType hole(clang::NestedNameSpecifier* NNS,
clang::QualType hole(clang::NestedNameSpecifier NNS,
const clang::IdentifierInfo* member,
TemplateArguments arguments) {
if(NNS->getKind() != clang::NestedNameSpecifier::TypeSpec) {
if(NNS.getKind() != clang::NestedNameSpecifier::Kind::Type) {
return clang::QualType();
}
auto TST = NNS->getAsType()->getAs<clang::TemplateSpecializationType>();
auto TST = NNS.getAsType()->getAs<clang::TemplateSpecializationType>();
if(!TST) {
return clang::QualType();
}
@@ -738,17 +729,18 @@ public:
return clang::QualType();
auto T = arguments[0].getAsType();
auto prefix =
clang::NestedNameSpecifier::Create(context, nullptr, Alloc.getTypePtr());
clang::NestedNameSpecifier prefix(Alloc.getTypePtr());
auto rebind = sema.getPreprocessor().getIdentifierInfo("rebind");
auto DTST = context.getDependentTemplateSpecializationType(
clang::ElaboratedTypeKeyword::None,
clang::DependentTemplateStorage(prefix, rebind, false),
arguments);
auto DTN = context.getDependentTemplateName(
clang::DependentTemplateStorage(prefix, rebind, false));
auto TST = context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None,
DTN,
arguments,
{});
prefix = clang::NestedNameSpecifier::Create(context, prefix, DTST.getTypePtr());
prefix = clang::NestedNameSpecifier(TST.getTypePtr());
auto other = sema.getPreprocessor().getIdentifierInfo("other");
auto DNT = context.getDependentNameType(clang::ElaboratedTypeKeyword::Typename,
@@ -770,9 +762,11 @@ public:
for(auto& arg: replaceArguments) {
canonicalArguments.emplace_back(context.getCanonicalTemplateArgument(arg));
}
auto result = context.getTemplateSpecializationType(TST->getTemplateName(),
replaceArguments,
canonicalArguments);
auto result =
context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None,
TST->getTemplateName(),
replaceArguments,
canonicalArguments);
LOG_DEBUG(
"{}" "hole: 'allocator_traits::rebind_alloc' → '{}'",
pad(),
@@ -857,9 +851,39 @@ public:
return TL.getType();
}
/// Push frames for outer templates in a DependentTemplateName qualifier chain.
/// For `A<X>::template B<Y>::template C<Z>::type`, walking the chain from the
/// DependentNameType's NNS pushes frames for A (T1→X) and B (T2→Y) so that
/// when "type" is found in C<Z>, the substitute step correctly maps outer
/// template parameters to the actual arguments from the NNS chain.
void push_nns_qualifier_frames(clang::NestedNameSpecifier NNS) {
if(!NNS || NNS.getKind() != clang::NestedNameSpecifier::Kind::Type)
return;
auto* TST = NNS.getAsType()->getAs<clang::TemplateSpecializationType>();
if(!TST)
return;
if(auto* DTN = TST->getTemplateName().getAsDependentTemplateName()) {
push_nns_qualifier_frames(DTN->getQualifier());
if(auto it = resolved.find(TST); it != resolved.end()) {
if(auto* rTST = it->second->getAs<clang::TemplateSpecializationType>()) {
if(auto* TD = rTST->getTemplateName().getAsTemplateDecl()) {
stack.push(TD, TST->template_arguments());
}
}
}
} else if(auto* TD = TST->getTemplateName().getAsTemplateDecl()) {
stack.push(TD, TST->template_arguments());
}
}
clang::QualType TransformDependentNameType(clang::TypeLocBuilder& TLB,
clang::DependentNameTypeLoc TL,
bool DeducedTSTContext = false) {
bool DeducedTSTContext = false,
clang::QualType ObjectType = {},
clang::NamedDecl* UnqualLookup = nullptr) {
auto* DNT = TL.getTypePtr();
LOG_DEBUG("{}" "resolve '{}'", pad(), clang::QualType(DNT, 0).getAsString());
++indent;
@@ -884,8 +908,14 @@ public:
return original;
}
// Save entry stack size to clean up any frames leaked by nested operations
// (NNS transform, push_nns_qualifier_frames, lookup, TransformType).
auto entry_stack_size = stack.data.size();
auto NNSLoc = TransformNestedNameSpecifierLoc(TL.getQualifierLoc());
if(!NNSLoc) {
while(stack.data.size() > entry_stack_size)
stack.pop();
active_resolutions.erase(DNT);
LOG_DEBUG("{}→ <unresolved>", pad());
--indent;
@@ -897,7 +927,21 @@ public:
return original;
}
auto* NNS = NNSLoc.getNestedNameSpecifier();
auto NNS = NNSLoc.getNestedNameSpecifier();
// For nested dependent template specializations like A<X>::B<Y>::C<Z>::type,
// push frames for outer templates (A, B) using args from the original NNS
// chain. Without this, the lookup fabricates self-referential injected args
// for outer templates, leaving their parameters unresolved.
auto origNNS = DNT->getQualifier();
if(origNNS.getKind() == clang::NestedNameSpecifier::Kind::Type) {
if(auto* TST = origNNS.getAsType()->getAs<clang::TemplateSpecializationType>()) {
if(auto* DTN = TST->getTemplateName().getAsDependentTemplateName()) {
push_nns_qualifier_frames(DTN->getQualifier());
}
}
}
auto stack_size = stack.data.size();
auto* decl = preferred(lookup(NNS, DNT->getIdentifier()));
auto type = get_decl_type(decl);
@@ -943,6 +987,11 @@ public:
}
}
// Clean up all frames pushed during this resolution (push_nns_qualifier_frames,
// nested lookups via TransformType, etc.) to prevent leaking into sibling calls.
while(stack.data.size() > entry_stack_size)
stack.pop();
active_resolutions.erase(DNT);
if(!result.isNull()) {
@@ -963,43 +1012,57 @@ public:
return original;
}
using Base::TransformDependentTemplateSpecializationType;
clang::QualType rebuild_dtst(clang::TypeLocBuilder& TLB,
clang::DependentTemplateSpecializationTypeLoc TL) {
auto* DTST = TL.getTypePtr();
return TLB.push<clang::DependentTemplateSpecializationTypeLoc>(clang::QualType(DTST, 0))
.getType();
clang::QualType rebuild_tst(clang::TypeLocBuilder& TLB,
clang::TemplateSpecializationTypeLoc TL) {
auto* TST = TL.getTypePtr();
return TLB.push<clang::TemplateSpecializationTypeLoc>(clang::QualType(TST, 0)).getType();
}
clang::QualType TransformDependentTemplateSpecializationType(
clang::TypeLocBuilder& TLB,
clang::DependentTemplateSpecializationTypeLoc TL) {
auto* DTST = TL.getTypePtr();
LOG_DEBUG("{}" "resolve DTST '{}'", pad(), clang::QualType(DTST, 0).getAsString());
clang::QualType
TransformTemplateSpecializationType(clang::TypeLocBuilder& TLB,
clang::TemplateSpecializationTypeLoc TL,
clang::QualType ObjectType = {},
clang::NamedDecl* FirstQualifierInScope = nullptr,
bool AllowInjectedClassName = false) {
auto* TST = TL.getTypePtr();
auto* DTN = TST->getTemplateName().getAsDependentTemplateName();
if(!DTN) {
return Base::TransformTemplateSpecializationType(TLB,
TL,
ObjectType,
FirstQualifierInScope,
AllowInjectedClassName);
}
LOG_DEBUG("{}" "resolve dependent TST '{}'", pad(), clang::QualType(TST, 0).getAsString());
++indent;
if(auto iter = resolved.find(DTST); iter != resolved.end()) {
if(auto iter = resolved.find(TST); iter != resolved.end()) {
--indent;
TLB.pushTrivial(context, iter->second, {});
return iter->second;
}
// Save stack state to clean up frames pushed by NNS/lookup side effects.
auto stack_size = stack.data.size();
auto NNSLoc = TransformNestedNameSpecifierLoc(TL.getQualifierLoc());
if(!NNSLoc) {
LOG_DEBUG("{}→ <unresolved DTST>", pad());
while(stack.data.size() > stack_size)
stack.pop();
LOG_DEBUG("{}→ <unresolved dependent TST>", pad());
--indent;
return rebuild_dtst(TLB, TL);
return rebuild_tst(TLB, TL);
}
auto* NNS = NNSLoc.getNestedNameSpecifier();
auto NNS = NNSLoc.getNestedNameSpecifier();
clang::TemplateArgumentListInfo info;
using iterator = clang::TemplateArgumentLocContainerIterator<
clang::DependentTemplateSpecializationTypeLoc>;
using iterator =
clang::TemplateArgumentLocContainerIterator<clang::TemplateSpecializationTypeLoc>;
if(TransformTemplateArguments(iterator(TL, 0), iterator(TL, TL.getNumArgs()), info)) {
LOG_DEBUG("{}→ <unresolved DTST>", pad());
LOG_DEBUG("{}→ <unresolved dependent TST>", pad());
--indent;
return rebuild_dtst(TLB, TL);
return rebuild_tst(TLB, TL);
}
llvm::SmallVector<clang::TemplateArgument, 4> arguments;
@@ -1007,22 +1070,21 @@ public:
arguments.push_back(arg.getArgument());
}
auto* name = DTST->getDependentTemplateName().getName().getIdentifier();
auto* name = DTN->getName().getIdentifier();
if(!name) {
LOG_DEBUG("{}→ <unresolved DTST>", pad());
LOG_DEBUG("{}→ <unresolved dependent TST>", pad());
--indent;
return rebuild_dtst(TLB, TL);
return rebuild_tst(TLB, TL);
}
if(auto result = hole(NNS, name, arguments); !result.isNull()) {
LOG_DEBUG("{}" "hole: '{}' → '{}'", pad(), name->getName().str(), result.getAsString());
--indent;
resolved.try_emplace(DTST, result);
resolved.try_emplace(TST, result);
TLB.pushTrivial(context, result, {});
return result;
}
auto stack_size = stack.data.size();
if(auto* decl = preferred(lookup(NNS, name))) {
if(auto* TATD = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(decl)) {
if(deduce_template_arguments(TATD, arguments)) {
@@ -1037,26 +1099,29 @@ public:
if(!type.isNull()) {
LOG_DEBUG("{}" "→ '{}' (alias)", pad(), type.getAsString());
--indent;
resolved.try_emplace(DTST, type);
resolved.try_emplace(TST, type);
TLB.pushTrivial(context, type, {});
return type;
}
}
} else if(auto* CTD = llvm::dyn_cast<clang::ClassTemplateDecl>(decl)) {
// Resolve DTST to a concrete TemplateSpecializationType.
// e.g. __alloc_traits<allocator<T>>::rebind<T> → rebind<T> (a TST)
// This allows subsequent lookup of members (like "other") to work.
// Keep lookup frames on stack — the caller (e.g. TransformNestedNameSpecifierLoc
// processing A<X>::B<Y>::C<Z>) needs them for parameter substitution.
// Pop lookup frames — we only needed them to find the CTD.
while(stack.data.size() > stack_size) {
stack.pop();
}
clang::TemplateName TN(CTD);
llvm::SmallVector<clang::TemplateArgument> canonArgs;
for(auto& arg: arguments) {
canonArgs.push_back(context.getCanonicalTemplateArgument(arg));
}
auto result = context.getTemplateSpecializationType(TN, arguments, canonArgs);
auto result =
context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None,
TN,
arguments,
canonArgs);
LOG_DEBUG("{}" "→ TST '{}' (class)", pad(), result.getAsString());
--indent;
resolved.try_emplace(DTST, result);
resolved.try_emplace(TST, result);
TLB.pushTrivial(context, result, {});
return result;
}
@@ -1065,10 +1130,10 @@ public:
stack.pop();
}
LOG_DEBUG("{}→ <unresolved DTST>", pad());
LOG_DEBUG("{}→ <unresolved dependent TST>", pad());
--indent;
auto fallback = rebuild_dtst(TLB, TL);
resolved.try_emplace(DTST, fallback);
auto fallback = rebuild_tst(TLB, TL);
resolved.try_emplace(TST, fallback);
return fallback;
}
@@ -1077,14 +1142,11 @@ public:
/// its own TransformTypedefType). Using substitute() here ensures that typedef
/// expansion does NOT trigger heuristic lookup, preventing the typedef ↔ lookup cycle.
clang::QualType TransformTypedefType(clang::TypeLocBuilder& TLB, clang::TypedefTypeLoc TL) {
if(auto* TND = TL.getTypedefNameDecl()) {
if(auto* TND = TL.getDecl()) {
auto underlying = TND->getUnderlyingType();
if(underlying->isDependentType()) {
auto type = substitute(underlying);
if(!type.isNull()) {
if(auto ET = llvm::dyn_cast<clang::ElaboratedType>(type)) {
type = ET->getNamedType();
}
TLB.pushTrivial(context, type, {});
return type;
}
@@ -1140,7 +1202,7 @@ clang::QualType TemplateResolver::resugar(clang::QualType type, clang::Decl* dec
return resugar.TransformType(type);
}
TemplateResolver::lookup_result TemplateResolver::lookup(const clang::NestedNameSpecifier* NNS,
TemplateResolver::lookup_result TemplateResolver::lookup(clang::NestedNameSpecifier NNS,
clang::DeclarationName name) {
PseudoInstantiator instantiator(sema, resolved);
return instantiator.lookup(NNS, name);

View File

@@ -41,17 +41,17 @@ public:
using lookup_result = clang::DeclContext::lookup_result;
/// Look up the name in the given nested name specifier.
lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name);
lookup_result lookup(clang::NestedNameSpecifier NNS, clang::DeclarationName name);
lookup_result lookup(const clang::DependentNameType* type) {
return lookup(type->getQualifier(), type->getIdentifier());
}
lookup_result lookup(const clang::DependentTemplateSpecializationType* type) {
auto& template_name = type->getDependentTemplateName();
auto identifier = template_name.getName().getIdentifier();
lookup_result lookup(const clang::TemplateSpecializationType* type,
const clang::DependentTemplateName* DTN) {
auto identifier = DTN->getName().getIdentifier();
if(identifier) {
return lookup(template_name.getQualifier(), identifier);
return lookup(DTN->getQualifier(), identifier);
} else {
/// TODO: Operators don't have an IdentifierInfo; need DeclarationName-based lookup.
return {};

View File

@@ -756,8 +756,8 @@ public:
return traverse_node(X, [&] { return Base::TraverseDecl(X); });
}
bool TraverseTypeLoc(clang::TypeLoc X) {
return traverse_node(&X, [&] { return Base::TraverseTypeLoc(X); });
bool TraverseTypeLoc(clang::TypeLoc X, bool TraverseQualifier = true) {
return traverse_node(&X, [&] { return Base::TraverseTypeLoc(X, TraverseQualifier); });
}
bool TraverseTemplateArgumentLoc(const clang::TemplateArgumentLoc& X) {
@@ -814,9 +814,9 @@ public:
// This means we'd never see 'int' in 'const int'! Work around that here.
// (The reason for the behavior is to avoid traversing the nested Type twice,
// but we ignore TraverseType anyway).
bool TraverseQualifiedTypeLoc(clang::QualifiedTypeLoc QX) {
bool TraverseQualifiedTypeLoc(clang::QualifiedTypeLoc QX, bool TraverseQualifier = true) {
return traverse_node<clang::TypeLoc>(&QX, [&] {
return TraverseTypeLoc(QX.getUnqualifiedLoc());
return TraverseTypeLoc(QX.getUnqualifiedLoc(), TraverseQualifier);
});
}
@@ -825,7 +825,7 @@ public:
}
// Uninteresting parts of the AST that don't have locations within them.
bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier*) {
bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier) {
return true;
}

View File

@@ -515,7 +515,7 @@ public:
/// using Foo = int; Foo foo;
/// ^~~~ reference
VISIT_TYPELOC(TypedefTypeLoc) {
auto decl = loc.getTypedefNameDecl();
auto decl = loc.getDecl();
auto location = loc.getNameLoc();
handleDeclOccurrence(decl, RelationKind::Reference, location);
handleRelation(decl, RelationKind::Reference, decl, location);
@@ -561,14 +561,9 @@ public:
/// std::allocator<T>::rebind<U>
/// ^~~~ reference
VISIT_TYPELOC(DependentTemplateSpecializationTypeLoc) {
auto location = loc.getTemplateNameLoc();
// for(auto decl: resolver.lookup(loc.getTypePtr())) {
// handleDeclOccurrence(decl, RelationKind::WeakReference, location);
// handleRelation(decl, RelationKind::WeakReference, decl, location);
// }
return true;
}
/// Note: In LLVM 22+, DependentTemplateSpecializationTypeLoc was merged
/// into TemplateSpecializationTypeLoc. Dependent template cases are now
/// handled by the TemplateSpecializationTypeLoc visitor above.
/// ============================================================================
/// Specifier
@@ -576,32 +571,19 @@ public:
bool VisitNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) {
auto NNS = loc.getNestedNameSpecifier();
switch(NNS->getKind()) {
case clang::NestedNameSpecifier::Namespace: {
auto decl = NNS->getAsNamespace();
switch(NNS.getKind()) {
case clang::NestedNameSpecifier::Kind::Namespace: {
auto [ns, prefix] = NNS.getAsNamespaceAndPrefix();
auto location = loc.getLocalBeginLoc();
handleDeclOccurrence(decl, RelationKind::Reference, location);
handleRelation(decl, RelationKind::Reference, decl, location);
handleDeclOccurrence(ns, RelationKind::Reference, location);
handleRelation(ns, RelationKind::Reference, ns, location);
break;
}
case clang::NestedNameSpecifier::NamespaceAlias: {
auto decl = NNS->getAsNamespaceAlias();
auto location = loc.getLocalBeginLoc();
handleDeclOccurrence(decl, RelationKind::Reference, location);
handleRelation(decl, RelationKind::Reference, decl, location);
break;
}
case clang::NestedNameSpecifier::Identifier: {
assert(NNS->isDependent() && "Identifier NNS should be dependent");
// FIXME: use TemplateResolver here.
break;
}
case clang::NestedNameSpecifier::TypeSpec:
case clang::NestedNameSpecifier::Global:
case clang::NestedNameSpecifier::Super: {
case clang::NestedNameSpecifier::Kind::Type:
case clang::NestedNameSpecifier::Kind::Global:
case clang::NestedNameSpecifier::Kind::MicrosoftSuper:
case clang::NestedNameSpecifier::Kind::Null: {
break;
};
}

View File

@@ -10,6 +10,7 @@
#include "clang/Basic/FileEntry.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Driver/CreateInvocationFromArgs.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/PPCallbacks.h"
@@ -319,9 +320,10 @@ std::unique_ptr<clang::CompilerInstance>
}
auto instance = std::make_unique<clang::CompilerInstance>(std::move(invocation));
instance->createDiagnostics(*vfs, new clang::IgnoringDiagConsumer(), true);
instance->createDiagnostics(new clang::IgnoringDiagConsumer(), true);
instance->getDiagnostics().setSuppressAllDiagnostics(true);
instance->createFileManager(vfs);
instance->setVirtualFileSystem(vfs);
instance->createFileManager();
return instance;
}

View File

@@ -1,7 +1,7 @@
#include "test/test.h"
#include "command/argument_parser.h"
#include "clang/Driver/Options.h"
#include "clang/Options/Options.h"
namespace clice::testing {
@@ -9,7 +9,7 @@ namespace {
TEST_SUITE(ArgumentParser) {
using option = clang::driver::options::ID;
using option = clang::options::ID;
void EXPECT_ID(llvm::StringRef command, option opt) {
auto id = get_option_id(command);

View File

@@ -621,7 +621,7 @@ TEST_CASE(Types) {
)c");
EXPECT_SIZE(2);
EXPECT_HINT("0", ": S1");
EXPECT_HINT("1", ": S2::Inner<int>");
EXPECT_HINT("1", ": Inner<int>");
// Lambda
run(R"c(

View File

@@ -442,9 +442,9 @@ TEST_CASE(Types) {
TEST_CASE(CXXFeatures) {
EXPECT_SELECT(R"(
template <typename T>
int x = @[T::$U::]ccc();
int x = T::@[$U]::ccc();
)",
"NestedNameSpecifierLoc");
"DependentNameTypeLoc");
EXPECT_SELECT(R"(
struct Foo {};
struct Bar : @[v$ir$tual private Foo] {};
@@ -486,9 +486,9 @@ TEST_CASE(UsingEnum) {
"TypedefTypeLoc");
EXPECT_SELECT(R"(
namespace ns { enum class A {}; };
using enum @[$ns::]A;
@[using enum $ns::A];
)",
"NestedNameSpecifierLoc");
"UsingEnumDecl");
EXPECT_SELECT(R"(
namespace ns { enum class A {}; };
@[using $enum ns::A];