Skip to content

Add option for generating origin pragmas in Curry interfaces

Fredrik Wieczerkowski requested to merge origin-pragmas into master

While Curry interfaces work well in a compiler context, they present some UX issues in the context of source-oriented developer tooling such as the Curry language server. A specific inconvenience arises when the user opens a Curry package that contains previously generated interfaces (e.g. .curry/.../Prelude.icurry): The frontend will reference the definitions from the interface rather than the original source file (in this case Prelude.curry), resulting in the user being taken to Prelude.icurry rather than Prelude.curry when ctrl-clicking on some symbol defined in Prelude.

One idea to solve this was to use line pragmas (#143) in the generated interfaces. The problem is that line pragmas, at least if implemented in roughly analogous way to GHC, would require modifying spans at a lexer level, in order to offset errors and warnings too. Some preliminary experiments can be found in the line-pragmas branch. In practice, this mechanism turned out to be brittle and not well-suited for our task, since the Curry interfaces are not merely a preprocessor expansion of the original source file.

Therefore this PR proposes to introduce a new pragma of the form

{-# ORIGIN "<path to source file>" <start line> <start column> <end line> <end column> #-}

This pragma encapsulates the original span of an identifier and is generated as part of a Curry interface when the option --origin-pragmas is set1. For example, Prelude.icurry could now look like this, assuming we are using a PAKCS installation in /opt/pakcs:

...
print 0 :: Show a => a -> IO ()
{-# ORIGIN "/opt/pakcs/lib/Prelude.curry" 1994 1 1994 5 #-};
putChar 1 :: Char -> IO ()
{-# ORIGIN "/opt/pakcs/lib/Prelude.curry" 1978 1 1978 7 #-};
putStr 1 :: [Char] -> IO ()
{-# ORIGIN "/opt/pakcs/lib/Prelude.curry" 1985 1 1985 6 #-};
putStrLn 1 :: [Char] -> IO ()
{-# ORIGIN "/opt/pakcs/lib/Prelude.curry" 1990 1 1990 8 #-};
read 1 :: Read a => [Char] -> a
...

The pragma is generated for all IDecls2, along with imports and the top-level interface, to ensure that module identifiers reference the correct source location too, and is automatically applied/generated when importing/exporting the interface.

I have tested this branch using the language server and the mechanism seems to work quite well, but would appreciate some more testing and feedback, since there might be edge cases that do not arise in trivial packages.


  1. The rationale for gating the generation of origin pragmas behind a flag is to avoid the overhead of these pragmas in a compiler context. Since the language server generates its own .icurry files anyways, it can set the flag without burdening other Curry compilers or tools.

  2. Implementation note: We generate the pragmas after the declarations to avoid parsing ambiguities.

Merge request reports