packages feed

libclang-bindings 0.1.0.0 → 0.2.0.0

raw patch · 20 files changed

+599/−192 lines, 20 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Clang.HighLevel.Types: fromRange :: (MonadIO m, HasCallStack) => CXTranslationUnit -> Range SingleLoc -> m CXSourceRange
- Clang.HighLevel.Types: fromSingle :: (MonadIO m, HasCallStack) => CXTranslationUnit -> SingleLoc -> m CXSourceLocation
- Clang.HighLevel.Types: toMulti :: MonadIO m => CXSourceLocation -> m MultiLoc
- Clang.HighLevel.Types: toRange :: MonadIO m => CXSourceRange -> m (Range MultiLoc)
- Clang.Paths: nullSourcePath :: SourcePath -> Bool
+ Clang.HighLevel: ClangRealPathException :: SourcePath -> CallStack -> ClangRealPathException
+ Clang.HighLevel: clang_getRealPath :: (MonadIO m, HasCallStack) => CXFile -> m RealPath
+ Clang.HighLevel: clang_tryGetRealPath :: MonadIO m => CXFile -> m (Maybe RealPath)
+ Clang.HighLevel: data ClangRealPathException
+ Clang.HighLevel: toMultiCXFile :: MonadIO m => CXSourceLocation -> m (MultiLoc CXFile)
+ Clang.HighLevel.Types: RealPath :: Text -> RealPath
+ Clang.HighLevel.Types: SourcePath :: Text -> SourcePath
+ Clang.HighLevel.Types: newtype RealPath
+ Clang.HighLevel.Types: newtype SourcePath
+ Clang.HighLevel.Types: toMultiCXFile :: MonadIO m => CXSourceLocation -> m (MultiLoc CXFile)
+ Clang.HighLevel.Types: toMultiRealPath :: (MonadIO m, HasCallStack) => CXSourceLocation -> m (MultiLoc RealPath)
+ Clang.HighLevel.Types: toMultiSourcePath :: MonadIO m => CXSourceLocation -> m (MultiLoc SourcePath)
+ Clang.HighLevel.Types: toRangeRealPath :: (MonadIO m, HasCallStack) => CXSourceRange -> m (Range (MultiLoc RealPath))
+ Clang.HighLevel.Types: toRangeSourcePath :: MonadIO m => CXSourceRange -> m (Range (MultiLoc SourcePath))
+ Clang.LowLevel.Core: CXType_PredefinedSugar :: CXTypeKind
+ Clang.LowLevel.Core: clang_File_isEqual :: MonadIO m => CXFile -> CXFile -> m Bool
+ Clang.LowLevel.Core: clang_File_tryGetRealPathName :: MonadIO m => CXFile -> m Text
+ Clang.LowLevel.Core: clang_hashCursor :: MonadIO m => CXCursor -> m CUInt
+ Clang.Paths: RealPath :: Text -> RealPath
+ Clang.Paths: getRealPath :: RealPath -> FilePath
+ Clang.Paths: getRealPathText :: RealPath -> Text
+ Clang.Paths: getSourcePathText :: SourcePath -> Text
+ Clang.Paths: instance GHC.Classes.Eq Clang.Paths.RealPath
+ Clang.Paths: instance GHC.Classes.Ord Clang.Paths.RealPath
+ Clang.Paths: instance GHC.Internal.Show.Show Clang.Paths.RealPath
+ Clang.Paths: newtype RealPath
+ Clang.Paths: realPathToSourcePath :: RealPath -> SourcePath
- Clang.HighLevel: clang_Cursor_getSpellingNameRange :: MonadIO m => CXCursor -> CUInt -> CUInt -> m (Maybe (Range MultiLoc))
+ Clang.HighLevel: clang_Cursor_getSpellingNameRange :: (MonadIO m, HasCallStack) => CXCursor -> CUInt -> CUInt -> m (Maybe (Range (MultiLoc RealPath)))
- Clang.HighLevel: clang_getCursorExtent :: MonadIO m => CXCursor -> m (Range MultiLoc)
+ Clang.HighLevel: clang_getCursorExtent :: (MonadIO m, HasCallStack) => CXCursor -> m (Range (MultiLoc RealPath))
- Clang.HighLevel: clang_getCursorLocation :: MonadIO m => CXCursor -> m MultiLoc
+ Clang.HighLevel: clang_getCursorLocation :: (MonadIO m, HasCallStack) => CXCursor -> m (MultiLoc RealPath)
- Clang.HighLevel: clang_getCursorLocation' :: MonadIO m => CXCursor -> m SingleLoc
+ Clang.HighLevel: clang_getCursorLocation' :: (MonadIO m, HasCallStack) => CXCursor -> m (SingleLoc RealPath)
- Clang.HighLevel: clang_getDiagnosticFixIt :: MonadIO m => CXDiagnostic -> CUInt -> m (Range MultiLoc, Text)
+ Clang.HighLevel: clang_getDiagnosticFixIt :: MonadIO m => CXDiagnostic -> CUInt -> m (Range (MultiLoc SourcePath), Text)
- Clang.HighLevel: clang_getDiagnosticLocation :: MonadIO m => CXDiagnostic -> m MultiLoc
+ Clang.HighLevel: clang_getDiagnosticLocation :: MonadIO m => CXDiagnostic -> m (MultiLoc SourcePath)
- Clang.HighLevel: clang_getDiagnosticRange :: MonadIO m => CXDiagnostic -> CUInt -> m (Range MultiLoc)
+ Clang.HighLevel: clang_getDiagnosticRange :: MonadIO m => CXDiagnostic -> CUInt -> m (Range (MultiLoc SourcePath))
- Clang.HighLevel: clang_getExpansionLocation :: MonadIO m => CXSourceLocation -> m SingleLoc
+ Clang.HighLevel: clang_getExpansionLocation :: (MonadIO m, HasCallStack) => CXSourceLocation -> m (SingleLoc RealPath)
- Clang.HighLevel: clang_getFileLocation :: MonadIO m => CXSourceLocation -> m SingleLoc
+ Clang.HighLevel: clang_getFileLocation :: (MonadIO m, HasCallStack) => CXSourceLocation -> m (SingleLoc RealPath)
- Clang.HighLevel: clang_getSpellingLocation :: MonadIO m => CXSourceLocation -> m SingleLoc
+ Clang.HighLevel: clang_getSpellingLocation :: (MonadIO m, HasCallStack) => CXSourceLocation -> m (SingleLoc RealPath)
- Clang.HighLevel: clang_getTokenExtent :: MonadIO m => CXTranslationUnit -> CXToken -> m (Range MultiLoc)
+ Clang.HighLevel: clang_getTokenExtent :: (MonadIO m, HasCallStack) => CXTranslationUnit -> CXToken -> m (Range (MultiLoc RealPath))
- Clang.HighLevel: clang_getTokenLocation :: MonadIO m => CXTranslationUnit -> CXToken -> m MultiLoc
+ Clang.HighLevel: clang_getTokenLocation :: (MonadIO m, HasCallStack) => CXTranslationUnit -> CXToken -> m (MultiLoc RealPath)
- Clang.HighLevel: clang_tokenize :: (MonadIO m, HasCallStack) => CXTranslationUnit -> Range SingleLoc -> m [Token TokenSpelling]
+ Clang.HighLevel: clang_tokenize :: MonadIO m => CXTranslationUnit -> CXSourceRange -> m [Token SourcePath TokenSpelling]
- Clang.HighLevel: prettyMultiLoc :: ShowFile -> MultiLoc -> String
+ Clang.HighLevel: prettyMultiLoc :: (path -> String) -> ShowFile -> MultiLoc path -> String
- Clang.HighLevel: prettyRangeMultiLoc :: Range MultiLoc -> String
+ Clang.HighLevel: prettyRangeMultiLoc :: Eq path => (path -> String) -> Range (MultiLoc path) -> String
- Clang.HighLevel: prettyRangeSingleLoc :: Range SingleLoc -> String
+ Clang.HighLevel: prettyRangeSingleLoc :: Eq path => (path -> String) -> Range (SingleLoc path) -> String
- Clang.HighLevel: prettySingleLoc :: ShowFile -> SingleLoc -> String
+ Clang.HighLevel: prettySingleLoc :: (path -> String) -> ShowFile -> SingleLoc path -> String
- Clang.HighLevel.Types: Diagnostic :: Text -> SimpleEnum CXDiagnosticSeverity -> MultiLoc -> Text -> Maybe Text -> Maybe Text -> Int -> Text -> [Range MultiLoc] -> [FixIt] -> [Diagnostic] -> Diagnostic
+ Clang.HighLevel.Types: Diagnostic :: Text -> SimpleEnum CXDiagnosticSeverity -> MultiLoc SourcePath -> Text -> Maybe Text -> Maybe Text -> Int -> Text -> [Range (MultiLoc SourcePath)] -> [FixIt] -> [Diagnostic] -> Diagnostic
- Clang.HighLevel.Types: FixIt :: Range MultiLoc -> Text -> FixIt
+ Clang.HighLevel.Types: FixIt :: Range (MultiLoc SourcePath) -> Text -> FixIt
- Clang.HighLevel.Types: MultiLoc :: !SingleLoc -> !Maybe PresumedLoc -> !Maybe SingleLoc -> !Maybe SingleLoc -> MultiLoc
+ Clang.HighLevel.Types: MultiLoc :: !SingleLoc path -> !Maybe PresumedLoc -> !Maybe (SingleLoc path) -> !Maybe (SingleLoc path) -> MultiLoc path
- Clang.HighLevel.Types: SingleLoc :: !SourcePath -> !Int -> !Int -> !Int -> SingleLoc
+ Clang.HighLevel.Types: SingleLoc :: !path -> !Int -> !Int -> !Int -> SingleLoc path
- Clang.HighLevel.Types: Token :: !SimpleEnum CXTokenKind -> !a -> !Range MultiLoc -> !SimpleEnum CXCursorKind -> Token a
+ Clang.HighLevel.Types: Token :: !SimpleEnum CXTokenKind -> !a -> !Range (MultiLoc path) -> !SimpleEnum CXCursorKind -> Token path a
- Clang.HighLevel.Types: [diagnosticLocation] :: Diagnostic -> MultiLoc
+ Clang.HighLevel.Types: [diagnosticLocation] :: Diagnostic -> MultiLoc SourcePath
- Clang.HighLevel.Types: [diagnosticRanges] :: Diagnostic -> [Range MultiLoc]
+ Clang.HighLevel.Types: [diagnosticRanges] :: Diagnostic -> [Range (MultiLoc SourcePath)]
- Clang.HighLevel.Types: [fixItRange] :: FixIt -> Range MultiLoc
+ Clang.HighLevel.Types: [fixItRange] :: FixIt -> Range (MultiLoc SourcePath)
- Clang.HighLevel.Types: [multiLocExpansion] :: MultiLoc -> !SingleLoc
+ Clang.HighLevel.Types: [multiLocExpansion] :: MultiLoc path -> !SingleLoc path
- Clang.HighLevel.Types: [multiLocFile] :: MultiLoc -> !Maybe SingleLoc
+ Clang.HighLevel.Types: [multiLocFile] :: MultiLoc path -> !Maybe (SingleLoc path)
- Clang.HighLevel.Types: [multiLocPresumed] :: MultiLoc -> !Maybe PresumedLoc
+ Clang.HighLevel.Types: [multiLocPresumed] :: MultiLoc path -> !Maybe PresumedLoc
- Clang.HighLevel.Types: [multiLocSpelling] :: MultiLoc -> !Maybe SingleLoc
+ Clang.HighLevel.Types: [multiLocSpelling] :: MultiLoc path -> !Maybe (SingleLoc path)
- Clang.HighLevel.Types: [singleLocColumn] :: SingleLoc -> !Int
+ Clang.HighLevel.Types: [singleLocColumn] :: SingleLoc path -> !Int
- Clang.HighLevel.Types: [singleLocLine] :: SingleLoc -> !Int
+ Clang.HighLevel.Types: [singleLocLine] :: SingleLoc path -> !Int
- Clang.HighLevel.Types: [singleLocOffset] :: SingleLoc -> !Int
+ Clang.HighLevel.Types: [singleLocOffset] :: SingleLoc path -> !Int
- Clang.HighLevel.Types: [singleLocPath] :: SingleLoc -> !SourcePath
+ Clang.HighLevel.Types: [singleLocPath] :: SingleLoc path -> !path
- Clang.HighLevel.Types: [tokenCursorKind] :: Token a -> !SimpleEnum CXCursorKind
+ Clang.HighLevel.Types: [tokenCursorKind] :: Token path a -> !SimpleEnum CXCursorKind
- Clang.HighLevel.Types: [tokenExtent] :: Token a -> !Range MultiLoc
+ Clang.HighLevel.Types: [tokenExtent] :: Token path a -> !Range (MultiLoc path)
- Clang.HighLevel.Types: [tokenKind] :: Token a -> !SimpleEnum CXTokenKind
+ Clang.HighLevel.Types: [tokenKind] :: Token path a -> !SimpleEnum CXTokenKind
- Clang.HighLevel.Types: [tokenSpelling] :: Token a -> !a
+ Clang.HighLevel.Types: [tokenSpelling] :: Token path a -> !a
- Clang.HighLevel.Types: compareSingleLoc :: SingleLoc -> SingleLoc -> Maybe Ordering
+ Clang.HighLevel.Types: compareSingleLoc :: Eq path => SingleLoc path -> SingleLoc path -> Maybe Ordering
- Clang.HighLevel.Types: data MultiLoc
+ Clang.HighLevel.Types: data MultiLoc path
- Clang.HighLevel.Types: data SingleLoc
+ Clang.HighLevel.Types: data SingleLoc path
- Clang.HighLevel.Types: data Token a
+ Clang.HighLevel.Types: data Token path a
- Clang.HighLevel.Types: rangeContainsLoc :: Range SingleLoc -> SingleLoc -> Maybe Bool
+ Clang.HighLevel.Types: rangeContainsLoc :: Eq path => Range (SingleLoc path) -> SingleLoc path -> Maybe Bool

