Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make interpolated strings frozen #2304

Merged
merged 1 commit into from
Mar 25, 2021
Merged
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
10 changes: 10 additions & 0 deletions spec/ruby/language/string_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,4 +308,14 @@ def long_string_literals
eval(code).should_not.frozen?
end
end

ruby_version_is ""..."3.0" do
it "creates a frozen String when # frozen-string-literal: true is used" do
code = <<~'RUBY'
# frozen-string-literal: true
"a#{6*7}c"
RUBY
eval(code).should.frozen?
end
end
end
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() && !inCore());
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
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
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());
} else {
head = createDStrNode(head.getPosition()).add(head);
head = createDStrNode(head.getPosition(), front.isFrozen())
.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().isFrozenStringLiteral()).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