Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ struct parser_params {
int parser_ruby_sourceline; /* current line no. */
VALUE parser_ruby_sourcefile_string;
rb_encoding *enc;
int immutable_strings;

int parser_yydebug;

Expand Down Expand Up @@ -3816,6 +3817,11 @@ strings : string
else {
node = evstr2dstr(node);
}

if (parser->immutable_strings) {
node = NEW_CALL(node, idFreeze, 0);
}

$$ = node;
/*%
$$ = $1;
Expand All @@ -3838,7 +3844,9 @@ string : tCHAR
string1 : tSTRING_BEG string_contents tSTRING_END
{
/*%%%*/

$$ = $2;

/*%
$$ = dispatch1(string_literal, $2);
%*/
Expand All @@ -3865,7 +3873,12 @@ xstring : tXSTRING_BEG xstring_contents tSTRING_END
break;
}
}
$$ = node;

if (parser->immutable_strings) {
$$ = NEW_CALL(node, idFreeze, 0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

} else {
$$ = node;
}
/*%
$$ = dispatch1(xstring_literal, $2);
%*/
Expand Down Expand Up @@ -6653,6 +6666,12 @@ parser_set_encode(struct parser_params *parser, const char *name)
#endif
}

static void
parser_set_immutable(struct parser_params *parser, const char *name, const char *val)
{
if (strcmp(val, "string") == 0) { parser->immutable_strings = 1; }
}

static int
comment_at_top(struct parser_params *parser)
{
Expand Down Expand Up @@ -6710,6 +6729,7 @@ static const struct magic_comment magic_comments[] = {
{"coding", magic_comment_encoding, parser_encode_length},
{"encoding", magic_comment_encoding, parser_encode_length},
{"warn_indent", parser_set_token_info},
{"immutable", parser_set_immutable}
};
#endif

Expand Down Expand Up @@ -10862,6 +10882,7 @@ parser_initialize(struct parser_params *parser)
parser->heap = NULL;
#endif
parser->enc = rb_utf8_encoding();
parser->immutable_strings = 0;
}

#ifdef RIPPER
Expand Down
72 changes: 72 additions & 0 deletions test/ruby/test_string_literal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- immutable: string -*-

require 'test/unit'
require_relative 'test_string_literal_mutable.rb'


STRINGS = [
'hello',
"hello",
%{Hello},
%Q{Hello},
%q{Hello},
<<-EOS
Hello World
EOS
]

class TestStringLiteral < Test::Unit::TestCase
def mutate(str)
str.slice!(1, 2)
end

def interpolated_string(message)
"Interpolated: #{message}"
end

def some_string
"A nice frozen string!"
end

def test_strings_are_immutable_in_this_file
STRINGS.each do |s|
exception = assert_raise RuntimeError, s do
mutate(s)
end
assert_match /can't modify frozen String/, exception.message
end
end

def test_strings_in_other_file_are_mutable
mutate(TestStringLiteralMutable::CONSTANT)
assert_equal "SING", TestStringLiteralMutable::CONSTANT
end

def test_literal_strings_should_have_the_same_object_id
s1 = some_string
s2 = some_string
assert_equal s1.object_id, s2.object_id
end

def test_different_literal_strings_with_the_same_value_in_the_same_file_should_have_the_same_object_id
s1 = some_string
s2 = "A nice frozen string!"
assert_equal s2.object_id, s2.object_id
end

def test_string_interpolation
str = interpolated_string("blah blah")
exception = assert_raise RuntimeError do
mutate(str)
end
assert_match /can't modify frozen String/, exception.message
end

def test_interpolated_strings_should_have_a_different_object_id
s1 = interpolated_string('x')
s2 = interpolated_string('x')
assert_equal "Interpolated: x", s1
assert_equal "Interpolated: x", s2
assert_not_equal s1.object_id, s2.object_id
end
end
3 changes: 3 additions & 0 deletions test/ruby/test_string_literal_mutable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module TestStringLiteralMutable
CONSTANT = "STRING"
end