mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 21:43:37 +00:00
Auto merge of #17558 - beetrees:fix-double-rounding, r=Veykril
fix: Fix double rounding of `f32` literals Fixes #17556 by delaying parsing until the type is known. Also adds a test to check the issue is fixed.
This commit is contained in:
commit
da27b89ca5
4 changed files with 47 additions and 33 deletions
|
@ -56,29 +56,29 @@ pub struct Label {
|
||||||
}
|
}
|
||||||
pub type LabelId = Idx<Label>;
|
pub type LabelId = Idx<Label>;
|
||||||
|
|
||||||
// We convert float values into bits and that's how we don't need to deal with f32 and f64.
|
// We leave float values as a string to avoid double rounding.
|
||||||
// For PartialEq, bits comparison should work, as ordering is not important
|
// For PartialEq, string comparison should work, as ordering is not important
|
||||||
// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360
|
// https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360
|
||||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct FloatTypeWrapper(u64);
|
pub struct FloatTypeWrapper(Box<str>);
|
||||||
|
|
||||||
impl FloatTypeWrapper {
|
impl FloatTypeWrapper {
|
||||||
pub fn new(value: f64) -> Self {
|
pub fn new(value: String) -> Self {
|
||||||
Self(value.to_bits())
|
Self(value.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_f64(self) -> f64 {
|
pub fn to_f64(&self) -> f64 {
|
||||||
f64::from_bits(self.0)
|
self.0.parse().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_f32(self) -> f32 {
|
pub fn to_f32(&self) -> f32 {
|
||||||
f64::from_bits(self.0) as f32
|
self.0.parse().unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FloatTypeWrapper {
|
impl fmt::Display for FloatTypeWrapper {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{:?}", f64::from_bits(self.0))
|
f.write_str(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,10 +120,7 @@ impl From<ast::LiteralKind> for Literal {
|
||||||
match ast_lit_kind {
|
match ast_lit_kind {
|
||||||
LiteralKind::IntNumber(lit) => {
|
LiteralKind::IntNumber(lit) => {
|
||||||
if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
|
if let builtin @ Some(_) = lit.suffix().and_then(BuiltinFloat::from_suffix) {
|
||||||
Literal::Float(
|
Literal::Float(FloatTypeWrapper::new(lit.value_string()), builtin)
|
||||||
FloatTypeWrapper::new(lit.float_value().unwrap_or(Default::default())),
|
|
||||||
builtin,
|
|
||||||
)
|
|
||||||
} else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinUint::from_suffix) {
|
} else if let builtin @ Some(_) = lit.suffix().and_then(BuiltinUint::from_suffix) {
|
||||||
Literal::Uint(lit.value().unwrap_or(0), builtin)
|
Literal::Uint(lit.value().unwrap_or(0), builtin)
|
||||||
} else {
|
} else {
|
||||||
|
@ -133,7 +130,7 @@ impl From<ast::LiteralKind> for Literal {
|
||||||
}
|
}
|
||||||
LiteralKind::FloatNumber(lit) => {
|
LiteralKind::FloatNumber(lit) => {
|
||||||
let ty = lit.suffix().and_then(BuiltinFloat::from_suffix);
|
let ty = lit.suffix().and_then(BuiltinFloat::from_suffix);
|
||||||
Literal::Float(FloatTypeWrapper::new(lit.value().unwrap_or(Default::default())), ty)
|
Literal::Float(FloatTypeWrapper::new(lit.value_string()), ty)
|
||||||
}
|
}
|
||||||
LiteralKind::ByteString(bs) => {
|
LiteralKind::ByteString(bs) => {
|
||||||
let text = bs.value().map_or_else(|_| Default::default(), Box::from);
|
let text = bs.value().map_or_else(|_| Default::default(), Box::from);
|
||||||
|
|
|
@ -1432,8 +1432,8 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
||||||
hir_def::hir::Literal::Int(it, _) => Box::from(&it.to_le_bytes()[0..size()?]),
|
hir_def::hir::Literal::Int(it, _) => Box::from(&it.to_le_bytes()[0..size()?]),
|
||||||
hir_def::hir::Literal::Uint(it, _) => Box::from(&it.to_le_bytes()[0..size()?]),
|
hir_def::hir::Literal::Uint(it, _) => Box::from(&it.to_le_bytes()[0..size()?]),
|
||||||
hir_def::hir::Literal::Float(f, _) => match size()? {
|
hir_def::hir::Literal::Float(f, _) => match size()? {
|
||||||
8 => Box::new(f.into_f64().to_le_bytes()),
|
8 => Box::new(f.to_f64().to_le_bytes()),
|
||||||
4 => Box::new(f.into_f32().to_le_bytes()),
|
4 => Box::new(f.to_f32().to_le_bytes()),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(MirLowerError::TypeError("float with size other than 4 or 8 bytes"))
|
return Err(MirLowerError::TypeError("float with size other than 4 or 8 bytes"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5310,6 +5310,28 @@ const FOO$0: f64 = expf64(1.2);
|
||||||
```
|
```
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
|
// check `f32` isn't double rounded via `f64`
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
/// This is a doc
|
||||||
|
const FOO$0: f32 = 1.9999999403953552_f32;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
*FOO*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
test
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const FOO: f32 = 1.9999999
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a doc
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
//! There are many AstNodes, but only a few tokens, so we hand-write them here.
|
//! There are many AstNodes, but only a few tokens, so we hand-write them here.
|
||||||
|
|
||||||
use std::{
|
use std::{borrow::Cow, num::ParseIntError};
|
||||||
borrow::Cow,
|
|
||||||
num::{ParseFloatError, ParseIntError},
|
|
||||||
};
|
|
||||||
|
|
||||||
use rustc_lexer::unescape::{
|
use rustc_lexer::unescape::{
|
||||||
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
|
unescape_byte, unescape_char, unescape_mixed, unescape_unicode, EscapeError, MixedUnit, Mode,
|
||||||
|
@ -393,9 +390,9 @@ impl ast::IntNumber {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn float_value(&self) -> Option<f64> {
|
pub fn value_string(&self) -> String {
|
||||||
let (_, text, _) = self.split_into_parts();
|
let (_, text, _) = self.split_into_parts();
|
||||||
text.replace('_', "").parse::<f64>().ok()
|
text.replace('_', "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,14 +429,9 @@ impl ast::FloatNumber {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> Result<f64, ParseFloatError> {
|
pub fn value_string(&self) -> String {
|
||||||
let (text, _) = self.split_into_parts();
|
let (text, _) = self.split_into_parts();
|
||||||
text.replace('_', "").parse::<f64>()
|
text.replace('_', "")
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value_f32(&self) -> Result<f32, ParseFloatError> {
|
|
||||||
let (text, _) = self.split_into_parts();
|
|
||||||
text.replace('_', "").parse::<f32>()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,10 +501,13 @@ mod tests {
|
||||||
|
|
||||||
fn check_float_value(lit: &str, expected: impl Into<Option<f64>> + Copy) {
|
fn check_float_value(lit: &str, expected: impl Into<Option<f64>> + Copy) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FloatNumber { syntax: make::tokens::literal(lit) }.value().ok(),
|
FloatNumber { syntax: make::tokens::literal(lit) }.value_string().parse::<f64>().ok(),
|
||||||
|
expected.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IntNumber { syntax: make::tokens::literal(lit) }.value_string().parse::<f64>().ok(),
|
||||||
expected.into()
|
expected.into()
|
||||||
);
|
);
|
||||||
assert_eq!(IntNumber { syntax: make::tokens::literal(lit) }.float_value(), expected.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_int_value(lit: &str, expected: impl Into<Option<u128>>) {
|
fn check_int_value(lit: &str, expected: impl Into<Option<u128>>) {
|
||||||
|
|
Loading…
Reference in a new issue