Skip to content

Commit 473cfed

Browse files
committed
Shareable constant nodes
1 parent 20e768d commit 473cfed

File tree

8 files changed

+172
-16
lines changed

8 files changed

+172
-16
lines changed

config.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ warnings:
248248
- INTEGER_IN_FLIP_FLOP
249249
- INVALID_CHARACTER
250250
- INVALID_NUMBERED_REFERENCE
251+
- INVALID_SHAREABLE_CONSTANT_VALUE
251252
- KEYWORD_EOL
252253
- LITERAL_IN_CONDITION_DEFAULT
253254
- LITERAL_IN_CONDITION_VERBOSE
@@ -667,6 +668,15 @@ flags:
667668
- name: FORCED_US_ASCII_ENCODING
668669
comment: "internal bytes forced the encoding to US-ASCII"
669670
comment: Flags for regular expression and match last line nodes.
671+
- name: ShareableConstantNodeFlags
672+
values:
673+
- name: LITERAL
674+
comment: "constant writes that should be modified with shareable constant value literal"
675+
- name: EXPERIMENTAL_EVERYTHING
676+
comment: "constant writes that should be modified with shareable constant value experimental everything"
677+
- name: EXPERIMENTAL_COPY
678+
comment: "constant writes that should be modified with shareable constant value experimental copy"
679+
comment: Flags for shareable constant nodes.
670680
- name: StringFlags
671681
values:
672682
- name: FORCED_UTF8_ENCODING
@@ -3063,6 +3073,29 @@ nodes:
30633073
30643074
self
30653075
^^^^
3076+
- name: ShareableConstantNode
3077+
fields:
3078+
- name: flags
3079+
type: flags
3080+
kind: ShareableConstantNodeFlags
3081+
- name: write
3082+
type: node
3083+
kind:
3084+
- ConstantWriteNode
3085+
- ConstantAndWriteNode
3086+
- ConstantOrWriteNode
3087+
- ConstantOperatorWriteNode
3088+
- ConstantPathWriteNode
3089+
- ConstantPathAndWriteNode
3090+
- ConstantPathOrWriteNode
3091+
- ConstantPathOperatorWriteNode
3092+
comment: The constant write that should be modified with the shareability state.
3093+
comment: |
3094+
This node wraps a constant write to indicate that when the value is written, it should have its shareability state modified.
3095+
3096+
# shareable_constant_value: literal
3097+
C = { a: 1 }
3098+
^^^^^^^^^^^^
30663099
- name: SingletonClassNode
30673100
fields:
30683101
- name: locals

include/prism/parser.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,13 @@ typedef struct {
448448
void (*callback)(void *data, pm_parser_t *parser, pm_token_t *token);
449449
} pm_lex_callback_t;
450450

451+
/** The type of shareable constant value that can be set. */
452+
typedef uint8_t pm_shareable_constant_value_t;
453+
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_NONE = 0x0;
454+
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_LITERAL = 0x1;
455+
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING = 0x2;
456+
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY = 0x4;
457+
451458
/**
452459
* This struct represents a node in a linked list of scopes. Some scopes can see
453460
* into their parent scopes, while others cannot.
@@ -487,6 +494,12 @@ typedef struct pm_scope {
487494
*/
488495
int8_t numbered_parameters;
489496

