Matthew Lindfield Seager

Matthew Lindfield Seager

Pragma Precedence Preoccupation

The latest Ruby Weekly newsletter linked to a helpful blog post about magic comments (or pragmas) in Ruby by Mehdi Farsi. It’s a short read and I appreciated getting some more info on a topic I’ve never thought too much about. A couple of his examples made me a little suspicious though, so I did some more digging.

When specifying encoding, he demonstrates that the first magic comment is applied and any other encoding comments are ignored. That makes intuitive sense to me: you can’t change encoding part way through a file (at least I really hope you can’t, encodings cause enough confusion as it is!!!).

For his next two tests, Mehdi simply gave the magic comment two different settings at the top of the file and observed that the setting in the second one takes effect. He then concluded that only the last one is processed while the others are skipped.

Another possible conclusion is that both were processed and the different settings were in effect for different parts of the file. The way to test that would be to use a bigger example and turn the settings on and off multiple times:

# indentation-precedence-test.rb
# warn_indent: true
puts "\n*** Indentation Warnings: true ***"
def bad_indentation_1
"#{__method__} (line #{__LINE__})"
  end
p bad_indentation_1

# warn_indent: false
puts "\n*** Indentation Warnings: false ***"

def bad_indentation_2
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_2

# warn_indent: true
puts "\n*** Indentation Warnings: true ***"

def bad_indentation_3
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_3

# warn_indent: false
puts "\n*** Indentation Warnings: false ***"

def bad_indentation_4
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_4

In this test we try to turn the indentation warnings on and off (and on and off again). When I run it with ruby indentation-precedence-test.rb I get the following output:

indentation-precedece-test.rb:6: warning: mismatched indentations at 'end' with 'def' at 4
indentation-precedece-test.rb:24: warning: mismatched indentations at 'end' with 'def' at 22

*** Indentation Warnings: true ***
"bad_indentation_1 (line 5)"

*** Indentation Warnings: false ***
"bad_indentation_2 (line 14)"

*** Indentation Warnings: true ***
"bad_indentation_3 (line 23)"

*** Indentation Warnings: false ***
"bad_indentation_4 (line 32)"

Despite having four badly indented methods we only received two warnings; one for the first method (lines 4-6) and one for the third method (lines 22-24). These two methods were (badly) defined after turning warnings on. Conversely, the other two methods don’t cause warnings. They were (badly) defined after turning warnings off. This confirms that all of the warn_indent settings in the magic comments are applied, simply changing the state from that point in the file onwards.

But what about in other files when you require them? Does the setting carry over?

# indentation-test-requiring.rb
puts "\n*** Main file, Indentation Warnings: default ***"

def bad_indentation_1
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_1

# warn_indent: true
puts "\n*** Main file, Indentation Warnings: true ***"

def bad_indentation_2
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_2

require_relative 'indentation-test-inherit.rb'

# warn_indent: false
puts "\n*** Indentation Warnings: false ***"

def bad_indentation_4
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_4

require_relative 'indentation-test-override.rb'

puts "\n*** Main file, Indentation Warnings: did they change??? ***"

def bad_indentation_6
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_6
# indentation-test-inherit.rb
puts "\n*** New file, Indentation Warnings: Do they inherit??? ***"

def bad_indentation_3
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_3
# indentation-test-override.rb
# warn_indent: true
puts "\n*** Another new file, Indentation Warnings: true ***"

def bad_indentation_5
"#{__method__} (line #{__LINE__})"
  end

p bad_indentation_5

In this example we only get two warnings, one for method 2 in the main file and one for method 5 in the “override” file (that we explicitly turned them back on for).

This shows us a few things:

  1. Those warnings are off in a file by default (method 1 and method 3)
  2. Turning it on for one file doesn’t turn it on for files that are later required (on for method 2, off for method 3 in a new file)
  3. Turning it on in a required file doesn’t effect methods defined after that point in the requiring file (on for method 5 in a second new file, still off for method 6 in the original file)
  4. The warnings are output when the file is first parsed (which further explains point 3 above)

But do the same rules hold for frozen string literals?


Let’s start by confirming the default state for frozen string literals is off:

# frozens-test.rb
string_1 = 'frozen? 1'
p "string_1.frozen?: #{string_1.frozen?}" # => "string_1.frozen?: false"

Now let’s turn it on for the next one:

# frozens-test.rb
string_1 = 'frozen? 1'
p "string_1.frozen?: #{string_1.frozen?}" # => "string_1.frozen?: false"

# frozen_string_literal: true
string_2 = 'frozen? 2'
p "string_2.frozen?: #{string_2.frozen?}" # => "string_2.frozen?: false"

Wait, what!? It’s not frozen! Unlike the indentations warning, it seems this particular magic comment has to come at the top of the file. But in Mehdi’s test he had another comment before it. Let’s test if all comments and whitespace are okay:

# frozens-test-2.rb

# frozen_string_literal: true
# Random comment after turning string literals on...



#... and lots of white space before we turn them back off
# frozen_string_literal: false

# Different comment types are fine...
=begin

  If you want to read
  a block-commented Haiku
  this will have to do

=end

# Other pragmas (magic comments) are fine too
# # warn_indent: true

# We can still turn them on...
# frozen_string_literal: true
string_3 = 'frozen? 3'
p "string_3.frozen?: #{string_3.frozen?}" # => "string_3.frozen?: true"

It turns out comments are fine, white space is fine, and other pragmas (magic comments) are fine.

So maybe it just needs to be before any strings?

# frozens-test-3.rb

2 + 2

# frozen_string_literal: true
string_4 = 'frozen? 4'
p "string_4.frozen?: #{string_4.frozen?}" # => "string_4.frozen?: false"

Nope! Adding 2 numbers together (or even defining an empty method), prevents it from being used. In my testing, the only vaguely code-like things that seem to be allowed before this magic comment are pointless references to existing objects:

# frozens-test-4.rb

# nil is okay:
nil

# Mentioning special variables (like the last exception) seems to be okay
$!

# Referencing the args array is okay too, even if it's not empty
$*

# Referencing the file path is also fine
__FILE__

# frozen_string_literal: true
string_5 = 'frozen? 5'
p "string_5.frozen?: #{string_5.frozen?}" # => "string_5.frozen?: true"

And just when you thought I’d plumbed the boring depths of this, I found one last exception to the rule:

# frozens-test-5.rb

# It doesn't like special variables on consecutive lines!!!
$!
$*

# frozen_string_literal: true
string_6 = 'frozen? 6'
p "string_6.frozen?: #{string_6.frozen?}" # => "string_6.frozen?: false"

So in conclusion:

  1. The # frozen_string_literal has to come before any (useful) code
  2. If you set it multiple times, the final setting (before any useful code) wins