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

[stlib] String conversion of list literals and lists with representable items #3521

Open
wants to merge 11 commits into
base: nightly
Choose a base branch
from
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ what we publish.

### ⭐️ New

- List literals with variadic items and lists with representable items are now
convertible and representables as strings. This improves the ergonomics of
the language, becoming more familiar to Python developers.

```mojo
l = [1, False, "Mojo is awesome 🔥"]
print(l)
# [1, False, 'Mojo is awesome 🔥']
l = List(1, 2, 3)
print(str(l))
# [1, 2, 3]
```

([PR #3521](https://github.com/modularml/mojo/pull/3521) by [@msaelices](https://github.com/msaelices))

- Mojo can now interpret simple LLVM intrinsics in parameter expressions,
enabling things like `count_leading_zeros` to work at compile time:
[Issue #933](https://github.com/modularml/mojo/issues/933).
Expand Down
75 changes: 74 additions & 1 deletion stdlib/src/builtin/builtin_list.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ These are Mojo built-ins, so you don't need to import them.
"""

from memory import Reference, UnsafePointer
from python import PythonObject

from sys.intrinsics import _type_is_eq

from collections import Dict

# ===----------------------------------------------------------------------===#
# ListLiteral
# ===----------------------------------------------------------------------===#


struct ListLiteral[*Ts: CollectionElement](Sized, CollectionElement):
struct ListLiteral[*Ts: CollectionElement](
Sized, CollectionElement, Formattable, Representable, Stringable
):
"""The type of a literal heterogeneous list expression.

A list consists of zero or more values, separated by commas.
Expand Down Expand Up @@ -80,6 +85,74 @@ struct ListLiteral[*Ts: CollectionElement](Sized, CollectionElement):
"""
return len(self.storage)

fn __str__(self) -> String:
"""Returns a string representation of a ListLiteral.

Returns:
The string representation of the ListLiteral.

Here is an example below:
```mojo
var my_list = [1, 2, "Mojo"]
print(str(my_list))
```.
"""
var output = String()
var writer = output._unsafe_to_formatter()
self.format_to(writer)
return output^

fn __repr__(self) -> String:
"""Returns a string representation of a ListLiteral.

Returns:
The string representation of the ListLiteral.

Here is an example below:
```mojo
var my_list = [1, 2, "Mojo"]
print(repr(my_list))
```.
"""
return self.__str__()

@no_inline
fn format_to(self, inout writer: Formatter):
"""Format the list literal.

Args:
writer: The formatter to use.
"""
writer.write("[")

@parameter
for i in range(len(VariadicList(Ts))):
alias T = Ts[i]
var s: String
if i > 0:
writer.write(", ")

@parameter
if _type_is_eq[T, PythonObject]():
s = str(rebind[PythonObject](self.storage[i]))
elif _type_is_eq[T, Int]():
s = repr(rebind[Int](self.storage[i]))
elif _type_is_eq[T, Float64]():
s = repr(rebind[Float64](self.storage[i]))
elif _type_is_eq[T, Bool]():
s = repr(rebind[Bool](self.storage[i]))
elif _type_is_eq[T, String]():
s = repr(rebind[String](self.storage[i]))
elif _type_is_eq[T, StringLiteral]():
s = repr(rebind[StringLiteral](self.storage[i]))
else:
s = String("unknown")
constrained[
False, "cannot convert list literal element to string"
]()
writer.write(s)
writer.write("]")

# ===-------------------------------------------------------------------===#
# Methods
# ===-------------------------------------------------------------------===#
Expand Down
16 changes: 16 additions & 0 deletions stdlib/src/builtin/str.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,19 @@ fn str[T: StringableRaising](value: T) raises -> String:
If there is an error when computing the string representation of the type.
"""
return value.__str__()


@no_inline
fn str[T: RepresentableCollectionElement](value: List[T]) -> String:
"""Get the string representation of a list of representable items.

Parameters:
T: The type conforming to RepresentableCollectionElement.

Args:
value: The list to get the string representation of.

Returns:
The string representation of the list.
"""
return value.__str__()
12 changes: 11 additions & 1 deletion stdlib/test/builtin/test_list_literal.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ def test_contains():
assert_true("Mojo" not in h and String("Mojo") in h)


def test_repr_and_str():
var l = [1, 2, 3]
assert_equal(str(l), "[1, 2, 3]")
assert_equal(repr(l), "[1, 2, 3]")
var l2 = [1, False, String("Mojo"), "is awesome 🔥"]
assert_equal(str(l2), "[1, False, 'Mojo', 'is awesome 🔥']")
assert_equal(repr(l2), "[1, False, 'Mojo', 'is awesome 🔥']")


def main():
test_contains()
test_list()
test_repr_and_str()
test_variadic_list()
test_contains()
5 changes: 4 additions & 1 deletion stdlib/test/builtin/test_str.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ from testing import assert_equal


def test_str_none():
assert_equal(str(None), "None")
# TODO(#3393): Change to str(None) when MLIR types do not confuse overload resolution.
# The error we are receiving with str(None) is:
# cannot bind MLIR type 'None' to trait 'RepresentableCollectionElement'
assert_equal(str(NoneType()), "None")


def main():
Expand Down