497+
/**
498+
* The current state of constant shareability for this scope. This is
499+
* changed by magic shareable_constant_value comments.
500+
*/
501+
pm_shareable_constant_value_t shareable_constant;
502+
490503
/**
491504
* A boolean indicating whether or not this scope can see into its parent.
492505
* If closed is true, then the scope cannot see into its parent.

lib/prism/translation/parser/compiler.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,11 @@ def visit_self_node(node)
14231423
builder.self(token(node.location))
14241424
end
14251425

1426+
# A shareable constant.
1427+
def visit_shareable_constant_node(node)
1428+
visit(node.write)
1429+
end
1430+
14261431
# class << self; end
14271432
# ^^^^^^^^^^^^^^^^^^
14281433
def visit_singleton_class_node(node)

lib/prism/translation/ripper.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,6 +2858,11 @@ def visit_self_node(node)
28582858
on_var_ref(on_kw("self"))
28592859
end
28602860

2861+
# A shareable constant.
2862+
def visit_shareable_constant_node(node)
2863+
visit(node.write)
2864+
end
2865+
28612866
# class << self; end
28622867
# ^^^^^^^^^^^^^^^^^^
28632868
def visit_singleton_class_node(node)

lib/prism/translation/ruby_parser.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,11 @@ def visit_self_node(node)
12741274
s(node, :self)
12751275
end
12761276

1277+
# A shareable constant.
1278+
def visit_shareable_constant_node(node)
1279+
visit(node.write)
1280+
end
1281+
12771282
# class << self; end
12781283
# ^^^^^^^^^^^^^^^^^^
12791284
def visit_singleton_class_node(node)

src/prism.c

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5731,6 +5731,25 @@ pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) {
57315731
return node;
57325732
}
57335733

5734+
/**
5735+
* Allocate and initialize a new ShareableConstantNode node.
5736+
*/
5737+
static pm_shareable_constant_node_t *
5738+
pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shareable_constant_value_t value) {
5739+
pm_shareable_constant_node_t *node = PM_ALLOC_NODE(parser, pm_shareable_constant_node_t);
5740+
5741+
*node = (pm_shareable_constant_node_t) {
5742+
{
5743+
.type = PM_SHAREABLE_CONSTANT_NODE,
5744+
.flags = (pm_node_flags_t) value,
5745+
.location = PM_LOCATION_NODE_VALUE(write)
5746+
},
5747+
.write = write
5748+
};
5749+
5750+
return node;
5751+
}
5752+
57345753
/**
57355754
* Allocate a new SingletonClassNode node.
57365755
*/
@@ -6745,6 +6764,7 @@ pm_parser_scope_push(pm_parser_t *parser, bool closed) {
67456764
.locals = { 0 },
67466765
.parameters = PM_SCOPE_PARAMETERS_NONE,
67476766
.numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_NONE,
6767+
.shareable_constant = (closed || parser->current_scope == NULL) ? PM_SCOPE_SHAREABLE_CONSTANT_NONE : parser->current_scope->shareable_constant,
67486768
.closed = closed
67496769
};
67506770

@@ -6790,6 +6810,27 @@ pm_parser_scope_forwarding_keywords_check(pm_parser_t *parser, const pm_token_t
67906810
pm_parser_scope_forwarding_param_check(parser, token, PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS, PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH);
67916811
}
67926812

