Skip to content

Commit cc71806

Browse files
Merge pull request #151 from fpco/fix/133
Add CI of error-message-lines
2 parents b3d044a + dd235d5 commit cc71806

File tree

5 files changed

+151
-7
lines changed

5 files changed

+151
-7
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
run: |
4646
if [ ${{ matrix.os }} == "ubuntu-latest" ] ; then
4747
stack test --stack-yaml stack-${{ matrix.stackage }}.yaml --flag inline-c:gsl-example --flag inline-c-cpp:std-vector-example
48+
./inline-c-cpp/test-error-message-line-numbers.sh --stack-yaml stack-${{ matrix.stackage }}.yaml
4849
else
4950
stack test --stack-yaml stack-${{ matrix.stackage }}.yaml --flag inline-c-cpp:std-vector-example
51+
./inline-c-cpp/test-error-message-line-numbers.sh --stack-yaml stack-${{ matrix.stackage }}.yaml
5052
fi

inline-c-cpp/src/Language/C/Inline/Cpp/Exception.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,10 @@ exceptionalValue typeStr =
210210

211211
tryBlockQuoteExp :: QuasiQuoter -> String -> Q Exp
212212
tryBlockQuoteExp block blockStr = do
213-
let (ty, body) = C.splitTypedC blockStr
213+
let (ty, body, bodyLineShift) = C.splitTypedC blockStr
214214
_ <- C.include "HaskellException.hxx"
215215
basePtrVarName <- newName "basePtr"
216+
there <- location
216217
let inlineCStr = unlines
217218
[ ty ++ " {"
218219
, " void** __inline_c_cpp_base_ptr__ = $(void** " ++ nameBase basePtrVarName ++ ");"
@@ -223,7 +224,9 @@ tryBlockQuoteExp block blockStr = do
223224
, " HaskellException** __inline_c_cpp_haskellexception__ = (HaskellException**)(__inline_c_cpp_base_ptr__ + 4);"
224225
, " *__inline_c_cpp_exception_type__ = 0;"
225226
, " try {"
227+
, C.lineDirective (C.shiftLines (bodyLineShift - 1) there)
226228
, body
229+
, C.lineDirective $(C.here)
227230
, " } catch (const HaskellException &e) {"
228231
, " *__inline_c_cpp_exception_type__ = " ++ show ExTypeHaskellException ++ ";"
229232
, " *__inline_c_cpp_haskellexception__ = new HaskellException(e);"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
set -x
3+
sed -i -e 's/.*uncomment this line.*//g' inline-c-cpp/test/tests.hs
4+
stack test $@ inline-c-cpp >& error-log
5+
cat error-log
6+
grep -n 'Test this line' inline-c-cpp/test/tests.hs | awk -F ':' '{print $1}' > exp
7+
cat exp
8+
grep 'tests.hs:[0-9]*:.*error' error-log | awk -F ':' '{print $2}' > out
9+
cat out
10+
set -xe
11+
diff exp out

inline-c-cpp/test/tests.hs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,60 @@ main = Hspec.hspec $ do
284284

285285
result `shouldBeRight` 0xDEADBEEF
286286

287+
Hspec.it "code can contain preprocessor directives" $ do
288+
result <- try $ [C.throwBlock| int {
289+
#ifndef THE_MACRO_THAT_HAS_NOT_BEEN_DEFINED
290+
return 0xDEADBEEF;
291+
#else
292+
return 0xBEEFCAFE;
293+
#endif
294+
} |]
295+
296+
result `shouldBeRight` 0xDEADBEEF
297+
298+
{- Manual test cases for testing lineDirective and splitTypedC -- For CI, uncomment this line.
299+
300+
Hspec.it "error reporting test case" $ do
301+
result <- try $ [C.throwBlock| int { 0 = 0; return 0xDEADBEEF; /* Test this line. */}|]
302+
result `shouldBeRight` 0xDEADBEEF
303+
304+
Hspec.it "error reporting test case" $ do
305+
result <- try $ [C.throwBlock| int
306+
{ 1 = 1; return 0xDEADBEEF; /* Test this line. */}
307+
|]
308+
result `shouldBeRight` 0xDEADBEEF
309+
310+
Hspec.it "error reporting test case" $ do
311+
result <- try $ [C.throwBlock| int
312+
{
313+
2 = 2; /* Test this line. */
314+
return 0xDEADBEEF;
315+
}
316+
|]
317+
result `shouldBeRight` 0xDEADBEEF
318+
319+
Hspec.it "error reporting test case" $ do
320+
result <- try $ [C.throwBlock|
321+
int
322+
{
323+
3 = 3; /* Test this line. */
324+
return 0xDEADBEEF;
325+
}
326+
|]
327+
result `shouldBeRight` 0xDEADBEEF
328+
329+
Hspec.it "error reporting test case" $ do
330+
result <- try $ [C.throwBlock|
331+
332+
int
333+
{
334+
4 = 4; /* Test this line. */
335+
return 0xDEADBEEF;
336+
}
337+
|]
338+
result `shouldBeRight` 0xDEADBEEF
339+
-- For CI, uncomment this line. -}
340+
287341
Hspec.describe "Macros" $ do
288342
Hspec.it "generated std::vector instances work correctly" $ do
289343
intVec <- StdVector.new @C.CInt

inline-c/src/Language/C/Inline/Internal.hs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ module Language.C.Inline.Internal
5353
, runParserInQ
5454
, splitTypedC
5555

56+
-- * Line directives
57+
, lineDirective
58+
, here
59+
, shiftLines
60+
5661
-- * Utility functions for writing quasiquoters
5762
, genericQuote
5863
, funPtrQuote
@@ -295,7 +300,7 @@ inlineCode Code{..} = do
295300
-- Write out definitions
296301
ctx <- getContext
297302
let out = fromMaybe id $ ctxOutput ctx
298-
let directive = maybe "" (\l -> "#line " ++ show (fst $ TH.loc_start l) ++ " " ++ show (TH.loc_filename l ) ++ "\n") codeLoc
303+
let directive = maybe "" lineDirective codeLoc
299304
void $ emitVerbatim $ out $ directive ++ codeDefs
300305
-- Create and add the FFI declaration.
301306
ffiImportName <- uniqueFfiImportName
@@ -681,14 +686,37 @@ genericQuote purity build = quoteCode $ \rawStr -> do
681686
go (paramType : params) = do
682687
[t| $(return paramType) -> $(go params) |]
683688

684-
splitTypedC :: String -> (String, String)
685-
-- ^ Returns the type and the body separately
686-
splitTypedC s = (trim ty, case body of
687-
[] -> []
688-
r -> r)
689+
690+
-- NOTE: splitTypedC wouldn't be necessary if inline-c-cpp could reuse C.block
691+
-- internals with a clean interface.
692+
-- This would be a significant refactoring but presumably it would lead to an
693+
-- api that could let users write their own quasiquoters a bit more conveniently.
694+
695+
-- | Returns the type and the body separately.
696+
splitTypedC :: String -> (String, String, Int)
697+
splitTypedC s = (trim ty, bodyIndent <> body, bodyLineShift)
689698
where (ty, body) = span (/= '{') s
690699
trim x = L.dropWhileEnd C.isSpace (dropWhile C.isSpace x)
691700

701+
-- We may need to correct the line number of the body
702+
bodyLineShift = length (filter (== '\n') ty)
703+
704+
-- Indentation is relevant for error messages when the syntax is:
705+
-- [C.foo| type
706+
-- { foo(); }
707+
-- |]
708+
bodyIndent =
709+
let precedingSpaceReversed =
710+
takeWhile (\c -> C.isSpace c) $
711+
reverse $
712+
ty
713+
(precedingSpacesTabsReversed, precedingLine) =
714+
span (`notElem` ("\n\r" :: [Char])) precedingSpaceReversed
715+
in case precedingLine of
716+
('\n':_) -> reverse precedingSpacesTabsReversed
717+
('\r':_) -> reverse precedingSpacesTabsReversed
718+
_ -> "" -- it wasn't indentation after all; just spaces after the type
719+
692720
-- | Data to parse for the 'funPtr' quasi-quoter.
693721
data FunPtrDecl = FunPtrDecl
694722
{ funPtrReturnType :: C.Type C.CIdentifier
@@ -756,6 +784,52 @@ funPtrQuote callSafety = quoteCode $ \rawCode -> do
756784
]
757785
return (s ++ s')
758786

787+
------------------------------------------------------------------------
788+
-- Line directives
789+
790+
-- | Tell the C compiler where the next line came from.
791+
--
792+
-- Example:
793+
--
794+
-- @@@
795+
-- there <- location
796+
-- f (unlines
797+
-- [ lineDirective $(here)
798+
-- , "generated_code_user_did_not_write()"
799+
-- , lineDirective there
800+
-- ] ++ userCode
801+
-- ])
802+
-- @@@
803+
--
804+
-- Use @lineDirective $(C.here)@ when generating code, so that any errors or
805+
-- warnings report the location of the generating haskell module, rather than
806+
-- tangentially related user code that doesn't contain the actual problem.
807+
lineDirective :: TH.Loc -> String
808+
lineDirective l = "#line " ++ show (fst $ TH.loc_start l) ++ " " ++ show (TH.loc_filename l ) ++ "\n"
809+
810+
-- | Get the location of the code you're looking at, for use with
811+
-- 'lineDirective'; place before generated code that user did not write.
812+
here :: TH.ExpQ
813+
here = [| $(TH.location >>= \(TH.Loc a b c (d1, d2) (e1, e2)) ->
814+
[|Loc
815+
$(TH.lift a)
816+
$(TH.lift b)
817+
$(TH.lift c)
818+
($(TH.lift d1), $(TH.lift d2))
819+
($(TH.lift e1), $(TH.lift e2))
820+
|])
821+
|]
822+
823+
shiftLines :: Int -> TH.Loc -> TH.Loc
824+
shiftLines n l = l
825+
{ TH.loc_start =
826+
let (startLn, startCol) = TH.loc_start l
827+
in (startLn + n, startCol)
828+
, TH.loc_end =
829+
let (endLn, endCol) = TH.loc_end l
830+
in (endLn + n, endCol)
831+
}
832+
759833
------------------------------------------------------------------------
760834
-- Utils
761835

0 commit comments

Comments
 (0)