By allowing the global "using for" directive only at the file level, Solidity make it difficult to modularize value type codebases

I have recently refactored my math library PRBMath to use user-defined value types UD60x18 and SD59x18 instead of the traditional library syntax.

I had a pleasant experience so far, but I just bumped into a major limitation.

I wanted to modularize the code base, i.e. to split the logic in my UD60x18.sol and SD59x18.sol files into multiple sub-files, like this:

└── ud60x18
   ├── Casting.sol
   ├── Conversions.sol
   ├── Errors.sol
   ├── Helpers.sol
   ├── Math.sol
   └── ValueType.sol

Where the actual type UD60x18 is defined in ValueType.sol and all other files import it from there.

I worked on this on the modularization branch, but I bumped into this compiler error:

Compiler run failed
error[4117]: TypeError: Can only use “global” with types defined in the same source unit at file level.

I think that this is a severe limitation because it prevents user from modularizing their code bases. I am basically required to maintain a monolith file in UD60x18.sol, which is generally a bad practice in programming.

Therefore, my feature request from you is to lift this limitation, and allow developers to use the using for ... global directive in files other than that in which the value type has been defined. I wonder if something like this would work:

  • The compiler finds the first use of using for ... global by searching through the imports
  • The compiler accepts the first use of the directive even if it’s not in the same file as the value type
  • If there is another declaration of the directive, the compiler throws an error

Update: I have found a way to enable some modularization by bundling the code in the Helpers.sol and the Math.sol file in the ValueType.sol. This works because apparently circular imports (at least with specific symbols) are allowed!? See commit 0714c9.

Whatever the case, I will not delete this post because I believe this is an important feature that is missing. I would prefer to modularize the helpers and the math functions in separate files.

Yes, circular imports have always been possible. Only circular inheritance in contracts is disallowed.
Note that only the type needs to be defined in the same file, but not the functions.
This means you can do something like this:

type MyType is uint;
import "./functions.sol" as F;
using {F.add, F.mul, F.pow} for MyType global;

and all the functions are defined in functions.sol

2 Likes

TIL!

Woo-hoo :partying_face:

This is great, great news. Thanks a lot for the quick answer and the tip, Chris!

Thank you for building on top of the latest features!

1 Like