-
-
Notifications
You must be signed in to change notification settings - Fork 48
Explain the \do_not_use\ source code annotation #226
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| - Feature Name: `\do_not_use\` annotation | ||
| - Start Date: 2026-04-03 | ||
| - RFC PR: (leave this empty) | ||
| - Pony Issue: (leave this empty) | ||
|
|
||
| # Summary | ||
|
|
||
| Make the compiler emit a "Do not use!" warning when compiling code with the `\do_not_use\` annotation. | ||
|
|
||
| # Motivation | ||
|
|
||
| Sometimes, implementing a trait or interface can only be done partially: some methods don't make sense. For example, in an infinite-precision integer class that implement `SignedInteger` trait, the methods `min_value` and `max_value` have no sense: there is no minimal or maximal values possible, because the trait contract applies only to fixed-width integers. But the `SignedInteger` trait features cover 95% of the infinite-precision implementation, and it isn't worth adding a new type like `SignedInfiniteInteger` to cover these cases. Reusing an existing type makes the class more useful to client programmers that can substitute it to existing types with limited changes in their code. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally don't find this to be a compelling motivation for this RFC. It seems like a narrow use case for what would be a broad feature, and furthermore, I think this particular use case would be better solved by either creating a new trait (which has downsides as you mentioned, but is reasonable), or implementing the methods on the trait (as described in my other advice below).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried to find an example on public code. |
||
|
|
||
| # Detailed design | ||
|
|
||
| When the Pony compiler encounters a `\do_not_use\` annotation in the code, it emits a "Do not use" warning message at compilation time with information (source file, line number, method or other [contexts](https://tutorial.ponylang.io/appendices/annotations#what-can-be-annotated) where annotations can be used) where this information has been encountered. The programmer can see that her code uses features that must not be used because results are impredictable. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't give sufficient detail to describe what it means "that her code uses features". What does "use" mean here? Need a pedantic level of detail on what cases would trigger a warning to judge the merits of an RFC like this. |
||
|
|
||
| Similarly, the compiler or pony-doc must injects the warning message into the documentation of the feature. The text of the message in the documentation must warn the reader not to use the feature, perhaps with an icon or a visual hint like found in othe languages documentation (Random example from https://doc.rust-lang.org/stable/std/primitive.u128.html#method.bit_width). | ||
|
|
||
| ## Where can it be used? | ||
|
|
||
| Usually this annotation only makes sense when applied to global symbols like: | ||
|
|
||
| * `actor` | ||
| * `class` | ||
| * `struct` | ||
| * `primitive` | ||
| * `trait` | ||
| * `interface` | ||
| * `new` | ||
| * `fun` | ||
| * `be` | ||
|
|
||
| But it must also be accepted in other contexts like `if` or `match` because it can occur in platform-dependant code (i.e. within `ifdef` blocks). | ||
|
|
||
| ## Example | ||
|
|
||
| To continue with the infinite-precision integer example and the `SignedInteger` trait. Here are methods that must be annotated with `\do_not_use\`: | ||
|
|
||
| * `min_value`: There is no minimal value in an infinite integer, as we can't represent -∞ with `SignedInteger` trait. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not related to this RFC's proposal, but to comment on what a reasonable approach would be for your specific use case: The minimum value of an infinite signed integer would be negative infinity. I would expect any infinite-precision integer class to be able to represent both positive and negative infinity - instances of the class that would always compare as greater or less than (respectively) every other value. We have this for floating point values using a particular bit pattern, so I see no reason why an arbitrary-precision integer library couldn't include an inner flag which marks a value as infinite, and handle it appropriately throughout the implemented methods, including this one. |
||
| * `max_value`: Similarly, there is no maximal value and +∞ can't be represented as we can always create a new value greater than any existing instance. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, I'd expect an "infinite signed integer" library to be able to represent an infinite value. |
||
| * `clz`: No way to count the number of leading `0` bits. Only valid for fixed-size integer representations. | ||
| * `clz_unsafe`: Idem. | ||
|
Comment on lines
+42
to
+43
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I called |
||
| * `rotl` and `rotr`: Rotating bits can't happen if the width of the integer representation is not fixed. | ||
| * `bit_reverse` and `bswap`: These methods could be implemented with an infinite-precision integer representation, but their meaning is not clear and results probably would surprise the user. | ||
|
Comment on lines
+44
to
+45
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's possible to implement rotation and reversal among the bits that exist in the particular variable-bit-width instance you're calling it on. Again, I'd expect these both to treat the bit width as being the current bit width of the current value, with a mental model of having no leading zero bits, conceptually.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please let me disagree. No infinite precision library that I know of (GMP https://gmplib.org/manual/Integer-Logic-and-Bit-Fiddling, num https://docs.rs/num-integer/0.1.46/num_integer/trait.Integer.html or https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software) implement those bit operations nor the infinity on integers. One of the reasons is because the notions make sense only on fixed-width 2-complement representation, and most of the time arbitrary precision libraries don't use this internal representation for arbitrary precision integers. |
||
|
|
||
| The remaining >100 methods of `SignedInteger` have a valid implementation in this variable-width integer class. More than 95% of the trait featurures have sense while a few methods produce results that can surprise the programmer who uses them. | ||
|
|
||
| # How We Teach This | ||
|
|
||
| This new compiler annotation will be added to the [list of recognized annotations](https://tutorial.ponylang.io/appendices/annotations#annotations-in-the-pony-compiler) with explanations on what to do when encountered. | ||
|
|
||
| It can be mentioned in the tutorial, in a place discussing compiler error messages (to be written?). It is not mandatory as it is an advanced feature, but useful for completeness of the documentation. | ||
|
|
||
| # How We Test This | ||
|
|
||
| Tests of the `\do_not_use\` annotations are similar to those of the other annotations. Minimally, we must test that a warning message is output during the compilation when the annotation is present in the source. | ||
|
|
||
| # Drawbacks | ||
|
|
||
| Not implementing such feature, a client programmer could use API that is not *reliable* and get surprising/not-expected results. In absence of such annotation, client can create bugs when they use these API. This annotation is used to prevent programmers from using these API inadvertently. | ||
|
|
||
| # Alternatives | ||
|
|
||
| ## Poor man alternative | ||
|
|
||
| Annotations are implementation-specific hints to the compiler. One can run `grep` in the `Makefile` and print the warnings if this annotation is found in the source files. The problem with such solution is that you get the warning message as soon as you compile a source where the `\do_not_use\` annotation is present, even if you don't use a forbidden method. In our infinite-precision integer example, as soon as a programmer uses this class, the warning is always issued. Vague warnings are not useful. | ||
|
|
||
| ## Change types ontology | ||
|
|
||
| Taking again the example of the infinite-precision integer, one can change the types structure. Like we've seen, the variable-size integer can't implement some methods of the `SignedInteger` trait. We can define new traits like `VariableWidthInteger` and `FixedWidthInteger` and change the type dependencies. | ||
|
|
||
| ```pony | ||
| trait VariableWidthInteger is (FixedWidthIntger & BitOperable) | ||
| ... | ||
|
|
||
| type SignedInteger is (FixedWidthInteger | VariableWidthInteger) | ||
| ``` | ||
|
|
||
| Building a correct types ontology is not easy. This would render the base types of the stdlib more complex to users. Additional complexity is not a good thing. | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After thinking of it for a week, an particularly for the integer example, I think that the
These are completely orthogonal capabilities. An arbitrary precision integer Instead of keeping the existing The current hierarchy is flat and monolithic because it was designed around a closed world of machine types ( In the end, I think that my proposal of If the community is interested, I can suggest a new type hierarchy that would correct Pony's numeric types hierarchy. This redesign would separates what is genuinely universal (algebra, ordering) from what is incidental to machine-word representations (fixed width, bit twiddling, overflow, wrapping).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It sounds like you're thinking you should close this RFC and open a new one for adjusting the type hierarchy. I'm open to reviewing a PR for the type hierarchy, though I still personally believe that it would be a reasonable alternative to implement those methods for infinite-precision integers. But if the new type hierarchy makes sense and doesn't break too much existing code, I'm open to that as well.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From Adrian in the sync call today: he's interested to discuss a redesigned type hierarchy, and also thinks it would be interesting to discuss Complex number types. |
||
| # Unresolved questions | ||
|
|
||
| As presented, this is a compilation-time only message. But should we have the compiler emit code when encountering the annotation to output the warning message at run-time when compiled in debug mode? This would be realy nice as the client programmer is warned even when she uses a pre-compiled library and she hasn't read the documentation. That is really a safe-guard! | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if we were to add this kind of advisory annotation we would also want to take a step back and consider the broader set of use cases (e.g. things like
deprecated, and cases I'm not thinking of off the top of my head) rather than accept just this one annotation in the RFC - thinking of the bigger picture would inform a more coherent overall design and user experience.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered
\deprecated\too, but I thought it was too much specialized. And what is missing to compiler annotations is the ability to have parameters. I would like to explain why a method is deprecated and which other method of the API to use instead.The reason for
\do_not_use\was to prevent the user with a minimal warning. And let the user find by herself what is the reason in the documentation.