6813+
/**
6814+
* Get the current state of constant shareability.
6815+
*/
6816+
static inline pm_shareable_constant_value_t
6817+
pm_parser_scope_shareable_constant_get(pm_parser_t *parser) {
6818+
return parser->current_scope->shareable_constant;
6819+
}
6820+
6821+
/**
6822+
* Set the current state of constant shareability. We'll set it on all of the
6823+
* open scopes so that reads are quick.
6824+
*/
6825+
static void
6826+
pm_parser_scope_shareable_constant_set(pm_parser_t *parser, pm_shareable_constant_value_t shareable_constant) {
6827+
pm_scope_t *scope = parser->current_scope;
6828+
6829+
do {
6830+
scope->shareable_constant = shareable_constant;
6831+
} while (!scope->closed && (scope = scope->previous) != NULL);
6832+
}
6833+
67936834
/**
67946835
* Save the current param name as the return value and set it to the given
67956836
* constant id.
@@ -7345,6 +7386,28 @@ parser_lex_magic_comment(pm_parser_t *parser, bool semantic_token_seen) {
73457386
}
73467387
}
73477388

7389+
// If we have hit a ractor pragma, attempt to lex that.
7390+
uint32_t value_length = (uint32_t) (value_end - value_start);
7391+
if (key_length == 24 && pm_strncasecmp(key_source, (const uint8_t *) "shareable_constant_value", 24) == 0) {
7392+
if (value_length == 4 && pm_strncasecmp(value_start, (const uint8_t *) "none", 4) == 0) {
7393+
pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_NONE);
7394+
} else if (value_length == 7 && pm_strncasecmp(value_start, (const uint8_t *) "literal", 7) == 0) {
7395+
pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_LITERAL);
7396+
} else if (value_length == 23 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_everything", 23) == 0) {
7397+
pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING);
7398+
} else if (value_length == 17 && pm_strncasecmp(value_start, (const uint8_t *) "experimental_copy", 17) == 0) {
7399+
pm_parser_scope_shareable_constant_set(parser, PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY);
7400+
} else {
7401+
PM_PARSER_WARN_TOKEN_FORMAT(
7402+
parser,
7403+
parser->current,
7404+
PM_WARN_INVALID_SHAREABLE_CONSTANT_VALUE,
7405+
(int) value_length,
7406+
(const char *) value_start
7407+
);
7408+
}
7409+
}
7410+
73487411
// When we're done, we want to free the string in case we had to
73497412
// allocate memory for it.
73507413
pm_string_free(&key);
@@ -11991,6 +12054,21 @@ parse_target_validate(pm_parser_t *parser, pm_node_t *target) {
1199112054
return result;
1199212055
}
1199312056

12057+
/**
12058+
* Potentially wrap a constant write node in a shareable constant node depending
12059+
* on the current state.
12060+
*/
12061+
static pm_node_t *
12062+
parse_shareable_constant_write(pm_parser_t *parser, pm_node_t *write) {
12063+
pm_shareable_constant_value_t shareable_constant = pm_parser_scope_shareable_constant_get(parser);
12064+
12065+
if (shareable_constant != PM_SCOPE_SHAREABLE_CONSTANT_NONE) {
12066+
return (pm_node_t *) pm_shareable_constant_node_create(parser, write, shareable_constant);
12067+
}
12068+
12069+
return write;
12070+
}
12071+
1199412072
/**
1199512073
* Convert the given node into a valid write node.
1199612074
*/
@@ -12005,15 +12083,17 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
1200512083
pm_node_destroy(parser, target);
1200612084
return (pm_node_t *) node;
1200712085
}
12008-
case PM_CONSTANT_PATH_NODE:
12009-
return (pm_node_t *) pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value);
12086+
case PM_CONSTANT_PATH_NODE: {
12087+
pm_node_t *node = (pm_node_t *) pm_constant_path_write_node_create(parser, (pm_constant_path_node_t *) target, operator, value);
12088+
return parse_shareable_constant_write(parser, node);
12089+
}
1201012090
case PM_CONSTANT_READ_NODE: {
12011-
pm_constant_write_node_t *node = pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value);
12091+
pm_node_t *node = (pm_node_t *) pm_constant_write_node_create(parser, (pm_constant_read_node_t *) target, operator, value);
1201212092
if (context_def_p(parser)) {
12013-
pm_parser_err_node(parser, (pm_node_t *) node, PM_ERR_WRITE_TARGET_IN_METHOD);
12093+
pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_IN_METHOD);
1201412094
}
1201512095
pm_node_destroy(parser, target);
12016-
return (pm_node_t *) node;
12096+
return parse_shareable_constant_write(parser, node);
1201712097
}
1201812098
case PM_BACK_REFERENCE_READ_NODE:
1201912099
case PM_NUMBERED_REFERENCE_READ_NODE:
@@ -17954,16 +18034,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1795418034
parser_lex(parser);
1795518035

1795618036
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
17957-
return (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18037+
pm_node_t *write = (pm_node_t *) pm_constant_path_and_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18038+
18039+
return parse_shareable_constant_write(parser, write);
1795818040
}
1795918041
case PM_CONSTANT_READ_NODE: {
1796018042
parser_lex(parser);
1796118043

1796218044
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ);
17963-
pm_node_t *result = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
18045+
pm_node_t *write = (pm_node_t *) pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
1796418046

