Skip to content

[RFC] Redesign UserValue to avoid breaking code that inherits from it #355

Description

@whitequark

UserValue fulfills an important role: it allows code downstream from nMigen to define objects that can be fluently used in nMigen code, i.e. on RHS and, possibly, LHS of nMigen assignments. It was originally created to reflect the non-primitive nature of Records, and also because there was some disagreement on the best way to handle Records. These disagreements have persisted, and I both acknowledge them, and see UserValue as an important tool to explore any solutions.

Unfortunately, as currently implemented, it has a fatal flaw. It pollutes the namespace of the classes that inherit from it. We add new methods to Value liberally (see #352 for the latest addition), and each of these methods has the potential of clashing with downstream code. As it is, any new method on Value is a breaking change, which is not acceptable.

Fortunately, there is a good solution. One can observe that UserValues (for example, any Records--at least post-#354) do not really need to implement any numeric operations because they are not numbers--indeed, if they were simple numbers, they would be normal Values. They don't necessarily implement bit vector operations either--for example, Record overrides __getitem__. They only need to be implicitly castable to Value as opposed to be a kind of Value.

I propose that we change UserValue to not inherit from Value at all, and make sure it injects a small and well-defined set of attributes into derived classes. Its primary purpose would then to be a marker class that Value.cast uses to recognize that it should first lower its argument.

Unresolved questions:

  • Should we rename lower to as_value? The former is a fairly generic term, and therefore potentially useful to derived classes. The latter, in context of nMigen, is quite unambiguous, and very unlikely to clash with user-defined record fields.
  • Should we provide eq? Some UserValues, such as Records, will be commonly used on LHS, and writing rec1.as_value().eq(rec2) is a lot less convenient than rec1.eq(rec2). However, one can implement a UserValue that lowers to a constant, and that would be never usable on LHS.
    One option is to leave it to the specific derived class to implement eq, since the implementation is as simple as return Assign(self, value, src_loc_at=1). Such an approach could also be useful because the derived class might be able to implement useful diagnostics that are not applicable to Values in general.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions