bazelbuild / starlark

Starlark Language
Apache License 2.0
2.38k stars 158 forks source link

Add support to manually freeze mutable values in starlark #272

Open matts1 opened 4 months ago

matts1 commented 4 months ago

There are a variety of reasons why one might want to freeze a value yourself. Here's one example - defining a foo and a foo_set rule, you can't have a mutable type in your foo rule and be able to use a foo as a foo_set.

FooInfo = provider(fields = {
    ...,
    "dict_value": "Dict[k, v]", 

FooSetInfo = provider(fields = {
    "foos": "depset[FooInfo]"
})

The following code is not considered valid:

def _foo_impl(ctx):
    foo = FooInfo(dict_value = {})
    return [foo, FooSetInfo(foos = depset([foo])]

However, the following code is considered valid:

def foo_set_impl(ctx):
    return FooSetInfo(foos = depset([
        target[FooInfo] for target in ctx.attr.foos
    ])

I propose adding a dict.freeze() and list.freeze() method (or .frozen(), if doing so creates a frozen copy instead of mutating the original value).

stepancheg commented 4 months ago

If proposed "freeze" deep? I.e. does it freeze the content or just create an immutable value, like Python's frozenset?

Also, to clarify, you only propose to freeze the lists and dicts, not arbitrary types?

matts1 commented 4 months ago

I was reading the documentation, and it says "Two mutable data structures are available: lists and dicts.". This is why I only proposed to freeze those two types.

I hadn't really considered a deep freeze. If it's a deep freeze I guess freezing arbitrary types would be useful, since you could freeze struct(a={}), for example. It's probably a question of what's easiest to implement - a shallow freeze would probably be sufficient, but if a deep freeze is easier there's certainly no harm in doing so.

I think if it is a shallow freeze, a method is probably better than a function to ensure that it's only ever used on the correct type, but for a deep freeze, a function freeze would be the way to go.