1796518047
pm_node_destroy(parser, node);
17966-
return result;
18048+
return parse_shareable_constant_write(parser, write);
1796718049
}
1796818050
case PM_INSTANCE_VARIABLE_READ_NODE: {
1796918051
parser_lex(parser);
@@ -18065,16 +18147,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1806518147
parser_lex(parser);
1806618148

1806718149
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
18068-
return (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18150+
pm_node_t *write = (pm_node_t *) pm_constant_path_or_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18151+
18152+
return parse_shareable_constant_write(parser, write);
1806918153
}
1807018154
case PM_CONSTANT_READ_NODE: {
1807118155
parser_lex(parser);
1807218156

1807318157
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ);
18074-
pm_node_t *result = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
18158+
pm_node_t *write = (pm_node_t *) pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
1807518159

1807618160
pm_node_destroy(parser, node);
18077-
return result;
18161+
return parse_shareable_constant_write(parser, write);
1807818162
}
1807918163
case PM_INSTANCE_VARIABLE_READ_NODE: {
1808018164
parser_lex(parser);
@@ -18186,16 +18270,18 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t
1818618270
parser_lex(parser);
1818718271

1818818272
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
18189-
return (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18273+
pm_node_t *write = (pm_node_t *) pm_constant_path_operator_write_node_create(parser, (pm_constant_path_node_t *) node, &token, value);
18274+
18275+
return parse_shareable_constant_write(parser, write);
1819018276
}
1819118277
case PM_CONSTANT_READ_NODE: {
1819218278
parser_lex(parser);
1819318279

1819418280
pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR);
18195-
pm_node_t *result = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
18281+
pm_node_t *write = (pm_node_t *) pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value);
1819618282

1819718283
pm_node_destroy(parser, node);
18198-
return result;
18284+
return parse_shareable_constant_write(parser, write);
1819918285
}
1820018286
case PM_INSTANCE_VARIABLE_READ_NODE: {
1820118287
parser_lex(parser);

templates/src/diagnostic.c.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
327327
[PM_WARN_FLOAT_OUT_OF_RANGE] = { "Float %.*s%s out of range", PM_WARNING_LEVEL_VERBOSE },
328328
[PM_WARN_INTEGER_IN_FLIP_FLOP] = { "integer literal in flip-flop", PM_WARNING_LEVEL_DEFAULT },
329329
[PM_WARN_INVALID_CHARACTER] = { "invalid character syntax; use %s%s%s", PM_WARNING_LEVEL_DEFAULT },
330+
[PM_WARN_INVALID_SHAREABLE_CONSTANT_VALUE] = { "invalid value for shareable_constant_value: %.*s", PM_WARNING_LEVEL_VERBOSE },
330331
[PM_WARN_INVALID_NUMBERED_REFERENCE] = { "'%.*s' is too big for a number variable, always nil", PM_WARNING_LEVEL_DEFAULT },
331332
[PM_WARN_KEYWORD_EOL] = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE },
332333
[PM_WARN_LITERAL_IN_CONDITION_DEFAULT] = { "%sliteral in %s", PM_WARNING_LEVEL_DEFAULT },

test/prism/location_test.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,15 @@ def test_SelfNode
781781
assert_location(SelfNode, "self")
782782
end
783783

784+
def test_ShareableConstantNode
785+
source = <<~RUBY
786+
# shareable_constant_value: literal
787+
C = { foo: 1 }
788+
RUBY
789+
790+
assert_location(ShareableConstantNode, source, 36...50)
791+
end
792+
784793
def test_SingletonClassNode
785794
assert_location(SingletonClassNode, "class << self; end")
786795
end
@@ -915,8 +924,7 @@ def test_all_tested
915924

916925
def assert_location(kind, source, expected = 0...source.length, **options)
917926
result = Prism.parse(source, **options)
918-
assert_equal [], result.comments
919-
assert_equal [], result.errors
927+
assert result.success?
920928

921929
node = result.value.statements.body.last
922930
node = yield node if block_given?

0 commit comments

Comments
 (0)