Skip to content

Commit 2e754f2

Browse files
authored
BREAKING: Change to disallow indexing KCL records/objects with strings (KittyCAD#6529)
* Change to disallow indexing records/objects with strings * Update output * Remove outdated sim test * Fix tests
1 parent 7191369 commit 2e754f2

File tree

26 files changed

+289
-1625
lines changed

26 files changed

+289
-1625
lines changed

rust/kcl-lib/src/execution/exec_ast.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -861,8 +861,8 @@ impl Node<MemberExpression> {
861861
};
862862

863863
// Check the property and object match -- e.g. ints for arrays, strs for objects.
864-
match (object, property) {
865-
(KclValue::Object { value: map, meta: _ }, Property::String(property)) => {
864+
match (object, property, self.computed) {
865+
(KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
866866
if let Some(value) = map.get(&property) {
867867
Ok(value.to_owned())
868868
} else {
@@ -872,7 +872,11 @@ impl Node<MemberExpression> {
872872
}))
873873
}
874874
}
875-
(KclValue::Object { .. }, p) => {
875+
(KclValue::Object { .. }, Property::String(property), true) => Err(KclError::Semantic(KclErrorDetails {
876+
message: format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
877+
source_ranges: vec![self.clone().into()],
878+
})),
879+
(KclValue::Object { .. }, p, _) => {
876880
let t = p.type_name();
877881
let article = article_for(t);
878882
Err(KclError::Semantic(KclErrorDetails {
@@ -885,6 +889,7 @@ impl Node<MemberExpression> {
885889
(
886890
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
887891
Property::UInt(index),
892+
_,
888893
) => {
889894
let value_of_arr = arr.get(index);
890895
if let Some(value) = value_of_arr {
@@ -896,7 +901,7 @@ impl Node<MemberExpression> {
896901
}))
897902
}
898903
}
899-
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p) => {
904+
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p, _) => {
900905
let t = p.type_name();
901906
let article = article_for(t);
902907
Err(KclError::Semantic(KclErrorDetails {
@@ -906,10 +911,10 @@ impl Node<MemberExpression> {
906911
source_ranges: vec![self.clone().into()],
907912
}))
908913
}
909-
(KclValue::Solid { value }, Property::String(prop)) if prop == "sketch" => Ok(KclValue::Sketch {
914+
(KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
910915
value: Box::new(value.sketch),
911916
}),
912-
(KclValue::Sketch { value: sk }, Property::String(prop)) if prop == "tags" => Ok(KclValue::Object {
917+
(KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
913918
meta: vec![Metadata {
914919
source_range: SourceRange::from(self.clone()),
915920
}],
@@ -919,7 +924,7 @@ impl Node<MemberExpression> {
919924
.map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
920925
.collect(),
921926
}),
922-
(being_indexed, _) => {
927+
(being_indexed, _, _) => {
923928
let t = being_indexed.human_friendly_type();
924929
let article = article_for(t);
925930
Err(KclError::Semantic(KclErrorDetails {
@@ -1919,10 +1924,11 @@ impl Property {
19191924
LiteralIdentifier::Identifier(identifier) => {
19201925
let name = &identifier.name;
19211926
if !computed {
1922-
// Treat the property as a literal
1927+
// This is dot syntax. Treat the property as a literal.
19231928
Ok(Property::String(name.to_string()))
19241929
} else {
1925-
// Actually evaluate memory to compute the property.
1930+
// This is bracket syntax. Actually evaluate memory to
1931+
// compute the property.
19261932
let prop = exec_state.stack().get(name, property_src)?;
19271933
jvalue_to_prop(prop, property_sr, name)
19281934
}
@@ -1940,10 +1946,9 @@ impl Property {
19401946
}))
19411947
}
19421948
}
1943-
LiteralValue::String(s) => Ok(Property::String(s)),
19441949
_ => Err(KclError::Semantic(KclErrorDetails {
19451950
source_ranges: vec![sr],
1946-
message: "Only strings or numbers (>= 0) can be properties/indexes".to_owned(),
1951+
message: "Only numbers (>= 0) can be indexes".to_owned(),
19471952
})),
19481953
}
19491954
}

rust/kcl-lib/src/execution/mod.rs

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,44 +1515,6 @@ const fnBox = box(3, 6, 10)"#;
15151515
return myBox
15161516
}
15171517
1518-
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
1519-
"#;
1520-
parse_execute(ast).await.unwrap();
1521-
}
1522-
1523-
#[tokio::test(flavor = "multi_thread")]
1524-
async fn test_get_member_of_object_with_function_brace() {
1525-
let ast = r#"fn box = (obj) => {
1526-
let myBox = startSketchOn(XY)
1527-
|> startProfile(at = obj["start"])
1528-
|> line(end = [0, obj["l"]])
1529-
|> line(end = [obj["w"], 0])
1530-
|> line(end = [0, -obj["l"]])
1531-
|> close()
1532-
|> extrude(length = obj["h"])
1533-
1534-
return myBox
1535-
}
1536-
1537-
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
1538-
"#;
1539-
parse_execute(ast).await.unwrap();
1540-
}
1541-
1542-
#[tokio::test(flavor = "multi_thread")]
1543-
async fn test_get_member_of_object_with_function_mix_period_brace() {
1544-
let ast = r#"fn box = (obj) => {
1545-
let myBox = startSketchOn(XY)
1546-
|> startProfile(at = obj["start"])
1547-
|> line(end = [0, obj["l"]])
1548-
|> line(end = [obj["w"], 0])
1549-
|> line(end = [10 - obj["w"], -obj.l])
1550-
|> close()
1551-
|> extrude(length = obj["h"])
1552-
1553-
return myBox
1554-
}
1555-
15561518
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
15571519
"#;
15581520
parse_execute(ast).await.unwrap();

rust/kcl-lib/src/parsing/parser.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,7 @@ fn member_expression_dot(i: &mut TokenSlice) -> PResult<(LiteralIdentifier, usiz
13101310
/// E.g. `people[0]` or `people[i]` or `people['adam']`
13111311
fn member_expression_subscript(i: &mut TokenSlice) -> PResult<(LiteralIdentifier, usize, bool)> {
13121312
let _ = open_bracket.parse_next(i)?;
1313+
// TODO: This should be an expression, not just a literal or identifier.
13131314
let property = alt((
13141315
literal.map(LiteralIdentifier::Literal),
13151316
nameable_identifier.map(Box::new).map(LiteralIdentifier::Identifier),

rust/kcl-lib/src/simulation_tests.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -498,27 +498,6 @@ mod double_map_fn {
498498
super::execute(TEST_NAME, false).await
499499
}
500500
}
501-
mod property_of_object {
502-
const TEST_NAME: &str = "property_of_object";
503-
504-
/// Test parsing KCL.
505-
#[test]
506-
fn parse() {
507-
super::parse(TEST_NAME)
508-
}
509-
510-
/// Test that parsing and unparsing KCL produces the original KCL input.
511-
#[tokio::test(flavor = "multi_thread")]
512-
async fn unparse() {
513-
super::unparse(TEST_NAME).await
514-
}
515-
516-
/// Test that KCL is executed correctly.
517-
#[tokio::test(flavor = "multi_thread")]
518-
async fn kcl_test_execute() {
519-
super::execute(TEST_NAME, false).await
520-
}
521-
}
522501
mod index_of_array {
523502
const TEST_NAME: &str = "index_of_array";
524503

@@ -816,6 +795,27 @@ mod invalid_member_object_prop {
816795
super::execute(TEST_NAME, false).await
817796
}
818797
}
798+
mod invalid_member_object_using_string {
799+
const TEST_NAME: &str = "invalid_member_object_using_string";
800+
801+
/// Test parsing KCL.
802+
#[test]
803+
fn parse() {
804+
super::parse(TEST_NAME)
805+
}
806+
807+
/// Test that parsing and unparsing KCL produces the original KCL input.
808+
#[tokio::test(flavor = "multi_thread")]
809+
async fn unparse() {
810+
super::unparse(TEST_NAME).await
811+
}
812+
813+
/// Test that KCL is executed correctly.
814+
#[tokio::test(flavor = "multi_thread")]
815+
async fn kcl_test_execute() {
816+
super::execute(TEST_NAME, false).await
817+
}
818+
}
819819
mod non_string_key_of_object {
820820
const TEST_NAME: &str = "non_string_key_of_object";
821821

rust/kcl-lib/tests/computed_var/ast.snap

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -249,36 +249,6 @@ description: Result of parsing computed_var.kcl
249249
"type": "ExpressionStatement",
250250
"type": "ExpressionStatement"
251251
},
252-
{
253-
"commentStart": 0,
254-
"declaration": {
255-
"commentStart": 0,
256-
"end": 0,
257-
"id": {
258-
"commentStart": 0,
259-
"end": 0,
260-
"name": "p",
261-
"start": 0,
262-
"type": "Identifier"
263-
},
264-
"init": {
265-
"commentStart": 0,
266-
"end": 0,
267-
"raw": "\"foo\"",
268-
"start": 0,
269-
"type": "Literal",
270-
"type": "Literal",
271-
"value": "foo"
272-
},
273-
"start": 0,
274-
"type": "VariableDeclarator"
275-
},
276-
"end": 0,
277-
"kind": "const",
278-
"start": 0,
279-
"type": "VariableDeclaration",
280-
"type": "VariableDeclaration"
281-
},
282252
{
283253
"commentStart": 0,
284254
"declaration": {
@@ -373,7 +343,7 @@ description: Result of parsing computed_var.kcl
373343
},
374344
"init": {
375345
"commentStart": 0,
376-
"computed": true,
346+
"computed": false,
377347
"end": 0,
378348
"object": {
379349
"commentStart": 0,
@@ -386,7 +356,7 @@ description: Result of parsing computed_var.kcl
386356
"property": {
387357
"commentStart": 0,
388358
"end": 0,
389-
"name": "p",
359+
"name": "foo",
390360
"start": 0,
391361
"type": "Identifier",
392362
"type": "Identifier"
@@ -820,7 +790,7 @@ description: Result of parsing computed_var.kcl
820790
}
821791
}
822792
],
823-
"6": [
793+
"5": [
824794
{
825795
"commentStart": 0,
826796
"end": 0,
@@ -831,7 +801,7 @@ description: Result of parsing computed_var.kcl
831801
}
832802
}
833803
],
834-
"7": [
804+
"6": [
835805
{
836806
"commentStart": 0,
837807
"end": 0,
@@ -842,7 +812,7 @@ description: Result of parsing computed_var.kcl
842812
}
843813
}
844814
],
845-
"8": [
815+
"7": [
846816
{
847817
"commentStart": 0,
848818
"end": 0,

rust/kcl-lib/tests/computed_var/input.kcl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ ten = arr[i]
77

88
assert(ten, isEqualTo = 10, error = "oops")
99

10-
p = "foo"
1110
obj = { foo = 1, bar = 0 }
12-
one = obj[p]
11+
one = obj.foo
1312

1413
assert(one, isEqualTo = 1, error = "oops")
1514

rust/kcl-lib/tests/computed_var/program_memory.snap

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ description: Variables in memory after executing computed_var.kcl
117117
}
118118
}
119119
},
120-
"p": {
121-
"type": "String",
122-
"value": "foo"
123-
},
124120
"ten": {
125121
"type": "Number",
126122
"value": 10.0,

rust/kcl-lib/tests/computed_var/unparsed.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ ten = arr[i]
1111

1212
assert(ten, isEqualTo = 10, error = "oops")
1313

14-
p = "foo"
1514
obj = { foo = 1, bar = 0 }
16-
one = obj[p]
15+
one = obj.foo
1716

1817
assert(one, isEqualTo = 1, error = "oops")
1918

rust/kcl-lib/tests/invalid_index_str/execution_error.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ description: Error from executing invalid_index_str.kcl
44
---
55
KCL Semantic error
66

7-
× semantic: Only integers >= 0 can be used as the index of an array, but
8-
you're using a string
7+
× semantic: Only numbers (>= 0) can be indexes
98
╭─[2:5]
109
1arr = [1, 2, 3]
1110
2x = arr["s"]

rust/kcl-lib/tests/invalid_member_object_prop/execution_error.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ description: Error from executing invalid_member_object_prop.kcl
44
---
55
KCL Semantic error
66

7-
× semantic: Only arrays and objects can be indexed, but you're trying to
8-
index a boolean (true/false value)
7+
× semantic: Only numbers (>= 0) can be indexes
98
╭─[2:5]
109
1b = true
1110
2x = b["property"]

0 commit comments

Comments
 (0)