About loc pragma

TSUYUSATO Kitsune (@MakeNowJust)

About Me

  • TSUYUSATO Kitsune
  • GitHub: @MakeNowJust
  • Twitter: @make_now_just
  • Frontend Engineer at Space Pirates LLC.

No.4 Crystal Contributor

loc pragma?

  • One of the hidden (undocumented) features of Crystal
  • It can update location on parsing explicitly.

Example: ECR

How ECR works

  1. Macro ECR.def_to_s calls run macro with ecr/process and given ECR file.
  2. Program ecr/process prints Crystal source code generated from given ECR file.
  3. Then, this generated source code is embeded as a ECR.def_to_s result.

ecr-sample.cr

1. require "ecr"
2.
3. class Foo
4. getter title
5.
6. def initialize(@title : String)
7. end
8.
9. ECR.def_to_s "#{__DIR__}/foo.ecr"
10. end
11.
12. puts Foo.new("hello world").to_s

ecr-sample.ecr

1. <html>
2. <head>
3. <title><%= title %></title>
4. </head>
5. <body>
6. <h1><%= tilte %></h1>
7. </body>
8. </html>

 

Execute ecr-sample.cr

1. Error in foo.cr:12: instantiating 'Foo#to_s()'
2.
3. puts Foo.new("hello world").to_s
4. ^~~~
5.
6. in .../src/object.cr:95: instantiating 'String.class#build()'
7.
8. String.build do |io|
9. ^~~~~
10.
11. in .../src/string.cr:268: instantiating 'String::Builder.class#build(Int32)'
12.
13. String::Builder.build(capacity) do |builder|
14. ^~~~~
15.
16. in .../src/string.cr:268: instantiating 'String::Builder.class#build(Int32)'
17.
18. String::Builder.build(capacity) do |builder|
19. ^~~~~
20.
21. in .../src/object.cr:95: instantiating 'String.class#build()'
22.
23. String.build do |io|
24. ^~~~~
25.
26. in .../src/object.cr:96: instantiating 'to_s(String::Builder)'
27.
28. to_s io
29. ^~~~
30.
31. in foo.ecr:6: undefined local variable or method 'tilte'
32.
33. <h1><%= tilte %></h1>
34. ^~~~~

 

How does ECR tell original source code location to compiler?

Run ecr/process at hand.

$ echo 'require "ecr/process"' |
    crystal run --stdin-filename process.cr -- ecr-sample.ecr ___io___

(MEMO: the second command-line argument of ecr/process means a variable name to write output.)

Result of ecr-sample.ecr

1. ___io___ << "<html>\n <head>\n <title>"
2. (title).to_s ___io___
3. ___io___ << "</title>\n </head>\n <body>\n <h1>"
4. (tilte).to_s ___io___
5. ___io___ << "</h1>\n </body>\n</html>\n"

 

loc pragma syntax

  • #<loc:"filename",line_number,column_number>
  • Override parsing location as given values.
  • It looks like line comment, but it is not a comment.
  • So, source code after loc pragma is also parsed.

loc pragma syntax (push and pop)

  • #<loc:push> and #<loc:pop>
  • #<loc:push> saves current location.
  • #<loc:pop> restores the location saved by previous #<loc:push>.

It is useful to distinguish between macro generated code and original code.

Example: fix macro argument location

new.cr

1. macro new(node)
2. {{node}}.new
3. end
4.
5. new BadType

 

Execute new.cr

1. Error in new.cr:5: expanding macro
2.
3. new BadType
4. ^~~
5.
6. in new.cr:5: expanding macro
7.
8. new BadType
9. ^
10.
11. in macro 'new' new.cr:1, line 1:
12.
13. > 1. BadType.new
14.
15. undefined constant BadType

 

new.cr with loc pragma

1. macro new(node)
2. #<loc:{{node.filename}},{{node.line_number}},{{node.column_number}}>{{node}}.new
3. end
4.
5. new BadType

 

Execute new.cr with loc pragma

1. Error in new.cr:5: expanding macro
2.
3. new BadType
4. ^~~
5.
6. in new.cr:5: undefined constant BadType
7.
8. new BadType
9. ^~~~~~~

Then, it shows really clear error message.

If all expanded macro argument outputs its loc pragma...

  • I think it is more natural and useful.
  • However loc pragma is just text, so it causes some issues. e.g.
  • When macro expansion appears in doc comment...
  • When macro expansion appears in string literal...

So, currently the compiler does not outputs loc pragma for macro argument.

But now,

I created a new Pull Request with new approach.

crystal-lang/crystal#7008

It does not outputs loc pragma directly, but it keeps loc pragmas as internal data.

Unfortunately it is not merged...

Stay tuned ❤️