Compare commits
15 Commits
main
...
feat/upgra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c727c12d62 | ||
|
|
7f85125818 | ||
|
|
16a4844b22 | ||
|
|
90bfebb122 | ||
|
|
9873e26cd1 | ||
|
|
fd100ecd60 | ||
|
|
71723d89f1 | ||
|
|
f9a3bd8526 | ||
|
|
4115351834 | ||
|
|
7050fc5d20 | ||
|
|
0133f5db23 | ||
|
|
207d6af089 | ||
|
|
5468a6efb1 | ||
|
|
2d193d80cd | ||
|
|
3e73150569 |
171
.github/workflows/build-llvm.yml
vendored
171
.github/workflows/build-llvm.yml
vendored
@@ -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
|
||||
|
||||
35
.github/workflows/test-cmake.yml
vendored
35
.github/workflows/test-cmake.yml
vendored
@@ -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
|
||||
|
||||
@@ -81,6 +81,7 @@ function(setup_llvm LLVM_VERSION)
|
||||
clangBasic
|
||||
clangDriver
|
||||
clangFormat
|
||||
clangOptions
|
||||
clangFrontend
|
||||
clangLex
|
||||
clangSema
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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>()) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user