Add type restrictions to big#15689
Conversation
src/big/big_decimal.cr
Outdated
| # BigDecimal.new(1).div(BigDecimal.new(3), 5) # => BigDecimal(@value=33333, @scale=5) | ||
| # ``` | ||
| def div(other : BigDecimal, precision = DEFAULT_PRECISION) : BigDecimal | ||
| def div(other : BigDecimal, precision : UInt64 | Int32 = DEFAULT_PRECISION) : BigDecimal |
There was a problem hiding this comment.
I think any unions exclusively consisting of integral types should just be Int::Primitive
There's no real reason for these two types to be mentioned explicitly
There was a problem hiding this comment.
Oh, good call, will make that change.
There was a problem hiding this comment.
I don't think Int::Primitive is a good type for that. Most of the time it should be either Int or the biggest supported integer type (making use of autocasting).
There was a problem hiding this comment.
I can't think of a reason where a negative value would be provided, so UInt64?
There was a problem hiding this comment.
Negative values might not make sense, but even a positive value can be of a signed integer type. And that wouldn't autocast to an unsigned one.
Int should be fine here.
src/big/big_decimal.cr
Outdated
| # Factors out any extra powers of ten in the internal representation. | ||
| # For instance, value=100 scale=2 => value=1 scale=0 | ||
| protected def factor_powers_of_ten | ||
| protected def factor_powers_of_ten : UInt64? |
There was a problem hiding this comment.
It's a protected method whose result is never used, should be : Nil
| end | ||
|
|
||
| def <=>(other : Float::Primitive) | ||
| def <=>(other : Float::Primitive) : Int32? |
There was a problem hiding this comment.
All the <=> methods should normally just return Int32
There was a problem hiding this comment.
Agreed, and the Nil return is a bit confusing here as well (though not incorrect when considering you're comparing with a NaN).
There was a problem hiding this comment.
All the <=> methods should normally just return Int32
The contract in general is to return nil when not comparable. That is relevant for some types, but not all.
There was a problem hiding this comment.
This Float::Primitive overload was indeed added as a breaking change in 1.9 because it needs to return nil. Note that BigFloat itself doesn't have NaN, since we are still using GMP, not MPFR
| self | ||
| end | ||
|
|
||
| private def mpq |
There was a problem hiding this comment.
Why are some of these functions not typed at all?
Should be : LibGMP::MPQ*
There was a problem hiding this comment.
The cr-source-typer has a new flag to ignore private defs, as well as type restrictions that are resolved to 3+ union types.
I realize my PR description doesn't show that with the single line command 😬
src/big/big_float.cr
Outdated
| end | ||
|
|
||
| def self.default_precision | ||
| def self.default_precision : UInt64 |
There was a problem hiding this comment.
The LibGMP method actually returns a LibGMP::BitcntT => LibGMP::UI => LibC::ULong, so the method returns a target dependent integer.
I think this outlines of the tool: it infers types for a specific target. It also exposes an issue in the stdlib: it leaks target dependent types.
There was a problem hiding this comment.
Any recommendations on what this line should be? I can remove the return statement here, and agreed about the rest of your statement about leaking target dependent types.
There was a problem hiding this comment.
Ah, I see the failed build as a consequence of this change. I'll remove the return type at least.
There was a problem hiding this comment.
Instead of looking at the type of an expression in the current program, the typer tool could also apply some more generic type inference based on restrictions of the source/sink of these expressions.
In this case, it's pretty obvious that the return type of this method should be equal to the type restriction of LibGMP.mpf_get_default_prec, which is LibGMP::BitcntT.
Going this way ensures using the correct, target-independent aliases.
There was a problem hiding this comment.
Makes sense - probably more work than is justified for this kind of edge case, but doing more inspection up and down the call-chain has been something I've been curious to do (for additional static code analysis type things).
| end | ||
|
|
||
| def self.default_precision=(prec : Int) | ||
| def self.default_precision=(prec : Int) : Nil |
There was a problem hiding this comment.
This is worrysome: a setter method should always return its argument.
There was a problem hiding this comment.
There is the older and abandoned #10083 for this
There was a problem hiding this comment.
My own 2-cents: it should be a linting rule rather than a language rule, that the setter return what is handed to it. Seems there are other opinions on what setter methods should return, too (like matching the getter).
There was a problem hiding this comment.
Yes, we'd want this to be different. But this change is fine for this PR.
There was a problem hiding this comment.
You want it to return what was passed in so that chained assignment is handled. Ruby, for example, ignores returning a value from setters for this reason.
PRECISION = BigFloat.default_precision = 5| end | ||
|
|
||
| def self.default_precision=(prec : Int) | ||
| def self.default_precision=(prec : Int) : Nil |
There was a problem hiding this comment.
Yes, we'd want this to be different. But this change is fine for this PR.
| # BigDecimal.new(1).div(BigDecimal.new(3), 5) # => BigDecimal(@value=33333, @scale=5) | ||
| # ``` | ||
| def div(other : BigDecimal, precision = DEFAULT_PRECISION) : BigDecimal | ||
| def div(other : BigDecimal, precision : Int = DEFAULT_PRECISION) : BigDecimal |
There was a problem hiding this comment.
This comes from way before this PR, but I would have expected precision to be a BigInteger in BigDecimal 🤔
There was a problem hiding this comment.
FWIW, bigdecimal-rs, which this implementation is based on, does the same
This is the output of compiling cr-source-typer and running it with the below incantation:
This is related to #15682 .