diff --git a/docs/changelog.md b/docs/changelog.md index ffef44549b..b77e990bb1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -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). diff --git a/stdlib/src/builtin/builtin_list.mojo b/stdlib/src/builtin/builtin_list.mojo index 5b180fb584..ca2e66ef40 100644 --- a/stdlib/src/builtin/builtin_list.mojo +++ b/stdlib/src/builtin/builtin_list.mojo @@ -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. @@ -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 # ===-------------------------------------------------------------------===# diff --git a/stdlib/src/builtin/str.mojo b/stdlib/src/builtin/str.mojo index 93f1482cf2..8a9e01a7a8 100644 --- a/stdlib/src/builtin/str.mojo +++ b/stdlib/src/builtin/str.mojo @@ -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__() diff --git a/stdlib/test/builtin/test_list_literal.mojo b/stdlib/test/builtin/test_list_literal.mojo index 38ae7764fe..15e727ade1 100644 --- a/stdlib/test/builtin/test_list_literal.mojo +++ b/stdlib/test/builtin/test_list_literal.mojo @@ -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() diff --git a/stdlib/test/builtin/test_str.mojo b/stdlib/test/builtin/test_str.mojo index 434915d8b3..06161d8bcb 100644 --- a/stdlib/test/builtin/test_str.mojo +++ b/stdlib/test/builtin/test_str.mojo @@ -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():