Skip to content

Commit 921a5e3

Browse files
authored
Detect unhashable object types at the ASR level (#2664)
* Detect unhashable types for `dict` key * Tests: Add error tests and update references * Create a function to check for hashable objects * Tests: Add error tests for `set` and update references * Tests: Update error tests and references * Check for unhashable types in type-annotations * Tests: Update tests and references * Fix indentation
1 parent 265e21b commit 921a5e3

40 files changed

+380
-3
lines changed

src/lpython/semantics/python_ast_to_asr.cpp

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,15 @@ class CommonVisitor : public AST::BaseVisitor<Struct> {
15901590
}
15911591
}
15921592

1593+
bool is_hashable(ASR::ttype_t* object_type) {
1594+
if (ASR::is_a<ASR::List_t>(*object_type)
1595+
|| ASR::is_a<ASR::Dict_t>(*object_type)
1596+
|| ASR::is_a<ASR::Set_t>(*object_type)) {
1597+
return false;
1598+
}
1599+
return true;
1600+
}
1601+
15931602
AST::expr_t* get_var_intent_and_annotation(AST::expr_t *annotation, ASR::intentType &intent) {
15941603
if (AST::is_a<AST::Subscript_t>(*annotation)) {
15951604
AST::Subscript_t *s = AST::down_cast<AST::Subscript_t>(annotation);
@@ -1701,6 +1710,17 @@ class CommonVisitor : public AST::BaseVisitor<Struct> {
17011710
if (AST::is_a<AST::Name_t>(*s->m_slice) || AST::is_a<AST::Subscript_t>(*s->m_slice)) {
17021711
ASR::ttype_t *type = ast_expr_to_asr_type(loc, *s->m_slice,
17031712
is_allocatable, is_const, raise_error, abi, is_argument);
1713+
if (!is_hashable(type)) {
1714+
diag.add(diag::Diagnostic(
1715+
"Unhashable type: '" + ASRUtils::type_to_str(type) + "'",
1716+
diag::Level::Error, diag::Stage::Semantic, {
1717+
diag::Label("Mutable type '" + ASRUtils::type_to_str(type)
1718+
+ "' cannot be stored in a set.",
1719+
{s->m_slice->base.loc})
1720+
})
1721+
);
1722+
throw SemanticAbort();
1723+
}
17041724
return ASRUtils::TYPE(ASR::make_Set_t(al, loc, type));
17051725
} else {
17061726
throw SemanticError("Only Name in Subscript supported for now in `set`"
@@ -1736,6 +1756,17 @@ class CommonVisitor : public AST::BaseVisitor<Struct> {
17361756
}
17371757
ASR::ttype_t *key_type = ast_expr_to_asr_type(loc, *t->m_elts[0],
17381758
is_allocatable, is_const, raise_error, abi, is_argument);
1759+
if (!is_hashable(key_type)) {
1760+
diag.add(diag::Diagnostic(
1761+
"Unhashable type: '" + ASRUtils::type_to_str(key_type) + "'",
1762+
diag::Level::Error, diag::Stage::Semantic, {
1763+
diag::Label("Mutable type '" + ASRUtils::type_to_str(key_type)
1764+
+ "' cannot become a key in dict. Hint: Use an immutable type for key.",
1765+
{t->m_elts[0]->base.loc})
1766+
})
1767+
);
1768+
throw SemanticAbort();
1769+
}
17391770
ASR::ttype_t *value_type = ast_expr_to_asr_type(loc, *t->m_elts[1],
17401771
is_allocatable, is_const, raise_error, abi, is_argument);
17411772
raise_error_when_dict_key_is_float_or_complex(key_type, loc);
@@ -3779,7 +3810,7 @@ class CommonVisitor : public AST::BaseVisitor<Struct> {
37793810
ai.m_step, type, nullptr);
37803811
return false;
37813812
} else if (ASR::is_a<ASR::Dict_t>(*type)) {
3782-
throw SemanticError("unhashable type in dict: 'slice'", loc);
3813+
throw SemanticError("Unhashable type in dict: 'slice'", loc);
37833814
}
37843815
} else if(AST::is_a<AST::Tuple_t>(*m_slice) &&
37853816
ASRUtils::is_array(type)) {
@@ -6096,6 +6127,17 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
60966127
ASR::expr_t *key = ASRUtils::EXPR(tmp);
60976128
if (key_type == nullptr) {
60986129
key_type = ASRUtils::expr_type(key);
6130+
if (!is_hashable(key_type)) {
6131+
diag.add(diag::Diagnostic(
6132+
"Unhashable type: '" + ASRUtils::type_to_str(key_type) + "'",
6133+
diag::Level::Error, diag::Stage::Semantic, {
6134+
diag::Label("Mutable type '" + ASRUtils::type_to_str(key_type)
6135+
+ "' cannot become a key in dict. Hint: Use an immutable type for key.",
6136+
{key->base.loc})
6137+
})
6138+
);
6139+
throw SemanticAbort();
6140+
}
60996141
} else {
61006142
if (!ASRUtils::check_equal_type(ASRUtils::expr_type(key), key_type)) {
61016143
throw SemanticError("All dictionary keys must be of the same type",
@@ -6565,6 +6607,17 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
65656607
ASR::expr_t *value = ASRUtils::EXPR(tmp);
65666608
if (type == nullptr) {
65676609
type = ASRUtils::expr_type(value);
6610+
if (!is_hashable(type)) {
6611+
diag.add(diag::Diagnostic(
6612+
"Unhashable type: '" + ASRUtils::type_to_str(type) + "'",
6613+
diag::Level::Error, diag::Stage::Semantic, {
6614+
diag::Label("Mutable type '" + ASRUtils::type_to_str(type)
6615+
+ "' cannot be stored in a set.",
6616+
{value->base.loc})
6617+
})
6618+
);
6619+
throw SemanticAbort();
6620+
}
65686621
} else {
65696622
if (!ASRUtils::check_equal_type(ASRUtils::expr_type(value), type)) {
65706623
throw SemanticError("All Set values must be of the same type for now",

tests/errors/test_dict_key1.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_dict_key1():
4+
my_dict: dict[list[i32], str] = {[1, 2]: "first", [3, 4]: "second"}
5+
6+
test_dict_key1()

tests/errors/test_dict_key2.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_dict_key2():
4+
my_dict: dict[dict[i32, str], str] = {{1: "a", 2: "b"}: "first", {3: "c", 4: "d"}: "second"}
5+
6+
test_dict_key2()

tests/errors/test_dict_key3.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_dict_key3():
4+
my_dict: dict[set[str], str] = {{1, 2}: "first", {3, 4}: "second"}
5+
6+
test_dict_key3()

tests/errors/test_dict_key4.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_dict_key4():
2+
print({[1, 2]: "first", [3, 4]: "second"})
3+
4+
test_dict_key4()

tests/errors/test_dict_key5.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_dict_key5():
2+
print({{1: "a", 2: "b"}: "first", {3: "c", 4: "d"}: "second"})
3+
4+
test_dict_key5()

tests/errors/test_dict_key6.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_dict_key6():
2+
print({{1, 2}: "first", {3, 4}: "second"})
3+
4+
test_dict_key6()

tests/errors/test_set_object1.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_set_object1():
4+
my_set: set[list[i32]] = {[1, 2], [3, 4]}
5+
6+
test_set_object1()

tests/errors/test_set_object2.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_set_object2():
4+
my_set: set[dict[i32, str]] = {{1: "a", 2: "b"}, {3: "c", 4: "d"}}
5+
6+
test_set_object2()

tests/errors/test_set_object3.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from lpython import i32
2+
3+
def test_set_object3():
4+
my_set: set[set[i32]] = {{1, 2}, {3, 4}}
5+
6+
test_set_object3()

tests/errors/test_set_object4.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_set_object4():
2+
print({[1, 2], [3, 4]})
3+
4+
test_set_object4()

tests/errors/test_set_object5.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_set_object5():
2+
print({{1: "a", 2: "b"}, {3: "c", 4: "d"}})
3+
4+
test_set_object5()

tests/errors/test_set_object6.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_set_object6():
2+
print({{1, 2}, {3, 4}})
3+
4+
test_set_object6()

tests/reference/asr-test_dict7-1415e14.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"stdout": null,
99
"stdout_hash": null,
1010
"stderr": "asr-test_dict7-1415e14.stderr",
11-
"stderr_hash": "a51d1d4a46839e1f4258410e979ba83a14abe8c011482e30be2336cd",
11+
"stderr_hash": "843409ee199a2581d9cd1abab45bb59e5e0372d56ef94f1b15aea584",
1212
"returncode": 2
1313
}

tests/reference/asr-test_dict7-1415e14.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
semantic error: unhashable type in dict: 'slice'
1+
semantic error: Unhashable type in dict: 'slice'
22
--> tests/errors/test_dict7.py:4:11
33
|
44
4 | print(d[1:2])
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key1-6e57a28",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key1.py",
5+
"infile_hash": "0ee4ab5e47edab5de323d7cf97cf3e726e54882e4a5fadb82ee9aedc",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key1-6e57a28.stderr",
11+
"stderr_hash": "4ee828a6b9a93bfb8285c2006843243b5327f915f9548a2f1b3f1480",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'list'
2+
--> tests/errors/test_dict_key1.py:4:19
3+
|
4+
4 | my_dict: dict[list[i32], str] = {[1, 2]: "first", [3, 4]: "second"}
5+
| ^^^^^^^^^ Mutable type 'list' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key2-18ea6fb",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key2.py",
5+
"infile_hash": "25b325264991082018c989f990a6b71409e7af0df4a27e5b5142a349",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key2-18ea6fb.stderr",
11+
"stderr_hash": "5883683aaf0a4ae56b5fd86f56f6900e3e752a72bc675af9c607d998",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'dict'
2+
--> tests/errors/test_dict_key2.py:4:19
3+
|
4+
4 | my_dict: dict[dict[i32, str], str] = {{1: "a", 2: "b"}: "first", {3: "c", 4: "d"}: "second"}
5+
| ^^^^^^^^^^^^^^ Mutable type 'dict' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key3-9fc7793",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key3.py",
5+
"infile_hash": "9675711d37ed0e58ddd82a53ec580cc21c58a9b94ad598b706fb78f8",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key3-9fc7793.stderr",
11+
"stderr_hash": "bd995f8512a83892aa1be985c6f7ff1761691829150549ba4ac84f17",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'set'
2+
--> tests/errors/test_dict_key3.py:4:19
3+
|
4+
4 | my_dict: dict[set[str], str] = {{1, 2}: "first", {3, 4}: "second"}
5+
| ^^^^^^^^ Mutable type 'set' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key4-dc7abfc",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key4.py",
5+
"infile_hash": "197ac00a9a0a5763f939d8b5aec2e33a5b3ec769d93149a1c93999c1",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key4-dc7abfc.stderr",
11+
"stderr_hash": "ff55c824acc6a3bc2c7f8845b345bcf5d66d13374526ab958a005dc7",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'list'
2+
--> tests/errors/test_dict_key4.py:2:12
3+
|
4+
2 | print({[1, 2]: "first", [3, 4]: "second"})
5+
| ^^^^^^ Mutable type 'list' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key5-87496d1",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key5.py",
5+
"infile_hash": "08a7118a664a5ac63f470b5a47d19ed7c35a06e3c8ae40a7b44010ea",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key5-87496d1.stderr",
11+
"stderr_hash": "c7ae39bf80d3a6d1817fbd7aba5455e96623b1225abeb9428af2c73a",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'dict'
2+
--> tests/errors/test_dict_key5.py:2:12
3+
|
4+
2 | print({{1: "a", 2: "b"}: "first", {3: "c", 4: "d"}: "second"})
5+
| ^^^^^^^^^^^^^^^^ Mutable type 'dict' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_dict_key6-1d334b2",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_dict_key6.py",
5+
"infile_hash": "14ea00618e1414afe9f93d0aa0d4fd5b4332883465126cbba6faab76",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_dict_key6-1d334b2.stderr",
11+
"stderr_hash": "74a8ee0549333b4659afc7deec824a14bbc672316b22e3c99a026846",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'set'
2+
--> tests/errors/test_dict_key6.py:2:12
3+
|
4+
2 | print({{1, 2}: "first", {3, 4}: "second"})
5+
| ^^^^^^ Mutable type 'set' cannot become a key in dict. Hint: Use an immutable type for key.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_set_object1-d9bd2e1",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_set_object1.py",
5+
"infile_hash": "9450d7ca46f30271944800137d28413648bafdbeb7f0a7ac0906c832",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_set_object1-d9bd2e1.stderr",
11+
"stderr_hash": "b528f86f591ab403348d8dd5037d2385fdb7ce29501215a69d10702f",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'list'
2+
--> tests/errors/test_set_object1.py:4:17
3+
|
4+
4 | my_set: set[list[i32]] = {[1, 2], [3, 4]}
5+
| ^^^^^^^^^ Mutable type 'list' cannot be stored in a set.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_set_object2-41401ff",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_set_object2.py",
5+
"infile_hash": "e7360eff7caf0991c5bd4c505a947d23e2bc01277e9a2966362400df",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_set_object2-41401ff.stderr",
11+
"stderr_hash": "4fe845a8f949fce5b955b86d5a5ad60f0e1ae84e3c17b01572d37e2a",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'dict'
2+
--> tests/errors/test_set_object2.py:4:17
3+
|
4+
4 | my_set: set[dict[i32, str]] = {{1: "a", 2: "b"}, {3: "c", 4: "d"}}
5+
| ^^^^^^^^^^^^^^ Mutable type 'dict' cannot be stored in a set.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_set_object3-680b593",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_set_object3.py",
5+
"infile_hash": "f1dea0a951aa880721aa38a0dcf254983e7d50ab408c64c87b9a078e",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_set_object3-680b593.stderr",
11+
"stderr_hash": "05d3a6338fd929fef485c7403500a1f2111dc8e638a3369ff942bea2",
12+
"returncode": 2
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
semantic error: Unhashable type: 'set'
2+
--> tests/errors/test_set_object3.py:4:17
3+
|
4+
4 | my_set: set[set[i32]] = {{1, 2}, {3, 4}}
5+
| ^^^^^^^^ Mutable type 'set' cannot be stored in a set.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"basename": "asr-test_set_object4-243eb04",
3+
"cmd": "lpython --show-asr --no-color {infile} -o {outfile}",
4+
"infile": "tests/errors/test_set_object4.py",
5+
"infile_hash": "0b339aaa798fca7bd12920c583b0d60d70fe2f8afeb68a1811992f59",
6+
"outfile": null,
7+
"outfile_hash": null,
8+
"stdout": null,
9+
"stdout_hash": null,
10+
"stderr": "asr-test_set_object4-243eb04.stderr",
11+
"stderr_hash": "dff44d0e30f3fed351e8df2bc1875c3a9972db927a58400df456ec12",
12+
"returncode": 2
13+
}

0 commit comments

Comments
 (0)