diff --git a/.claude/skills/upgrade-llvm/SKILL.md b/.claude/skills/upgrade-llvm/SKILL.md index 03c742343..cd28fdfb7 100644 --- a/.claude/skills/upgrade-llvm/SKILL.md +++ b/.claude/skills/upgrade-llvm/SKILL.md @@ -24,9 +24,9 @@ Download the artifact matching the development machine: ```bash gh run view -gh run download -n x64-linux-gnu-releasedbg.tar.xz -D .llvm-download +gh run download -n x86_64-unknown-linux-gnu.releasedbg.tar.xz -D .llvm-download mkdir -p .llvm -tar -xf .llvm-download/x64-linux-gnu-releasedbg.tar.xz -C .llvm +tar -xf .llvm-download/x86_64-unknown-linux-gnu.releasedbg.tar.xz -C .llvm ``` Configure clice to build against it: diff --git a/.github/workflows/build-llvm.yml b/.github/workflows/build-llvm.yml index 9af5f462f..1cff61079 100644 --- a/.github/workflows/build-llvm.yml +++ b/.github/workflows/build-llvm.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: inputs: llvm_version: - description: "LLVM version to build (e.g., 21.1.8)" + description: "LLVM version to build (e.g., 22.1.8)" required: true type: string patch_ref: - description: "clice-llvm ref for patches (default: main). Set to a release tag (e.g., 21.1.8+r2) to reproduce a previous build." + description: "clice-llvm ref for patches (default: main). Set to a release tag (e.g., 22.1.8) to reproduce a previous build." required: false default: "main" type: string @@ -64,12 +64,12 @@ jobs: - os: ubuntu-24.04 llvm_mode: RelWithDebInfo lto: OFF - target_triple: aarch64-linux-gnu + target_triple: aarch64-unknown-linux-gnu pixi_env: cross-linux-arm64 - os: ubuntu-24.04 llvm_mode: RelWithDebInfo lto: ON - target_triple: aarch64-linux-gnu + target_triple: aarch64-unknown-linux-gnu pixi_env: cross-linux-arm64 # Windows arm64 (from x64 windows-2022) @@ -155,17 +155,18 @@ jobs: - name: Clone llvm-project shell: bash + env: + VERSION: ${{ inputs.llvm_version || '22.1.8' }} run: | - VERSION="${{ inputs.llvm_version || '21.1.8' }}" echo "Cloning LLVM ${VERSION}..." git clone --branch "llvmorg-${VERSION}" --depth 1 https://github.com/llvm/llvm-project.git .llvm - name: Apply patches shell: bash + env: + VERSION: ${{ inputs.llvm_version || '22.1.8' }} + REF: ${{ inputs.patch_ref || 'main' }} run: | - VERSION="${{ inputs.llvm_version || '21.1.8' }}" - REF="${{ inputs.patch_ref || 'main' }}" - if git clone --branch "$REF" --depth 1 https://github.com/clice-io/clice-llvm.git .clice-llvm 2>/dev/null; then echo "Using patches from clice-llvm@$REF" else @@ -201,32 +202,21 @@ jobs: - name: Package LLVM install directory shell: bash run: | - # Determine platform/arch from target triple or runner OS if [[ -n "${{ matrix.target_triple }}" ]]; then - case "${{ matrix.target_triple }}" in - x86_64-apple-darwin) PLATFORM="macos"; ARCH="x64" ;; - aarch64-linux-gnu) PLATFORM="linux"; ARCH="arm64" ;; - aarch64-pc-windows-msvc) PLATFORM="windows"; ARCH="arm64" ;; - esac + TRIPLE="${{ matrix.target_triple }}" else - PLATFORM="linux"; ARCH="x64" - if [[ "${{ matrix.os }}" == windows-* ]]; then - PLATFORM="windows" - elif [[ "${{ matrix.os }}" == macos-* ]]; then - PLATFORM="macos"; ARCH="arm64" - fi + case "${{ matrix.os }}" in + ubuntu-*) TRIPLE="x86_64-unknown-linux-gnu" ;; + macos-*) TRIPLE="aarch64-apple-darwin" ;; + windows-*) TRIPLE="x86_64-pc-windows-msvc" ;; + esac fi - case "$PLATFORM" in - linux) TOOLCHAIN="gnu" ;; - macos) TOOLCHAIN="clang" ;; - windows) TOOLCHAIN="msvc" ;; - esac if [[ "${{ matrix.llvm_mode }}" == "Debug" ]]; then MODE_TAG="debug"; else MODE_TAG="releasedbg"; fi SUFFIX="" if [[ "${{ matrix.lto }}" == "ON" ]]; then SUFFIX+="-lto"; fi if [[ "${{ matrix.llvm_mode }}" == "Debug" && "${{ matrix.os }}" != windows-* ]]; then SUFFIX+="-asan"; fi - ARCHIVE="${ARCH}-${PLATFORM}-${TOOLCHAIN}-${MODE_TAG}${SUFFIX}.tar.xz" + ARCHIVE="${TRIPLE}.${MODE_TAG}${SUFFIX}.tar.xz" set -eo pipefail tar -C .llvm/build-install -cf - . | xz -T0 -9 -c > "${ARCHIVE}" diff --git a/.github/workflows/cross-pair.yml b/.github/workflows/cross-pair.yml index 7a0622fa6..949506068 100644 --- a/.github/workflows/cross-pair.yml +++ b/.github/workflows/cross-pair.yml @@ -12,8 +12,8 @@ on: target_triple: required: true type: string - # Unified arch-os-abi name (e.g. arm64-linux-gnu) used for artifact - # naming; the triple above keeps the compiler's spelling. + # Standard triple (e.g. aarch64-unknown-linux-gnu) used for artifact + # naming; matches the compiler triple. target_name: required: true type: string diff --git a/.github/workflows/cross-test.yml b/.github/workflows/cross-test.yml index fa1b9cb24..c2398d6e5 100644 --- a/.github/workflows/cross-test.yml +++ b/.github/workflows/cross-test.yml @@ -3,6 +3,9 @@ name: cross-test on: workflow_call: +permissions: + contents: read + jobs: macos-x64: uses: ./.github/workflows/cross-pair.yml @@ -10,18 +13,18 @@ jobs: build_os: macos-15 test_os: macos-15-intel target_triple: x86_64-apple-darwin - target_name: x64-macos-darwin - asset_name: clice-x64-macos-darwin.tar.gz + target_name: x86_64-apple-darwin + asset_name: clice.x86_64-apple-darwin.tar.gz linux-arm64: uses: ./.github/workflows/cross-pair.yml with: build_os: ubuntu-24.04 test_os: ubuntu-24.04-arm - target_triple: aarch64-linux-gnu - target_name: arm64-linux-gnu + target_triple: aarch64-unknown-linux-gnu + target_name: aarch64-unknown-linux-gnu pixi_env: cross-linux-arm64 - asset_name: clice-arm64-linux-gnu.tar.gz + asset_name: clice.aarch64-unknown-linux-gnu.tar.gz windows-arm64: uses: ./.github/workflows/cross-pair.yml @@ -29,6 +32,6 @@ jobs: build_os: windows-2025 test_os: windows-11-arm target_triple: aarch64-pc-windows-msvc - target_name: arm64-windows-msvc - asset_name: clice-arm64-windows-msvc.zip + target_name: aarch64-pc-windows-msvc + asset_name: clice.aarch64-pc-windows-msvc.zip pixi_env: cross-windows-arm64 diff --git a/.github/workflows/native-test.yml b/.github/workflows/native-test.yml index 541c836a9..cd38dc2d1 100644 --- a/.github/workflows/native-test.yml +++ b/.github/workflows/native-test.yml @@ -21,20 +21,20 @@ jobs: # release artifacts once their test suites pass. - os: windows-2025 build_type: RelWithDebInfo - asset_name: clice-x64-windows-msvc.zip - target_name: x64-windows-msvc + asset_name: clice.x86_64-pc-windows-msvc.zip + target_name: x86_64-pc-windows-msvc - os: ubuntu-24.04 build_type: Debug - os: ubuntu-24.04 build_type: RelWithDebInfo - asset_name: clice-x64-linux-gnu.tar.gz - target_name: x64-linux-gnu + asset_name: clice.x86_64-unknown-linux-gnu.tar.gz + target_name: x86_64-unknown-linux-gnu - os: macos-15 build_type: Debug - os: macos-15 build_type: RelWithDebInfo - asset_name: clice-arm64-macos-darwin.tar.gz - target_name: arm64-macos-darwin + asset_name: clice.aarch64-apple-darwin.tar.gz + target_name: aarch64-apple-darwin runs-on: ${{ matrix.os }} steps: - name: Checkout repository diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1ca0b4456..4782a1a29 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -46,7 +46,7 @@ jobs: for RUN in $(gh run list --repo "$GITHUB_REPOSITORY" --workflow main \ --branch main --event push --status success --limit 100 \ --json databaseId -q '.[].databaseId'); do - LIVE=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN/artifacts?name=package-clice-x64-linux-gnu.tar.gz" \ + LIVE=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN/artifacts?name=package-clice.x86_64-unknown-linux-gnu.tar.gz" \ --jq '[.artifacts[] | select(.expired | not)] | length') if [ "$LIVE" -gt 0 ]; then COMMIT=$(gh run view "$RUN" --repo "$GITHUB_REPOSITORY" \ @@ -130,17 +130,17 @@ jobs: echo "| Platform | Server | VS Code extension | Symbols |" echo "| --- | --- | --- | --- |" row() { - PKG="clice-$2.$3" - SYM="clice-$2.symbols.$4" - VSIX="clice-$5-$V.vsix" + PKG="clice-$V.$2.$3" + SYM="clice-$V.$2.symbols.$4" + VSIX="clice-$V.$2.vsix" echo "| $1 | [$PKG]($URL/$PKG) | [$VSIX]($URL/$VSIX) | [$SYM]($URL/$SYM) |" } - row "Linux x64" x64-linux-gnu tar.gz tar.xz linux-x64 - row "Linux arm64" arm64-linux-gnu tar.gz tar.xz linux-arm64 - row "macOS arm64" arm64-macos-darwin tar.gz tar.xz darwin-arm64 - row "macOS x64" x64-macos-darwin tar.gz tar.xz darwin-x64 - row "Windows x64" x64-windows-msvc zip zip win32-x64 - row "Windows arm64" arm64-windows-msvc zip zip win32-arm64 + row "Linux x64" x86_64-unknown-linux-gnu tar.gz tar.xz + row "Linux arm64" aarch64-unknown-linux-gnu tar.gz tar.xz + row "macOS arm64" aarch64-apple-darwin tar.gz tar.xz + row "macOS x64" x86_64-apple-darwin tar.gz tar.xz + row "Windows x64" x86_64-pc-windows-msvc zip zip + row "Windows arm64" aarch64-pc-windows-msvc zip zip echo echo "Symbol packages hold GSYM data for crash log symbolization" echo "via \`scripts/symbolize.py\`. VS Code users on the Marketplace" diff --git a/.github/workflows/publish-clice.yml b/.github/workflows/publish-clice.yml index b92d8d5c0..f0509c397 100644 --- a/.github/workflows/publish-clice.yml +++ b/.github/workflows/publish-clice.yml @@ -42,12 +42,24 @@ jobs: fi # A green run may still have nothing to promote: path-filtered # runs (e.g. docs-only commits) skip the build jobs, and package - # artifacts expire after 30 days. Fail here with one clear message - # instead of in six download jobs. - LIVE=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts?name=package-clice-x64-linux-gnu.tar.gz" \ - --jq '[.artifacts[] | select(.expired | not)] | length') - if [ "$LIVE" -eq 0 ]; then - echo "::error::run $RUN_ID for $TAG has no live package artifacts (path-filtered build, or expired after 30 days) — rerun that commit's main workflow first" + # artifacts expire after 30 days. Check every package here so a + # partial run fails with one clear message before any asset is + # uploaded, instead of promoting an incomplete release. + MISSING="" + for NAME in package-clice.x86_64-unknown-linux-gnu.tar.gz \ + package-clice.aarch64-unknown-linux-gnu.tar.gz \ + package-clice.aarch64-apple-darwin.tar.gz \ + package-clice.x86_64-apple-darwin.tar.gz \ + package-clice.x86_64-pc-windows-msvc.zip \ + package-clice.aarch64-pc-windows-msvc.zip; do + LIVE=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts?name=$NAME" \ + --jq '[.artifacts[] | select(.expired | not)] | length') + if [ "$LIVE" -eq 0 ]; then + MISSING="$MISSING $NAME" + fi + done + if [ -n "$MISSING" ]; then + echo "::error::run $RUN_ID for $TAG is missing live package artifacts:$MISSING (path-filtered build, or expired after 30 days) — rerun that commit's main workflow first" exit 1 fi echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" @@ -70,18 +82,24 @@ jobs: fail-fast: false matrix: include: - - asset_name: clice-x64-linux-gnu.tar.gz - symbol_asset_name: clice-x64-linux-gnu.symbols.tar.xz - - asset_name: clice-arm64-linux-gnu.tar.gz - symbol_asset_name: clice-arm64-linux-gnu.symbols.tar.xz - - asset_name: clice-arm64-macos-darwin.tar.gz - symbol_asset_name: clice-arm64-macos-darwin.symbols.tar.xz - - asset_name: clice-x64-macos-darwin.tar.gz - symbol_asset_name: clice-x64-macos-darwin.symbols.tar.xz - - asset_name: clice-x64-windows-msvc.zip - symbol_asset_name: clice-x64-windows-msvc.symbols.zip - - asset_name: clice-arm64-windows-msvc.zip - symbol_asset_name: clice-arm64-windows-msvc.symbols.zip + - triple: x86_64-unknown-linux-gnu + ext: tar.gz + symbol_ext: tar.xz + - triple: aarch64-unknown-linux-gnu + ext: tar.gz + symbol_ext: tar.xz + - triple: aarch64-apple-darwin + ext: tar.gz + symbol_ext: tar.xz + - triple: x86_64-apple-darwin + ext: tar.gz + symbol_ext: tar.xz + - triple: x86_64-pc-windows-msvc + ext: zip + symbol_ext: zip + - triple: aarch64-pc-windows-msvc + ext: zip + symbol_ext: zip runs-on: ubuntu-latest permissions: contents: write @@ -92,35 +110,40 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh run download "${{ needs.locate.outputs.run_id }}" \ - --repo "$GITHUB_REPOSITORY" -n "package-${{ matrix.asset_name }}" -D pkg + --repo "$GITHUB_REPOSITORY" -n "package-clice.${{ matrix.triple }}.${{ matrix.ext }}" -D pkg - name: Upload assets to the release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ inputs.release_tag }} run: | + VERSION="${RELEASE_TAG#v}" + ASSET="clice-${VERSION}.${{ matrix.triple }}.${{ matrix.ext }}" + SYMBOL_ASSET="clice-${VERSION}.${{ matrix.triple }}.symbols.${{ matrix.symbol_ext }}" cd pkg - mv clice.tar.gz "${{ matrix.asset_name }}" 2>/dev/null || mv clice.zip "${{ matrix.asset_name }}" - mv clice-symbol.tar.xz "${{ matrix.symbol_asset_name }}" 2>/dev/null \ - || mv clice-symbol.zip "${{ matrix.symbol_asset_name }}" + mv clice.tar.gz "$ASSET" 2>/dev/null || mv clice.zip "$ASSET" + mv clice-symbol.tar.xz "$SYMBOL_ASSET" 2>/dev/null \ + || mv clice-symbol.zip "$SYMBOL_ASSET" gh release upload "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \ - --clobber "${{ matrix.asset_name }}" "${{ matrix.symbol_asset_name }}" + --clobber "$ASSET" "$SYMBOL_ASSET" - # Bridge for publish-vscode.yml: make the package available as an - # artifact of the current run, with the original inner file names the + # Bridge for publish-vscode.yml: restore the inner file names the # staging step expects. - name: Restore inner file names + env: + RELEASE_TAG: ${{ inputs.release_tag }} run: | + VERSION="${RELEASE_TAG#v}" cd pkg - case "${{ matrix.asset_name }}" in - *.zip) mv "${{ matrix.asset_name }}" clice.zip ;; - *) mv "${{ matrix.asset_name }}" clice.tar.gz ;; + case "${{ matrix.ext }}" in + zip) mv "clice-${VERSION}.${{ matrix.triple }}.zip" clice.zip ;; + *) mv "clice-${VERSION}.${{ matrix.triple }}.tar.gz" clice.tar.gz ;; esac - name: Re-upload package artifact uses: actions/upload-artifact@v7 with: - name: package-${{ matrix.asset_name }} + name: package-clice.${{ matrix.triple }}.${{ matrix.ext }} path: | pkg/clice.tar.gz pkg/clice.zip diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index f99057b09..24faa7291 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -52,23 +52,29 @@ jobs: matrix: include: - vsce_target: linux-x64 - package_artifact: package-clice-x64-linux-gnu.tar.gz + triple: x86_64-unknown-linux-gnu + ext: tar.gz build_artifact: native-build-ubuntu-24.04-RelWithDebInfo - vsce_target: linux-arm64 - package_artifact: package-clice-arm64-linux-gnu.tar.gz - build_artifact: cross-build-arm64-linux-gnu + triple: aarch64-unknown-linux-gnu + ext: tar.gz + build_artifact: cross-build-aarch64-unknown-linux-gnu - vsce_target: darwin-arm64 - package_artifact: package-clice-arm64-macos-darwin.tar.gz + triple: aarch64-apple-darwin + ext: tar.gz build_artifact: native-build-macos-15-RelWithDebInfo - vsce_target: darwin-x64 - package_artifact: package-clice-x64-macos-darwin.tar.gz - build_artifact: cross-build-x64-macos-darwin + triple: x86_64-apple-darwin + ext: tar.gz + build_artifact: cross-build-x86_64-apple-darwin - vsce_target: win32-x64 - package_artifact: package-clice-x64-windows-msvc.zip + triple: x86_64-pc-windows-msvc + ext: zip build_artifact: native-build-windows-2025-RelWithDebInfo - vsce_target: win32-arm64 - package_artifact: package-clice-arm64-windows-msvc.zip - build_artifact: cross-build-arm64-windows-msvc + triple: aarch64-pc-windows-msvc + ext: zip + build_artifact: cross-build-aarch64-pc-windows-msvc runs-on: ubuntu-latest @@ -86,7 +92,7 @@ jobs: - name: Download server binaries uses: actions/download-artifact@v8 with: - name: ${{ inputs.source == 'package' && matrix.package_artifact || matrix.build_artifact }} + name: ${{ inputs.source == 'package' && format('package-clice.{0}.{1}', matrix.triple, matrix.ext) || matrix.build_artifact }} path: /tmp/server # Both sources end up as editors/vscode/clice/{bin,lib/clang,...}, the @@ -134,9 +140,15 @@ jobs: fi - name: Package platform extension + env: + RELEASE_TAG: ${{ inputs.release_tag || (startsWith(github.ref, 'refs/tags/v') && github.ref_name || '') }} run: | FLAG="${{ (inputs.prerelease || contains(github.ref_name, '-')) && '--pre-release' || '' }}" - pixi run build-vscode $FLAG --target ${{ matrix.vsce_target }} + OUT=() + if [ -n "$RELEASE_TAG" ]; then + OUT=(-o "clice-${RELEASE_TAG#v}.${{ matrix.triple }}.vsix") + fi + pixi run build-vscode $FLAG --target ${{ matrix.vsce_target }} "${OUT[@]}" - name: Upload vsix artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/release-llvm.yml b/.github/workflows/release-llvm.yml index e229f8431..27dfdb430 100644 --- a/.github/workflows/release-llvm.yml +++ b/.github/workflows/release-llvm.yml @@ -27,11 +27,11 @@ jobs: matrix: include: - os: ubuntu-24.04 - artifact: x64-linux-gnu-releasedbg.tar.xz + artifact: x86_64-unknown-linux-gnu.releasedbg.tar.xz - os: macos-15 - artifact: arm64-macos-clang-releasedbg.tar.xz + artifact: aarch64-apple-darwin.releasedbg.tar.xz - os: windows-2025 - artifact: x64-windows-msvc-releasedbg.tar.xz + artifact: x86_64-pc-windows-msvc.releasedbg.tar.xz runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 @@ -127,20 +127,20 @@ jobs: fail-fast: false matrix: artifact: - - arm64-linux-gnu-releasedbg-lto.tar.xz - - arm64-linux-gnu-releasedbg.tar.xz - - arm64-windows-msvc-releasedbg-lto.tar.xz - - arm64-windows-msvc-releasedbg.tar.xz - - arm64-macos-clang-debug-asan.tar.xz - - arm64-macos-clang-releasedbg-lto.tar.xz - - arm64-macos-clang-releasedbg.tar.xz - - x64-linux-gnu-debug-asan.tar.xz - - x64-linux-gnu-releasedbg-lto.tar.xz - - x64-linux-gnu-releasedbg.tar.xz - - x64-macos-clang-releasedbg-lto.tar.xz - - x64-macos-clang-releasedbg.tar.xz - - x64-windows-msvc-releasedbg-lto.tar.xz - - x64-windows-msvc-releasedbg.tar.xz + - aarch64-unknown-linux-gnu.releasedbg-lto.tar.xz + - aarch64-unknown-linux-gnu.releasedbg.tar.xz + - aarch64-pc-windows-msvc.releasedbg-lto.tar.xz + - aarch64-pc-windows-msvc.releasedbg.tar.xz + - aarch64-apple-darwin.debug-asan.tar.xz + - aarch64-apple-darwin.releasedbg-lto.tar.xz + - aarch64-apple-darwin.releasedbg.tar.xz + - x86_64-unknown-linux-gnu.debug-asan.tar.xz + - x86_64-unknown-linux-gnu.releasedbg-lto.tar.xz + - x86_64-unknown-linux-gnu.releasedbg.tar.xz + - x86_64-apple-darwin.releasedbg-lto.tar.xz + - x86_64-apple-darwin.releasedbg.tar.xz + - x86_64-pc-windows-msvc.releasedbg-lto.tar.xz + - x86_64-pc-windows-msvc.releasedbg.tar.xz steps: - uses: actions/checkout@v7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d652d24c..02fbb8e2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,27 +173,10 @@ add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}") # Version header, regenerated on every build so the embedded git describe # tracks the checked-out commit (content-unchanged writes are elided). set(CLICE_VERSION_HEADER "${PROJECT_BINARY_DIR}/generated/version.h") -# Target identifier stamped next to the version: the explicit triple for -# cross builds (see cmake/toolchain.cmake); native builds compose the same -# arch-os-abi form the release asset names use, so a crash log names the -# exact downloadable artifact. -if(DEFINED CLICE_TARGET_TRIPLE) - string(TOLOWER "${CLICE_TARGET_TRIPLE}" CLICE_TARGET_RAW) -else() - string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" CLICE_TARGET_RAW) -endif() -if(CLICE_TARGET_RAW MATCHES "^(x86_64|amd64|x64)") - set(CLICE_TARGET_ARCH "x64") -else() - set(CLICE_TARGET_ARCH "arm64") -endif() -if(CLICE_TARGET_RAW MATCHES "windows") - set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-windows-msvc") -elseif(CLICE_TARGET_RAW MATCHES "darwin|apple|macos") - set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-macos-darwin") -else() - set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-linux-gnu") -endif() +# Target identifier stamped next to the version: the same canonical +# triple the release asset names use, so a crash log names the exact +# downloadable artifact. +clice_target_triple(CLICE_TARGET_STRING) add_custom_target(generate_version_header COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${PROJECT_SOURCE_DIR} diff --git a/cmake/llvm.cmake b/cmake/llvm.cmake index 2f5b1e2cb..4d14a1779 100644 --- a/cmake/llvm.cmake +++ b/cmake/llvm.cmake @@ -1,49 +1,35 @@ include_guard() include(FetchContent) -function(_download_llvm LLVM_VERSION) +# Canonical target triple: the explicit CLICE_TARGET_TRIPLE for cross +# builds, composed from the host otherwise. This exact spelling names the +# prebuilt LLVM archives and the clice release assets. +function(clice_target_triple OUT_VAR) if(DEFINED CLICE_TARGET_TRIPLE) - if(CLICE_TARGET_TRIPLE MATCHES "linux") - set(_PLATFORM "linux") - set(_TOOLCHAIN "gnu") - elseif(CLICE_TARGET_TRIPLE MATCHES "darwin") - set(_PLATFORM "macos") - set(_TOOLCHAIN "clang") - elseif(CLICE_TARGET_TRIPLE MATCHES "windows") - set(_PLATFORM "windows") - set(_TOOLCHAIN "msvc") - else() - message(FATAL_ERROR "Unsupported platform: ${CLICE_TARGET_TRIPLE}") - endif() - - if(CLICE_TARGET_TRIPLE MATCHES "^aarch64") - set(_ARCH "arm64") - elseif(CLICE_TARGET_TRIPLE MATCHES "^x86_64") - set(_ARCH "x64") - else() - message(FATAL_ERROR "Unsupported arch: ${CLICE_TARGET_TRIPLE}") - endif() + set(${OUT_VAR} "${CLICE_TARGET_TRIPLE}" PARENT_SCOPE) + return() + endif() + + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64") + set(_ARCH "aarch64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64") + set(_ARCH "x86_64") else() - if(WIN32) - set(_PLATFORM "windows") - set(_TOOLCHAIN "msvc") - elseif(APPLE) - set(_PLATFORM "macos") - set(_TOOLCHAIN "clang") - else() - set(_PLATFORM "linux") - set(_TOOLCHAIN "gnu") - endif() - - if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|ARM64") - set(_ARCH "arm64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64") - set(_ARCH "x64") - else() - message(FATAL_ERROR "Unsupported processor: ${CMAKE_SYSTEM_PROCESSOR}") - endif() + message(FATAL_ERROR "Unsupported processor: ${CMAKE_SYSTEM_PROCESSOR}") endif() + if(WIN32) + set(${OUT_VAR} "${_ARCH}-pc-windows-msvc" PARENT_SCOPE) + elseif(APPLE) + set(${OUT_VAR} "${_ARCH}-apple-darwin" PARENT_SCOPE) + else() + set(${OUT_VAR} "${_ARCH}-unknown-linux-gnu" PARENT_SCOPE) + endif() +endfunction() + +function(_download_llvm LLVM_VERSION) + clice_target_triple(_TRIPLE) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_MODE "debug") else() @@ -58,7 +44,7 @@ function(_download_llvm LLVM_VERSION) string(APPEND _SUFFIX "-asan") endif() - set(_FILENAME "${_ARCH}-${_PLATFORM}-${_TOOLCHAIN}-${_MODE}${_SUFFIX}.tar.xz") + set(_FILENAME "${_TRIPLE}.${_MODE}${_SUFFIX}.tar.xz") string(REPLACE "+" "%2B" _URL_VERSION "${LLVM_VERSION}") FetchContent_Declare(llvm_prebuilt @@ -93,7 +79,7 @@ function(setup_llvm LLVM_VERSION) target_link_libraries(llvm-libs INTERFACE ${LLVM_RESOLVED} clangAST clangASTMatchers clangBasic clangDriver - clangFormat clangFrontend clangLex clangSema clangSerialization + clangFormat clangFrontend clangLex clangOptions clangSema clangSerialization clangTidy clangTidyUtils clangTidyAbseilModule clangTidyAlteraModule clangTidyAndroidModule clangTidyBoostModule clangTidyBugproneModule clangTidyCERTModule diff --git a/cmake/package.cmake b/cmake/package.cmake index d6331a82f..389fd78e5 100644 --- a/cmake/package.cmake +++ b/cmake/package.cmake @@ -1,7 +1,7 @@ include_guard() include(${CMAKE_CURRENT_LIST_DIR}/llvm.cmake) -setup_llvm("21.1.8+r2") +setup_llvm("22.1.8") # install dependencies include(FetchContent) diff --git a/cmake/toolchain.cmake b/cmake/toolchain.cmake index e55e66f11..e85409a4e 100644 --- a/cmake/toolchain.cmake +++ b/cmake/toolchain.cmake @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.30) # Cross-compilation support via CLICE_TARGET_TRIPLE. # Examples: -# -DCLICE_TARGET_TRIPLE=x86_64-apple-darwin (macOS x64 from arm64) -# -DCLICE_TARGET_TRIPLE=aarch64-linux-gnu (Linux arm64 from x64) +# -DCLICE_TARGET_TRIPLE=x86_64-apple-darwin (macOS x64 from arm64) +# -DCLICE_TARGET_TRIPLE=aarch64-unknown-linux-gnu (Linux arm64 from x64) # -DCLICE_TARGET_TRIPLE=aarch64-pc-windows-msvc (Windows arm64 from x64) if(DEFINED CLICE_TARGET_TRIPLE) if(CLICE_TARGET_TRIPLE MATCHES "^x86_64-apple-darwin") @@ -11,8 +11,8 @@ if(DEFINED CLICE_TARGET_TRIPLE) elseif(CLICE_TARGET_TRIPLE MATCHES "^aarch64-.*linux") set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) - set(CMAKE_C_COMPILER_TARGET "aarch64-linux-gnu" CACHE STRING "") - set(CMAKE_CXX_COMPILER_TARGET "aarch64-linux-gnu" CACHE STRING "") + set(CMAKE_C_COMPILER_TARGET "aarch64-unknown-linux-gnu" CACHE STRING "") + set(CMAKE_CXX_COMPILER_TARGET "aarch64-unknown-linux-gnu" CACHE STRING "") if(DEFINED ENV{CONDA_PREFIX} AND NOT DEFINED CMAKE_SYSROOT) set(CMAKE_SYSROOT "$ENV{CONDA_PREFIX}/aarch64-conda-linux-gnu/sysroot" CACHE PATH "") endif() @@ -71,11 +71,7 @@ if(WIN32) set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "") set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "") endif() - # TODO(prebuilt-respin): switch to "MultiThreaded" (/MT). /MD makes the exe - # import MSVCP140.dll and VCRUNTIME140.dll, which come from the Visual C++ - # redistributable rather than from Windows. The CRT is baked into every - # object, so the prebuilt LLVM must be respun to /MT in the same change. - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded" CACHE STRING "") set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld-link") set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld-link") set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld-link") @@ -91,10 +87,7 @@ else() endif() if(APPLE) - # TODO(prebuilt-respin): set an explicit CMAKE_OSX_DEPLOYMENT_TARGET. With - # none it follows the build machine, so the artifact is stamped minos 15.0 - # and will not launch on macOS 14 or older. Lowering it needs the prebuilt - # built against the same target. + set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "") # conda-forge clang 22's bundled config files (-clang++.cfg) # inject -L/-rpath pointing into the conda env at link time, binding @@ -107,14 +100,4 @@ if(APPLE) string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " --no-default-config") string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT " --no-default-config") string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT " --no-default-config") - - # Debug links the prebuilt LLVM ASan dylibs, which reference conda's - # @rpath libc++ with rpaths baked for the machine that built them. - # Debug binaries are CI-internal, so resolve libc++ from the build - # env instead. TODO(prebuilt-respin): remove once the prebuilt is - # respun — its dylibs will then link the system libc++ above. - if(DEFINED ENV{CONDA_PREFIX}) - string(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT " -Wl,-rpath,$ENV{CONDA_PREFIX}/lib") - string(APPEND CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT " -Wl,-rpath,$ENV{CONDA_PREFIX}/lib") - endif() endif() diff --git a/docs/en/changelog/llvm-changelog.md b/docs/en/changelog/llvm-changelog.md index efcfd93c3..467563aea 100644 --- a/docs/en/changelog/llvm-changelog.md +++ b/docs/en/changelog/llvm-changelog.md @@ -1,3 +1,35 @@ # LLVM Changelog Breaking changes encountered during each LLVM upgrade, with upstream commit references. + +## LLVM 21 → 22 + +### Type System + +| Change | Commit | PR | Impact on clice | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `NestedNameSpecifier` redesigned as a value type; `ElaboratedType` removed — tag/typedef/using/template-specialization types now carry the elaborated keyword and qualifier themselves. NNS kinds reduced to `Null`/`Global`/`Type`/`Namespace`/`MicrosoftSuper`; the old `Identifier` kind is now a `DependentNameType` component of kind `Type`. | `91cdd35008e9` | [#147835](https://github.com/llvm/llvm-project/pull/147835) | Rewrote NNS handling in the template resolver, semantic visitor, unifier, display and USR generation. `rewrite_specifier` collapsed to a single type-component rewrite; NNS occurrence handling dropped the `Identifier`/`NamespaceAlias` cases (dependent chains are now visited as `DependentNameTypeLoc`s). | +| `DependentTemplateSpecializationType` removed; a dependent specialization is a `TemplateSpecializationType` whose `TemplateName` is a `DependentTemplateName`. `ASTContext::getDependentTemplateSpecializationType` gone; `getTemplateSpecializationType` takes an `ElaboratedTypeKeyword` first parameter. | `ba9d1c41c41d` | [#158109](https://github.com/llvm/llvm-project/pull/158109) | Resolver's DTST lookup/rewrite/pseudo-SFINAE paths merged into the TST paths, keyed on `getAsDependentTemplateName()`. | +| `InjectedClassNameType` is now a `TagType` subclass and no longer stores the injected specialization; use `CXXRecordDecl::getCanonicalTemplateSpecializationType(Ctx)`. `TagDecl::getTypeForDecl` deleted; use `ASTContext::getCanonicalTagType`. | `91cdd35008e9` | [#147835](https://github.com/llvm/llvm-project/pull/147835) | Resolver, unifier, USR generation and hover layout info migrated to the canonical accessors. | +| `NamespaceBaseDecl` introduced; a `Namespace` NNS component stores it (covers both namespaces and namespace aliases). | `4a9eaad9e128` | [#149123](https://github.com/llvm/llvm-project/pull/149123) | Namespace lookup goes through `getAsNamespaceAndPrefix().Namespace->getNamespace()`; alias occurrences reference the base decl directly. | +| `UsingType::getFoundDecl`/`getUnderlyingType` renamed to `getDecl`/`desugar`; `TypedefTypeLoc::getTypedefNameDecl` is `getDecl`. | `91cdd35008e9` | [#147835](https://github.com/llvm/llvm-project/pull/147835) | Mechanical renames in unifier, resolver and semantic visitor. | +| Implicit variable template specializations no longer store written template arguments. | `1cb47c19f8ec` | [#156329](https://github.com/llvm/llvm-project/pull/156329) | `display::template_args` falls back to the converted argument list, so hover titles print `baz>` (converted, qualified) instead of the as-written form. | +| `RecursiveASTVisitor` type traversals take a `TraverseQualifier` flag; `DynTypedNode::getSourceRange` takes `IncludeQualifier` (default excludes the qualifier of qualified type locs). | `91cdd35008e9` | [#147835](https://github.com/llvm/llvm-project/pull/147835) | Semantic visitor overrides updated; selection-tree node ranges now exclude qualifiers (matching clangd), so a cursor on a dependent name selects its `DependentNameTypeLoc` name token. | + +### Driver / Frontend + +| Change | Commit | PR | Impact on clice | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| Option handling split out of clangDriver into a clangOptions library: `clang/Driver/Options.inc` → `clang/Options/Options.inc`, `GetResourcesPath` → `clang/Options/OptionUtils.h` (namespace `clang`). | `f63d33da0a51` | [#167374](https://github.com/llvm/llvm-project/pull/167374) | Include-path and call-site updates in the argument parser. | +| `OptTable` gained subcommand support: the `OPTION` record has a trailing `SUBCOMMANDIDS_OFFSET` column. | `fdbd17d1fb0d` | [#155026](https://github.com/llvm/llvm-project/pull/155026) | The `OPTION` macros expanding `Options.inc` take the extra parameter. | +| `clang::createInvocation` moved to `clang/Driver/CreateInvocationFromArgs.h`. | `d090311aa7df` | [#169599](https://github.com/llvm/llvm-project/pull/169599) | Include updates in compilation and scan. | +| `CompilerInstance` owns its VFS explicitly: seed it with `setVirtualFileSystem`/`createVirtualFileSystem` (the latter subsumes `createVFSFromCompilerInvocation`), then `createDiagnostics(Client, ShouldOwnClient)` and `createFileManager()` take no VFS. | `30633f308941` | [#158381](https://github.com/llvm/llvm-project/pull/158381) | Compilation and scan set the VFS on the instance before creating diagnostics and the file manager. | + +### Other + +| Change | Commit | PR | Impact on clice | +| --------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `llvm::sys::fs::make_absolute(current_directory, path)` moved to `llvm::sys::path::make_absolute`. | `f122484b998d` | [#161459](https://github.com/llvm/llvm-project/pull/161459) | Call-site update in header search config. | +| `size_t` and friends are named sugar types (`__size_t`), so e.g. `operator new`'s parameter prints as `__size_t` with an `aka unsigned long`. | `7c402b8b81d2` | [#149613](https://github.com/llvm/llvm-project/pull/149613) | Hover snapshots updated. | +| Anonymous tag types print as `(unnamed enum)`/`(unnamed struct)` uniformly. | `4ef641916408` | [#169445](https://github.com/llvm/llvm-project/pull/169445) | Hover snapshots updated. | +| `PrintingPolicy::SuppressScope` now also drops written qualifiers of template names, and canonical tag types print fully qualified. | `91cdd35008e9` | [#147835](https://github.com/llvm/llvm-project/pull/147835) | Inlay type hints lose nested-class scopes (`Nested` instead of `S2::Nested`, matching clangd); hover of `this` prints the namespace-qualified class type. | diff --git a/docs/en/features/document-symbols.md b/docs/en/features/document-symbols.md index 0da7b35eb..a2d92235b 100644 --- a/docs/en/features/document-symbols.md +++ b/docs/en/features/document-symbols.md @@ -382,6 +382,34 @@ Provides the file outline and breadcrumb navigation via `textDocument/documentSy ) {} ``` +- [x] Scoped types — a written class scope appears in the detail exactly once, for nested classes, template-ids, aliases and dependent names alike + + ```cpp + namespace scoped { + + struct Outer { + struct Inner {}; + template struct Box {}; + using Alias = int; + }; + + struct User { + Outer::Inner plain; + Outer::Box boxed; + Outer::Alias aliased; + const Outer::Inner frozen; + }; + + template + struct Holder { + typename T::type value; + typename T::inner::type deep; + typename T::template rebind bound; + }; + + } // namespace scoped + ``` + ## Missing Symbols diff --git a/scripts/build-llvm.py b/scripts/build-llvm.py index f532f9164..d81fd6dec 100644 --- a/scripts/build-llvm.py +++ b/scripts/build-llvm.py @@ -103,7 +103,7 @@ def main(): ) parser.add_argument( "--target-triple", - help="Cross-compilation target triple (e.g. x86_64-apple-darwin, aarch64-linux-gnu, aarch64-pc-windows-msvc)", + help="Cross-compilation target triple (e.g. x86_64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-pc-windows-msvc)", ) args = parser.parse_args() @@ -182,6 +182,7 @@ def main(): f"-DCMAKE_C_FLAGS={c_flags}", f"-DCMAKE_CXX_FLAGS={c_flags}", "-DLLVM_USE_LINKER=lld-link", + "-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", ] else: cxx_flags = "-w" diff --git a/scripts/check_artifact_deps.py b/scripts/check_artifact_deps.py index ab2d9dcf9..b12e998a0 100644 --- a/scripts/check_artifact_deps.py +++ b/scripts/check_artifact_deps.py @@ -13,10 +13,9 @@ Both branches use LLVM binutils (``llvm-otool`` / ``llvm-readelf``), which ship in the build env and can read Mach-O even from a Linux host. -TODO: read PE too. The Windows artifacts import ``MSVCP140.dll`` and -``VCRUNTIME140.dll``, which come from the Visual C++ redistributable rather than -from Windows; treat those as violations once the build switches to a static CRT -(see TODO(prebuilt-respin) in cmake/toolchain.cmake). +TODO: read PE too. With the /MT switch (static CRT), the Windows artifacts +should no longer import ``MSVCP140.dll`` or ``VCRUNTIME140.dll``; add PE +support to verify this. """ import argparse @@ -25,7 +24,7 @@ import sys from pathlib import Path -# Linux NEEDED whitelist, derived from the real packaged x64-linux-gnu binary. +# Linux NEEDED whitelist, derived from the real packaged x86_64-unknown-linux-gnu binary. # clice statically links libstdc++/libgcc, so a portable build only pulls in the # glibc runtime pieces. Notably libstdc++.so.6 / libc++.so are absent: if either # ever appears it would resolve from the conda RUNPATH (see check_elf), which is diff --git a/scripts/release-llvm.py b/scripts/release-llvm.py index 052dd81c3..f9c5ac57f 100644 --- a/scripts/release-llvm.py +++ b/scripts/release-llvm.py @@ -23,20 +23,20 @@ from typing import Iterable, List, Optional ARTIFACTS = [ - "arm64-linux-gnu-releasedbg-lto.tar.xz", - "arm64-linux-gnu-releasedbg.tar.xz", - "arm64-windows-msvc-releasedbg-lto.tar.xz", - "arm64-windows-msvc-releasedbg.tar.xz", - "arm64-macos-clang-debug-asan.tar.xz", - "arm64-macos-clang-releasedbg-lto.tar.xz", - "arm64-macos-clang-releasedbg.tar.xz", - "x64-linux-gnu-debug-asan.tar.xz", - "x64-linux-gnu-releasedbg-lto.tar.xz", - "x64-linux-gnu-releasedbg.tar.xz", - "x64-macos-clang-releasedbg-lto.tar.xz", - "x64-macos-clang-releasedbg.tar.xz", - "x64-windows-msvc-releasedbg-lto.tar.xz", - "x64-windows-msvc-releasedbg.tar.xz", + "aarch64-unknown-linux-gnu.releasedbg-lto.tar.xz", + "aarch64-unknown-linux-gnu.releasedbg.tar.xz", + "aarch64-pc-windows-msvc.releasedbg-lto.tar.xz", + "aarch64-pc-windows-msvc.releasedbg.tar.xz", + "aarch64-apple-darwin.debug-asan.tar.xz", + "aarch64-apple-darwin.releasedbg-lto.tar.xz", + "aarch64-apple-darwin.releasedbg.tar.xz", + "x86_64-unknown-linux-gnu.debug-asan.tar.xz", + "x86_64-unknown-linux-gnu.releasedbg-lto.tar.xz", + "x86_64-unknown-linux-gnu.releasedbg.tar.xz", + "x86_64-apple-darwin.releasedbg-lto.tar.xz", + "x86_64-apple-darwin.releasedbg.tar.xz", + "x86_64-pc-windows-msvc.releasedbg-lto.tar.xz", + "x86_64-pc-windows-msvc.releasedbg.tar.xz", ] ARCHIVE_MAGIC = b"!\n" @@ -82,9 +82,9 @@ def _run_build(build_dir: Path) -> bool: def _manifest_for(artifact: str, manifests_dir: Path) -> Optional[Path]: manifest_dirs = { - "linux": "prune-manifest-ubuntu-24.04", - "macos": "prune-manifest-macos-15", - "windows": "prune-manifest-windows-2025", + "linux-gnu": "prune-manifest-ubuntu-24.04", + "apple-darwin": "prune-manifest-macos-15", + "windows-msvc": "prune-manifest-windows-2025", } for platform, dirname in manifest_dirs.items(): if platform in artifact: diff --git a/src/command/argument_parser.cpp b/src/command/argument_parser.cpp index 3e7496211..e5263c41d 100644 --- a/src/command/argument_parser.cpp +++ b/src/command/argument_parser.cpp @@ -12,8 +12,8 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/xxhash.h" -#include "clang/Driver/Driver.h" #include "clang/Driver/Types.h" +#include "clang/Options/OptionUtils.h" namespace clice { @@ -24,11 +24,11 @@ namespace eo = kota::option; namespace detail { #define OPTTABLE_STR_TABLE_CODE -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTTABLE_STR_TABLE_CODE #define OPTTABLE_PREFIXES_TABLE_CODE -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTTABLE_PREFIXES_TABLE_CODE static_assert(OptionPrefixesTable[0].value() == 0, "pfx_none: 0 prefixes"); @@ -89,7 +89,8 @@ const eo::OptTable& table() { HELP, \ HELP_TEXTS, \ META_VAR, \ - VALUES) \ + VALUES, \ + SUBCOMMAND_IDS_OFFSET) \ eo::Option{ \ .prefixes = prefixes(PREFIXES_OFFSET), \ .prefixed_name = str_at(NAME_OFFSET), \ @@ -104,7 +105,7 @@ const eo::OptTable& table() { .help_text = HELP, \ .meta_var = META_VAR, \ }, -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTION }; @@ -204,7 +205,7 @@ llvm::StringRef resource_dir() { if(exe.empty()) { return std::string{}; } - return clang::driver::Driver::GetResourcesPath(exe); + return clang::GetResourcesPath(exe); }(); return dir; } diff --git a/src/command/argument_parser.h b/src/command/argument_parser.h index 3a1b4b29d..d25b24679 100644 --- a/src/command/argument_parser.h +++ b/src/command/argument_parser.h @@ -28,9 +28,10 @@ enum ID : unsigned { HELP, \ HELP_TEXTS, \ META_VAR, \ - VALUES) \ + VALUES, \ + SUBCOMMAND_IDS_OFFSET) \ OPT_##ID, -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTION }; diff --git a/src/command/search_config.cpp b/src/command/search_config.cpp index fa1b71ef4..426586bb7 100644 --- a/src/command/search_config.cpp +++ b/src/command/search_config.cpp @@ -25,7 +25,7 @@ SearchConfig extract_search_config(llvm::ArrayRef arguments, auto make_absolute = [&](std::string_view 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::sys::path::make_absolute(directory, abs_path); } llvm::sys::path::remove_dots(abs_path, true); return abs_path.str().str(); diff --git a/src/compile/compilation.cpp b/src/compile/compilation.cpp index 457d03ec7..dc84f61f1 100644 --- a/src/compile/compilation.cpp +++ b/src/compile/compilation.cpp @@ -9,6 +9,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/xxhash.h" #include "clang/Basic/Stack.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/MultiplexConsumer.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Lex/PreprocessorOptions.h" @@ -249,13 +250,9 @@ CompilationStatus CompilationUnitRef::Self::run_clang( self.instance = std::make_unique(std::move(invocation)); auto& instance = *self.instance; - instance.createDiagnostics(*params.vfs, diagnostic_consumer.release(), true); - - if(auto remapping = clang::createVFSFromCompilerInvocation(instance.getInvocation(), - instance.getDiagnostics(), - params.vfs)) { - instance.createFileManager(std::move(remapping)); - } + instance.createVirtualFileSystem(params.vfs, diagnostic_consumer.get()); + instance.createDiagnostics(diagnostic_consumer.release(), true); + instance.createFileManager(); if(!instance.createTarget()) { return CompilationStatus::SetupFail; diff --git a/src/feature/hover.cpp b/src/feature/hover.cpp index b36bb98e3..5c02ac852 100644 --- a/src/feature/hover.cpp +++ b/src/feature/hover.cpp @@ -349,9 +349,14 @@ auto decl_hover(const clang::NamedDecl* decl, } else if(const auto* var_template = llvm::dyn_cast(decl)) { info.type = display::type(context, var_template->getTemplatedDecl()->getType(), options); } else if(const auto* typedef_decl = llvm::dyn_cast(decl)) { - info.type = display::type(context, - typedef_decl->getUnderlyingType().getDesugaredType(context), - options); + /// TagType is not sugar, so desugaring would stop at the + /// as-written node; canonicalize to render the underlying type + /// fully qualified. Dependent types keep their sugar — their + /// canonical form spells parameters as `type-parameter-N-M`. + auto underlying = typedef_decl->getUnderlyingType(); + underlying = underlying->isDependentType() ? underlying.getDesugaredType(context) + : context.getCanonicalType(underlying); + info.type = display::type(context, underlying, options); } else if(const auto* alias_template = llvm::dyn_cast(decl)) { info.type = display::type(context, alias_template->getTemplatedDecl()->getUnderlyingType(), @@ -690,11 +695,12 @@ void add_layout_info(const clang::NamedDecl& decl, HoverInfo& info) { const auto& context = decl.getASTContext(); if(auto* record = llvm::dyn_cast(&decl)) { - if(auto size = context.getTypeSizeInCharsIfKnown(record->getTypeForDecl())) { + auto type = context.getCanonicalTagType(record); + if(auto size = context.getTypeSizeInCharsIfKnown(type)) { info.size = size->getQuantity() * 8; } if(!record->isDependentType() && record->isCompleteDefinition()) { - info.align = context.getTypeAlign(record->getTypeForDecl()); + info.align = context.getTypeAlign(type); } return; } diff --git a/src/index/usr_generation.cpp b/src/index/usr_generation.cpp index d5df00df4..19205695e 100644 --- a/src/index/usr_generation.cpp +++ b/src/index/usr_generation.cpp @@ -506,14 +506,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) { @@ -698,6 +698,10 @@ void USRGenerator::VisitType(QualType T) { continue; } if(const TagType* TT = T->getAs()) { + if(const auto* ICNT = dyn_cast(TT)) { + T = ICNT->getDecl()->getCanonicalTemplateSpecializationType(Ctx); + continue; + } Out << '$'; VisitTagDecl(TT->getDecl()); return; @@ -741,10 +745,6 @@ void USRGenerator::VisitType(QualType T) { Out << ':' << DNT->getIdentifier()->getName(); return; } - if(const InjectedClassNameType* InjT = T->getAs()) { - T = InjT->getInjectedSpecializationType(); - continue; - } if(const auto* VT = T->getAs()) { Out << (T->isExtVectorType() ? ']' : '['); Out << VT->getNumElements(); diff --git a/src/semantic/display.cpp b/src/semantic/display.cpp index 266b9efcb..d944108b0 100644 --- a/src/semantic/display.cpp +++ b/src/semantic/display.cpp @@ -57,10 +57,6 @@ llvm::StringRef simple_name(const clang::DeclarationName& name) { /// The identifier text of the decl (or builtin) behind a type, empty /// otherwise. llvm::StringRef type_identifier(clang::QualType type) { - if(const auto* elaborated = llvm::dyn_cast(type)) { - return type_identifier(elaborated->getNamedType()); - } - if(const auto* builtin = llvm::dyn_cast(type)) { clang::PrintingPolicy pp(clang::LangOptions{}); pp.adjustForCPlusPlus(); @@ -124,6 +120,10 @@ std::string template_args(const clang::NamedDecl& decl) { /// AST, e.g. friend decls. Currently we fallback to template /// arguments without location information. clang::printTemplateArgumentList(os, record->getTemplateArgs().asArray(), policy); + } else if(auto* var = llvm::dyn_cast(&decl)) { + /// Implicit variable template specializations carry no written + /// arguments; fall back to the converted ones. + clang::printTemplateArgumentList(os, var->getTemplateArgs().asArray(), policy); } return args; } @@ -177,12 +177,11 @@ bool is_sugared_template_parameter(clang::QualType type) { // SubstTemplateTypeParmType is encompassed within a TypedefType, we may lose // the chance to visit it. // For example, given a QT that represents `std::vector::value_type`: - // `-ElaboratedType 'value_type' sugar - // `-TypedefType 'vector::value_type' sugar - // |-Typedef 'value_type' - // `-SubstTemplateTypeParmType 'int *' sugar class depth 0 index 0 T - // |-ClassTemplateSpecialization 'vector' - // `-PointerType 'int *' + // `-TypedefType 'vector::value_type' sugar + // |-Typedef 'value_type' + // `-SubstTemplateTypeParmType 'int *' sugar class depth 0 index 0 T + // |-ClassTemplateSpecialization 'vector' + // `-PointerType 'int *' // `-BuiltinType 'int' // Applying `getPointeeType` to QT results in 'int', a child of our target // node SubstTemplateTypeParmType. @@ -345,8 +344,8 @@ auto name_of(const clang::NamedDecl* decl, const Options& options) -> std::strin /// Handle 'using namespace'. They all have the same name - . if(auto* directive = llvm::dyn_cast(decl)) { os << "using namespace "; - if(auto* qualifier = directive->getQualifier()) { - qualifier->print(os, policy); + if(auto qualifier = directive->getQualifier()) { + qualifier.print(os, policy); } directive->getNominatedNamespaceAsWritten()->printName(os); return name; @@ -373,8 +372,8 @@ auto name_of(const clang::NamedDecl* decl, const Options& options) -> std::strin } /// Print nested name qualifier if it was written in the source code. - if(auto* qualifier = qualifier_loc(*decl).getNestedNameSpecifier()) { - qualifier->print(os, policy); + if(auto qualifier = qualifier_loc(*decl).getNestedNameSpecifier()) { + qualifier.print(os, policy); } /// Print the name itself. @@ -648,16 +647,47 @@ auto type(clang::ASTContext& context, clang::QualType type, const Options& optio Type result; llvm::raw_string_ostream os(result.text); - /// Special case: if the outer type is a tag type without qualifiers, then - /// include the tag for extra clarity. This isn't very idiomatic, so don't - /// attempt it for complex cases, including pointers/references, template + /// Special case: if the outer type is a canonical tag type, then include + /// the tag for extra clarity. This isn't very idiomatic, so don't attempt + /// it for complex cases, including pointers/references, template /// specializations, etc. if(options.tag_keyword_prefix && !type.isNull() && !type.hasQualifiers() && policy.SuppressTagKeyword) { - if(auto* tag = llvm::dyn_cast(type.getTypePtr())) { + if(auto* tag = llvm::dyn_cast(type.getTypePtr()); + tag && tag->isCanonicalUnqualified()) { os << tag->getDecl()->getKindName() << " "; } } + + /// Class scopes carry meaning, but SuppressScope also drops two of + /// them: the computed scope of a canonical tag (a deduced `auto` + /// prints as its deduced, canonical type) and the written qualifier + /// of a template-id. Print those back. Every other node (typedefs, + /// dependent names, written tag types) prints its written qualifier + /// regardless of the policy — restoring theirs would duplicate it. + /// Complex cases (pointers/references, cv-qualifiers) are not + /// attempted, mirroring the tag-keyword special case above. + if(policy.SuppressScope && !type.isNull() && !type.hasQualifiers()) { + auto printed = type; + if(auto* AT = llvm::dyn_cast(printed.getTypePtr()); + AT && AT->isDeduced() && !AT->getDeducedType().isNull()) { + printed = AT->getDeducedType(); + } + + bool scope_suppressed = false; + if(auto* tag = llvm::dyn_cast(printed.getTypePtr())) { + scope_suppressed = tag->isCanonicalUnqualified(); + } else if(auto* TST = + llvm::dyn_cast(printed.getTypePtr())) { + scope_suppressed = !TST->getTemplateName().getAsDependentTemplateName(); + } + + if(scope_suppressed && !printed.hasQualifiers()) { + if(auto prefix = printed->getPrefix()) { + prefix.print(os, policy); + } + } + } type.print(os, policy); if(!type.isNull() && options.show_aka) { diff --git a/src/semantic/resolver.cpp b/src/semantic/resolver.cpp index f6e0d9633..faa7a9af4 100644 --- a/src/semantic/resolver.cpp +++ b/src/semantic/resolver.cpp @@ -20,8 +20,9 @@ /// │ parameters from the stack; dependent names pass /// │ through untouched (no lookup) /// └─ Policy::Resolve — Substitute plus heuristic resolution of -/// DependentNameType/DependentTemplateSpecializationType -/// via member lookup and argument deduction +/// dependent names and dependent template +/// specializations via member lookup and +/// argument deduction /// /// Key invariant: typedef/alias expansion always runs under Policy::Substitute, /// so it can never re-enter heuristic lookup. Violating this causes @@ -204,7 +205,7 @@ clang::QualType get_decl_type(clang::Decl* decl) { if(auto* TND = llvm::dyn_cast(decl)) return TND->getUnderlyingType(); if(auto* RD = llvm::dyn_cast(decl)) - return clang::QualType(RD->getTypeForDecl(), 0); + return RD->getASTContext().getCanonicalTagType(RD); return clang::QualType(); } @@ -498,7 +499,7 @@ class PseudoInstantiator { /// Look up `name` in the given type. First rewrites the type (to substitute /// any template parameters in it), then extracts the ClassTemplateDecl or - /// TypeAliasTemplateDecl from the resulting TST/DTST and dispatches to the + /// TypeAliasTemplateDecl from the resulting TST and dispatches to the /// appropriate lookup overload. lookup_result lookup(clang::QualType type, clang::DeclarationName name) { clang::Decl* TD = nullptr; @@ -512,22 +513,23 @@ class PseudoInstantiator { if(auto TST = type->getAs()) { TD = TST->getTemplateName().getAsTemplateDecl(); args = TST->template_arguments(); - } else if(auto DTST = type->getAs()) { - // If this DTST was already resolved (possibly to itself when unresolvable), - // skip the redundant lookup. - if(pack_narrowing == 0 && resolved.count(DTST)) { - return lookup_result(); - } - auto& template_name = DTST->getDependentTemplateName(); - auto name = template_name.getName().getIdentifier(); - if(!name) { - return {}; - } + if(auto dependent = + !TD ? TST->getTemplateName().getAsDependentTemplateName() : nullptr) { + // If this dependent specialization was already resolved (possibly to + // itself when unresolvable), skip the redundant lookup. + if(pack_narrowing == 0 && resolved.count(TST)) { + return lookup_result(); + } - if(auto decl = preferred(lookup(template_name.getQualifier(), name))) { - TD = decl; - args = DTST->template_arguments(); + auto name = dependent->getName().getIdentifier(); + if(!name) { + return {}; + } + + if(auto decl = preferred(lookup(dependent->getQualifier(), name))) { + TD = decl; + } } } @@ -550,60 +552,29 @@ class PseudoInstantiator { return lookup_result(); } - lookup_result lookup(const clang::NestedNameSpecifier* NNS, clang::DeclarationName name) { - if(!NNS) { - return lookup_result(); - } - - if(pack_narrowing == 0) { - if(auto iter = resolved.find(NNS); iter != resolved.end()) { - return lookup(iter->second, name); - } - } - + lookup_result lookup(clang::NestedNameSpecifier NNS, clang::DeclarationName 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::`) - // - 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()) { - if(pack_narrowing == 0 && !truncated && !ctd_guard_tripped) { - resolved.try_emplace(NNS, type); - } - return lookup(type, name); - } - return {}; - } - - case clang::NestedNameSpecifier::TypeSpec: { - return lookup(clang::QualType(NNS->getAsType(), 0), name); + // - Type: concrete or dependent type used as qualifier (e.g. `vector::`); + // dependent chains (`base::type::inner`) are DependentNameTypes resolved + // recursively through the type lookup + // - Global/Namespace/MicrosoftSuper: not dependent, cannot resolve further + switch(NNS.getKind()) { + case clang::NestedNameSpecifier::Kind::Type: { + return lookup(clang::QualType(NNS.getAsType(), 0), name); } /// Namespaces and the global scope are ordinary declaration /// contexts; a plain lookup returns the full overload set. - case clang::NestedNameSpecifier::Namespace: { - return NNS->getAsNamespace()->lookup(name); - } - - case clang::NestedNameSpecifier::NamespaceAlias: { - return NNS->getAsNamespaceAlias()->getNamespace()->lookup(name); + case clang::NestedNameSpecifier::Kind::Namespace: { + return NNS.getAsNamespaceAndPrefix().Namespace->getNamespace()->lookup(name); } - case clang::NestedNameSpecifier::Global: { + case clang::NestedNameSpecifier::Kind::Global: { return context.getTranslationUnitDecl()->lookup(name); } - case clang::NestedNameSpecifier::Super: { + case clang::NestedNameSpecifier::Kind::Null: + case clang::NestedNameSpecifier::Kind::MicrosoftSuper: { return {}; } } @@ -799,16 +770,12 @@ class PseudoInstantiator { /// Sugar nodes: rewrite what they point at; the wrapper is dropped, /// which is fine because consumers compare canonically or look /// through sugar. - case clang::Type::Elaborated: { - result = rewrite(llvm::cast(T)->getNamedType(), policy); - break; - } case clang::Type::Paren: { result = rewrite(llvm::cast(T)->getInnerType(), policy); break; } case clang::Type::Using: { - result = rewrite(llvm::cast(T)->getUnderlyingType(), policy); + result = rewrite(llvm::cast(T)->desugar(), policy); break; } case clang::Type::MacroQualified: { @@ -837,12 +804,33 @@ class PseudoInstantiator { case clang::Type::InjectedClassName: { auto ICT = llvm::cast(T); - result = rewrite(ICT->getInjectedSpecializationType(), policy); + result = rewrite(ICT->getDecl()->getCanonicalTemplateSpecializationType(context), + policy); break; } case clang::Type::TemplateSpecialization: { - result = rewrite_template(llvm::cast(T), policy); + auto TST = llvm::cast(T); + auto dependent = TST->getTemplateName().getAsDependentTemplateName(); + if(dependent && policy == Policy::Resolve) { + result = resolve_dependent_template(TST); + } else if(dependent) { + auto NNS = rewrite_specifier(dependent->getQualifier(), policy); + llvm::SmallVector arguments; + bool changed = rewrite_arguments(TST->template_arguments(), arguments, policy); + if(NNS != dependent->getQualifier() || changed) { + auto name = context.getDependentTemplateName( + clang::DependentTemplateStorage(NNS, + dependent->getName(), + dependent->hasTemplateKeyword())); + result = context.getTemplateSpecializationType(TST->getKeyword(), + name, + arguments, + /*CanonicalArgs=*/{}); + } + } else { + result = rewrite_template(TST, policy); + } break; } @@ -853,32 +841,9 @@ class PseudoInstantiator { } else { auto NNS = rewrite_specifier(DNT->getQualifier(), policy); if(NNS != DNT->getQualifier()) { - result = context.getDependentNameType( - DNT->getKeyword(), - const_cast(NNS), - DNT->getIdentifier()); - } - } - break; - } - - case clang::Type::DependentTemplateSpecialization: { - auto DTST = llvm::cast(T); - if(policy == Policy::Resolve) { - result = resolve_dependent_template(DTST); - } else { - auto& template_name = DTST->getDependentTemplateName(); - auto NNS = rewrite_specifier(template_name.getQualifier(), policy); - llvm::SmallVector arguments; - bool changed = rewrite_arguments(DTST->template_arguments(), arguments, policy); - if(NNS != template_name.getQualifier() || changed) { - result = context.getDependentTemplateSpecializationType( - DTST->getKeyword(), - clang::DependentTemplateStorage( - const_cast(NNS), - template_name.getName(), - template_name.hasTemplateKeyword()), - arguments); + result = context.getDependentNameType(DNT->getKeyword(), + NNS, + DNT->getIdentifier()); } } break; @@ -993,10 +958,11 @@ class PseudoInstantiator { case clang::Type::MemberPointer: { auto MPT = llvm::cast(T); - auto cls = MPT->getQualifier() ? MPT->getQualifier()->getAsType() : nullptr; - if(!cls) { + auto qualifier = MPT->getQualifier(); + if(!qualifier || qualifier.getKind() != clang::NestedNameSpecifier::Kind::Type) { break; } + auto cls = qualifier.getAsType(); auto pointee = rewrite(MPT->getPointeeType(), policy); auto rewritten_cls = rewrite(clang::QualType(cls, 0), policy); if(pointee == MPT->getPointeeType() && rewritten_cls.getTypePtr() == cls) { @@ -1011,12 +977,10 @@ class PseudoInstantiator { if(!rewritten_cls->isDependentType() && !rewritten_cls->isRecordType()) { break; } - auto qualifier = clang::NestedNameSpecifier::Create(context, - nullptr, - rewritten_cls.getTypePtr()); - result = context.getMemberPointerType(pointee, - qualifier, - rewritten_cls->getAsCXXRecordDecl()); + result = context.getMemberPointerType( + pointee, + clang::NestedNameSpecifier(rewritten_cls.getTypePtr()), + rewritten_cls->getAsCXXRecordDecl()); break; } @@ -1286,11 +1250,11 @@ class PseudoInstantiator { /// name (libc++ binds `_Alloc::template rebind` this way). A /// TemplateSpecializationType cannot carry those (clang asserts); /// rebuild the dependent form with the substituted arguments instead. - if(auto dependent = name.getAsDependentTemplateName()) { - return context.getDependentTemplateSpecializationType( - clang::ElaboratedTypeKeyword::None, - *dependent, - arguments); + if(name.getAsDependentTemplateName()) { + return context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None, + name, + arguments, + /*CanonicalArgs=*/{}); } llvm::SmallVector canonical; @@ -1354,7 +1318,7 @@ class PseudoInstantiator { llvm::dyn_cast_or_null(name.getAsTemplateDecl())) { void* pos = nullptr; if(auto CTSD = CTD->findSpecialization(canonical, pos)) { - underlying = context.getTypeDeclType(CTSD); + underlying = context.getCanonicalTagType(CTSD); } } } @@ -1379,11 +1343,19 @@ class PseudoInstantiator { if(aliased.isNull()) { return clang::QualType(); } - return context.getTemplateSpecializationType(name, arguments, canonical, aliased); + return context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None, + name, + arguments, + canonical, + aliased); } } - return context.getTemplateSpecializationType(name, arguments, canonical, underlying); + return context.getTemplateSpecializationType(clang::ElaboratedTypeKeyword::None, + name, + arguments, + canonical, + underlying); } clang::QualType rewrite_template(const clang::TemplateSpecializationType* TST, Policy policy) { @@ -1530,11 +1502,10 @@ class PseudoInstantiator { if(auto dependent = argument.getAsTemplate().getAsDependentTemplateName()) { auto qualifier = rewrite_specifier(dependent->getQualifier(), policy); if(qualifier != dependent->getQualifier()) { - auto name = - context.getDependentTemplateName(clang::DependentTemplateStorage( - const_cast(qualifier), - dependent->getName(), - dependent->hasTemplateKeyword())); + auto name = context.getDependentTemplateName( + clang::DependentTemplateStorage(qualifier, + dependent->getName(), + dependent->hasTemplateKeyword())); out.emplace_back(name); changed = true; continue; @@ -1652,56 +1623,19 @@ class PseudoInstantiator { return expanded; } - const clang::NestedNameSpecifier* rewrite_specifier(const clang::NestedNameSpecifier* NNS, - Policy policy) { - if(!NNS) { - return nullptr; - } - - switch(NNS->getKind()) { - case clang::NestedNameSpecifier::TypeSpec: { - auto prefix = rewrite_specifier(NNS->getPrefix(), policy); - - /// A dependent component written as `prefix::template B` keeps - /// its qualifier in the specifier chain, not in the type node - /// itself; resolve it in the scope of the rewritten prefix. - clang::QualType type; - auto component = clang::QualType(NNS->getAsType(), 0); - auto DTST = - llvm::dyn_cast(NNS->getAsType()); - if(DTST && !DTST->getDependentTemplateName().getQualifier() && - policy == Policy::Resolve) { - type = resolve_dependent_template(DTST, prefix); - } else { - type = rewrite(component, policy); - } - - if(prefix == NNS->getPrefix() && type.getTypePtr() == NNS->getAsType()) { - return NNS; - } - return clang::NestedNameSpecifier::Create( - context, - const_cast(prefix), - type.getTypePtr()); - } - - /// Identifier components are resolved by lookup itself; the prefix - /// may still contain substitutable types. - case clang::NestedNameSpecifier::Identifier: { - auto prefix = rewrite_specifier(NNS->getPrefix(), policy); - if(prefix == NNS->getPrefix()) { - return NNS; - } - return clang::NestedNameSpecifier::Create( - context, - const_cast(prefix), - NNS->getAsIdentifier()); - } + clang::NestedNameSpecifier rewrite_specifier(clang::NestedNameSpecifier NNS, Policy policy) { + /// Only type components can contain substitutable parameters; a + /// dependent chain (`base::type::inner`) is a DependentNameType whose + /// own qualifier the rewrite recurses into. + if(!NNS || NNS.getKind() != clang::NestedNameSpecifier::Kind::Type) { + return NNS; + } - default: { - return NNS; - } + auto type = rewrite(clang::QualType(NNS.getAsType(), 0), policy); + if(type.getTypePtr() == NNS.getAsType()) { + return NNS; } + return clang::NestedNameSpecifier(type.getTypePtr()); } /// Pseudo-SFINAE: decide whether a partial specialization's dependent @@ -1761,19 +1695,18 @@ class PseudoInstantiator { /*wants_template=*/false); } - case clang::Type::DependentTemplateSpecialization: { - auto DTST = llvm::cast(T); - auto& template_name = DTST->getDependentTemplateName(); - auto identifier = template_name.getName().getIdentifier(); - auto qualifier = template_name.getQualifier(); - if(specifier_absent(qualifier, guard)) { - return true; - } - return identifier && scope_lacks(qualifier, identifier, /*wants_template=*/true); - } - case clang::Type::TemplateSpecialization: { auto TST = llvm::cast(T); + if(auto dependent = TST->getTemplateName().getAsDependentTemplateName()) { + auto identifier = dependent->getName().getIdentifier(); + auto qualifier = dependent->getQualifier(); + if(specifier_absent(qualifier, guard)) { + return true; + } + return identifier && + scope_lacks(qualifier, identifier, /*wants_template=*/true); + } + /// Check the arguments as written: alias sugar (`void_t<...>`) /// desugars to a type that no longer contains the probe. for(auto& argument: TST->template_arguments()) { @@ -1785,10 +1718,6 @@ class PseudoInstantiator { return false; } - case clang::Type::Elaborated: { - return member_absent(llvm::cast(T)->getNamedType(), - guard + 1); - } case clang::Type::Paren: { return member_absent(llvm::cast(T)->getInnerType(), guard + 1); } @@ -1813,36 +1742,31 @@ class PseudoInstantiator { } /// Does any link of the specifier chain provably name a missing member? - bool specifier_absent(const clang::NestedNameSpecifier* NNS, unsigned guard) { - if(!NNS || guard > 16) { + bool specifier_absent(clang::NestedNameSpecifier NNS, unsigned guard) { + if(!NNS || guard > 16 || NNS.getKind() != clang::NestedNameSpecifier::Kind::Type) { return false; } - if(specifier_absent(NNS->getPrefix(), guard + 1)) { - return true; - } - switch(NNS->getKind()) { - case clang::NestedNameSpecifier::Identifier: { - return scope_lacks(NNS->getPrefix(), NNS->getAsIdentifier()); - } - case clang::NestedNameSpecifier::TypeSpec: { - const clang::Type* T = NNS->getAsType(); - if(auto DTST = llvm::dyn_cast(T)) { - auto& template_name = DTST->getDependentTemplateName(); - auto scope = template_name.getQualifier() ? template_name.getQualifier() - : NNS->getPrefix(); - auto identifier = template_name.getName().getIdentifier(); - return identifier && scope_lacks(scope, identifier, /*wants_template=*/true); - } - if(auto DNT = llvm::dyn_cast(T)) { - return scope_lacks(DNT->getQualifier(), DNT->getIdentifier()); - } + const clang::Type* T = NNS.getAsType(); + if(auto TST = llvm::dyn_cast(T)) { + auto dependent = TST->getTemplateName().getAsDependentTemplateName(); + if(!dependent) { return false; } - default: { - return false; + auto scope = dependent->getQualifier(); + if(specifier_absent(scope, guard + 1)) { + return true; + } + auto identifier = dependent->getName().getIdentifier(); + return identifier && scope_lacks(scope, identifier, /*wants_template=*/true); + } + if(auto DNT = llvm::dyn_cast(T)) { + if(specifier_absent(DNT->getQualifier(), guard + 1)) { + return true; } + return scope_lacks(DNT->getQualifier(), DNT->getIdentifier()); } + return false; } /// Resolve `scope` and ask whether it is a known template or record that @@ -1907,7 +1831,7 @@ class PseudoInstantiator { return wants_template && llvm::isa(decl); } - bool scope_lacks(const clang::NestedNameSpecifier* scope, + bool scope_lacks(clang::NestedNameSpecifier scope, clang::DeclarationName name, bool wants_template = false) { if(!scope) { @@ -1937,8 +1861,8 @@ class PseudoInstantiator { auto resolved_scope = rewrite_specifier(scope, Policy::Resolve); bool lacks = false; - if(resolved_scope && resolved_scope->getKind() == clang::NestedNameSpecifier::TypeSpec) { - auto type = resolve(clang::QualType(resolved_scope->getAsType(), 0)); + if(resolved_scope.getKind() == clang::NestedNameSpecifier::Kind::Type) { + auto type = resolve(clang::QualType(resolved_scope.getAsType(), 0)); if(!type.isNull()) { if(auto TST = type->getAs()) { if(auto CTD = llvm::dyn_cast_or_null( @@ -2000,7 +1924,7 @@ class PseudoInstantiator { return clang::QualType(DNT, 0); } - auto* NNS = rewrite_specifier(DNT->getQualifier(), Policy::Resolve); + auto NNS = rewrite_specifier(DNT->getQualifier(), Policy::Resolve); auto stack_size = stack.data.size(); auto* decl = preferred(lookup(NNS, DNT->getIdentifier())); auto type = get_decl_type(decl); @@ -2055,40 +1979,35 @@ class PseudoInstantiator { return clang::QualType(DNT, 0); } - /// `scope` carries the enclosing specifier prefix for components whose own - /// qualifier is null (see rewrite_specifier). Such resolutions are not - /// cached: the node's identity does not include the scope it was found in. - clang::QualType - resolve_dependent_template(const clang::DependentTemplateSpecializationType* DTST, - const clang::NestedNameSpecifier* scope = nullptr) { - LOG_DEBUG("{}" "resolve DTST '{}'", pad(), clang::QualType(DTST, 0).getAsString()); + /// Resolve a specialization whose template is a dependent name + /// (`T::template rebind`) to a concrete TST when lookup finds the + /// template. + clang::QualType resolve_dependent_template(const clang::TemplateSpecializationType* TST) { + LOG_DEBUG("{}" "resolve TST '{}'", pad(), clang::QualType(TST, 0).getAsString()); indent += 1; - auto& template_name = DTST->getDependentTemplateName(); - /// Scope-threaded and pack-narrowed resolutions both depend on - /// context the node pointer does not capture; neither may be cached. - bool cacheable = (template_name.getQualifier() != nullptr || !scope) && pack_narrowing == 0; + auto& template_name = *TST->getTemplateName().getAsDependentTemplateName(); + /// Pack-narrowed resolutions depend on context the node pointer does + /// not capture; they may not be cached. + bool cacheable = pack_narrowing == 0; if(cacheable) { - if(auto iter = resolved.find(DTST); iter != resolved.end()) { + if(auto iter = resolved.find(TST); iter != resolved.end()) { indent -= 1; return iter->second; } } - const clang::NestedNameSpecifier* NNS = - template_name.getQualifier() - ? rewrite_specifier(template_name.getQualifier(), Policy::Resolve) - : scope; + auto NNS = rewrite_specifier(template_name.getQualifier(), Policy::Resolve); llvm::SmallVector arguments; - rewrite_arguments(DTST->template_arguments(), arguments, Policy::Resolve); + rewrite_arguments(TST->template_arguments(), arguments, Policy::Resolve); auto* name = template_name.getName().getIdentifier(); if(!name) { - LOG_DEBUG("{}→ ", pad()); + LOG_DEBUG("{}→ ", pad()); indent -= 1; - return clang::QualType(DTST, 0); + return clang::QualType(TST, 0); } auto stack_size = stack.data.size(); @@ -2107,13 +2026,13 @@ class PseudoInstantiator { LOG_DEBUG("{}" "→ '{}' (alias)", pad(), type.getAsString()); indent -= 1; if(cacheable && !truncated && !ctd_guard_tripped) { - resolved.try_emplace(DTST, type); + resolved.try_emplace(TST, type); } return type; } } } else if(auto* CTD = llvm::dyn_cast(decl)) { - // Resolve DTST to a concrete TemplateSpecializationType. + // Resolve TST to a concrete TemplateSpecializationType. // e.g. __alloc_traits>::rebind → rebind (a TST) // This allows subsequent lookup of members (like "other") to work. // Keep lookup frames on stack — the caller (e.g. rewrite_specifier @@ -2122,7 +2041,7 @@ class PseudoInstantiator { LOG_DEBUG("{}" "→ TST '{}' (class)", pad(), result.getAsString()); indent -= 1; if(cacheable && !truncated && !ctd_guard_tripped) { - resolved.try_emplace(DTST, result); + resolved.try_emplace(TST, result); } return result; } @@ -2131,15 +2050,15 @@ class PseudoInstantiator { stack.pop(); } - LOG_DEBUG("{}→ ", pad()); + LOG_DEBUG("{}→ ", pad()); indent -= 1; - auto fallback = clang::QualType(DTST, 0); + auto fallback = clang::QualType(TST, 0); /// Only a conclusive failure may be cached: an exhausted step budget /// or a tripped recursion guard proves nothing, and the cache is /// TU-wide — a truncated query must not poison this node for later /// queries that could still resolve it. if(cacheable && !truncated && !ctd_guard_tripped) { - resolved.try_emplace(DTST, fallback); + resolved.try_emplace(TST, fallback); } return fallback; } @@ -2179,7 +2098,7 @@ clang::QualType TemplateResolver::resolve(clang::QualType type) { return instantiator.resolve(type); } -TemplateResolver::lookup_result TemplateResolver::lookup(const clang::NestedNameSpecifier* NNS, +TemplateResolver::lookup_result TemplateResolver::lookup(clang::NestedNameSpecifier NNS, clang::DeclarationName name) { PseudoInstantiator instantiator(context, resolved); return instantiator.lookup(NNS, name); @@ -2233,7 +2152,7 @@ static TemplateResolver::lookup_result /// unwrap it to the equivalent template specialization the lookup /// understands. if(auto* ICNT = type->getAs()) { - type = ICNT->getInjectedSpecializationType(); + type = ICNT->getDecl()->getCanonicalTemplateSpecializationType(context); } PseudoInstantiator instantiator(context, resolved); @@ -2258,13 +2177,17 @@ TemplateResolver::lookup_result TemplateResolver::lookup(const clang::Unresolved } TemplateResolver::lookup_result - TemplateResolver::lookup(const clang::DependentTemplateSpecializationType* type) { - auto& template_name = type->getDependentTemplateName(); - auto name = template_name.getName(); + TemplateResolver::lookup(const clang::TemplateSpecializationType* type) { + auto dependent = type->getTemplateName().getAsDependentTemplateName(); + if(!dependent) { + return {}; + } + + auto name = dependent->getName(); if(auto identifier = name.getIdentifier()) { - return lookup(template_name.getQualifier(), identifier); + return lookup(dependent->getQualifier(), identifier); } - return lookup(template_name.getQualifier(), + return lookup(dependent->getQualifier(), context.DeclarationNames.getCXXOperatorName(name.getOperator())); } diff --git a/src/semantic/resolver.h b/src/semantic/resolver.h index 1577ed682..83c91ec91 100644 --- a/src/semantic/resolver.h +++ b/src/semantic/resolver.h @@ -27,13 +27,15 @@ class TemplateResolver { 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); + /// Look up a dependent template name (`T::template rebind`); returns an + /// empty result for specializations whose template is already known. + lookup_result lookup(const clang::TemplateSpecializationType* type); lookup_result lookup(const clang::DependentScopeDeclRefExpr* expr) { return lookup(expr->getQualifier(), expr->getNameInfo().getName()); diff --git a/src/semantic/semantics.cpp b/src/semantic/semantics.cpp index b54ee38c1..2bdb452ba 100644 --- a/src/semantic/semantics.cpp +++ b/src/semantic/semantics.cpp @@ -406,12 +406,13 @@ class SemanticsBuilder : public clang::RecursiveASTVisitor { return ret; } - bool TraverseTypeLoc(clang::TypeLoc X) { + bool TraverseTypeLoc(clang::TypeLoc X, bool traverse_qualifier = true) { if(!X) { return true; } - return traverse_node(SemanticNode(X), [&] { return Base::TraverseTypeLoc(X); }); + return traverse_node(SemanticNode(X), + [&] { return Base::TraverseTypeLoc(X, traverse_qualifier); }); } bool TraverseTemplateArgumentLoc(const clang::TemplateArgumentLoc& X) { @@ -483,17 +484,18 @@ class SemanticsBuilder : public clang::RecursiveASTVisitor { // 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) { - return traverse_node(SemanticNode(static_cast(QX)), - [&] { return TraverseTypeLoc(QX.getUnqualifiedLoc()); }); + bool TraverseQualifiedTypeLoc(clang::QualifiedTypeLoc QX, bool traverse_qualifier = true) { + return traverse_node(SemanticNode(static_cast(QX)), [&] { + return TraverseTypeLoc(QX.getUnqualifiedLoc(), traverse_qualifier); + }); } - bool TraverseType(clang::QualType) { + bool TraverseType(clang::QualType, bool = true) { return true; } // Uninteresting parts of the AST that don't have locations within them. - bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier*) { + bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier) { return true; } @@ -1297,7 +1299,7 @@ void type_loc_occurrences(clang::TypeLoc TL, Occurrences& out, types::TemplateRe /// using Foo = int; Foo foo; /// ^~~~ reference if(auto TTL = TL.getAs()) { - occur(out, TTL.getTypedefNameDecl(), RelationKind::Reference, TTL.getNameLoc()); + occur(out, TTL.getDecl(), RelationKind::Reference, TTL.getNameLoc()); return; } @@ -1330,7 +1332,7 @@ void type_loc_occurrences(clang::TypeLoc TL, Occurrences& out, types::TemplateRe /// through the using shadow to the imported type. if(auto UTL = TL.getAs()) { occur(out, - UTL.getTypePtr()->getFoundDecl()->getTargetDecl(), + UTL.getTypePtr()->getDecl()->getTargetDecl(), RelationKind::Reference, UTL.getNameLoc()); return; @@ -1356,6 +1358,14 @@ void type_loc_occurrences(clang::TypeLoc TL, Occurrences& out, types::TemplateRe auto* target = TD->getTemplatedDecl() ? TD->getTemplatedDecl() : static_cast(TD); occur(out, target, RelationKind::Reference, TSTL.getTemplateNameLoc()); + return; + } + + /// std::allocator::rebind — the template itself is a + /// dependent name; weak reference, resolved on the primary + /// template. + for(const auto* target: types::decls_of(TSTL.getType(), resolver)) { + occur(out, target, RelationKind::WeakReference, TSTL.getTemplateNameLoc()); } return; } @@ -1384,56 +1394,33 @@ void type_loc_occurrences(clang::TypeLoc TL, Occurrences& out, types::TemplateRe } return; } - - /// std::allocator::rebind - /// ^~~~ weak reference - if(auto DTSTL = TL.getAs()) { - for(const auto* target: types::decls_of(DTSTL.getType(), resolver)) { - occur(out, target, RelationKind::WeakReference, DTSTL.getTemplateNameLoc()); - } - return; - } } -void nns_occurrences(clang::NestedNameSpecifierLoc NNSL, - Occurrences& out, - types::TemplateResolver* resolver) { - auto* NNS = NNSL.getNestedNameSpecifier(); - switch(NNS->getKind()) { - case clang::NestedNameSpecifier::Namespace: { - occur(out, NNS->getAsNamespace(), RelationKind::Reference, NNSL.getLocalBeginLoc()); - break; - } - - case clang::NestedNameSpecifier::NamespaceAlias: { +void nns_occurrences(clang::NestedNameSpecifierLoc NNSL, Occurrences& out) { + auto NNS = NNSL.getNestedNameSpecifier(); + switch(NNS.getKind()) { + /// A namespace or namespace alias; the alias decl itself is + /// referenced, not the namespace it names. + case clang::NestedNameSpecifier::Kind::Namespace: { occur(out, - NNS->getAsNamespaceAlias(), + NNS.getAsNamespaceAndPrefix().Namespace, RelationKind::Reference, NNSL.getLocalBeginLoc()); break; } - case clang::NestedNameSpecifier::Identifier: { - assert(NNS->isDependent() && "Identifier NNS should be dependent"); - if(resolver) { - for(auto* target: - resolver->lookup(NNS->getPrefix(), - clang::DeclarationName(NNS->getAsIdentifier()))) { - occur(out, target, RelationKind::WeakReference, NNSL.getLocalBeginLoc()); - } - } - break; - } - - case clang::NestedNameSpecifier::Super: { + case clang::NestedNameSpecifier::Kind::MicrosoftSuper: { /// __super::member (MS extension) — the qualifier names the base /// record. - occur(out, NNS->getAsRecordDecl(), RelationKind::Reference, NNSL.getLocalBeginLoc()); + occur(out, NNS.getAsMicrosoftSuper(), RelationKind::Reference, NNSL.getLocalBeginLoc()); break; } - case clang::NestedNameSpecifier::TypeSpec: - case clang::NestedNameSpecifier::Global: { + /// Type components (including dependent chains, which are + /// DependentNameTypes) are visited as TypeLocs. + case clang::NestedNameSpecifier::Kind::Null: + case clang::NestedNameSpecifier::Kind::Type: + case clang::NestedNameSpecifier::Kind::Global: { break; }; } @@ -1703,7 +1690,7 @@ llvm::SmallVector resolve_occurrences(const SemanticNode& nod } case SemanticNode::Kind::NestedNameSpecifierLoc: { - nns_occurrences(*node.get(), out, resolver); + nns_occurrences(*node.get(), out); break; } diff --git a/src/semantic/types.cpp b/src/semantic/types.cpp index 6a1876e5c..713e4aa86 100644 --- a/src/semantic/types.cpp +++ b/src/semantic/types.cpp @@ -36,12 +36,6 @@ auto decls_of(clang::QualType type, TemplateResolver* resolver) return {}; } - // 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()) { - type = ET->getNamedType(); - } - /// Dependent names carry no declaration structurally; resolve them /// heuristically when a resolver is available. if(const auto* DNT = type->getAs()) { @@ -54,16 +48,6 @@ auto decls_of(clang::QualType type, TemplateResolver* resolver) return result; } - if(const auto* DTST = type->getAs()) { - llvm::SmallVector result; - if(resolver) { - for(auto* target: resolver->lookup(DTST)) { - result.push_back(target); - } - } - return result; - } - /// using typename B::type — the unresolved-using declaration itself /// first, then the resolver's candidates for what it imports. if(const auto* UUT = type->getAs()) { @@ -81,6 +65,19 @@ auto decls_of(clang::QualType type, TemplateResolver* resolver) if(auto TST = type->getAs()) { auto decl = TST->getTemplateName().getAsTemplateDecl(); + + /// A dependent template name (`T::template rebind`) has no + /// declaration structurally; resolve it heuristically. + if(!decl) { + llvm::SmallVector result; + if(resolver) { + for(auto* target: resolver->lookup(TST)) { + result.push_back(target); + } + } + return result; + } + if(type->isDependentType()) { return {decl}; } @@ -150,6 +147,7 @@ auto declared_type(const clang::TypeDecl* decl) -> clang::QualType { if(const auto* spec = llvm::dyn_cast(decl)) { if(const auto* args = spec->getTemplateArgsAsWritten()) { return context.getTemplateSpecializationType( + clang::ElaboratedTypeKeyword::None, clang::TemplateName(spec->getSpecializedTemplate()), args->arguments(), /*CanonicalArgs=*/{}); diff --git a/src/semantic/unifier.cpp b/src/semantic/unifier.cpp index 9eff44b44..a7357f5f6 100644 --- a/src/semantic/unifier.cpp +++ b/src/semantic/unifier.cpp @@ -21,16 +21,12 @@ clang::QualType peel(clang::QualType type, clang::Qualifiers& quals) { const clang::Type* T = type.getTypePtr(); switch(T->getTypeClass()) { - case clang::Type::Elaborated: { - type = llvm::cast(T)->getNamedType(); - continue; - } case clang::Type::Paren: { type = llvm::cast(T)->getInnerType(); continue; } case clang::Type::Using: { - type = llvm::cast(T)->getUnderlyingType(); + type = llvm::cast(T)->desugar(); continue; } case clang::Type::Typedef: { @@ -257,7 +253,7 @@ bool Unifier::template_id(clang::QualType type, type = peel(type, quals); if(auto ICNT = llvm::dyn_cast(type)) { - type = ICNT->getInjectedSpecializationType(); + type = ICNT->getDecl()->getCanonicalTemplateSpecializationType(context); } if(auto TST = llvm::dyn_cast(type)) { @@ -339,6 +335,13 @@ bool Unifier::unify(clang::QualType pattern, clang::QualType argument) { case clang::Type::TemplateSpecialization: case clang::Type::InjectedClassName: case clang::Type::Record: { + /// A specialization of a dependent template name is a + /// non-deduced context: it constrains nothing. + if(auto TST = llvm::dyn_cast(pattern); + TST && TST->getTemplateName().getAsDependentTemplateName()) { + return true; + } + clang::TemplateName pattern_name, argument_name; TemplateArguments pattern_args, argument_args; if(!template_id(pattern, pattern_name, pattern_args)) { @@ -444,8 +447,13 @@ bool Unifier::unify(clang::QualType pattern, clang::QualType argument) { if(!AM) { return false; } - auto pattern_cls = PM->getQualifier() ? PM->getQualifier()->getAsType() : nullptr; - auto argument_cls = AM->getQualifier() ? AM->getQualifier()->getAsType() : nullptr; + auto type_of = [](clang::NestedNameSpecifier NNS) -> const clang::Type* { + return NNS && NNS.getKind() == clang::NestedNameSpecifier::Kind::Type + ? NNS.getAsType() + : nullptr; + }; + auto pattern_cls = type_of(PM->getQualifier()); + auto argument_cls = type_of(AM->getQualifier()); if(!pattern_cls || !argument_cls) { return false; } @@ -527,7 +535,6 @@ bool Unifier::unify(clang::QualType pattern, clang::QualType argument) { /// Dependent forms we cannot look into are non-deduced contexts: /// they constrain nothing. case clang::Type::DependentName: - case clang::Type::DependentTemplateSpecialization: case clang::Type::Decltype: case clang::Type::UnresolvedUsing: case clang::Type::PackExpansion: { diff --git a/src/syntax/scan.cpp b/src/syntax/scan.cpp index 8d47e54a9..945c3a849 100644 --- a/src/syntax/scan.cpp +++ b/src/syntax/scan.cpp @@ -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" @@ -323,9 +324,10 @@ std::unique_ptr } auto instance = std::make_unique(std::move(invocation)); - instance->createDiagnostics(*vfs, new clang::IgnoringDiagConsumer(), true); + instance->setVirtualFileSystem(std::move(vfs)); + instance->createDiagnostics(new clang::IgnoringDiagConsumer(), true); instance->getDiagnostics().setSuppressAllDiagnostics(true); - instance->createFileManager(vfs); + instance->createFileManager(); return instance; } diff --git a/tests/snap/document_symbol/detail_scoped_types.cpp b/tests/snap/document_symbol/detail_scoped_types.cpp new file mode 100644 index 000000000..c71f2c344 --- /dev/null +++ b/tests/snap/document_symbol/detail_scoped_types.cpp @@ -0,0 +1,30 @@ +/// # Symbol Detail +/// +/// ## Scoped types — a written class scope appears in the detail exactly once, for nested classes, template-ids, aliases and dependent names alike +/// +/// - status: supported +/// - order: 6 + +namespace scoped { + +struct Outer { + struct Inner {}; + template struct Box {}; + using Alias = int; +}; + +struct User { + Outer::Inner plain; + Outer::Box boxed; + Outer::Alias aliased; + const Outer::Inner frozen; +}; + +template +struct Holder { + typename T::type value; + typename T::inner::type deep; + typename T::template rebind bound; +}; + +} // namespace scoped diff --git a/tests/snap/document_symbol/detail_scoped_types.snap.yml b/tests/snap/document_symbol/detail_scoped_types.snap.yml new file mode 100644 index 000000000..d83508e57 --- /dev/null +++ b/tests/snap/document_symbol/detail_scoped_types.snap.yml @@ -0,0 +1,18 @@ +--- +created_at: 2026-08-07 +input_file: detail_scoped_types.cpp +--- +- { name: "scoped", kind: Namespace, range: "7:0-29:1", selection_range: "7:10-7:16" } +- { name: "Outer", kind: Struct, range: "9:0-13:1", selection_range: "9:7-9:12", detail: "struct" } +- { name: "Inner", kind: Struct, range: "10:4-10:19", selection_range: "10:11-10:16", detail: "struct" } +- { name: "Box", kind: Struct, range: "11:26-11:39", selection_range: "11:33-11:36", detail: "template struct" } +- { name: "Alias", kind: Class, range: "12:4-12:21", selection_range: "12:10-12:15", detail: "type alias" } +- { name: "User", kind: Struct, range: "15:0-20:1", selection_range: "15:7-15:11", detail: "struct" } +- { name: "plain", kind: Field, range: "16:4-16:22", selection_range: "16:17-16:22", detail: "Outer::Inner" } +- { name: "boxed", kind: Field, range: "17:4-17:25", selection_range: "17:20-17:25", detail: "Outer::Box" } +- { name: "aliased", kind: Field, range: "18:4-18:24", selection_range: "18:17-18:24", detail: "Outer::Alias" } +- { name: "frozen", kind: Field, range: "19:4-19:29", selection_range: "19:23-19:29", detail: "const Outer::Inner" } +- { name: "Holder", kind: Struct, range: "23:0-27:1", selection_range: "23:7-23:13", detail: "template struct" } +- { name: "value", kind: Field, range: "24:4-24:26", selection_range: "24:21-24:26", detail: "typename T::type" } +- { name: "deep", kind: Field, range: "25:4-25:32", selection_range: "25:28-25:32", detail: "typename T::inner::type" } +- { name: "bound", kind: Field, range: "26:4-26:42", selection_range: "26:37-26:42", detail: "typename T::template rebind" } diff --git a/tests/snap/hover/auto.snap.yml b/tests/snap/hover/auto.snap.yml index 0e49c98cb..fb10a5512 100644 --- a/tests/snap/hover/auto.snap.yml +++ b/tests/snap/hover/auto.snap.yml @@ -201,7 +201,7 @@ auto on alias --- ```cpp -cls_type // aka: alias_class::cls +cls_type // aka: cls ``` 24_auto_alias_template: { range: "187:0-187:4" } diff --git a/tests/snap/hover/docs.snap.yml b/tests/snap/hover/docs.snap.yml index 3218dbc5f..dade0de19 100644 --- a/tests/snap/hover/docs.snap.yml +++ b/tests/snap/hover/docs.snap.yml @@ -50,7 +50,7 @@ docs_ast::X ``` 05_var_tmpl_ref_doc: { range: "15:11-15:14" } -### variable `baz>` +### variable `baz>` --- Type: `docs_ast::X` diff --git a/tests/snap/hover/expressions.snap.yml b/tests/snap/hover/expressions.snap.yml index 019513eb6..72830e662 100644 --- a/tests/snap/hover/expressions.snap.yml +++ b/tests/snap/hover/expressions.snap.yml @@ -20,12 +20,12 @@ Size: 14 bytes ### `expression` --- -Type: `unsigned long` +Type: `__size_t (aka unsigned long)` Value = `1` 04_alignof_expr: { range: "16:8-16:15" } ### `expression` --- -Type: `unsigned long` +Type: `__size_t (aka unsigned long)` Value = `1` diff --git a/tests/snap/hover/misc.snap.yml b/tests/snap/hover/misc.snap.yml index 497c15a49..3bac0c446 100644 --- a/tests/snap/hover/misc.snap.yml +++ b/tests/snap/hover/misc.snap.yml @@ -89,11 +89,11 @@ public: static void operator delete(void *p) noexcept --- → `void *` Parameters: -- `unsigned long` +- `__size_t (aka unsigned long)` --- ```cpp -void *operator new(unsigned long) +void *operator new(__size_t) ``` 09_ctor_paren: { range: "53:5-53:10" } diff --git a/tests/snap/hover/tag_decls.snap.yml b/tests/snap/hover/tag_decls.snap.yml index 2f7d78846..6f950db0f 100644 --- a/tests/snap/hover/tag_decls.snap.yml +++ b/tests/snap/hover/tag_decls.snap.yml @@ -83,7 +83,7 @@ ONE ### enumerator `ONE` --- -Type: `enum anon_enum::(unnamed)` +Type: `enum anon_enum::(unnamed enum)` Value = `0` --- diff --git a/tests/snap/hover/templates.cpp b/tests/snap/hover/templates.cpp index 520c6c6b6..9e3780b95 100644 --- a/tests/snap/hover/templates.cpp +++ b/tests/snap/hover/templates.cpp @@ -259,3 +259,12 @@ template int slot = 1; using probe = decltype(sl§(36_variable_template_doc)ot); } + +// A member alias with a dependent underlying type keeps its sugar: +// canonicalizing would spell the parameter as `type-parameter-0-0`. +namespace dependent_member_alias { +template +struct Wrap { + using §(37_dependent_member_alias)element = const T&; +}; +} diff --git a/tests/snap/hover/templates.snap.yml b/tests/snap/hover/templates.snap.yml index 9c3ddddf4..66d9fed65 100644 --- a/tests/snap/hover/templates.snap.yml +++ b/tests/snap/hover/templates.snap.yml @@ -413,3 +413,15 @@ comment from specialization // In namespace var_primary_fallback int slot ``` + +37_dependent_member_alias: { range: "267:8-267:15" } +### type `element` + +--- +Type: `const T &` + +--- +```cpp +// In Wrap +public: using element = const T & +``` diff --git a/tests/snap/hover/this_expr.snap.yml b/tests/snap/hover/this_expr.snap.yml index be8dc9053..c3ab2ced4 100644 --- a/tests/snap/hover/this_expr.snap.yml +++ b/tests/snap/hover/this_expr.snap.yml @@ -15,7 +15,7 @@ ns::Foo1 * --- ```cpp -const Foo2 * +const ns::Foo2 * ``` 03_this_specialization: { range: "28:13-28:17" } @@ -23,7 +23,7 @@ const Foo2 * --- ```cpp -Foo3 * +ns::Foo3 * ``` 04_this_partial_specialization: { range: "39:13-39:17" } @@ -31,5 +31,5 @@ Foo3 * --- ```cpp -const Foo4 * +const ns::Foo4 * ``` diff --git a/tests/unit/semantic/selection_tests.cpp b/tests/unit/semantic/selection_tests.cpp index 4cf66f662..43fd56ca0 100644 --- a/tests/unit/semantic/selection_tests.cpp +++ b/tests/unit/semantic/selection_tests.cpp @@ -440,11 +440,13 @@ TEST_CASE(Types) { } TEST_CASE(CXXFeatures) { + /// A dependent qualifier component (`T::U`) is a DependentNameType; + /// the cursor's innermost node owns its name token. EXPECT_SELECT(R"( template - int x = §⟦T::§U::⟧ccc(); + int x = T::§⟦§U⟧::ccc(); )", - "NestedNameSpecifierLoc"); + "DependentNameTypeLoc"); EXPECT_SELECT(R"( struct Foo {}; struct Bar : §⟦v§ir§tual private Foo⟧ {};