Skip to content

Commit

Permalink
Make interpolated strings frozen
Browse files Browse the repository at this point in the history
  • Loading branch information
kirs committed Mar 24, 2021
1 parent 90afa7e commit 8a0e7b4
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

p "foo #{14}".frozen?
3 changes: 3 additions & 0 deletions spec/ruby/language/fixtures/non_frozen_interpolation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: false

p "foo #{14}".frozen?
10 changes: 10 additions & 0 deletions spec/ruby/language/string_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@ def long_string_literals
it "produce different objects for literals with the same content in different files if they have different encodings" do
ruby_exe(fixture(__FILE__, "freeze_magic_comment_across_files_diff_enc.rb")).chomp.should == "true"
end

it "interpolated string is frozen" do
ruby_exe(fixture(__FILE__, "freeze_magic_comment_interpolation.rb")).chomp.should == "true"
end

describe "when disabled" do
it "interpolated string is not frozen" do
ruby_exe(fixture(__FILE__, "non_frozen_interpolation.rb")).chomp.should == "false"
end
end
end

end
Expand Down
2 changes: 2 additions & 0 deletions spec/tags/language/string_tags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ slow:Ruby String literals with a magic frozen comment produce the same object fo
slow:Ruby String literals with a magic frozen comment produce the same object for literals with the same content in different files
slow:Ruby String literals with a magic frozen comment produce different objects for literals with the same content in different files if the other file doesn't have the comment
slow:Ruby String literals with a magic frozen comment produce different objects for literals with the same content in different files if they have different encodings
slow:Ruby String literals with a magic frozen comment interpolated string is frozen
slow:Ruby String literals with a magic frozen comment when disabled interpolated string is not frozen
2 changes: 1 addition & 1 deletion src/main/java/org/truffleruby/aot/ParserCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private static RubySource loadSource(String feature) {

private static RootParseNode parse(RubySource source) {
final StaticScope staticScope = new StaticScope(StaticScope.Type.LOCAL, null);
final ParserConfiguration parserConfiguration = new ParserConfiguration(null, false, true, false);
final ParserConfiguration parserConfiguration = new ParserConfiguration(null, false, true, false, true);
RubyDeferredWarnings rubyWarnings = new RubyDeferredWarnings();
RootParseNode rootParseNode = TranslatorDriver
.parseToJRubyAST(null, source, staticScope, parserConfiguration, rubyWarnings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ public final class InterpolatedStringNode extends RubyContextSourceNode {

private final Rope emptyRope;

public InterpolatedStringNode(ToSNode[] children, Encoding encoding) {
private final boolean frozen;

public InterpolatedStringNode(ToSNode[] children, Encoding encoding, boolean frozen) {
assert children.length > 0;
this.children = children;
this.emptyRope = RopeOperations.emptyRope(encoding);
this.frozen = frozen;
}

@ExplodeLoop
Expand All @@ -47,6 +50,9 @@ public Object execute(VirtualFrame frame) {
builder = executeStringAppend(builder, toInterpolate);
}

if (frozen) {
builder.freeze();
}

return builder;
}
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/org/truffleruby/parser/BodyTranslator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1304,30 +1304,38 @@ public RubyNode visitDRegxNode(DRegexpParseNode node) {

@Override
public RubyNode visitDStrNode(DStrParseNode node) {
final RubyNode ret = translateInterpolatedString(node.getPosition(), node.getEncoding(), node.children());
final RubyNode ret = translateInterpolatedString(
node.getPosition(),
node.getEncoding(),
node.children(),
node.isFrozen());
return addNewlineIfNeeded(node, ret);
}

@Override
public RubyNode visitDSymbolNode(DSymbolParseNode node) {
SourceIndexLength sourceSection = node.getPosition();

final RubyNode stringNode = translateInterpolatedString(sourceSection, node.getEncoding(), node.children());
final RubyNode stringNode = translateInterpolatedString(
sourceSection,
node.getEncoding(),
node.children(),
false);

final RubyNode ret = StringToSymbolNodeGen.create(stringNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}

private RubyNode translateInterpolatedString(SourceIndexLength sourceSection,
Encoding encoding, ParseNode[] childNodes) {
Encoding encoding, ParseNode[] childNodes, boolean frozen) {
final ToSNode[] children = new ToSNode[childNodes.length];

for (int i = 0; i < childNodes.length; i++) {
children[i] = ToSNodeGen.create(childNodes[i].accept(this));
}

final RubyNode ret = new InterpolatedStringNode(children, encoding);
final RubyNode ret = new InterpolatedStringNode(children, encoding, frozen);
ret.unsafeSetSourceSection(sourceSection);
return ret;
}
Expand Down Expand Up @@ -1362,7 +1370,7 @@ public RubyNode visitDVarNode(DVarParseNode node) {

@Override
public RubyNode visitDXStrNode(DXStrParseNode node) {
final DStrParseNode string = new DStrParseNode(node.getPosition(), node.getEncoding());
final DStrParseNode string = new DStrParseNode(node.getPosition(), node.getEncoding(), false);
string.addAll(node);
final ParseNode argsNode = buildArrayNode(node.getPosition(), string);
final ParseNode callNode = new FCallParseNode(node.getPosition(), "`", argsNode, null);
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/truffleruby/parser/TranslatorDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public RubyRootNode parse(RubySource rubySource, ParserContext parserContext, St
context,
isInlineSource,
!isEvalParse,
false);
false,
context.getCoreLibrary().isLoadingRubyCore());

if (language.options.FROZEN_STRING_LITERALS) {
parserConfiguration.setFrozenStringLiteral(true);
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/truffleruby/parser/ast/DStrParseNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,26 @@

/** A string which contains some dynamic elements which needs to be evaluated (introduced by #). */
public class DStrParseNode extends DParseNode implements ILiteralNode {
private boolean frozen;

public DStrParseNode(SourceIndexLength position, Encoding encoding) {
public DStrParseNode(SourceIndexLength position, Encoding encoding, boolean frozen) {
super(position, encoding);
setFrozen(frozen);
}

@Override
public NodeType getNodeType() {
return NodeType.DSTRNODE;
}

public boolean isFrozen() {
return frozen;
}

public void setFrozen(boolean frozen) {
this.frozen = frozen;
}

/** Accept for the visitor pattern.
*
* @param iVisitor the visitor **/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,24 @@ public class ParserConfiguration {
// whether we should save the end-of-file data as DATA
private boolean saveData = false;

private final boolean core;
private boolean frozenStringLiteral = false;
public boolean allowTruffleRubyPrimitives = false;

private Encoding defaultEncoding;
private RubyContext context;

public ParserConfiguration(RubyContext context, boolean inlineSource, boolean isFileParse, boolean saveData) {
public ParserConfiguration(
RubyContext context,
boolean inlineSource,
boolean isFileParse,
boolean saveData,
boolean core) {
this.context = context;
this.inlineSource = inlineSource;
this.isEvalParse = !isFileParse;
this.saveData = saveData;
this.core = core;
}

public void setFrozenStringLiteral(boolean frozenStringLiteral) {
Expand Down Expand Up @@ -130,4 +137,9 @@ public boolean isSaveData() {
public boolean isInlineSource() {
return inlineSource;
}

public boolean freezeInterpolatedStrings() {
// Core doesn't work with frozen string interpolation
return frozenStringLiteral && !core;
}
}
24 changes: 15 additions & 9 deletions src/main/java/org/truffleruby/parser/parser/ParserSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -1212,8 +1212,8 @@ public void setLexer(RubyLexer lexer) {
this.lexer = lexer;
}

public DStrParseNode createDStrNode(SourceIndexLength position) {
return new DStrParseNode(position, lexer.getEncoding());
public DStrParseNode createDStrNode(SourceIndexLength position, boolean frozen) {
return new DStrParseNode(position, lexer.getEncoding(), frozen);
}

public ParseNodeTuple createKeyValue(ParseNode key, ParseNode value) {
Expand Down Expand Up @@ -1269,12 +1269,12 @@ public ParseNode literal_concat(ParseNode head, ParseNode tail) {
}

if (head instanceof EvStrParseNode) {
head = createDStrNode(head.getPosition()).add(head);
head = createDStrNode(head.getPosition(), false).add(head);
}

if (lexer.getHeredocIndent() > 0) {
if (head instanceof StrParseNode) {
head = createDStrNode(head.getPosition()).add(head);
head = createDStrNode(head.getPosition(), false).add(head);
return list_append(head, tail);
} else if (head instanceof DStrParseNode) {
return list_append(head, tail);
Expand All @@ -1297,7 +1297,10 @@ public ParseNode literal_concat(ParseNode head, ParseNode tail) {

} else if (tail instanceof DStrParseNode) {
if (head instanceof StrParseNode) { // Str + oDStr -> Dstr(Str, oDStr.contents)
DStrParseNode newDStr = new DStrParseNode(head.getPosition(), ((DStrParseNode) tail).getEncoding());
DStrParseNode newDStr = new DStrParseNode(
head.getPosition(),
((DStrParseNode) tail).getEncoding(),
false);
newDStr.add(head);
newDStr.add(tail);
return newDStr;
Expand All @@ -1308,12 +1311,15 @@ public ParseNode literal_concat(ParseNode head, ParseNode tail) {

// tail must be EvStrParseNode at this point
if (head instanceof StrParseNode) {

StrParseNode front = (StrParseNode) head;
//Do not add an empty string node
if (((StrParseNode) head).getValue().byteLength() == 0) {
head = createDStrNode(head.getPosition());
if (front.getValue().byteLength() == 0) {
head = createDStrNode(
head.getPosition(),
front.isFrozen() && configuration.freezeInterpolatedStrings());
} else {
head = createDStrNode(head.getPosition()).add(head);
head = createDStrNode(head.getPosition(), front.isFrozen() && configuration.freezeInterpolatedStrings())
.add(head);
}
}
return ((DStrParseNode) head).add(tail);
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/truffleruby/parser/parser/RubyParser.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/main/java/org/truffleruby/parser/parser/RubyParser.y
Original file line number Diff line number Diff line change
Expand Up @@ -2054,7 +2054,7 @@ literal : numeric {
| dsym

strings : string {
$$ = $1 instanceof EvStrParseNode ? new DStrParseNode($1.getPosition(), lexer.getEncoding()).add($1) : $1;
$$ = $1 instanceof EvStrParseNode ? new DStrParseNode($1.getPosition(), lexer.getEncoding(), support.getConfiguration().freezeInterpolatedStrings()).add($1) : $1;
/*
NODE *node = $1;
if (!node) {
Expand Down Expand Up @@ -2114,7 +2114,7 @@ word_list : /* none */ {
$$ = new ArrayParseNode(lexer.getPosition());
}
| word_list word ' ' {
$$ = $1.add($2 instanceof EvStrParseNode ? new DStrParseNode($1.getPosition(), lexer.getEncoding()).add($2) : $2);
$$ = $1.add($2 instanceof EvStrParseNode ? new DStrParseNode($1.getPosition(), lexer.getEncoding(), false).add($2) : $2);
}

word : string_content {
Expand Down Expand Up @@ -2711,7 +2711,7 @@ assoc : arg_value tASSOC arg_value {
}
| tSTRING_BEG string_contents tLABEL_END arg_value {
if ($2 instanceof StrParseNode) {
DStrParseNode dnode = new DStrParseNode(support.getPosition($2), lexer.getEncoding());
DStrParseNode dnode = new DStrParseNode(support.getPosition($2), lexer.getEncoding(), false);
dnode.add($2);
$$ = support.createKeyValue(new DSymbolParseNode(support.getPosition($2), dnode), $4);
} else if ($2 instanceof DStrParseNode) {
Expand Down

0 comments on commit 8a0e7b4

Please sign in to comment.