Files

CHANGELOG.md view
@@ -1,5 +1,86 @@ # Revision history for libclang-bindings +## ?.?.?.? -- YYYY-mm-dd++### Breaking changes++### New features++### Minor changes++### Bug fixes++## 0.2.0.0 -- 2026-10-06++### Breaking changes++* Add `CXType_PredefinedSugar` to `CXTypeKind`. LLVM/Clang 23 reports this kind+  for the predefined types `__size_t`, `__signed_size_t` and `__ptrdiff_t`;+  see [predefined-sugar-unexposed (llvm/llvm-project#202209)][llvm-202209].+* `SingleLoc`, `MultiLoc`, and `Token` are parameterized by path type+  (`SingleLoc path`, `MultiLoc path`, `Token path a`).+* High-level location functions such as `clang_getCursorLocation` return+  `RealPath` locations. They throw `ClangRealPathException` if the expansion+  location is in a virtual file, where 0.1.0.0 returned a location, and set a+  spelling or file location in a virtual file to `Nothing`. Use+  `toMultiSourcePath`/`toRangeSourcePath` for virtual files. See+  [location-wrappers-throw (#84)][issue-84].+* `RealPath` locations compare canonical paths: `Eq`, `Ord`,+  `compareSingleLoc` and `rangeContainsLoc` treat a file reached under two+  spellings as one file. See+  [binding-specs-path-spelling (well-typed/hs-bindgen#2236)][hs-bindgen-2236].+* Diagnostics use `SourcePath` locations: `Diagnostic`, `FixIt`,+  `clang_getDiagnosticLocation`, `clang_getDiagnosticRange` and+  `clang_getDiagnosticFixIt` do not throw for virtual files.+* `toMulti`/`toRange` renamed to `toMultiRealPath`/`toRangeRealPath`;+  new `toMultiSourcePath`/`toRangeSourcePath` for virtual files.+* `prettySingleLoc`, `prettyMultiLoc`, `prettyRangeSingleLoc` and+  `prettyRangeMultiLoc` take a `path -> String` argument. `SingleLoc`,+  `MultiLoc` and their ranges have `Show` instances only for `RealPath` and+  `SourcePath`.+* `clang_tokenize` takes a `CXSourceRange` instead of a `Range SingleLoc`, and+  returns `[Token SourcePath TokenSpelling]`. It passes the range to `libclang`+  unchanged, so a range that starts inside a macro expansion is tokenized from+  the macro definition; see the documentation of `clang_tokenize`.+* Remove `fromSingle` and `fromRange`. They looked up the `CXFile` by path,+  which fails for locations in buffers without one. Use `clang_getLocation`+  and `clang_getRange` from `Clang.LowLevel.Core` instead.+* Remove `nullSourcePath`. Use `Data.Text.null . getSourcePathText` instead.++### New features++* Bind `clang_File_tryGetRealPathName` and `clang_File_isEqual`.+* Add the `RealPath` newtype with `getRealPath`, `getRealPathText` and+  `realPathToSourcePath`. Add `getSourcePathText`.+* Add `clang_getRealPath`, which throws `ClangRealPathException` for a virtual+  file, and `clang_tryGetRealPath`, which returns `Nothing` for one.+* Add `toMultiCXFile`: builds `MultiLoc CXFile`.+* `SingleLoc` and `MultiLoc` derive `Functor`, `Foldable` and `Traversable`.+* Bind `clang_hashCursor`. See [hash-cursor (PR #81)][pr-81].++### Minor changes++* `Clang.HighLevel.Types` re-exports `RealPath` and `SourcePath`.++### Bug fixes++* `MultiLoc` no longer drops a presumed, spelling or file location that differs+  from the expansion location in only some of file, line and column. It used+  to drop, for example, the spelling location of a macro defined in the same+  file, and every presumed location set by a `#line` directive. See+  [macro-spelling-dropped (#85)][issue-85].+* `clang_tokenize` no longer throws for a range in a buffer without a file on+  disk, such as the predefines buffer holding macros defined with `-D`. See+  [header-redefines-D-macro (well-typed/hs-bindgen#2280)][hs-bindgen-2280].+* `clang_tokenize` returns `[]` for a range without tokens instead of failing.++[pr-81]: https://github.com/well-typed/libclang-bindings/pull/81+[issue-84]: https://github.com/well-typed/libclang-bindings/issues/84+[issue-85]: https://github.com/well-typed/libclang-bindings/issues/85+[llvm-202209]: https://github.com/llvm/llvm-project/pull/202209+[hs-bindgen-2236]: https://github.com/well-typed/hs-bindgen/issues/2236+[hs-bindgen-2280]: https://github.com/well-typed/hs-bindgen/issues/2280+ ## 0.1.0.0 -- 2026-07-14  ### Breaking changes@@ -10,15 +91,15 @@ ### New features  * Add a binding for `clang_isBeforeInTranslationUnit`. This function is only-  available for Clang versions 20.1 and newer; see [PR-53][pr-53].+  available for Clang versions 20.1 and newer; see [PR #53][pr-53]. * Add a binding for the `clang_Type_getOffsetOf` function. See [PR #37][pr-37]. * Add a new `clang_disposeToken` function to free a single `CXToken`. This is a   helper function alongside the existing `clang_disposeTokens` functions, which-  frees arrays of `CXToken`s. See [PR#42][pr-42].-* Add a new `foldTry` function that behaves like `foldWitHandler`, but it+  frees arrays of `CXToken`s. See [PR #42][pr-42].+* Add a new `foldTry` function that behaves like `foldWithHandler`, but it   returns the caught exception as a value like `Control.Exception.try` would.   The caught exception is represented using a new type called `FoldException`.-  See [PR #47][pr-47]+  See [PR #47][pr-47]. * Add a compile-time check of the `CLANG_VERSION` macro. See the   `Clang.Version.checkUserClangVersion` documentation for details. * Add `--with-so` option to the `configure` script, used to work around Cabal@@ -35,11 +116,11 @@   warnings for libclang APIs we actually call remain visible. See   [issue #58][issue-58]. -[pr-37]: https://github.com/well-typed/libclang/pull/37-[pr-42]: https://github.com/well-typed/libclang/pull/42-[pr-47]: https://github.com/well-typed/libclang/pull/47-[pr-53]: https://github.com/well-typed/libclang/pull/53-[issue-58]: https://github.com/well-typed/libclang/issues/58+[pr-37]: https://github.com/well-typed/libclang-bindings/pull/37+[pr-42]: https://github.com/well-typed/libclang-bindings/pull/42+[pr-47]: https://github.com/well-typed/libclang-bindings/pull/47+[pr-53]: https://github.com/well-typed/libclang-bindings/pull/53+[issue-58]: https://github.com/well-typed/libclang-bindings/issues/58  ## 0.1.0-alpha -- 2026-02-06 
cbits/clang_wrappers_ffi.h view
@@ -74,9 +74,9 @@  /* OMITTED: int      clang_getFileUniqueID (CXFile file, CXFileUniqueID * outID); */ -/* OMITTED: int      clang_File_isEqual (CXFile file1, CXFile file2); */--/* OMITTED: CXString clang_File_tryGetRealPathName (CXFile file); */+static inline void wrap_File_tryGetRealPathName(CXFile file, CXString * result) {+  *result = clang_File_tryGetRealPathName(file);+}  /* *** Physical source locations *** */ 
configure view
@@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles.-# Generated by GNU Autoconf 2.73 for libclang-bindings 0.1.0.0.+# Generated by GNU Autoconf 2.73 for libclang-bindings 0.2.0.0. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation,@@ -584,8 +584,8 @@ # Identity of this package. PACKAGE_NAME='libclang-bindings' PACKAGE_TARNAME='libclang-bindings'-PACKAGE_VERSION='0.1.0.0'-PACKAGE_STRING='libclang-bindings 0.1.0.0'+PACKAGE_VERSION='0.2.0.0'+PACKAGE_STRING='libclang-bindings 0.2.0.0' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1241,7 +1241,7 @@   # Omit some internal or obsolete options to make the list less imposing.   # This message is too long to be a string in the A/UX 3.1 sh.   cat <<_ACEOF-'configure' configures libclang-bindings 0.1.0.0 to adapt to many kinds of systems.+'configure' configures libclang-bindings 0.2.0.0 to adapt to many kinds of systems.  Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1304,7 +1304,7 @@  if test -n "$ac_init_help"; then   case $ac_init_help in-     short | recursive ) echo "Configuration of libclang-bindings 0.1.0.0:";;+     short | recursive ) echo "Configuration of libclang-bindings 0.2.0.0:";;    esac   cat <<\_ACEOF @@ -1392,7 +1392,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then   cat <<\_ACEOF-libclang-bindings configure 0.1.0.0+libclang-bindings configure 0.2.0.0 generated by GNU Autoconf 2.73  Copyright (C) 2026 Free Software Foundation, Inc.@@ -1616,7 +1616,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libclang-bindings $as_me 0.1.0.0, which was+It was created by libclang-bindings $as_me 0.2.0.0, which was generated by GNU Autoconf 2.73.  Invocation command line was    $ $0$ac_configure_args_raw@@ -4502,7 +4502,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log="-This file was extended by libclang-bindings $as_me 0.1.0.0, which was+This file was extended by libclang-bindings $as_me 0.2.0.0, which was generated by GNU Autoconf 2.73.  Invocation command line was    CONFIG_FILES    = $CONFIG_FILES@@ -4566,7 +4566,7 @@ cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\-libclang-bindings config.status 0.1.0.0+libclang-bindings config.status 0.2.0.0 configured by $0, generated by GNU Autoconf 2.73,   with options \\"\$ac_cs_config\\" 
configure.ac view
@@ -1,5 +1,5 @@ AC_PREREQ([2.71])-AC_INIT([libclang-bindings],[0.1.0.0])+AC_INIT([libclang-bindings],[0.2.0.0])  dnl --with-compiler argument: This argument is passed by Cabal, so we have to dnl handle it.  We do not use it, so we ignore the passed value.
libclang-bindings.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               libclang-bindings-version:            0.1.0.0+version:            0.2.0.0 license:            BSD-3-Clause license-file:       LICENSE author:             Well-Typed LLP@@ -46,7 +46,7 @@   type:     git   location: https://github.com/well-typed/libclang-bindings   subdir:   libclang-bindings-  tag:      release-0.1.0.0+  tag:      release-0.2.0.0  -------------------------------------------------------------------------------- -- Configuration@@ -180,6 +180,7 @@     Test.Discover     Test.Meta.IsConcrete     Test.Test.Exceptions+    Test.Test.Tokens     Test.Util.AST     Test.Util.Clang     Test.Util.FoldException
src/Clang/CStandard.hs view
@@ -20,7 +20,6 @@ import Clang.HighLevel.Types import Clang.Internal.Results import Clang.LowLevel.Core-import Clang.Paths  {-------------------------------------------------------------------------------   C standard
src/Clang/HighLevel.hs view
@@ -23,12 +23,18 @@   , prettyMultiLoc   , prettyRangeSingleLoc   , prettyRangeMultiLoc+    -- ** Conversion (CXFile)+  , toMultiCXFile     -- ** Convenience wrappers     -- *** for @CXSourceLocation@   , clang_getDiagnosticLocation   , clang_getCursorLocation   , clang_getCursorLocation'   , clang_getTokenLocation+    -- *** for @CXFile@+  , ClangRealPathException(..)+  , clang_getRealPath+  , clang_tryGetRealPath     -- *** for @CXSourceRange@   , clang_getDiagnosticRange   , clang_getDiagnosticFixIt
src/Clang/HighLevel/Diagnostics.hs view
@@ -16,6 +16,7 @@ import Clang.HighLevel.SourceLoc (MultiLoc, Range) import Clang.HighLevel.SourceLoc qualified as SourceLoc import Clang.LowLevel.Core+import Clang.Paths (SourcePath)  {-------------------------------------------------------------------------------   Definition@@ -29,7 +30,10 @@     , diagnosticSeverity :: SimpleEnum CXDiagnosticSeverity        -- | Source location (where Clang would print the caret @^@)-    , diagnosticLocation  :: MultiLoc+      --+      -- Uses 'SourcePath' rather than 'RealPath' because diagnostics may+      -- refer to root headers whose real path is not available.+    , diagnosticLocation  :: MultiLoc SourcePath        -- | Text of the diagnostic     , diagnosticSpelling  :: Text@@ -51,7 +55,7 @@       -- A diagnostic's source ranges highlight important elements in the source       -- code. On the command line, Clang displays source ranges by underlining       -- them with @~@ characters.-    , diagnosticRanges :: [Range MultiLoc]+    , diagnosticRanges :: [Range (MultiLoc SourcePath)]        -- | Fix-it hints     , diagnosticFixIts :: [FixIt]@@ -78,7 +82,7 @@       -- replaced with the returned replacement string. Note that source ranges       -- are half-open ranges [a, b), so the source code should be replaced from       -- a and up to (but not including) b.-      fixItRange :: Range MultiLoc+      fixItRange :: Range (MultiLoc SourcePath)        -- | Text that should replace the source code     , fixItReplacement :: Text
src/Clang/HighLevel/SourceLoc.hs view
@@ -7,11 +7,14 @@     -- * Comparisons   , compareSingleLoc   , rangeContainsLoc-    -- * Conversion-  , toMulti-  , toRange-  , fromSingle-  , fromRange+    -- * Conversion (CXFile)+  , toMultiCXFile+    -- * Conversion (RealPath)+  , toMultiRealPath+  , toRangeRealPath+    -- * Conversion (SourcePath)+  , toMultiSourcePath+  , toRangeSourcePath     -- * Get single location   , clang_getExpansionLocation   , clang_getPresumedLocation@@ -26,6 +29,10 @@   , prettyMultiLoc   , prettyRangeSingleLoc   , prettyRangeMultiLoc+    -- * File to RealPath+  , ClangRealPathException(..)+  , clang_getRealPath+  , clang_tryGetRealPath     -- * Convenience wrappers     -- * for @CXSourceLocation@   , clang_getDiagnosticLocation@@ -40,31 +47,33 @@   , clang_getTokenExtent   ) where +import Control.Exception (Exception, throwIO) import Control.Monad import Control.Monad.IO.Class import Data.List (intercalate) import Data.Text (Text)+import Data.Text qualified as Text import Foreign.C import GHC.Generics (Generic) import GHC.Stack  import Clang.LowLevel.Core qualified as Core+import Clang.LowLevel.Core.Pointers (CXFile) import Clang.Paths  {-------------------------------------------------------------------------------   Definition -------------------------------------------------------------------------------} --- | A /single/ location in a file+-- | A single location in a file ----- See 'MultiLoc' for additional discussion.-data SingleLoc = SingleLoc {-      singleLocPath   :: !SourcePath+data SingleLoc path = SingleLoc {+      singleLocPath   :: !path     , singleLocLine   :: !Int     , singleLocColumn :: !Int     , singleLocOffset :: !Int     }-  deriving stock (Eq, Ord, Generic)+  deriving stock (Eq, Ord, Generic, Functor, Foldable, Traversable)  -- | Presumed location --@@ -95,19 +104,23 @@ -- which is the position at the caret, and a \"spelling location\", which -- corresponds to the location of the @int@ token in the macro definition. --+-- The presumed, spelling and file locations are 'Nothing' when they coincide+-- with the expansion location, that is, when they agree with it on file, line+-- and column.+-- -- References: -- -- * <https://clang.llvm.org/doxygen/classclang_1_1SourceLocation.html> -- * <https://clang.llvm.org/doxygen/classclang_1_1SourceManager.html> --   (@getExpansionLoc@, @getSpellingLoc@, @getDecomposedSpellingLoc@)-data MultiLoc = MultiLoc {+data MultiLoc path = MultiLoc {       -- | Expansion location       --       -- If the location refers into a macro expansion, this corresponds to the       -- location of the macro expansion.       --       -- See <https://clang.llvm.org/doxygen/group__CINDEX__LOCATIONS.html#gadee4bea0fa34550663e869f48550eb1f>-      multiLocExpansion :: !SingleLoc+      multiLocExpansion :: !(SingleLoc path)        -- | Presumed location       --@@ -126,7 +139,7 @@       -- See <https://github.com/llvm/llvm-project/pull/72400>.       --       -- See <https://clang.llvm.org/doxygen/group__CINDEX__LOCATIONS.html#ga01f1a342f7807ea742aedd2c61c46fa0>-    , multiLocSpelling :: !(Maybe SingleLoc)+    , multiLocSpelling :: !(Maybe (SingleLoc path))        -- | File location       --@@ -136,9 +149,9 @@       -- location of the use of the argument.       --       -- See <https://clang.llvm.org/doxygen/group__CINDEX__LOCATIONS.html#gae0ee9ff0ea04f2446832fc12a7fd2ac8>-    , multiLocFile :: !(Maybe SingleLoc)+    , multiLocFile :: !(Maybe (SingleLoc path))     }-  deriving stock (Eq, Ord, Generic)+  deriving stock (Eq, Ord, Generic, Functor, Foldable, Traversable)  -- | Range --@@ -159,7 +172,7 @@ -- | Compare locations -- -- Returns 'Nothing' if the locations aren't in the same file.-compareSingleLoc :: SingleLoc -> SingleLoc -> Maybe Ordering+compareSingleLoc :: Eq path => SingleLoc path -> SingleLoc path -> Maybe Ordering compareSingleLoc a b = do     guard $ singleLocPath a == singleLocPath b     return $@@ -173,7 +186,7 @@ -- upper bound (following 'Core.CXSourceRange'). -- -- Returns 'Nothing' if the three locations are not all in the same file.-rangeContainsLoc :: Range SingleLoc -> SingleLoc -> Maybe Bool+rangeContainsLoc :: Eq path => Range (SingleLoc path) -> SingleLoc path -> Maybe Bool rangeContainsLoc Range{rangeStart, rangeEnd} loc = do     afterStart <- (/= LT) <$> compareSingleLoc loc rangeStart     beforeEnd  <- (== LT) <$> compareSingleLoc loc rangeEnd@@ -186,11 +199,16 @@   instances which we do not (yet?) define. -------------------------------------------------------------------------------} -instance Show SingleLoc         where show = show . prettySingleLoc ShowFile-instance Show MultiLoc          where show = show . prettyMultiLoc  ShowFile-instance Show (Range SingleLoc) where show = show . prettyRangeSingleLoc-instance Show (Range MultiLoc)  where show = show . prettyRangeMultiLoc+instance Show (SingleLoc RealPath)         where show = show . prettySingleLoc getRealPath ShowFile+instance Show (MultiLoc RealPath)          where show = show . prettyMultiLoc  getRealPath ShowFile+instance Show (Range (SingleLoc RealPath)) where show = show . prettyRangeSingleLoc getRealPath+instance Show (Range (MultiLoc RealPath))  where show = show . prettyRangeMultiLoc  getRealPath +instance Show (SingleLoc SourcePath)         where show = show . prettySingleLoc getSourcePath ShowFile+instance Show (MultiLoc SourcePath)          where show = show . prettyMultiLoc  getSourcePath ShowFile+instance Show (Range (SingleLoc SourcePath)) where show = show . prettyRangeSingleLoc getSourcePath+instance Show (Range (MultiLoc SourcePath))  where show = show . prettyRangeMultiLoc  getSourcePath+ deriving stock instance {-# OVERLAPPABLE #-} Show a => Show (Range a)  {-------------------------------------------------------------------------------@@ -202,22 +220,22 @@  data ShowFile = ShowFile | HideFile -prettySingleLoc :: ShowFile -> SingleLoc -> String-prettySingleLoc showFile loc = case showFile of+prettySingleLoc :: (path -> String) -> ShowFile -> SingleLoc path -> String+prettySingleLoc getPath showFile loc = case showFile of     -- Use space instead of first colon to avoid GHC literate preprocessor mangling-    ShowFile -> getSourcePath singleLocPath ++ " "+    ShowFile -> getPath singleLocPath ++ " "                   ++ show singleLocLine ++ ":" ++ show singleLocColumn     HideFile -> show singleLocLine ++ ":" ++ show singleLocColumn   where     SingleLoc{singleLocPath, singleLocLine, singleLocColumn} = loc -prettyMultiLoc :: ShowFile -> MultiLoc -> String-prettyMultiLoc showFile multiLoc =+prettyMultiLoc :: forall path. (path -> String) -> ShowFile -> MultiLoc path -> String+prettyMultiLoc getPath showFile multiLoc =     intercalate " " . concat $ [-        [ prettySingleLoc showFile multiLocExpansion ]-      , [ "<Presumed=" ++ presumed loc ++ ">" | Just loc <- [multiLocPresumed] ]-      , [ "<Spelling=" ++ single   loc ++ ">" | Just loc <- [multiLocSpelling] ]-      , [ "<File="     ++ single   loc ++ ">" | Just loc <- [multiLocFile]     ]+        [ prettySingleLoc getPath showFile multiLocExpansion ]+      , [ "<Presumed=" ++ presumed loc       ++ ">" | Just loc <- [multiLocPresumed] ]+      , [ "<Spelling=" ++ single getPath loc ++ ">" | Just loc <- [multiLocSpelling] ]+      , [ "<File="     ++ single getPath loc ++ ">" | Just loc <- [multiLocFile]     ]       ]   where     MultiLoc{@@ -226,35 +244,38 @@       , multiLocSpelling       , multiLocFile} = multiLoc -    presumed :: PresumedLoc -> [Char]-    presumed loc = single $ SingleLoc{+    expansionFilePath :: FilePath+    expansionFilePath = getPath (singleLocPath multiLocExpansion)++    presumed :: PresumedLoc -> String+    presumed loc = single getSourcePath SingleLoc{           singleLocPath   = presumedLocPath   loc         , singleLocLine   = presumedLocLine   loc         , singleLocColumn = presumedLocColumn loc         , singleLocOffset = 0 -- not used for pretty-printing         } -    single :: SingleLoc -> [Char]-    single loc =-        prettySingleLoc-          (if singleLocPath loc == singleLocPath multiLocExpansion-             then HideFile-             else ShowFile)+    single :: (p -> String) -> SingleLoc p -> String+    single get loc =+        prettySingleLoc get+          (if get (singleLocPath loc) == expansionFilePath+             then HideFile else ShowFile)           loc -prettyRangeSingleLoc :: Range SingleLoc -> String-prettyRangeSingleLoc = prettySourceRangeWith+prettyRangeSingleLoc :: Eq path => (path -> String) -> Range (SingleLoc path) -> String+prettyRangeSingleLoc getPath = prettySourceRangeWith       singleLocPath-      prettySingleLoc+      (prettySingleLoc getPath) -prettyRangeMultiLoc :: Range MultiLoc -> String-prettyRangeMultiLoc =+prettyRangeMultiLoc :: Eq path => (path -> String) -> Range (MultiLoc path) -> String+prettyRangeMultiLoc getPath =     prettySourceRangeWith       (singleLocPath . multiLocExpansion)-      prettyMultiLoc+      (prettyMultiLoc getPath)  prettySourceRangeWith ::-     (a -> SourcePath)+     Eq p+  => (a -> p)   -> (ShowFile -> a -> String)   -> Range a -> String prettySourceRangeWith path pretty Range{rangeStart, rangeEnd} = concat [@@ -271,100 +292,147 @@   Conversion -------------------------------------------------------------------------------} -toMulti :: MonadIO m => Core.CXSourceLocation -> m MultiLoc-toMulti location = do-    expansion <- clang_getExpansionLocation location+-- | Build a 'MultiLoc' holding raw 'CXFile' handles.+--+toMultiCXFile :: MonadIO m => Core.CXSourceLocation -> m (MultiLoc CXFile)+toMultiCXFile location = do+    expansion <- toSingleCXFile =<< Core.clang_getExpansionLocation location+    presumed  <- clang_getPresumedLocation location+    spelling  <- toSingleCXFile =<< Core.clang_getSpellingLocation location+    file      <- toSingleCXFile =<< Core.clang_getFileLocation location -    let differentSingle :: SingleLoc -> Maybe SingleLoc-        differentSingle loc = do-            guard $ singleLocPath   loc /= singleLocPath expansion-            guard $ singleLocLine   loc /= singleLocLine expansion-            guard $ singleLocColumn loc /= singleLocColumn expansion-            -- We don't compare the file offset-            return loc+    expansionName <- Core.clang_getFileName (singleLocPath expansion) -        differentPresumed :: PresumedLoc -> Maybe PresumedLoc+    let differentSingle loc = do+            sameFile <-+              Core.clang_File_isEqual (singleLocPath loc) (singleLocPath expansion)++            return $ do+              guard $+                   not sameFile+                || singleLocLine   loc /= singleLocLine   expansion+                || singleLocColumn loc /= singleLocColumn expansion+              pure loc+         differentPresumed loc = do-            guard $ presumedLocPath   loc /= singleLocPath expansion-            guard $ presumedLocLine   loc /= singleLocLine expansion-            guard $ presumedLocColumn loc /= singleLocColumn expansion+            guard $+                 getSourcePathText (presumedLocPath loc) /= expansionName+              || presumedLocLine   loc /= singleLocLine   expansion+              || presumedLocColumn loc /= singleLocColumn expansion             return loc -    MultiLoc expansion-      <$> (differentPresumed <$> clang_getPresumedLocation location)-      <*> (differentSingle   <$> clang_getSpellingLocation location)-      <*> (differentSingle   <$> clang_getFileLocation     location)+    spelling' <- differentSingle spelling+    file'     <- differentSingle file +    return MultiLoc{+        multiLocExpansion = expansion+      , multiLocPresumed  = differentPresumed presumed+      , multiLocSpelling  = spelling'+      , multiLocFile      = file'+      }+  where+    toSingleCXFile (f, line, column, offset) = return SingleLoc{+        singleLocPath   = f+      , singleLocLine   = fromIntegral line+      , singleLocColumn = fromIntegral column+      , singleLocOffset = fromIntegral offset+      } -toRange :: MonadIO m => Core.CXSourceRange -> m (Range MultiLoc)-toRange = toRangeWith toMulti+-- | Convert to 'MultiLoc RealPath'+--+-- The expansion location must be in a file on disk: it is resolved with+-- 'clang_getRealPath', which throws 'ClangRealPathException' otherwise (for+-- example, for a built-in macro definition or a directive in an unsaved file).+--+-- The spelling and file locations can be virtual even when the expansion+-- location is not, so they are resolved with 'clang_tryGetRealPath' and set to+-- 'Nothing' on failure. For example, any header that includes @stdio.h@ also+-- pulls in this field from glibc:+--+-- > // bits/types/__mbstate_t.h+-- > __WINT_TYPE__ __wch;+--+-- Since @clang@ predefines @\_\_WINT_TYPE\_\_@, the expansion location of the+-- field is in the glibc header, but its spelling location is in @clang@'s+-- @\<built-in\>@ buffer, whose 'CXFile' is @NULL@.+--+-- Consequently, 'Nothing' in 'multiLocSpelling' or 'multiLocFile' may also mean+-- that the location has no real path.+toMultiRealPath :: (MonadIO m, HasCallStack) => Core.CXSourceLocation -> m (MultiLoc RealPath)+toMultiRealPath location = do+    multi     <- toMultiCXFile location+    expansion <- traverse clang_getRealPath (multiLocExpansion multi)+    spelling  <- trySingleRealPath (multiLocSpelling multi)+    file      <- trySingleRealPath (multiLocFile multi)+    return MultiLoc{+        multiLocExpansion = expansion+      , multiLocPresumed  = multiLocPresumed multi+      , multiLocSpelling  = spelling+      , multiLocFile      = file+      }+  where+    trySingleRealPath :: MonadIO m => Maybe (SingleLoc CXFile) -> m (Maybe (SingleLoc RealPath))+    trySingleRealPath = \case+      Nothing  -> return Nothing+      Just loc -> fmap (\rp -> loc{ singleLocPath = rp }) <$>+                    clang_tryGetRealPath (singleLocPath loc) -fromSingle ::-     (MonadIO m, HasCallStack)-  => Core.CXTranslationUnit -> SingleLoc -> m Core.CXSourceLocation-fromSingle unit SingleLoc{singleLocPath, singleLocLine, singleLocColumn} = do-     let SourcePath path = singleLocPath-     file <- Core.clang_getFile unit path-     Core.clang_getLocation-       unit-       file-       (fromIntegral singleLocLine)-       (fromIntegral singleLocColumn)+toMultiSourcePath :: MonadIO m => Core.CXSourceLocation -> m (MultiLoc SourcePath)+toMultiSourcePath location =+    traverse (fmap SourcePath . Core.clang_getFileName) =<< toMultiCXFile location -fromRange ::-     (MonadIO m, HasCallStack)-  => Core.CXTranslationUnit -> Range SingleLoc -> m Core.CXSourceRange-fromRange unit Range{rangeStart, rangeEnd} = do-    rangeStart' <- fromSingle unit rangeStart-    rangeEnd'   <- fromSingle unit rangeEnd-    Core.clang_getRange rangeStart' rangeEnd'+toRangeRealPath :: (MonadIO m, HasCallStack) => Core.CXSourceRange -> m (Range (MultiLoc RealPath))+toRangeRealPath = toRangeWith toMultiRealPath +toRangeSourcePath :: MonadIO m => Core.CXSourceRange -> m (Range (MultiLoc SourcePath))+toRangeSourcePath = toRangeWith toMultiSourcePath+ {-------------------------------------------------------------------------------   Get single location -------------------------------------------------------------------------------} -clang_getExpansionLocation :: MonadIO m => Core.CXSourceLocation -> m SingleLoc+clang_getExpansionLocation :: (MonadIO m, HasCallStack) => Core.CXSourceLocation -> m (SingleLoc RealPath) clang_getExpansionLocation location =-    toSingle =<< Core.clang_getExpansionLocation location+    toSingleRealPath =<< Core.clang_getExpansionLocation location  clang_getPresumedLocation :: MonadIO m => Core.CXSourceLocation -> m PresumedLoc clang_getPresumedLocation location =     toPresumed <$> Core.clang_getPresumedLocation location -clang_getSpellingLocation :: MonadIO m => Core.CXSourceLocation -> m SingleLoc+clang_getSpellingLocation :: (MonadIO m, HasCallStack) => Core.CXSourceLocation -> m (SingleLoc RealPath) clang_getSpellingLocation location =-    toSingle =<< Core.clang_getSpellingLocation location+    toSingleRealPath =<< Core.clang_getSpellingLocation location -clang_getFileLocation :: MonadIO m => Core.CXSourceLocation -> m SingleLoc+clang_getFileLocation :: (MonadIO m, HasCallStack) => Core.CXSourceLocation -> m (SingleLoc RealPath) clang_getFileLocation location =-    toSingle =<< Core.clang_getFileLocation location+    toSingleRealPath =<< Core.clang_getFileLocation location  {-------------------------------------------------------------------------------   Convenience wrappers for @CXSourceLocation@ -------------------------------------------------------------------------------}  -- | Retrieve the source location of the given diagnostic.-clang_getDiagnosticLocation :: MonadIO m => Core.CXDiagnostic -> m MultiLoc+clang_getDiagnosticLocation :: MonadIO m => Core.CXDiagnostic -> m (MultiLoc SourcePath) clang_getDiagnosticLocation diagnostic =-    toMulti =<< Core.clang_getDiagnosticLocation diagnostic+    toMultiSourcePath =<< Core.clang_getDiagnosticLocation diagnostic --- | Retrieve the physical location of the source constructor referenced by the+-- | Retrieve the physical location of the source construct referenced by the -- given cursor.-clang_getCursorLocation :: MonadIO m => Core.CXCursor -> m MultiLoc+clang_getCursorLocation :: (MonadIO m, HasCallStack) => Core.CXCursor -> m (MultiLoc RealPath) clang_getCursorLocation cursor =-    toMulti =<< Core.clang_getCursorLocation cursor+    toMultiRealPath =<< Core.clang_getCursorLocation cursor  -- | Like 'clang_getCursorLocation', but only retrieve the expansion location-clang_getCursorLocation' :: MonadIO m => Core.CXCursor -> m SingleLoc+clang_getCursorLocation' :: (MonadIO m, HasCallStack) => Core.CXCursor -> m (SingleLoc RealPath) clang_getCursorLocation' cursor =     clang_getExpansionLocation =<< Core.clang_getCursorLocation cursor  -- | Retrieve the source location of the given token. clang_getTokenLocation ::-     MonadIO m-  => Core.CXTranslationUnit -> Core.CXToken -> m MultiLoc+     (MonadIO m, HasCallStack)+  => Core.CXTranslationUnit -> Core.CXToken -> m (MultiLoc RealPath) clang_getTokenLocation unit token =-    toMulti =<< Core.clang_getTokenLocation unit token+    toMultiRealPath =<< Core.clang_getTokenLocation unit token  {-------------------------------------------------------------------------------   Convenience wrappers for @CXSourceRange@@@ -373,57 +441,92 @@ -- | Retrieve a source range associated with the diagnostic. clang_getDiagnosticRange ::      MonadIO m-  => Core.CXDiagnostic -> CUInt -> m (Range MultiLoc)+  => Core.CXDiagnostic -> CUInt -> m (Range (MultiLoc SourcePath)) clang_getDiagnosticRange diagnostic range =-    toRange =<< Core.clang_getDiagnosticRange diagnostic range+    toRangeSourcePath =<< Core.clang_getDiagnosticRange diagnostic range  -- | Retrieve the replacement information for a given fix-it. clang_getDiagnosticFixIt ::      MonadIO m   => Core.CXDiagnostic   -> CUInt-  -> m (Range MultiLoc, Text)+  -> m (Range (MultiLoc SourcePath), Text) clang_getDiagnosticFixIt diagnostic fixit = do     (range, replacement) <- Core.clang_getDiagnosticFixIt diagnostic fixit-    (, replacement) <$> toRange range+    (, replacement) <$> toRangeSourcePath range  -- | Retrieve a range for a piece that forms the cursors spelling name. clang_Cursor_getSpellingNameRange ::-     MonadIO m+     (MonadIO m, HasCallStack)   => Core.CXCursor   -> CUInt   -> CUInt-  -> m (Maybe (Range MultiLoc))+  -> m (Maybe (Range (MultiLoc RealPath))) clang_Cursor_getSpellingNameRange cursor pieceIndex options = do     mRange <- Core.clang_Cursor_getSpellingNameRange cursor pieceIndex options     case mRange of       Nothing    -> return Nothing-      Just range -> Just <$> toRangeWith toMulti range+      Just range -> Just <$> toRangeWith toMultiRealPath range  -- | Retrieve the physical extent of the source construct referenced by the -- given cursor.-clang_getCursorExtent :: MonadIO m => Core.CXCursor -> m (Range MultiLoc)+clang_getCursorExtent :: (MonadIO m, HasCallStack) => Core.CXCursor -> m (Range (MultiLoc RealPath)) clang_getCursorExtent cursor =-    toRange =<< Core.clang_getCursorExtent cursor+    toRangeRealPath =<< Core.clang_getCursorExtent cursor  -- | Retrieve a source range that covers the given token. clang_getTokenExtent ::-     MonadIO m+     (MonadIO m, HasCallStack)   => Core.CXTranslationUnit   -> Core.CXToken-  -> m (Range MultiLoc)+  -> m (Range (MultiLoc RealPath)) clang_getTokenExtent unit token =-    toRange =<< Core.clang_getTokenExtent unit token+    toRangeRealPath =<< Core.clang_getTokenExtent unit token  {-------------------------------------------------------------------------------+  Exceptions+-------------------------------------------------------------------------------}++-- | Thrown by 'clang_getRealPath' when the file has no backing file on disk+data ClangRealPathException =+    ClangRealPathException SourcePath CallStack+  deriving stock (Show)+  deriving anyclass (Exception)++{-------------------------------------------------------------------------------   Auxiliary -------------------------------------------------------------------------------} -toSingle :: MonadIO m => (Core.CXFile, CUInt, CUInt, CUInt) -> m SingleLoc-toSingle (file, line, column, offset) = do-    path <- Core.clang_getFileName file+-- | Get the 'RealPath' for a 'Core.CXFile'+--+-- Precondition: the file must be on disk. Throws 'ClangRealPathException'+-- for virtual files.+clang_getRealPath :: (MonadIO m, HasCallStack) => Core.CXFile -> m RealPath+clang_getRealPath file = do+    path <- Core.clang_File_tryGetRealPathName file+    if Text.null path+      then do+        name <- SourcePath <$> Core.clang_getFileName file+        liftIO . throwIO $ ClangRealPathException name callStack+      else return (RealPath path)++-- | Try to get the 'RealPath' for a 'Core.CXFile'+--+-- Returns 'Nothing' for virtual/in-memory files.+clang_tryGetRealPath :: MonadIO m => Core.CXFile -> m (Maybe RealPath)+clang_tryGetRealPath file = do+    path <- Core.clang_File_tryGetRealPathName file+    return $ if Text.null path then Nothing else Just (RealPath path)++-- | Build a @SingleLoc RealPath@. Throws 'ClangRealPathException' for virtual+-- files.+toSingleRealPath ::+     (MonadIO m, HasCallStack)+  => (Core.CXFile, CUInt, CUInt, CUInt) -> m (SingleLoc RealPath)+toSingleRealPath (file, line, column, offset) = do+    realPath <- clang_getRealPath file     return SingleLoc{-        singleLocPath   = SourcePath   path+        singleLocPath   = realPath       , singleLocLine   = fromIntegral line       , singleLocColumn = fromIntegral column       , singleLocOffset = fromIntegral offset@@ -431,7 +534,7 @@  toPresumed :: (Text, CUInt, CUInt) -> PresumedLoc toPresumed (path, line, column) = PresumedLoc{-      presumedLocPath   = SourcePath   path+      presumedLocPath   = SourcePath path     , presumedLocLine   = fromIntegral line     , presumedLocColumn = fromIntegral column     }
src/Clang/HighLevel/Tokens.hs view
@@ -9,26 +9,28 @@ import Control.Monad.IO.Class import Data.Text (Text) import GHC.Generics (Generic)-import GHC.Stack  import Clang.Enum.Simple-import Clang.HighLevel.SourceLoc (MultiLoc, Range, SingleLoc)+import Clang.HighLevel.SourceLoc (MultiLoc, Range) import Clang.HighLevel.SourceLoc qualified as SourceLoc import Clang.LowLevel.Core hiding (clang_tokenize) import Clang.LowLevel.Core qualified as Core+import Clang.Paths (SourcePath)  {-------------------------------------------------------------------------------   Definition -------------------------------------------------------------------------------} -data Token a = Token {+data Token path a = Token {       tokenKind       :: !(SimpleEnum CXTokenKind)     , tokenSpelling   :: !a-    , tokenExtent     :: !(Range MultiLoc)+    , tokenExtent     :: !(Range (MultiLoc path))     , tokenCursorKind :: !(SimpleEnum CXCursorKind)     }-  deriving stock (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)+  deriving stock (Eq, Ord, Functor, Foldable, Traversable, Generic) +deriving stock instance (Show a, Show (Range (MultiLoc path))) => Show (Token path a)+ newtype TokenSpelling = TokenSpelling {       getTokenSpelling :: Text     }@@ -39,32 +41,43 @@ -------------------------------------------------------------------------------}  -- | Get all tokens in the specified range+--+-- @libclang@ lexes the source text between the spelling locations of the start+-- and end of the range.+--+-- Consequently, a range that starts inside a macro expansion starts in the+-- macro definition. For example, given+--+-- > #define T int+-- > T x;+--+-- the extent of @x@ starts at the @int@ produced by expanding @T@, and yields+-- the tokens @int T x@: the text from the @int@ in the definition to @x@. clang_tokenize ::-     (MonadIO m, HasCallStack)+     MonadIO m   => CXTranslationUnit-  -> Range SingleLoc-     -- ^ Range-     ---     -- We use 'Range' 'SingleLoc' here instead of 'CXSourceRange' in order to-     -- avoid ambiguity; see 'Clang.HighLevel.SourceLoc.MultiLoc' for discussion.-  -> m [Token TokenSpelling]-clang_tokenize unit range = liftIO $ do-    range' <- SourceLoc.fromRange unit range-    bracket-        (Core.clang_tokenize unit range')-        (uncurry $ Core.clang_disposeTokens unit) $ \(tokens, numTokens) -> do-      cursors <- clang_annotateTokens unit tokens numTokens-      forM [0 .. pred numTokens] $ \i -> do-        cursor <- index_CXCursorArray cursors i-        toToken unit (index_CXTokenArray tokens i) cursor+  -> CXSourceRange+  -> m [Token SourcePath TokenSpelling]+clang_tokenize unit range =+    liftIO $+      bracket+          (Core.clang_tokenize unit range)+          (uncurry $ Core.clang_disposeTokens unit) $ \(tokens, numTokens) -> do+        if numTokens == 0+          then return []+          else do+            cursors <- clang_annotateTokens unit tokens numTokens+            forM [0 .. pred numTokens] $ \i -> do+              cursor <- index_CXCursorArray cursors i+              toToken unit (index_CXTokenArray tokens i) cursor  toToken ::      MonadIO m-  => CXTranslationUnit -> CXToken -> CXCursor -> m (Token TokenSpelling)+  => CXTranslationUnit -> CXToken -> CXCursor -> m (Token SourcePath TokenSpelling) toToken unit token cursor = do     tokenKind       <- clang_getTokenKind token     tokenSpelling   <- TokenSpelling <$> clang_getTokenSpelling unit token-    tokenExtent     <- SourceLoc.clang_getTokenExtent unit token+    tokenExtent     <- SourceLoc.toRangeSourcePath =<< Core.clang_getTokenExtent unit token     tokenCursorKind <- clang_getCursorKind cursor     return Token{         tokenKind
src/Clang/HighLevel/Types.hs view
@@ -3,18 +3,24 @@ -- Intended for unqualified import; see "Clang.HighLevel" for more detailed -- discussion. module Clang.HighLevel.Types (+    -- * Paths+    RealPath(..)+  , SourcePath(..)     -- * Source locations-    SingleLoc(..)+  , SingleLoc(..)   , MultiLoc(..)   , Range(..)     -- ** Comparisons   , compareSingleLoc   , rangeContainsLoc-    -- ** Conversion-  , toMulti-  , toRange-  , fromSingle-  , fromRange+    -- ** Conversion (CXFile)+  , toMultiCXFile+    -- ** Conversion (RealPath)+  , toMultiRealPath+  , toRangeRealPath+    -- ** Conversion (SourcePath)+  , toMultiSourcePath+  , toRangeSourcePath     -- * Tokens   , Token(..)   , TokenSpelling(..)@@ -55,3 +61,4 @@ import Clang.HighLevel.Fold import Clang.HighLevel.SourceLoc import Clang.HighLevel.Tokens+import Clang.Paths
src/Clang/LowLevel/Core.hs view
@@ -99,6 +99,7 @@   , CXAvailabilityKind(..)   , clang_getTranslationUnitCursor   , clang_equalCursors+  , clang_hashCursor   , clang_getCursorSemanticParent   , clang_getCursorLexicalParent   , clang_getCursorTLSKind@@ -216,6 +217,8 @@   , clang_Location_isFromMainFile     -- * File manipulation routines   , clang_getFileName+  , clang_File_isEqual+  , clang_File_tryGetRealPathName     -- * Debugging   , clang_breakpoint     -- * Exceptions@@ -697,6 +700,13 @@     onHaskellHeap b $ \b' ->       cToBool <$> wrap_equalCursors a' b' +-- | Compute a hash value for the given cursor.+--+-- <https://clang.llvm.org/doxygen/group__CINDEX__CURSOR__MANIP.html#gabf27e0eaee38ae9e7453f55754d4929b>+clang_hashCursor :: MonadIO m => CXCursor -> m CUInt+clang_hashCursor cursor = liftIO $+    onHaskellHeap cursor wrap_hashCursor+ -- | Determine the semantic parent of the given cursor. -- -- The semantic parent of a cursor is the cursor that semantically contains the@@ -2000,6 +2010,23 @@ -- <https://clang.llvm.org/doxygen/group__CINDEX__FILES.html#ga626ff6335ab1e0a2b8c8823301225690> clang_getFileName :: MonadIO m => CXFile -> m Text clang_getFileName file = liftIO $ preallocate_$ wrap_getFileName file++-- | Check whether two files refer to the same underlying file.+--+-- <https://clang.llvm.org/doxygen/group__CINDEX__FILES.html#ga2efb64a9f8b36bdca41e24ced5fc6a69>+clang_File_isEqual :: MonadIO m => CXFile -> CXFile -> m Bool+clang_File_isEqual f1 f2 = liftIO $+    cToBool <$> nowrapper_File_isEqual f1 f2++-- | Get the real, canonical path name of the given file.+--+-- Returns the empty string for virtual files (files that are not saved to+-- disk, such as unsaved files passed to @clang_parseTranslationUnit@).+--+-- <https://clang.llvm.org/doxygen/group__CINDEX__FILES.html#ga2becf44b7e67e25038e6e4fdb3e22a96>+clang_File_tryGetRealPathName :: MonadIO m => CXFile -> m Text+clang_File_tryGetRealPathName file =+    liftIO $ preallocate_$ wrap_File_tryGetRealPathName file  -- | Retrieve the contents of the given file that is loaded in the given -- translation unit.
src/Clang/LowLevel/Core/Enums.hs view
@@ -183,6 +183,12 @@ -- We don't need them, and by omitting them we are compatible with a larger -- range of @libclang@ versions. --+-- NOTE: 'CXType_PredefinedSugar' only exists in @libclang@ 23 and later. We+-- include it anyway, because @libclang@ 23 reports it for types as common as+-- the argument of @malloc@. Older versions never report it, and the+-- 'Clang.Enum.Simple.IsSimpleEnum' instance does not rely on the header+-- declaring it.+-- -- NOTE: We omit @CXType_FirstBuiltin@ and @CXType_LastBuiltin@, which are -- aliases for the first and last builtin type in the list, respectively. If -- we need them, we should define them as separate constants.@@ -269,6 +275,18 @@    | CXType_ExtVector   | CXType_Atomic++    -- | Sugar for the predefined types @__size_t@, @__signed_size_t@ and+    -- @__ptrdiff_t@ (LLVM/Clang 23 and later).+    --+    -- Clang uses these for the type of @sizeof@ and of pointer subtraction,+    -- and in the signatures of library builtins such as @malloc@ and+    -- @strlen@. LLVM/Clang 22 reports the same types as 'CXType_Unexposed';+    -- earlier versions report the underlying integer type directly. That+    -- integer type is the canonical type.+    --+    -- See <https://github.com/llvm/llvm-project/pull/202209>.+  | CXType_PredefinedSugar   deriving stock (Show, Eq, Ord, Enum, Bounded, Generic)  {-------------------------------------------------------------------------------
src/Clang/LowLevel/Core/Instances.hsc view
@@ -149,6 +149,10 @@   simpleToC CXType_Attributed          = #const CXType_Attributed   simpleToC CXType_ExtVector           = #const CXType_ExtVector   simpleToC CXType_Atomic              = #const CXType_Atomic+  -- Headers before LLVM/Clang 23 do not declare CXType_PredefinedSugar, so we+  -- cannot take its value from the header. The value is fixed by the+  -- libclang ABI, and older versions never report it.+  simpleToC CXType_PredefinedSugar     = 182    simpleFromC (#const CXType_Invalid)             = Just CXType_Invalid   simpleFromC (#const CXType_Unexposed)           = Just CXType_Unexposed@@ -216,6 +220,7 @@   simpleFromC (#const CXType_Attributed)          = Just CXType_Attributed   simpleFromC (#const CXType_ExtVector)           = Just CXType_ExtVector   simpleFromC (#const CXType_Atomic)              = Just CXType_Atomic+  simpleFromC 182                                 = Just CXType_PredefinedSugar    simpleFromC _otherwise = Nothing 
src/Clang/LowLevel/FFI.hs view
@@ -4,14 +4,15 @@  #include "clang_config.h" +import Foreign.C.Types+import Foreign.Ptr+ import Clang.Enum.Simple import Clang.Internal.ByValue import Clang.Internal.ConstPtr import Clang.LowLevel.Core.Enums import Clang.LowLevel.Core.Pointers import Clang.LowLevel.Core.Structs-import Foreign.C.Types-import Foreign.Ptr  -- *** Top-level *** @@ -124,9 +125,11 @@  -- OMITTED: int      clang_getFileUniqueID (CXFile file, CXFileUniqueID * outID); --- OMITTED: int      clang_File_isEqual (CXFile file1, CXFile file2);+foreign import capi unsafe "clang_wrappers.h clang_File_isEqual"+  nowrapper_File_isEqual :: CXFile -> CXFile -> IO CInt --- OMITTED: CXString clang_File_tryGetRealPathName (CXFile file);+foreign import capi unsafe "clang_wrappers.h"+  wrap_File_tryGetRealPathName :: CXFile -> W CXString_ -> IO ()  -- *** Physical source locations *** 
src/Clang/Paths.hs view
@@ -2,8 +2,14 @@     -- * Source paths     SourcePath(..)   , getSourcePath-  , nullSourcePath+  , getSourcePathText +    -- * Real paths+  , RealPath(..)+  , getRealPath+  , getRealPathText+  , realPathToSourcePath+     -- * C include directories   , CIncludeDir(..)   ) where@@ -16,24 +22,52 @@   Source paths -------------------------------------------------------------------------------} --- | Filesystem path of a source file, typically a C header------ The 'Text' type is used because Clang uses UTF-8 internally for everything,--- including paths.+-- | Path of a source file as reported by @clang_getFileName@ ----- The format of the path is platform-dependent.  For example, different--- directory separators are used on different platforms.+-- For on-disk files this is typically the path used in the @#include@+-- directive. For virtual files (unsaved buffers, @#line@ directives)+-- it is the clang-assigned name. Platform-dependent format. newtype SourcePath = SourcePath Text-  -- 'Show' instance valid due to 'IsString' instance   deriving newtype (Eq, IsString, Ord, Show)  -- | Get the 'FilePath' representation of a 'SourcePath' getSourcePath :: SourcePath -> FilePath-getSourcePath (SourcePath path) = Text.unpack path+getSourcePath = Text.unpack . getSourcePathText --- | Determine if a 'SourcePath' is empty-nullSourcePath :: SourcePath -> Bool-nullSourcePath (SourcePath path) = Text.null path+-- | Get the 'Text' representation of a 'SourcePath'+getSourcePathText :: SourcePath -> Text+getSourcePathText (SourcePath path) = path++{-------------------------------------------------------------------------------+  Real paths+-------------------------------------------------------------------------------}++-- | Canonical absolute path of an on-disk file+--+-- Obtained via @clang_File_tryGetRealPathName@. Two 'RealPath' values+-- for the same physical file compare equal regardless of include spelling.+-- Platform-dependent format.+--+-- Virtual files (unsaved buffers, the @\<built-in\>@ buffer) have no real path,+-- so functions that must return a 'RealPath' for one throw+-- 'Clang.HighLevel.ClangRealPathException'. That includes the location+-- functions in "Clang.HighLevel". For virtual files, use+-- 'Clang.HighLevel.clang_tryGetRealPath' or a 'SourcePath' conversion such as+-- 'Clang.HighLevel.Types.toMultiSourcePath'.+newtype RealPath = RealPath Text+  deriving stock (Eq, Ord, Show)++-- | Get the 'FilePath' representation of a 'RealPath'+getRealPath :: RealPath -> FilePath+getRealPath = Text.unpack . getRealPathText++-- | Get the 'Text' representation of a 'RealPath'+getRealPathText :: RealPath -> Text+getRealPathText (RealPath path) = path++-- | Embed a 'RealPath' as a 'SourcePath'+realPathToSourcePath :: RealPath -> SourcePath+realPathToSourcePath (RealPath t) = SourcePath t  {-------------------------------------------------------------------------------   C include directories
+ test/Test/Test/Tokens.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Test tokenization of cursor extents+module Test.Test.Tokens (tests) where++import Data.Default (def)+import Data.Text (Text)+import Test.Tasty+import Test.Tasty.HUnit+import Test.Util.Clang qualified as Clang+import Test.Util.Input (TestInput)+import Test.Util.Input qualified as Input++import Clang.Args+import Clang.Enum.Bitfield+import Clang.Enum.Simple+import Clang.HighLevel qualified as HighLevel+import Clang.HighLevel.Types+import Clang.LowLevel.Core++{-------------------------------------------------------------------------------+  List of tests+-------------------------------------------------------------------------------}++tests :: TestTree+tests = testGroup "Test.Test.Tokens" [+      testCase "macroDefinition"        tokenize_macroDefinition+    , testCase "commandLineMacro"       tokenize_commandLineMacro+    , testCase "declStartsInExpansion"  tokenize_declStartsInExpansion+    ]++{-------------------------------------------------------------------------------+  Tests+-------------------------------------------------------------------------------}++tokenize_macroDefinition :: Assertion+tokenize_macroDefinition = do+    tokens <- tokenizeCursor def input CXCursor_MacroDefinition "FOO"+    assertEqual "" ["FOO", "(", "x", ")", "x", "+", "1"] tokens+  where+    input :: TestInput+    input = "#define FOO(x) x + 1\n"++-- | A @-D@ macro lives in Clang's predefines buffer, which has no 'CXFile'+tokenize_commandLineMacro :: Assertion+tokenize_commandLineMacro = do+    tokens <- tokenizeCursor args "" CXCursor_MacroDefinition "FOO"+    assertEqual "" ["FOO", "(", "x", ")", "x", "+", "1"] tokens+  where+    args :: ClangArgs+    args = ClangArgs ["-DFOO(x)=x + 1"]++-- | The range is tokenized as is, from the spelling location of its start+tokenize_declStartsInExpansion :: Assertion+tokenize_declStartsInExpansion = do+    tokens <- tokenizeCursor def input CXCursor_VarDecl "x"+    assertEqual "" ["int", "T", "x"] tokens+  where+    input :: TestInput+    input = Input.unlines [+        "#define T int"+      , "T x;"+      ]++{-------------------------------------------------------------------------------+  Auxiliary+-------------------------------------------------------------------------------}++-- | Tokenize the extent of the top-level cursor with the given kind and name+tokenizeCursor ::+     ClangArgs+  -> TestInput+  -> CXCursorKind+  -> Text+  -> IO [Text]+tokenizeCursor args input kind name =+    Clang.withInputUsing args flags input $ \unit -> do+      root    <- clang_getTranslationUnitCursor unit+      extents <- HighLevel.clang_visitChildren root $ simpleFold $ \curr -> do+        currKind <- fromSimpleEnum <$> clang_getCursorKind curr+        currName <- clang_getCursorSpelling curr+        if currKind == Right kind && currName == name+          then foldContinueWith =<< clang_getCursorExtent curr+          else foldContinue+      case extents of+        [extent] ->+          map (getTokenSpelling . tokenSpelling)+            <$> HighLevel.clang_tokenize unit extent+        _otherwise ->+          assertFailure $ "expected one cursor, found " ++ show (length extents)+  where+    flags :: BitfieldEnum CXTranslationUnit_Flags+    flags = bitfieldEnum [CXTranslationUnit_DetailedPreprocessingRecord]
test/Test/Util/Clang.hs view
@@ -6,6 +6,7 @@ module Test.Util.Clang (     -- * Top-level call into clang     withInput+  , withInputUsing   , parseUsing   ) where @@ -13,6 +14,8 @@ import Data.Default import Test.Util.Input (TestInput (..)) +import Clang.Args+import Clang.Enum.Bitfield import Clang.Enum.Simple import Clang.HighLevel qualified as HighLevel import Clang.HighLevel.Types@@ -23,15 +26,23 @@ -------------------------------------------------------------------------------}  withInput :: TestInput -> (CXTranslationUnit -> IO a) -> IO a-withInput (TestInput input) onSuccess =+withInput = withInputUsing def mempty++withInputUsing ::+     ClangArgs+  -> BitfieldEnum CXTranslationUnit_Flags+  -> TestInput+  -> (CXTranslationUnit -> IO a)+  -> IO a+withInputUsing args flags (TestInput input) onSuccess =     HighLevel.withUnsavedFile "test.h" input $ \file ->     HighLevel.withIndex DisplayDiagnostics   $ \ix   ->     HighLevel.withTranslationUnit2       ix       (Just "test.h")-      def+      args       [file]-      mempty+      flags       onFailure       onSuccess   where
test/Test/Version.hs view
@@ -78,7 +78,7 @@       version `elem` historicReleases        -- For the current versions we don't know which minor/patch to expect-    , major `elem` [21, 22]+    , major `elem` [21, 22, 23]     ]  -- | Historic @llvm@ releases
test/test-clang-bindings.hs view
@@ -4,6 +4,7 @@ import Test.Meta.IsConcrete qualified as IsConcrete import Test.Tasty import Test.Test.Exceptions qualified as Exceptions+import Test.Test.Tokens qualified as Tokens import Test.Version qualified as Version  {-------------------------------------------------------------------------------@@ -19,5 +20,6 @@         ]     , testGroup "Tests" [           Exceptions.tests+        , Tokens.tests         ]     ]