diff --git a/crates/mun_codegen/src/db.rs b/crates/mun_codegen/src/db.rs index 18eaaf938..93a2934e2 100644 --- a/crates/mun_codegen/src/db.rs +++ b/crates/mun_codegen/src/db.rs @@ -10,7 +10,6 @@ use inkwell::{ types::{AnyTypeEnum, StructType}, OptimizationLevel, }; -use mun_target::spec::Target; use std::sync::Arc; /// The `IrDatabase` enables caching of intermediate in the process of LLVM IR generation. It uses @@ -25,10 +24,6 @@ pub trait IrDatabase: hir::HirDatabase { #[salsa::input] fn optimization_lvl(&self) -> OptimizationLevel; - /// Returns the target for code generation. - #[salsa::input] - fn target(&self) -> Target; - /// Returns the target machine's data layout for code generation. #[salsa::invoke(crate::code_gen::target_data_query)] fn target_data(&self) -> Arc; diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 44b2d8794..27fa62534 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -12,8 +12,9 @@ use inkwell::{ values::{BasicValueEnum, CallSiteValue, FloatValue, FunctionValue, IntValue, StructValue}, AddressSpace, FloatPredicate, IntPredicate, }; -use std::{collections::HashMap, mem, sync::Arc}; +use std::{collections::HashMap, sync::Arc}; +use hir::ResolveBitness; use inkwell::basic_block::BasicBlock; use inkwell::values::{AggregateValueEnum, GlobalValue, PointerValue}; @@ -200,7 +201,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr); Some(self.gen_path_expr(p, expr, &resolver)) } - Expr::Literal(lit) => Some(self.gen_literal(lit)), + Expr::Literal(lit) => Some(self.gen_literal(lit, expr)), Expr::RecordLit { fields, .. } => Some(self.gen_record_lit(expr, fields)), Expr::BinaryOp { lhs, rhs, op } => { self.gen_binary_op(expr, *lhs, *rhs, op.expect("missing op")) @@ -259,16 +260,54 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { } /// Generates an IR value that represents the given `Literal`. - fn gen_literal(&mut self, lit: &Literal) -> BasicValueEnum { + fn gen_literal(&mut self, lit: &Literal, expr: ExprId) -> BasicValueEnum { match lit { - Literal::Int(v) => self - .db - .context() - .i64_type() - .const_int(unsafe { mem::transmute::(*v) }, true) - .into(), - - Literal::Float(v) => self.db.context().f64_type().const_float(*v as f64).into(), + Literal::Int(v) => { + let ty = match &self.infer[expr] { + hir::Ty::Apply(hir::ApplicationTy { + ctor: hir::TypeCtor::Int(int_ty), + .. + }) => int_ty, + _ => unreachable!( + "cannot construct an IR value for anything but an integral type" + ), + }; + + let context = self.db.context(); + let ir_ty = match ty.resolve(&self.db.target_data_layout()).bitness { + hir::IntBitness::X8 => context.i8_type().const_int(v.value as u64, false), + hir::IntBitness::X16 => context.i16_type().const_int(v.value as u64, false), + hir::IntBitness::X32 => context.i32_type().const_int(v.value as u64, false), + hir::IntBitness::X64 => context.i64_type().const_int(v.value as u64, false), + hir::IntBitness::X128 => { + context.i128_type().const_int_arbitrary_precision(&unsafe { + std::mem::transmute::(v.value) + }) + } + _ => unreachable!("unresolved bitness in code generation"), + }; + + ir_ty.into() + } + + Literal::Float(v) => { + let ty = match &self.infer[expr] { + hir::Ty::Apply(hir::ApplicationTy { + ctor: hir::TypeCtor::Float(float_ty), + .. + }) => float_ty, + _ => unreachable!("cannot construct an IR value for anything but a float type"), + }; + + let context = self.db.context(); + let ir_ty = match ty.bitness.resolve(&self.db.target_data_layout()) { + hir::FloatBitness::X32 => context.f32_type().const_float(v.value), + hir::FloatBitness::X64 => context.f64_type().const_float(v.value), + _ => unreachable!("unresolved bitness in code generation"), + }; + + ir_ty.into() + } Literal::Bool(value) => { let ty = self.db.context().bool_type(); diff --git a/crates/mun_codegen/src/ir/ty.rs b/crates/mun_codegen/src/ir/ty.rs index d5cc09f1c..a5df06da6 100644 --- a/crates/mun_codegen/src/ir/ty.rs +++ b/crates/mun_codegen/src/ir/ty.rs @@ -1,13 +1,17 @@ use super::try_convert_any_to_basic; -use crate::type_info::TypeSize; use crate::{ + type_info::TypeSize, type_info::{TypeGroup, TypeInfo}, CodeGenParams, IrDatabase, }; -use hir::{ApplicationTy, CallableDef, FloatBitness, FloatTy, IntBitness, IntTy, Ty, TypeCtor}; -use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum, FloatType, IntType, StructType}; -use inkwell::AddressSpace; -use mun_target::spec::Target; +use hir::{ + ApplicationTy, CallableDef, FloatBitness, FloatTy, IntBitness, IntTy, ResolveBitness, Ty, + TypeCtor, +}; +use inkwell::{ + types::{AnyTypeEnum, BasicType, BasicTypeEnum, FloatType, IntType, StructType}, + AddressSpace, +}; /// Given a mun type, construct an LLVM IR type #[rustfmt::skip] @@ -57,7 +61,7 @@ pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty, params: CodeGenParams) -> A /// Returns the LLVM IR type of the specified float type fn float_ty_query(db: &impl IrDatabase, fty: FloatTy) -> FloatType { let context = db.context(); - match fty.resolve(&db.target()).bitness { + match fty.bitness.resolve(&db.target_data_layout()) { FloatBitness::X64 => context.f64_type(), FloatBitness::X32 => context.f32_type(), _ => unreachable!(), @@ -67,7 +71,7 @@ fn float_ty_query(db: &impl IrDatabase, fty: FloatTy) -> FloatType { /// Returns the LLVM IR type of the specified int type fn int_ty_query(db: &impl IrDatabase, ity: IntTy) -> IntType { let context = db.context(); - match ity.resolve(&db.target()).bitness { + match ity.bitness.resolve(&db.target_data_layout()) { IntBitness::X128 => context.i128_type(), IntBitness::X64 => context.i64_type(), IntBitness::X32 => context.i32_type(), @@ -102,7 +106,7 @@ pub fn type_info_query(db: &impl IrDatabase, ty: Ty) -> TypeInfo { let ir_ty = float_ty_query(db, ty); let type_size = TypeSize::from_ir_type(&ir_ty, target.as_ref()); TypeInfo::new( - format!("core::{}", ty.resolve(&db.target())), + format!("core::{}", ty.resolve(&db.target_data_layout())), TypeGroup::FundamentalTypes, type_size, ) @@ -111,7 +115,7 @@ pub fn type_info_query(db: &impl IrDatabase, ty: Ty) -> TypeInfo { let ir_ty = int_ty_query(db, ty); let type_size = TypeSize::from_ir_type(&ir_ty, target.as_ref()); TypeInfo::new( - format!("core::{}", ty.resolve(&db.target())), + format!("core::{}", ty.resolve(&db.target_data_layout())), TypeGroup::FundamentalTypes, type_size, ) @@ -131,31 +135,3 @@ pub fn type_info_query(db: &impl IrDatabase, ty: Ty) -> TypeInfo { _ => unreachable!("{:?} unhandled", ty), } } - -trait ResolveBitness { - fn resolve(&self, _target: &Target) -> Self; -} - -impl ResolveBitness for FloatTy { - fn resolve(&self, _target: &Target) -> Self { - let bitness = match self.bitness { - FloatBitness::Undefined => FloatBitness::X64, - bitness => bitness, - }; - FloatTy { bitness } - } -} - -impl ResolveBitness for IntTy { - fn resolve(&self, _target: &Target) -> Self { - let bitness = match self.bitness { - IntBitness::Undefined => IntBitness::X64, - IntBitness::Xsize => IntBitness::X64, - bitness => bitness, - }; - IntTy { - bitness, - signedness: self.signedness, - } - } -} diff --git a/crates/mun_codegen/src/snapshots/test__literal_types.snap b/crates/mun_codegen/src/snapshots/test__literal_types.snap new file mode 100644 index 000000000..dc982ac7e --- /dev/null +++ b/crates/mun_codegen/src/snapshots/test__literal_types.snap @@ -0,0 +1,75 @@ +--- +source: crates/mun_codegen/src/test.rs +expression: "pub fn main(){\n let a = 123;\n let a = 123u8;\n let a = 123u16;\n let a = 123u32;\n let a = 123u64;\n let a = 123u128;\n let a = 123uint;\n let a = 1_000_000_u32;\n let a = 123i8;\n let a = 123i16;\n let a = 123i32;\n let a = 123i64;\n let a = 123123123123123123123123123123123i128;\n let a = 123int;\n let a = 1_000_000_i32;\n let a = 1_000_123.0e-2;\n let a = 1_000_123.0e-2f32;\n let a = 1_000_123.0e-2f64;\n let a = 1_000_123.0e-2float;\n}\n\npub fn add(a:u32) -> u32 {\n a + 12u32\n}" +--- +; == FILE IR ===================================== +; ModuleID = 'main.mun' +source_filename = "main.mun" + +%struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } + +@global_type_table = external global [1 x %struct.MunTypeInfo addrspace(4)*] + +define void @main() { +body: + %a18 = alloca double + %a17 = alloca double + %a16 = alloca float + %a15 = alloca double + %a14 = alloca i32 + %a13 = alloca i64 + %a12 = alloca i128 + %a11 = alloca i64 + %a10 = alloca i32 + %a9 = alloca i16 + %a8 = alloca i8 + %a7 = alloca i32 + %a6 = alloca i64 + %a5 = alloca i128 + %a4 = alloca i64 + %a3 = alloca i32 + %a2 = alloca i16 + %a1 = alloca i8 + %a = alloca i64 + store i64 123, i64* %a + store i8 123, i8* %a1 + store i16 123, i16* %a2 + store i32 123, i32* %a3 + store i64 123, i64* %a4 + store i128 123, i128* %a5 + store i64 123, i64* %a6 + store i32 1000000, i32* %a7 + store i8 123, i8* %a8 + store i16 123, i16* %a9 + store i32 123, i32* %a10 + store i64 123, i64* %a11 + store i128 123123123123123123123123123123123, i128* %a12 + store i64 123, i64* %a13 + store i32 1000000, i32* %a14 + store double 0x40C3889D70A3D70A, double* %a15 + store float 0x40C3889D80000000, float* %a16 + store double 0x40C3889D70A3D70A, double* %a17 + store double 0x40C3889D70A3D70A, double* %a18 + ret void +} + +define i32 @add(i32) { +body: + %a = alloca i32 + store i32 %0, i32* %a + %a1 = load i32, i32* %a + %add = add i32 %a1, 12 + ret i32 %add +} + + +; == GROUP IR ==================================== +; ModuleID = 'group_name' +source_filename = "group_name" + +%struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } + +@"type_info::::name" = private unnamed_addr constant [10 x i8] c"core::u32\00" +@"type_info::" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"daz5d\A6\BE\88\81=&Y\A1+\C6\1D", [10 x i8]* @"type_info::::name", i32 32, i8 4, i8 0 } +@global_type_table = global [1 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::"] + diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index fc913320f..7fdb870b4 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -1,5 +1,7 @@ use crate::{mock::MockDatabase, IrDatabase, ModuleBuilder}; -use hir::{diagnostics::DiagnosticSink, line_index::LineIndex, Module, SourceDatabase}; +use hir::{ + diagnostics::DiagnosticSink, line_index::LineIndex, HirDatabase, Module, SourceDatabase, +}; use inkwell::OptimizationLevel; use mun_target::spec::Target; use std::cell::RefCell; @@ -41,6 +43,38 @@ fn issue_133() { ); } +#[test] +fn literal_types() { + test_snapshot_unoptimized( + r" + pub fn main(){ + let a = 123; + let a = 123u8; + let a = 123u16; + let a = 123u32; + let a = 123u64; + let a = 123u128; + let a = 123uint; + let a = 1_000_000_u32; + let a = 123i8; + let a = 123i16; + let a = 123i32; + let a = 123i64; + let a = 123123123123123123123123123123123i128; + let a = 123int; + let a = 1_000_000_i32; + let a = 1_000_123.0e-2; + let a = 1_000_123.0e-2f32; + let a = 1_000_123.0e-2f64; + let a = 1_000_123.0e-2float; + } + + pub fn add(a:u32) -> u32 { + a + 12u32 + }", + ) +} + #[test] fn function() { test_snapshot( diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs index 038dea9fa..e204e0993 100644 --- a/crates/mun_compiler/src/driver.rs +++ b/crates/mun_compiler/src/driver.rs @@ -3,7 +3,7 @@ use crate::{db::CompilerDatabase, diagnostics::diagnostics, PathOrInline}; use mun_codegen::{IrDatabase, ModuleBuilder}; -use mun_hir::{FileId, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId}; +use mun_hir::{FileId, HirDatabase, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId}; use std::{path::PathBuf, sync::Arc}; diff --git a/crates/mun_hir/Cargo.toml b/crates/mun_hir/Cargo.toml index 7dddabf03..191fffe31 100644 --- a/crates/mun_hir/Cargo.toml +++ b/crates/mun_hir/Cargo.toml @@ -12,6 +12,7 @@ description = "Provides high-level intermediate representation of Mun code" salsa="0.12" superslice = "1.0" mun_syntax={path="../mun_syntax"} +mun_target={path="../mun_target"} rustc-hash = "1.1" once_cell = "0.2" relative-path = "0.4.0" diff --git a/crates/mun_hir/src/builtin_type.rs b/crates/mun_hir/src/builtin_type.rs index ddddaf7e7..60f05a7c5 100644 --- a/crates/mun_hir/src/builtin_type.rs +++ b/crates/mun_hir/src/builtin_type.rs @@ -125,6 +125,7 @@ impl BuiltinInt { pub fn from_suffix(suffix: &str) -> Option { let res = match suffix { + "int" => Self::INT, "isize" => Self::ISIZE, "i8" => Self::I8, "i16" => Self::I16, @@ -132,6 +133,7 @@ impl BuiltinInt { "i64" => Self::I64, "i128" => Self::I128, + "uint" => Self::UINT, "usize" => Self::USIZE, "u8" => Self::U8, "u16" => Self::U16, @@ -155,6 +157,7 @@ impl BuiltinFloat { let res = match suffix { "f32" => BuiltinFloat::F32, "f64" => BuiltinFloat::F64, + "float" => BuiltinFloat::FLOAT, _ => return None, }; Some(res) diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 449a6ceb1..f7a083af1 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -349,10 +349,12 @@ impl Function { } pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { + let body = self.body(db); + body.add_diagnostics(db, self.into(), sink); let infer = self.infer(db); infer.add_diagnostics(db, self, sink); - let mut validator = ExprValidator::new(self, db, sink); - validator.validate_body(); + let validator = ExprValidator::new(self, db); + validator.validate_body(sink); } } diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index 3f5ee7fb0..eb7d3a749 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -15,6 +15,8 @@ use crate::{ AstIdMap, ExprScopes, FileId, RawItems, Struct, }; use mun_syntax::{ast, Parse, SourceFile, SyntaxNode}; +use mun_target::abi; +use mun_target::spec::Target; pub use relative_path::RelativePathBuf; use std::sync::Arc; @@ -75,6 +77,14 @@ pub trait DefDatabase: SourceDatabase { #[salsa::query_group(HirDatabaseStorage)] pub trait HirDatabase: DefDatabase { + /// Returns the target for code generation. + #[salsa::input] + fn target(&self) -> Target; + + /// Returns the `TargetDataLayout` for the current target + #[salsa::invoke(target_data_layout)] + fn target_data_layout(&self) -> Arc; + #[salsa::invoke(ExprScopes::expr_scopes_query)] fn expr_scopes(&self, def: DefWithBody) -> Arc; @@ -119,3 +129,10 @@ fn line_index_query(db: &impl SourceDatabase, file_id: FileId) -> Arc let text = db.file_text(file_id); Arc::new(LineIndex::new(text.as_ref())) } + +fn target_data_layout(db: &impl HirDatabase) -> Arc { + let target = db.target(); + let data_layout = abi::TargetDataLayout::parse(&target) + .expect("unable to create TargetDataLayout from target"); + Arc::new(data_layout) +} diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index bfd2da13c..72fcc9704 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -1,7 +1,7 @@ use crate::adt::StructKind; use crate::in_file::InFile; -use crate::{FileId, HirDatabase, Name, Ty}; -use mun_syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr, TextRange}; +use crate::{FileId, HirDatabase, IntTy, Name, Ty}; +use mun_syntax::{ast, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange}; use std::{any::Any, fmt}; /// Diagnostic defines hir API for errors and warnings. @@ -556,3 +556,112 @@ impl Diagnostic for ExternNonPrimitiveParam { self } } + +/// An error that is emitted if a literal is too large to even parse +#[derive(Debug)] +pub struct IntLiteralTooLarge { + pub literal: InFile>, +} + +impl Diagnostic for IntLiteralTooLarge { + fn message(&self) -> String { + "int literal is too large".to_owned() + } + + fn source(&self) -> InFile { + self.literal.map(|ptr| ptr.into()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +/// An error that is emitted if a literal is too large for its suffix +#[derive(Debug)] +pub struct LiteralOutOfRange { + pub literal: InFile>, + pub int_ty: IntTy, +} + +impl Diagnostic for LiteralOutOfRange { + fn message(&self) -> String { + format!("literal out of range for `{}`", self.int_ty.ty_to_string()) + } + + fn source(&self) -> InFile { + self.literal.map(|ptr| ptr.into()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +/// An error that is emitted for a literal with an invalid suffix (e.g. `123_foo`) +#[derive(Debug)] +pub struct InvalidLiteralSuffix { + pub literal: InFile>, + pub suffix: SmolStr, +} + +impl Diagnostic for InvalidLiteralSuffix { + fn message(&self) -> String { + format!("invalid suffix `{}`", self.suffix) + } + + fn source(&self) -> InFile { + self.literal.map(|ptr| ptr.into()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +/// An error that is emitted for a literal with a floating point suffix with a non 10 base (e.g. +/// `0x123_f32`) +#[derive(Debug)] +pub struct InvalidFloatingPointLiteral { + pub literal: InFile>, + pub base: u32, +} + +impl Diagnostic for InvalidFloatingPointLiteral { + fn message(&self) -> String { + match self.base { + 2 => "binary float literal is not supported".to_owned(), + 8 => "octal float literal is not supported".to_owned(), + 16 => "hexadecimal float literal is not supported".to_owned(), + _ => "unsupported base for floating pointer literal".to_owned(), + } + } + + fn source(&self) -> InFile { + self.literal.map(|ptr| ptr.into()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +/// An error that is emitted for a malformed literal (e.g. `0b22222`) +#[derive(Debug)] +pub struct InvalidLiteral { + pub literal: InFile>, +} + +impl Diagnostic for InvalidLiteral { + fn message(&self) -> String { + "invalid literal value".to_owned() + } + + fn source(&self) -> InFile { + self.literal.map(|ptr| ptr.into()) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index efa8d7f81..03ee185e4 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -12,15 +12,19 @@ use crate::type_ref::{TypeRef, TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSou use either::Either; pub use mun_syntax::ast::PrefixOp as UnaryOp; use mun_syntax::ast::{ArgListOwner, BinOp, LoopBodyOwner, NameOwner, TypeAscriptionOwner}; -use mun_syntax::{ast, AstNode, AstPtr, T}; +use mun_syntax::{ast, AstNode, AstPtr, SmolStr, T}; use rustc_hash::FxHashMap; use std::ops::Index; use std::sync::Arc; pub use self::scope::ExprScopes; +use crate::builtin_type::{BuiltinFloat, BuiltinInt}; +use crate::diagnostics::DiagnosticSink; use crate::in_file::InFile; use crate::resolve::Resolver; +use std::borrow::Cow; use std::mem; +use std::str::FromStr; pub(crate) mod scope; pub(crate) mod validator; @@ -33,6 +37,11 @@ impl_arena_id!(ExprId); pub struct PatId(RawId); impl_arena_id!(PatId); +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum ExprDiagnostic { + LiteralError { expr: ExprId, err: LiteralError }, +} + /// The body of an item (function, const etc.). #[derive(Debug, Eq, PartialEq)] pub struct Body { @@ -49,6 +58,9 @@ pub struct Body { /// The `ExprId` of the actual body expression. body_expr: ExprId, ret_type: TypeRefId, + + /// Diagnostics encountered when parsing the ast expressions + diagnostics: Vec, } impl Body { @@ -79,6 +91,18 @@ impl Body { pub fn ret_type(&self) -> TypeRefId { self.ret_type } + + /// Adds all the `InferenceDiagnostic`s of the result to the `DiagnosticSink`. + pub(crate) fn add_diagnostics( + &self, + db: &impl HirDatabase, + owner: DefWithBody, + sink: &mut DiagnosticSink, + ) { + self.diagnostics + .iter() + .for_each(|it| it.add_to(db, owner, sink)) + } } impl Index for Body { @@ -181,8 +205,50 @@ pub enum Statement { pub enum Literal { String(String), Bool(bool), - Int(i64), - Float(f64), + Int(LiteralInt), + Float(LiteralFloat), +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum LiteralError { + /// We cannot parse the integer because its too large to fit in memory + IntTooLarge, + + /// A lexer error occurred. This might happen if the literal is malformed (e.g. 0b01012) + LexerError, + + /// Encountered an unknown suffix + InvalidIntSuffix(SmolStr), + + /// Encountered an unknown suffix + InvalidFloatSuffix(SmolStr), + + /// Trying to add floating point suffix to a literal that is not a floating point number + NonDecimalFloat(u32), +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct LiteralInt { + pub kind: LiteralIntKind, + pub value: u128, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum LiteralIntKind { + Suffixed(BuiltinInt), + Unsuffixed, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct LiteralFloat { + pub kind: LiteralFloatKind, + pub value: f64, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum LiteralFloatKind { + Suffixed(BuiltinFloat), + Unsuffixed, } impl Eq for Literal {} @@ -375,6 +441,7 @@ pub(crate) struct ExprCollector { ret_type: Option, type_ref_builder: TypeRefBuilder, current_file_id: FileId, + diagnostics: Vec, } impl<'a, DB> ExprCollector<&'a DB> @@ -393,6 +460,7 @@ where ret_type: None, type_ref_builder: TypeRefBuilder::default(), current_file_id: file_id, + diagnostics: Vec::new(), } } @@ -514,19 +582,40 @@ where ast::ExprKind::ReturnExpr(r) => self.collect_return(r), ast::ExprKind::BreakExpr(r) => self.collect_break(r), ast::ExprKind::BlockExpr(b) => self.collect_block(b), - ast::ExprKind::Literal(e) => { - let lit = match e.kind() { - ast::LiteralKind::Bool => Literal::Bool(e.token().kind() == T![true]), - ast::LiteralKind::IntNumber => { - Literal::Int(e.syntax().text().to_string().parse().unwrap()) + ast::ExprKind::Literal(e) => match e.kind() { + ast::LiteralKind::Bool => { + let lit = Literal::Bool(e.token().kind() == T![true]); + self.alloc_expr(Expr::Literal(lit), syntax_ptr) + } + ast::LiteralKind::IntNumber => { + let (text, suffix) = e.text_and_suffix(); + let (lit, errors) = integer_lit(&text, suffix.as_ref().map(SmolStr::as_str)); + let expr_id = self.alloc_expr(Expr::Literal(lit), syntax_ptr); + + for err in errors { + self.diagnostics + .push(ExprDiagnostic::LiteralError { expr: expr_id, err }) } - ast::LiteralKind::FloatNumber => { - Literal::Float(e.syntax().text().to_string().parse().unwrap()) + + expr_id + } + ast::LiteralKind::FloatNumber => { + let (text, suffix) = e.text_and_suffix(); + let (lit, errors) = float_lit(&text, suffix.as_ref().map(SmolStr::as_str)); + let expr_id = self.alloc_expr(Expr::Literal(lit), syntax_ptr); + + for err in errors { + self.diagnostics + .push(ExprDiagnostic::LiteralError { expr: expr_id, err }) } - ast::LiteralKind::String => Literal::String(Default::default()), - }; - self.alloc_expr(Expr::Literal(lit), syntax_ptr) - } + + expr_id + } + ast::LiteralKind::String => { + let lit = Literal::String(Default::default()); + self.alloc_expr(Expr::Literal(lit), syntax_ptr) + } + }, ast::ExprKind::PrefixExpr(e) => { let expr = self.collect_expr_opt(e.expr()); if let Some(op) = e.op_kind() { @@ -783,6 +872,7 @@ where ret_type: self .ret_type .expect("A body should have return type collected"), + diagnostics: self.diagnostics, }; mem::replace(&mut self.source_map.type_refs, type_ref_source_map); (body, self.source_map) @@ -830,3 +920,508 @@ pub(crate) fn resolver_for_scope( } r } + +/// Removes any underscores from a string if present +fn strip_underscores(s: &str) -> Cow { + if s.contains('_') { + let mut s = s.to_string(); + s.retain(|c| c != '_'); + Cow::Owned(s) + } else { + Cow::Borrowed(s) + } +} + +/// Parses the given string into a float literal +fn float_lit(str: &str, suffix: Option<&str>) -> (Literal, Vec) { + let str = strip_underscores(str); + filtered_float_lit(&str, suffix, 10) +} + +/// Parses the given string into a float literal (underscores are already removed from str) +fn filtered_float_lit(str: &str, suffix: Option<&str>, base: u32) -> (Literal, Vec) { + let mut errors = Vec::new(); + if base != 10 { + errors.push(LiteralError::NonDecimalFloat(base)); + } + let kind = match suffix { + Some(suf) => match BuiltinFloat::from_suffix(suf) { + Some(suf) => LiteralFloatKind::Suffixed(suf), + None => { + errors.push(LiteralError::InvalidFloatSuffix(SmolStr::new(suf))); + LiteralFloatKind::Unsuffixed + } + }, + None => LiteralFloatKind::Unsuffixed, + }; + + let value = if base == 10 { + f64::from_str(str).expect("could not parse floating point number, this is definitely a bug") + } else { + 0.0 + }; + (Literal::Float(LiteralFloat { kind, value }), errors) +} + +/// Parses the given string into an integer literal +fn integer_lit(str: &str, suffix: Option<&str>) -> (Literal, Vec) { + let str = strip_underscores(str); + + let base = match str.as_bytes() { + [b'0', b'x', ..] => 16, + [b'0', b'o', ..] => 8, + [b'0', b'b', ..] => 2, + _ => 10, + }; + + let mut errors = Vec::new(); + + let kind = match suffix { + Some(suf) => match BuiltinInt::from_suffix(suf) { + Some(ty) => LiteralIntKind::Suffixed(ty), + None => { + // 1f32 is a valid number, but its an integer disguised as a float + if BuiltinFloat::from_suffix(suf).is_some() { + return filtered_float_lit(&str, suffix, base); + } + + errors.push(LiteralError::InvalidIntSuffix(SmolStr::new(suf))); + LiteralIntKind::Unsuffixed + } + }, + _ => LiteralIntKind::Unsuffixed, + }; + + let str = &str[if base != 10 { 2 } else { 0 }..]; + let (value, err) = match u128::from_str_radix(str, base) { + Ok(i) => (i, None), + Err(_) => { + // Small bases are lexed as if they were base 10, e.g. the string might be + // `0b10201`. This will cause the conversion above to fail. + let from_lexer = base < 10 + && str + .chars() + .any(|c| c.to_digit(10).map_or(false, |d| d >= base)); + if from_lexer { + (0, Some(LiteralError::LexerError)) + } else { + (0, Some(LiteralError::IntTooLarge)) + } + } + }; + + // TODO: Add check here to see if literal will fit given the suffix! + + if let Some(err) = err { + errors.push(err); + } + + (Literal::Int(LiteralInt { kind, value }), errors) +} + +#[cfg(test)] +mod test { + use crate::builtin_type::{BuiltinFloat, BuiltinInt}; + use crate::expr::{float_lit, LiteralError, LiteralFloat, LiteralFloatKind}; + use crate::expr::{integer_lit, LiteralInt, LiteralIntKind}; + use crate::Literal; + use mun_syntax::SmolStr; + + #[test] + fn test_integer_literals() { + assert_eq!( + integer_lit("12", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 12 + }), + vec![] + ) + ); + assert_eq!( + integer_lit("0xF00BA", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 0xF00BA + }), + vec![] + ) + ); + assert_eq!( + integer_lit("10_000_000", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 10_000_000 + }), + vec![] + ) + ); + assert_eq!( + integer_lit("0o765431", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 0o765431 + }), + vec![] + ) + ); + assert_eq!( + integer_lit("0b01011100", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 0b01011100 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("0b02011100", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 0 + }), + vec![LiteralError::LexerError] + ) + ); + assert_eq!( + integer_lit("0o09", None), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 0 + }), + vec![LiteralError::LexerError] + ) + ); + + assert_eq!( + integer_lit("1234", Some("foo")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Unsuffixed, + value: 1234 + }), + vec![LiteralError::InvalidIntSuffix(SmolStr::new("foo"))] + ) + ); + + assert_eq!( + integer_lit("123", Some("i8")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::I8), + value: 123 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("i16")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::I16), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("i32")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::I32), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("i64")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::I64), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("i128")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::I128), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("isize")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::ISIZE), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("int")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::INT), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("123", Some("u8")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::U8), + value: 123 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("u16")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::U16), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("u32")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::U32), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("u64")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::U64), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("u128")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::U128), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("usize")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::USIZE), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1234", Some("uint")), + ( + Literal::Int(LiteralInt { + kind: LiteralIntKind::Suffixed(BuiltinInt::UINT), + value: 1234 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("1", Some("f32")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32), + value: 1.0 + }), + vec![] + ) + ); + + assert_eq!( + integer_lit("0x1", Some("f32")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32), + value: 0.0 + }), + vec![LiteralError::NonDecimalFloat(16)] + ) + ); + } + + #[test] + fn test_float_literals() { + assert_eq!( + float_lit("1234.1234", None), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Unsuffixed, + value: 1234.1234 + }), + vec![] + ) + ); + + assert_eq!( + float_lit("1_234.1_234", None), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Unsuffixed, + value: 1234.1234 + }), + vec![] + ) + ); + + assert_eq!( + float_lit("1234.1234e2", None), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Unsuffixed, + value: 123412.34 + }), + vec![] + ) + ); + + assert_eq!( + float_lit("1234.1234e2", Some("foo")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Unsuffixed, + value: 123412.34 + }), + vec![LiteralError::InvalidFloatSuffix(SmolStr::new("foo"))] + ) + ); + + assert_eq!( + float_lit("1234.1234e2", Some("f32")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Suffixed(BuiltinFloat::F32), + value: 123412.34 + }), + vec![] + ) + ); + + assert_eq!( + float_lit("1234.1234e2", Some("f64")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Suffixed(BuiltinFloat::F64), + value: 123412.34 + }), + vec![] + ) + ); + + assert_eq!( + float_lit("1234.1234e2", Some("float")), + ( + Literal::Float(LiteralFloat { + kind: LiteralFloatKind::Suffixed(BuiltinFloat::FLOAT), + value: 123412.34 + }), + vec![] + ) + ); + } +} + +mod diagnostics { + use super::{ExprDiagnostic, LiteralError}; + use crate::code_model::DefWithBody; + use crate::diagnostics::{ + DiagnosticSink, IntLiteralTooLarge, InvalidFloatingPointLiteral, InvalidLiteral, + InvalidLiteralSuffix, + }; + use crate::HirDatabase; + + impl ExprDiagnostic { + pub(crate) fn add_to( + &self, + db: &impl HirDatabase, + owner: DefWithBody, + sink: &mut DiagnosticSink, + ) { + let source_map = owner.body_source_map(db); + + match self { + ExprDiagnostic::LiteralError { expr, err } => { + let literal = source_map + .expr_syntax(*expr) + .expect("could not retrieve expr from source map") + .map(|expr_src| { + expr_src + .left() + .expect("could not retrieve expr from ExprSource") + .cast() + .expect("could not cast expression to literal") + }); + match err { + LiteralError::IntTooLarge => sink.push(IntLiteralTooLarge { literal }), + LiteralError::LexerError => sink.push(InvalidLiteral { literal }), + LiteralError::InvalidIntSuffix(suffix) => sink.push(InvalidLiteralSuffix { + literal, + suffix: suffix.clone(), + }), + LiteralError::InvalidFloatSuffix(suffix) => { + sink.push(InvalidLiteralSuffix { + literal, + suffix: suffix.clone(), + }) + } + LiteralError::NonDecimalFloat(base) => { + sink.push(InvalidFloatingPointLiteral { + literal, + base: *base, + }) + } + } + } + } + } + } +} diff --git a/crates/mun_hir/src/expr/validator.rs b/crates/mun_hir/src/expr/validator.rs index 0ab43901d..1e9452cee 100644 --- a/crates/mun_hir/src/expr/validator.rs +++ b/crates/mun_hir/src/expr/validator.rs @@ -6,26 +6,25 @@ use crate::{diagnostics::DiagnosticSink, Body, Expr, Function, HirDatabase, Infe use mun_syntax::{AstNode, SyntaxNodePtr}; use std::sync::Arc; +mod literal_out_of_range; mod uninitialized_access; #[cfg(test)] mod tests; -pub struct ExprValidator<'a, 'b: 'a, 'd, DB: HirDatabase> { +pub struct ExprValidator<'d, DB: HirDatabase> { func: Function, infer: Arc, body: Arc, body_source_map: Arc, - sink: &'a mut DiagnosticSink<'b>, db: &'d DB, } -impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { - pub fn new(func: Function, db: &'d DB, sink: &'a mut DiagnosticSink<'b>) -> Self { +impl<'d, DB: HirDatabase> ExprValidator<'d, DB> { + pub fn new(func: Function, db: &'d DB) -> Self { let (body, body_source_map) = db.body_with_source_map(func.into()); ExprValidator { func, - sink, db, infer: db.infer(func.into()), body, @@ -33,11 +32,13 @@ impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { } } - pub fn validate_body(&mut self) { - self.validate_uninitialized_access(); - self.validate_extern(); + pub fn validate_body(&self, sink: &mut DiagnosticSink) { + self.validate_literal_ranges(sink); + self.validate_uninitialized_access(sink); + self.validate_extern(sink); } - pub fn validate_extern(&mut self) { + + pub fn validate_extern(&self, sink: &mut DiagnosticSink) { if !self.func.is_extern(self.db) { return; } @@ -45,7 +46,7 @@ impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { // Validate that there is no body match self.body[self.func.body(self.db).body_expr] { Expr::Missing => {} - _ => self.sink.push(ExternCannotHaveBody { + _ => sink.push(ExternCannotHaveBody { func: self .func .source(self.db) @@ -62,7 +63,7 @@ impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { .type_ref_syntax(*ty_ref) .map(|ptr| ptr.syntax_node_ptr()) .unwrap(); - self.sink.push(ExternNonPrimitiveParam { + sink.push(ExternNonPrimitiveParam { param: InFile::new(self.func.source(self.db).file_id, arg_ptr), }) } @@ -75,7 +76,7 @@ impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { .type_ref_syntax(*fn_data.ret_type()) .map(|ptr| ptr.syntax_node_ptr()) .unwrap(); - self.sink.push(ExternNonPrimitiveParam { + sink.push(ExternNonPrimitiveParam { param: InFile::new(self.func.source(self.db).file_id, arg_ptr), }) } diff --git a/crates/mun_hir/src/expr/validator/literal_out_of_range.rs b/crates/mun_hir/src/expr/validator/literal_out_of_range.rs new file mode 100644 index 000000000..c926159b9 --- /dev/null +++ b/crates/mun_hir/src/expr/validator/literal_out_of_range.rs @@ -0,0 +1,43 @@ +use super::ExprValidator; +use crate::diagnostics::{DiagnosticSink, LiteralOutOfRange}; +use crate::ty::ResolveBitness; +use crate::{ty_app, TypeCtor}; +use crate::{Expr, HirDatabase, HirDisplay, Literal}; + +impl<'d, D: HirDatabase> ExprValidator<'d, D> { + /// Iterates over all expressions to determine if one of the literals has a value that is out of + /// range of its type. + pub fn validate_literal_ranges(&self, sink: &mut DiagnosticSink) { + self.body[self.body.body_expr].walk_child_exprs(move |expr_id| { + let expr = &self.body[expr_id]; + if let Expr::Literal(Literal::Int(lit)) = &expr { + let ty = &self.infer[expr_id]; + match ty { + ty_app!(TypeCtor::Int(int_ty)) => { + if lit.value > int_ty.resolve(&self.db.target_data_layout()).max() { + let literal = self + .body_source_map + .expr_syntax(expr_id) + .expect("could not retrieve expr from source map") + .map(|expr_src| { + expr_src + .left() + .expect("could not retrieve expr from ExprSource") + .cast() + .expect("could not cast expression to literal") + }); + sink.push(LiteralOutOfRange { + literal, + int_ty: *int_ty, + }) + } + } + _ => panic!( + "expected int literal to have int ty while instead it is `{}`", + ty.display(self.db) + ), + } + } + }) + } +} diff --git a/crates/mun_hir/src/expr/validator/tests.rs b/crates/mun_hir/src/expr/validator/tests.rs index 7a2cccaa7..da0cceee6 100644 --- a/crates/mun_hir/src/expr/validator/tests.rs +++ b/crates/mun_hir/src/expr/validator/tests.rs @@ -82,7 +82,7 @@ fn diagnostics(content: &str) -> String { let fun = Function { id: ctx.to_def(&def), }; - ExprValidator::new(fun, &db, &mut diag_sink).validate_body(); + ExprValidator::new(fun, &db).validate_body(&mut diag_sink); } } drop(diag_sink); diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs index 3c57c4605..274ef2bdc 100644 --- a/crates/mun_hir/src/expr/validator/uninitialized_access.rs +++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs @@ -1,5 +1,5 @@ use super::ExprValidator; -use crate::diagnostics::PossiblyUninitializedVariable; +use crate::diagnostics::{DiagnosticSink, PossiblyUninitializedVariable}; use crate::{BinaryOp, Expr, ExprId, HirDatabase, PatId, Path, Resolution, Resolver, Statement}; use std::collections::HashSet; @@ -10,9 +10,9 @@ enum ExprKind { Both, } -impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { +impl<'d, D: HirDatabase> ExprValidator<'d, D> { /// Validates that all binding access has previously been initialized. - pub(super) fn validate_uninitialized_access(&mut self) { + pub(super) fn validate_uninitialized_access(&self, sink: &mut DiagnosticSink) { let mut initialized_patterns = HashSet::new(); // Add all parameter patterns to the set of initialized patterns (they must have been @@ -22,6 +22,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } self.validate_expr_access( + sink, &mut initialized_patterns, self.body.body_expr, ExprKind::Normal, @@ -30,7 +31,8 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { /// Validates that the specified expr does not access unitialized bindings fn validate_expr_access( - &mut self, + &self, + sink: &mut DiagnosticSink, initialized_patterns: &mut HashSet, expr: ExprId, expr_side: ExprKind, @@ -38,23 +40,31 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { let body = self.body.clone(); match &body[expr] { Expr::Call { callee, args } => { - self.validate_expr_access(initialized_patterns, *callee, expr_side); + self.validate_expr_access(sink, initialized_patterns, *callee, expr_side); for arg in args.iter() { - self.validate_expr_access(initialized_patterns, *arg, expr_side); + self.validate_expr_access(sink, initialized_patterns, *arg, expr_side); } } Expr::Path(p) => { let resolver = crate::expr::resolver_for_expr(self.body.clone(), self.db, expr); - self.validate_path_access(initialized_patterns, &resolver, p, expr, expr_side); + self.validate_path_access( + sink, + initialized_patterns, + &resolver, + p, + expr, + expr_side, + ); } Expr::If { condition, then_branch, else_branch, } => { - self.validate_expr_access(initialized_patterns, *condition, ExprKind::Normal); + self.validate_expr_access(sink, initialized_patterns, *condition, ExprKind::Normal); let mut then_branch_initialized_patterns = initialized_patterns.clone(); self.validate_expr_access( + sink, &mut then_branch_initialized_patterns, *then_branch, ExprKind::Normal, @@ -62,6 +72,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { if let Some(else_branch) = else_branch { let mut else_branch_initialized_patterns = initialized_patterns.clone(); self.validate_expr_access( + sink, &mut else_branch_initialized_patterns, *else_branch, ExprKind::Normal, @@ -86,7 +97,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } } Expr::UnaryOp { expr, .. } => { - self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + self.validate_expr_access(sink, initialized_patterns, *expr, ExprKind::Normal); } Expr::BinaryOp { lhs, rhs, op } => { let lhs_expr_kind = match op { @@ -94,8 +105,8 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { Some(BinaryOp::Assignment { op: None }) => ExprKind::Place, _ => ExprKind::Normal, }; - self.validate_expr_access(initialized_patterns, *lhs, lhs_expr_kind); - self.validate_expr_access(initialized_patterns, *rhs, ExprKind::Normal) + self.validate_expr_access(sink, initialized_patterns, *lhs, lhs_expr_kind); + self.validate_expr_access(sink, initialized_patterns, *rhs, ExprKind::Normal) } Expr::Block { statements, tail } => { for statement in statements.iter() { @@ -105,6 +116,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } => { if let Some(initializer) = initializer { self.validate_expr_access( + sink, initialized_patterns, *initializer, ExprKind::Normal, @@ -114,6 +126,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } Statement::Expr(expr) => { self.validate_expr_access( + sink, initialized_patterns, *expr, ExprKind::Normal, @@ -125,25 +138,26 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } } if let Some(tail) = tail { - self.validate_expr_access(initialized_patterns, *tail, ExprKind::Normal) + self.validate_expr_access(sink, initialized_patterns, *tail, ExprKind::Normal) } } Expr::Return { expr } => { if let Some(expr) = expr { - self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal) + self.validate_expr_access(sink, initialized_patterns, *expr, ExprKind::Normal) } } Expr::Break { expr } => { if let Some(expr) = expr { - self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal) + self.validate_expr_access(sink, initialized_patterns, *expr, ExprKind::Normal) } } Expr::Loop { body } => { - self.validate_expr_access(initialized_patterns, *body, ExprKind::Normal) + self.validate_expr_access(sink, initialized_patterns, *body, ExprKind::Normal) } Expr::While { condition, body } => { - self.validate_expr_access(initialized_patterns, *condition, ExprKind::Normal); + self.validate_expr_access(sink, initialized_patterns, *condition, ExprKind::Normal); self.validate_expr_access( + sink, &mut initialized_patterns.clone(), *body, ExprKind::Normal, @@ -151,14 +165,19 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } Expr::RecordLit { fields, spread, .. } => { for field in fields.iter() { - self.validate_expr_access(initialized_patterns, field.expr, ExprKind::Normal); + self.validate_expr_access( + sink, + initialized_patterns, + field.expr, + ExprKind::Normal, + ); } if let Some(expr) = spread { - self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + self.validate_expr_access(sink, initialized_patterns, *expr, ExprKind::Normal); } } Expr::Field { expr, .. } => { - self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + self.validate_expr_access(sink, initialized_patterns, *expr, ExprKind::Normal); } Expr::Literal(_) => {} Expr::Missing => {} @@ -166,7 +185,8 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { } fn validate_path_access( - &mut self, + &self, + sink: &mut DiagnosticSink, initialized_patterns: &mut HashSet, resolver: &Resolver, path: &Path, @@ -193,7 +213,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { // Check if the binding has already been initialized if initialized_patterns.get(&pat).is_none() { let (_, body_source_map) = self.db.body_with_source_map(self.func.into()); - self.sink.push(PossiblyUninitializedVariable { + sink.push(PossiblyUninitializedVariable { file: self.func.module(self.db).file_id(), pat: body_source_map .expr_syntax(expr) diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 001ef2826..2ed324d7e 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -58,7 +58,10 @@ pub use crate::{ path::{Path, PathKind}, raw::RawItems, resolve::{Resolution, Resolver}, - ty::{lower::CallableDef, ApplicationTy, FloatTy, InferenceResult, IntTy, Ty, TypeCtor}, + ty::{ + lower::CallableDef, ApplicationTy, FloatTy, InferenceResult, IntTy, ResolveBitness, Ty, + TypeCtor, + }, }; use crate::{ diff --git a/crates/mun_hir/src/mock.rs b/crates/mun_hir/src/mock.rs index 4af824cbd..b151be167 100644 --- a/crates/mun_hir/src/mock.rs +++ b/crates/mun_hir/src/mock.rs @@ -1,6 +1,8 @@ +use crate::db::HirDatabase; use crate::db::SourceDatabase; use crate::input::{SourceRoot, SourceRootId}; use crate::{FileId, RelativePathBuf}; +use mun_target::spec::Target; use parking_lot::Mutex; use std::sync::Arc; @@ -40,6 +42,7 @@ impl MockDatabase { let text = Arc::new(text.to_owned()); let rel_path = RelativePathBuf::from("main.mun"); let file_id = FileId(0); + db.set_target(Target::host_target().unwrap()); db.set_file_relative_path(file_id, rel_path.clone()); db.set_file_text(file_id, Arc::new(text.to_string())); db.set_file_source_root(file_id, source_root_id); diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index 31897b006..25ea5562b 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -2,6 +2,7 @@ mod infer; pub(super) mod lower; mod op; mod primitives; +mod resolve; use crate::display::{HirDisplay, HirFormatter}; use crate::ty::infer::TypeVarId; @@ -11,6 +12,7 @@ pub(crate) use infer::infer_query; pub use infer::InferenceResult; pub(crate) use lower::{callable_item_sig, fn_sig_for_fn, type_for_def, CallableDef, TypableDef}; pub use primitives::{FloatTy, IntTy}; +pub use resolve::ResolveBitness; use std::fmt; use std::sync::Arc; diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index 02fae2e0f..4aa4e6561 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -22,6 +22,7 @@ use std::sync::Arc; mod place_expr; mod type_variable; +use crate::expr::{LiteralFloatKind, LiteralIntKind}; use crate::ty::primitives::{FloatTy, IntTy}; pub use type_variable::TypeVarId; @@ -322,8 +323,30 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { Expr::Literal(lit) => match lit { Literal::String(_) => Ty::Unknown, Literal::Bool(_) => Ty::simple(TypeCtor::Bool), - Literal::Int(_) => Ty::simple(TypeCtor::Int(IntTy::int())), - Literal::Float(_) => Ty::simple(TypeCtor::Float(FloatTy::float())), + Literal::Int(ty) => { + // TODO: Add inferencing support + let ty = if let LiteralIntKind::Suffixed(suffix) = ty.kind { + IntTy { + bitness: suffix.bitness, + signedness: suffix.signedness, + } + } else { + IntTy::int() + }; + + Ty::simple(TypeCtor::Int(ty)) + } + Literal::Float(ty) => { + // TODO: Add inferencing support + let ty = if let LiteralFloatKind::Suffixed(suffix) = ty.kind { + FloatTy { + bitness: suffix.bitness, + } + } else { + FloatTy::float() + }; + Ty::simple(TypeCtor::Float(ty)) + } }, Expr::Return { expr } => { if let Some(expr) = expr { @@ -900,9 +923,9 @@ impl From for ExprOrPatId { mod diagnostics { use crate::diagnostics::{ AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, - ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS, MismatchedStructLit, - MismatchedType, MissingElseBranch, MissingFields, NoFields, NoSuchField, - ParameterCountMismatch, ReturnMissingExpression, + ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS, LiteralOutOfRange, + MismatchedStructLit, MismatchedType, MissingElseBranch, MissingFields, NoFields, + NoSuchField, ParameterCountMismatch, ReturnMissingExpression, }; use crate::{ adt::StructKind, @@ -910,7 +933,7 @@ mod diagnostics { diagnostics::{DiagnosticSink, UnresolvedType, UnresolvedValue}, ty::infer::ExprOrPatId, type_ref::TypeRefId, - ExprId, Function, HirDatabase, Name, Ty, + ExprId, Function, HirDatabase, IntTy, Name, Ty, }; #[derive(Debug, PartialEq, Eq, Clone)] @@ -989,6 +1012,10 @@ mod diagnostics { id: ExprId, field: usize, }, + LiteralOutOfRange { + id: ExprId, + literal_ty: IntTy, + }, } impl InferenceDiagnostic { @@ -1237,6 +1264,22 @@ mod diagnostics { let field = owner.body_source_map(db).field_syntax(*id, *field).into(); sink.push(NoSuchField { file, field }); } + InferenceDiagnostic::LiteralOutOfRange { id, literal_ty } => { + let literal = body + .expr_syntax(*id) + .expect("could not retrieve expr from source map") + .map(|expr_src| { + expr_src + .left() + .expect("could not retrieve expr from ExprSource") + .cast() + .expect("could not cast expression to literal") + }); + sink.push(LiteralOutOfRange { + literal, + int_ty: *literal_ty, + }) + } } } } diff --git a/crates/mun_hir/src/ty/primitives.rs b/crates/mun_hir/src/ty/primitives.rs index 280a9b382..31c45606c 100644 --- a/crates/mun_hir/src/ty/primitives.rs +++ b/crates/mun_hir/src/ty/primitives.rs @@ -1,5 +1,8 @@ use crate::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; +use mun_target::abi; +use mun_target::abi::Integer; use std::fmt::{self}; +use std::{i128, i16, i32, i64, i8, u128, u16, u32, u64, u8}; #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct IntTy { @@ -129,6 +132,40 @@ impl IntTy { (Signedness::Unsigned, IntBitness::X128) => "u128", } } + + /// Returns the maximum positive number that this instance can contain. + pub fn max(self) -> u128 { + match self.signedness { + Signedness::Signed => match self.bitness { + IntBitness::X8 => i8::MAX as u128, + IntBitness::X16 => i16::MAX as u128, + IntBitness::X32 => i32::MAX as u128, + IntBitness::X64 => i64::MAX as u128, + IntBitness::X128 => i128::MAX as u128, + _ => unreachable!("cannot determine max size of variable bitness"), + }, + Signedness::Unsigned => match self.bitness { + IntBitness::X8 => u8::MAX as u128, + IntBitness::X16 => u16::MAX as u128, + IntBitness::X32 => u32::MAX as u128, + IntBitness::X64 => u64::MAX as u128, + IntBitness::X128 => u128::MAX, + _ => unreachable!("cannot determine max size of variable bitness"), + }, + } + } +} + +impl From for IntBitness { + fn from(i: Integer) -> Self { + match i { + Integer::I8 => IntBitness::X8, + Integer::I16 => IntBitness::X16, + Integer::I32 => IntBitness::X32, + Integer::I64 => IntBitness::X64, + Integer::I128 => IntBitness::X128, + } + } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_hir/src/ty/resolve.rs b/crates/mun_hir/src/ty/resolve.rs new file mode 100644 index 000000000..91a001d9b --- /dev/null +++ b/crates/mun_hir/src/ty/resolve.rs @@ -0,0 +1,49 @@ +use super::primitives::IntTy; +use crate::{FloatBitness, FloatTy, IntBitness}; +use mun_target::abi; +use mun_target::abi::TargetDataLayout; + +pub trait ResolveBitness { + /// Resolves any variable bitness into concrete values. + fn resolve(&self, target: &abi::TargetDataLayout) -> Self; +} + +impl ResolveBitness for IntBitness { + fn resolve(&self, data_layout: &abi::TargetDataLayout) -> IntBitness { + match self { + IntBitness::Xsize => data_layout.ptr_sized_integer().into(), + IntBitness::Undefined => IntBitness::X64, + IntBitness::X8 + | IntBitness::X16 + | IntBitness::X32 + | IntBitness::X64 + | IntBitness::X128 => *self, + } + } +} + +impl ResolveBitness for FloatBitness { + fn resolve(&self, _data_layout: &abi::TargetDataLayout) -> FloatBitness { + match self { + FloatBitness::X32 | FloatBitness::X64 => *self, + FloatBitness::Undefined => FloatBitness::X64, + } + } +} + +impl ResolveBitness for IntTy { + fn resolve(&self, target: &TargetDataLayout) -> Self { + IntTy { + bitness: self.bitness.resolve(target), + signedness: self.signedness, + } + } +} + +impl ResolveBitness for FloatTy { + fn resolve(&self, target: &TargetDataLayout) -> Self { + FloatTy { + bitness: self.bitness.resolve(target), + } + } +} diff --git a/crates/mun_hir/src/ty/snapshots/tests__infer_suffix_literals.snap b/crates/mun_hir/src/ty/snapshots/tests__infer_suffix_literals.snap new file mode 100644 index 000000000..6d1094510 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__infer_suffix_literals.snap @@ -0,0 +1,51 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "fn main(){\n 123;\n 123u8;\n 123u16;\n 123u32;\n 123u64;\n 123u128;\n 123uint;\n 1_000_000_u32;\n 123i8;\n 123i16;\n 123i32;\n 123i64;\n 123i128;\n 123int;\n 1_000_000_i32;\n 1_000_123.0e-2;\n 1_000_123.0e-2f32;\n 1_000_123.0e-2f64;\n 1_000_123.0e-2float;\n 9999999999999999999999999999999999999999999_f64;\n}\n\nfn add(a:u32) -> u32 {\n a + 12u32\n}\n\nfn errors() {\n 0b22222; // invalid literal\n 0b00010_f32; // non-10 base float\n 0o71234_f32; // non-10 base float\n 1234_foo; // invalid suffix\n 1234.0_bar; // invalid suffix\n 9999999999999999999999999999999999999999999; // too large\n 256_u8; // literal out of range for `u8`\n 128_i8; // literal out of range for `i8`\n 12712371237123_u32; // literal out of range `u32`\n 9999999999999999999999999; // literal out of range `int`\n}" +--- +[408; 415): invalid literal value +[440; 451): binary float literal is not supported +[478; 489): octal float literal is not supported +[516; 524): invalid suffix `foo` +[548; 558): invalid suffix `bar` +[582; 625): int literal is too large +[644; 650): literal out of range for `u8` +[689; 695): literal out of range for `i8` +[734; 752): literal out of range for `u32` +[788; 813): literal out of range for `int` +[9; 348) '{ ...f64; }': nothing +[15; 18) '123': int +[24; 29) '123u8': u8 +[35; 41) '123u16': u16 +[47; 53) '123u32': u32 +[59; 65) '123u64': u64 +[71; 78) '123u128': u128 +[84; 91) '123uint': uint +[97; 110) '1_000_000_u32': u32 +[116; 121) '123i8': i8 +[127; 133) '123i16': i16 +[139; 145) '123i32': i32 +[151; 157) '123i64': i64 +[163; 170) '123i128': i128 +[176; 182) '123int': int +[188; 201) '1_000_000_i32': i32 +[207; 221) '1_000_123.0e-2': float +[227; 244) '1_000_...e-2f32': f32 +[250; 267) '1_000_...e-2f64': f64 +[273; 292) '1_000_...2float': float +[298; 345) '999999...99_f64': f64 +[357; 358) 'a': u32 +[371; 388) '{ ...2u32 }': u32 +[377; 378) 'a': u32 +[377; 386) 'a + 12u32': u32 +[381; 386) '12u32': u32 +[402; 846) '{ ...int` }': nothing +[408; 415) '0b22222': int +[440; 451) '0b00010_f32': f32 +[478; 489) '0o71234_f32': f32 +[516; 524) '1234_foo': int +[548; 558) '1234.0_bar': float +[582; 625) '999999...999999': int +[644; 650) '256_u8': u8 +[689; 695) '128_i8': i8 +[734; 752) '127123...23_u32': u32 +[788; 813) '999999...999999': int diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index 258e4ccca..dec33274b 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -8,6 +8,53 @@ use mun_syntax::{ast, AstNode}; use std::fmt::Write; use std::sync::Arc; +#[test] +fn infer_suffix_literals() { + infer_snapshot( + r" + fn main(){ + 123; + 123u8; + 123u16; + 123u32; + 123u64; + 123u128; + 123uint; + 1_000_000_u32; + 123i8; + 123i16; + 123i32; + 123i64; + 123i128; + 123int; + 1_000_000_i32; + 1_000_123.0e-2; + 1_000_123.0e-2f32; + 1_000_123.0e-2f64; + 1_000_123.0e-2float; + 9999999999999999999999999999999999999999999_f64; + } + + fn add(a:u32) -> u32 { + a + 12u32 + } + + fn errors() { + 0b22222; // invalid literal + 0b00010_f32; // non-10 base float + 0o71234_f32; // non-10 base float + 1234_foo; // invalid suffix + 1234.0_bar; // invalid suffix + 9999999999999999999999999999999999999999999; // too large + 256_u8; // literal out of range for `u8` + 128_i8; // literal out of range for `i8` + 12712371237123_u32; // literal out of range `u32` + 9999999999999999999999999; // literal out of range `int` + } + ", + ) +} + #[test] fn infer_invalid_struct_type() { infer_snapshot( diff --git a/crates/mun_syntax/src/ast/expr_extensions.rs b/crates/mun_syntax/src/ast/expr_extensions.rs index be631116f..f05165636 100644 --- a/crates/mun_syntax/src/ast/expr_extensions.rs +++ b/crates/mun_syntax/src/ast/expr_extensions.rs @@ -1,10 +1,12 @@ use super::{children, BinExpr}; use crate::ast::{child_opt, AstChildren, Literal}; use crate::{ - ast, AstNode, + ast, AstNode, SmolStr, SyntaxKind::{self, *}, SyntaxToken, TextRange, TextUnit, }; +use std::iter::Peekable; +use std::str::CharIndices; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum PrefixOp { @@ -174,6 +176,103 @@ impl Literal { _ => unreachable!(), } } + + /// Returns a tuple containing the text part of the literal and an optional suffix. For example + /// `1e5f32` will result in `("1e5", Some("f32"))` + pub fn text_and_suffix(&self) -> (SmolStr, Option) { + let token = self.token(); + let text = token.text(); + match self.kind() { + LiteralKind::String => (text.clone(), None), + LiteralKind::IntNumber => { + let (str, suffix) = split_int_text_and_suffix(text); + (SmolStr::new(str), suffix.map(SmolStr::new)) + } + LiteralKind::FloatNumber => { + let (str, suffix) = split_float_text_and_suffix(text); + (SmolStr::new(str), suffix.map(SmolStr::new)) + } + LiteralKind::Bool => (text.clone(), None), + } + } +} + +/// Given a string containing an integer literal (e.g `0x123` or `1234u32`), splits the string in the +/// value part and the suffix part. +fn split_int_text_and_suffix(text: &str) -> (&str, Option<&str>) { + let base = match text.as_bytes() { + [b'0', b'x', ..] => 16, + [b'0', b'o', ..] => 8, + [b'0', b'b', ..] => 2, + _ => 10, + }; + + let mut iter = text.char_indices().peekable(); + + // Skip base specifier + if base != 10 { + iter.next(); + iter.next(); + } + + // Skip digits in the string + skip_digits(base, &mut iter); + + if let Some((idx, _)) = iter.next() { + (&text[0..idx], Some(&text[idx..])) + } else { + (text, None) + } +} + +/// Skips all digits in the iterator that belong to the given base +fn skip_digits(base: usize, iter: &mut Peekable) { + while let Some((_, c)) = iter.peek() { + if match c { + '0'..='9' => true, + 'a'..='f' | 'A'..='F' if base > 10 => true, + '_' => true, + _ => false, + } { + iter.next(); + } else { + break; + } + } +} + +/// Given a string containing an float literal (e.g `123.4` or `1234.4f32`), splits the string in the +/// value part and the suffix part. +fn split_float_text_and_suffix(text: &str) -> (&str, Option<&str>) { + let mut iter = text.char_indices().peekable(); + skip_digits(10, &mut iter); + + // Continue after a decimal seperator + if let Some((_, '.')) = iter.peek() { + iter.next(); + skip_digits(10, &mut iter); + } + + // Continue after exponent + if let Some((_, c)) = iter.peek() { + if *c == 'e' || *c == 'E' { + iter.next(); + + if let Some((_, c)) = iter.peek() { + if *c == '-' || *c == '+' { + iter.next(); + } + } + + skip_digits(10, &mut iter); + } + } + + if let Some((idx, _)) = iter.next() { + (&text[0..idx], Some(&text[idx..])) + } else { + (text, None) + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -201,3 +300,62 @@ impl ast::IfExpr { children(self) } } + +#[cfg(test)] +mod tests { + use super::{split_float_text_and_suffix, split_int_text_and_suffix}; + + #[test] + fn split_int_and_suffix() { + assert_eq!(split_int_text_and_suffix("123"), ("123", None)); + assert_eq!(split_int_text_and_suffix("0x123"), ("0x123", None)); + assert_eq!(split_int_text_and_suffix("123_456"), ("123_456", None)); + assert_eq!(split_int_text_and_suffix("0xfff32"), ("0xfff32", None)); + assert_eq!(split_int_text_and_suffix("0xff_f32"), ("0xff_f32", None)); + assert_eq!( + split_int_text_and_suffix("0xff_u32"), + ("0xff_", Some("u32")) + ); + assert_eq!( + split_int_text_and_suffix("0x0101u32"), + ("0x0101", Some("u32")) + ); + assert_eq!( + split_int_text_and_suffix("0xffffu32"), + ("0xffff", Some("u32")) + ); + assert_eq!( + split_int_text_and_suffix("0o71234u32"), + ("0o71234", Some("u32")) + ); + } + + #[test] + fn split_float_and_suffix() { + assert_eq!(split_float_text_and_suffix("123.0"), ("123.0", None)); + assert_eq!( + split_float_text_and_suffix("123.0f32"), + ("123.0", Some("f32")) + ); + assert_eq!( + split_float_text_and_suffix("123e10f32"), + ("123e10", Some("f32")) + ); + assert_eq!( + split_float_text_and_suffix("123E10f32"), + ("123E10", Some("f32")) + ); + assert_eq!( + split_float_text_and_suffix("123E+10f32"), + ("123E+10", Some("f32")) + ); + assert_eq!( + split_float_text_and_suffix("123E-10f32"), + ("123E-10", Some("f32")) + ); + assert_eq!( + split_float_text_and_suffix("123.123E10f32"), + ("123.123E10", Some("f32")) + ); + } +} diff --git a/crates/mun_syntax/src/parsing/lexer/numbers.rs b/crates/mun_syntax/src/parsing/lexer/numbers.rs index 789d8876b..2a53605de 100644 --- a/crates/mun_syntax/src/parsing/lexer/numbers.rs +++ b/crates/mun_syntax/src/parsing/lexer/numbers.rs @@ -14,7 +14,7 @@ pub(crate) fn scan_number(c: char, cursor: &mut Cursor) -> SyntaxKind { scan_digits(cursor, true); } '0'..='9' | '_' | '.' | 'e' | 'E' => { - scan_digits(cursor, true); + scan_digits(cursor, false); } _ => return INT_NUMBER, } @@ -28,17 +28,27 @@ pub(crate) fn scan_number(c: char, cursor: &mut Cursor) -> SyntaxKind { cursor.bump(); scan_digits(cursor, false); scan_float_exponent(cursor); + scan_suffix(cursor); return FLOAT_NUMBER; } if cursor.matches('e') || cursor.matches('E') { scan_float_exponent(cursor); + scan_suffix(cursor); return FLOAT_NUMBER; } + scan_suffix(cursor); INT_NUMBER } +fn scan_suffix(cursor: &mut Cursor) { + if cursor.matches_nth_if(0, is_ident_start) { + cursor.bump(); + cursor.bump_while(is_ident_continue); + } +} + fn scan_digits(cursor: &mut Cursor, allow_hex: bool) { while let Some(c) = cursor.current() { match c { diff --git a/crates/mun_syntax/src/tests/lexer.rs b/crates/mun_syntax/src/tests/lexer.rs index 35e3cf712..d0a5ac4d9 100644 --- a/crates/mun_syntax/src/tests/lexer.rs +++ b/crates/mun_syntax/src/tests/lexer.rs @@ -30,7 +30,10 @@ fn numbers() { 1.34 0x3Af 1e-3 - 100_000"#, + 100_000 + 0x3a_u32 + 1f32 + 0o71234"#, ) } diff --git a/crates/mun_syntax/src/tests/snapshots/lexer__numbers.snap b/crates/mun_syntax/src/tests/snapshots/lexer__numbers.snap index 04dfdedd8..a036761a0 100644 --- a/crates/mun_syntax/src/tests/snapshots/lexer__numbers.snap +++ b/crates/mun_syntax/src/tests/snapshots/lexer__numbers.snap @@ -1,6 +1,6 @@ --- source: crates/mun_syntax/src/tests/lexer.rs -expression: "1.34\n0x3Af\n1e-3\n100_000" +expression: "1.34\n0x3Af\n1e-3\n100_000\n0x3a_u32\n1f32\n0o71234" --- FLOAT_NUMBER 4 "1.34" WHITESPACE 1 "\n" @@ -9,4 +9,10 @@ WHITESPACE 1 "\n" FLOAT_NUMBER 4 "1e-3" WHITESPACE 1 "\n" INT_NUMBER 7 "100_000" +WHITESPACE 1 "\n" +INT_NUMBER 8 "0x3a_u32" +WHITESPACE 1 "\n" +INT_NUMBER 4 "1f32" +WHITESPACE 1 "\n" +INT_NUMBER 7 "0o71234" diff --git a/crates/mun_target/Cargo.toml b/crates/mun_target/Cargo.toml index 11450fb13..867c1f481 100644 --- a/crates/mun_target/Cargo.toml +++ b/crates/mun_target/Cargo.toml @@ -11,3 +11,6 @@ description = "Describes compilation targets for Mun" [dependencies] log = "0.4.8" failure = "0.1.7" + +[dev-dependencies] +insta="0.16" diff --git a/crates/mun_target/src/abi/align.rs b/crates/mun_target/src/abi/align.rs new file mode 100644 index 000000000..8615bc54c --- /dev/null +++ b/crates/mun_target/src/abi/align.rs @@ -0,0 +1,61 @@ +///! Taken from the +///! [librustc_target](https://github.com/rust-lang/rust/tree/master/src/librustc_target) crate. +use super::Size; + +/// Alignment of a type in bytes (always a power of two). +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Align { + pow2: u8, +} + +impl Align { + pub fn from_bits(bits: u64) -> Result { + Align::from_bytes(Size::from_bits(bits).bytes()) + } + + pub fn from_bytes(align: u64) -> Result { + // Treat an alignment of 0 bytes like 1-byte alignment. + if align == 0 { + return Ok(Align { pow2: 0 }); + } + + let mut bytes = align; + let mut pow2: u8 = 0; + while (bytes & 1) == 0 { + pow2 += 1; + bytes >>= 1; + } + if bytes != 1 { + return Err(format!("`{}` is not a power of 2", align)); + } + if pow2 > 29 { + return Err(format!("`{}` is too large", align)); + } + + Ok(Align { pow2 }) + } + + // pub fn bytes(self) -> u64 { + // 1 << self.pow2 + // } + // + // pub fn bits(self) -> u64 { + // self.bytes() * 8 + // } + // + // /// Computes the best alignment possible for the given offset + // /// (the largest power of two that the offset is a multiple of). + // /// + // /// N.B., for an offset of `0`, this happens to return `2^64`. + // pub fn max_for_offset(offset: Size) -> Align { + // Align { + // pow2: offset.bytes().trailing_zeros() as u8, + // } + // } + // + // /// Lower the alignment, if necessary, such that the given offset + // /// is aligned to it (the offset is a multiple of the alignment). + // pub fn restrict_for_offset(self, offset: Size) -> Align { + // self.min(Align::max_for_offset(offset)) + // } +} diff --git a/crates/mun_target/src/abi/integer.rs b/crates/mun_target/src/abi/integer.rs new file mode 100644 index 000000000..4c9a03751 --- /dev/null +++ b/crates/mun_target/src/abi/integer.rs @@ -0,0 +1,92 @@ +///! Taken from the +///! [librustc_target](https://github.com/rust-lang/rust/tree/master/src/librustc_target) crate. + +// use crate::abi::{AbiAndPrefAlign, Align, HasDataLayout, Size}; + +/// Integers, also used for enum discriminants. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub enum Integer { + I8, + I16, + I32, + I64, + I128, +} + +impl Integer { + // pub fn size(self) -> Size { + // match self { + // Integer::I8 => Size::from_bytes(1), + // Integer::I16 => Size::from_bytes(2), + // Integer::I32 => Size::from_bytes(4), + // Integer::I64 => Size::from_bytes(8), + // Integer::I128 => Size::from_bytes(16), + // } + // } + // + // pub fn align(self, cx: &C) -> AbiAndPrefAlign { + // let dl = cx.data_layout(); + // + // match self { + // Integer::I8 => dl.i8_align, + // Integer::I16 => dl.i16_align, + // Integer::I32 => dl.i32_align, + // Integer::I64 => dl.i64_align, + // Integer::I128 => dl.i128_align, + // } + // } + // + // /// Finds the smallest Integer type which can represent the signed value. + // pub fn fit_signed(x: i128) -> Integer { + // #[allow(clippy::match_overlapping_arm)] + // match x { + // -0x0000_0000_0000_0080..=0x0000_0000_0000_007f => Integer::I8, + // -0x0000_0000_0000_8000..=0x0000_0000_0000_7fff => Integer::I16, + // -0x0000_0000_8000_0000..=0x0000_0000_7fff_ffff => Integer::I32, + // -0x8000_0000_0000_0000..=0x7fff_ffff_ffff_ffff => Integer::I64, + // _ => Integer::I128, + // } + // } + // + // /// Finds the smallest Integer type which can represent the unsigned value. + // pub fn fit_unsigned(x: u128) -> Integer { + // #[allow(clippy::match_overlapping_arm)] + // match x { + // 0..=0x0000_0000_0000_00ff => Integer::I8, + // 0..=0x0000_0000_0000_ffff => Integer::I16, + // 0..=0x0000_0000_ffff_ffff => Integer::I32, + // 0..=0xffff_ffff_ffff_ffff => Integer::I64, + // _ => Integer::I128, + // } + // } + // + // /// Finds the smallest integer with the given alignment. + // pub fn for_align(cx: &C, wanted: Align) -> Option { + // let dl = cx.data_layout(); + // + // for &candidate in &[ + // Integer::I8, + // Integer::I16, + // Integer::I32, + // Integer::I64, + // Integer::I128, + // ] { + // if wanted == candidate.align(dl).abi && wanted.bytes() == candidate.size().bytes() { + // return Some(candidate); + // } + // } + // None + // } + // + // /// Find the largest integer with the given alignment or less. + // pub fn approximate_align(cx: &C, wanted: Align) -> Integer { + // let dl = cx.data_layout(); + // + // for &candidate in &[Integer::I128, Integer::I64, Integer::I32, Integer::I16] { + // if wanted >= candidate.align(dl).abi && wanted.bytes() >= candidate.size().bytes() { + // return candidate; + // } + // } + // Integer::I8 + // } +} diff --git a/crates/mun_target/src/abi/mod.rs b/crates/mun_target/src/abi/mod.rs new file mode 100644 index 000000000..7f337463c --- /dev/null +++ b/crates/mun_target/src/abi/mod.rs @@ -0,0 +1,290 @@ +///! Taken from the +///! [librustc_target](https://github.com/rust-lang/rust/tree/master/src/librustc_target) crate. +mod align; +mod integer; +mod size; + +use crate::spec::Target; + +pub use align::Align; +pub use integer::Integer; +pub use size::Size; + +/// Parsed [Data layout](http://llvm.org/docs/LangRef.html#data-layout) for a target, which contains +/// everything needed to compute layouts. +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct TargetDataLayout { + pub endian: Endian, + pub i1_align: AbiAndPrefAlign, + pub i8_align: AbiAndPrefAlign, + pub i16_align: AbiAndPrefAlign, + pub i32_align: AbiAndPrefAlign, + pub i64_align: AbiAndPrefAlign, + pub i128_align: AbiAndPrefAlign, + pub f32_align: AbiAndPrefAlign, + pub f64_align: AbiAndPrefAlign, + pub pointer_size: Size, + pub pointer_align: AbiAndPrefAlign, + pub aggregate_align: AbiAndPrefAlign, + + /// Alignments for vector types. + pub vector_align: Vec<(Size, AbiAndPrefAlign)>, + + pub instruction_address_space: u32, +} + +impl Default for TargetDataLayout { + /// Creates an instance of `TargetDataLayout`. + fn default() -> TargetDataLayout { + let align = |bits| Align::from_bits(bits).unwrap(); + TargetDataLayout { + endian: Endian::Big, + i1_align: AbiAndPrefAlign::new(align(8)), + i8_align: AbiAndPrefAlign::new(align(8)), + i16_align: AbiAndPrefAlign::new(align(16)), + i32_align: AbiAndPrefAlign::new(align(32)), + i64_align: AbiAndPrefAlign { + abi: align(32), + pref: align(64), + }, + i128_align: AbiAndPrefAlign { + abi: align(32), + pref: align(64), + }, + f32_align: AbiAndPrefAlign::new(align(32)), + f64_align: AbiAndPrefAlign::new(align(64)), + pointer_size: Size::from_bits(64), + pointer_align: AbiAndPrefAlign::new(align(64)), + aggregate_align: AbiAndPrefAlign { + abi: align(0), + pref: align(64), + }, + vector_align: vec![ + (Size::from_bits(64), AbiAndPrefAlign::new(align(64))), + (Size::from_bits(128), AbiAndPrefAlign::new(align(128))), + ], + instruction_address_space: 0, + } + } +} + +impl TargetDataLayout { + pub fn parse(target: &Target) -> Result { + // Parse an address space index from a string. + let parse_address_space = |s: &str, cause: &str| { + s.parse::().map_err(|err| { + format!( + "invalid address space `{}` for `{}` in \"data-layout\": {}", + s, cause, err + ) + }) + }; + + // Parse a bit count from a string. + let parse_bits = |s: &str, kind: &str, cause: &str| { + s.parse::().map_err(|err| { + format!( + "invalid {} `{}` for `{}` in \"data-layout\": {}", + kind, s, cause, err + ) + }) + }; + + // Parse a size string. + let size = |s: &str, cause: &str| parse_bits(s, "size", cause).map(Size::from_bits); + + // Parse an alignment string. + let align = |s: &[&str], cause: &str| { + if s.is_empty() { + return Err(format!( + "missing alignment for `{}` in \"data-layout\"", + cause + )); + } + let align_from_bits = |bits| { + Align::from_bits(bits).map_err(|err| { + format!( + "invalid alignment for `{}` in \"data-layout\": {}", + cause, err + ) + }) + }; + let abi = parse_bits(s[0], "alignment", cause)?; + let pref = s + .get(1) + .map_or(Ok(abi), |pref| parse_bits(pref, "alignment", cause))?; + Ok(AbiAndPrefAlign { + abi: align_from_bits(abi)?, + pref: align_from_bits(pref)?, + }) + }; + + let mut dl = TargetDataLayout::default(); + let mut i128_align_src = 64; + for spec in target.data_layout.split('-') { + let spec_parts = spec.split(':').collect::>(); + + match &*spec_parts { + ["e"] => dl.endian = Endian::Little, + ["E"] => dl.endian = Endian::Big, + [p] if p.starts_with('P') => { + dl.instruction_address_space = parse_address_space(&p[1..], "P")? + } + ["a", ref a @ ..] => dl.aggregate_align = align(a, "a")?, + ["f32", ref a @ ..] => dl.f32_align = align(a, "f32")?, + ["f64", ref a @ ..] => dl.f64_align = align(a, "f64")?, + [p @ "p", s, ref a @ ..] | [p @ "p0", s, ref a @ ..] => { + dl.pointer_size = size(s, p)?; + dl.pointer_align = align(a, p)?; + } + [s, ref a @ ..] if s.starts_with('i') => { + let bits = match s[1..].parse::() { + Ok(bits) => bits, + Err(_) => { + size(&s[1..], "i")?; // For the user error. + continue; + } + }; + let a = align(a, s)?; + match bits { + 1 => dl.i1_align = a, + 8 => dl.i8_align = a, + 16 => dl.i16_align = a, + 32 => dl.i32_align = a, + 64 => dl.i64_align = a, + _ => {} + } + if bits >= i128_align_src && bits <= 128 { + // Default alignment for i128 is decided by taking the alignment of + // largest-sized i{64..=128}. + i128_align_src = bits; + dl.i128_align = a; + } + } + [s, ref a @ ..] if s.starts_with('v') => { + let v_size = size(&s[1..], "v")?; + let a = align(a, s)?; + if let Some(v) = dl.vector_align.iter_mut().find(|v| v.0 == v_size) { + v.1 = a; + continue; + } + // No existing entry, add a new one. + dl.vector_align.push((v_size, a)); + } + _ => {} // Ignore everything else. + } + } + + // Perform consistency checks against the Target information. + let endian_str = match dl.endian { + Endian::Little => "little", + Endian::Big => "big", + }; + if endian_str != target.target_endian { + return Err(format!( + "inconsistent target specification: \"data-layout\" claims \ + architecture is {}-endian, while \"target-endian\" is `{}`", + endian_str, target.target_endian + )); + } + + if dl.pointer_size.bits().to_string() != target.target_pointer_width { + return Err(format!( + "inconsistent target specification: \"data-layout\" claims \ + pointers are {}-bit, while \"target-pointer-width\" is `{}`", + dl.pointer_size.bits(), + target.target_pointer_width + )); + } + + Ok(dl) + } + + // /// Returns exclusive upper bound on object size. + // /// + // /// The theoretical maximum object size is defined as the maximum positive `isize` value. + // /// This ensures that the `offset` semantics remain well-defined by allowing it to correctly + // /// index every address within an object along with one byte past the end, along with allowing + // /// `isize` to store the difference between any two pointers into an object. + // /// + // /// The upper bound on 64-bit currently needs to be lower because LLVM uses a 64-bit integer + // /// to represent object size in bits. It would need to be 1 << 61 to account for this, but is + // /// currently conservatively bounded to 1 << 47 as that is enough to cover the current usable + // /// address space on 64-bit ARMv8 and x86_64. + // pub fn obj_size_bound(&self) -> u64 { + // match self.pointer_size.bits() { + // 16 => 1 << 15, + // 32 => 1 << 31, + // 64 => 1 << 47, + // bits => panic!("obj_size_bound: unknown pointer bit size {}", bits), + // } + // } + + pub fn ptr_sized_integer(&self) -> Integer { + use Integer::*; + match self.pointer_size.bits() { + 16 => I16, + 32 => I32, + 64 => I64, + bits => panic!("ptr_sized_integer: unknown pointer bit size {}", bits), + } + } + + // pub fn vector_align(&self, vec_size: Size) -> AbiAndPrefAlign { + // for &(size, align) in &self.vector_align { + // if size == vec_size { + // return align; + // } + // } + // // Default to natural alignment, which is what LLVM does. + // // That is, use the size, rounded up to a power of 2. + // AbiAndPrefAlign::new(Align::from_bytes(vec_size.bytes().next_power_of_two()).unwrap()) + // } +} + +// pub trait HasDataLayout { +// fn data_layout(&self) -> &TargetDataLayout; +// } +// +// impl HasDataLayout for TargetDataLayout { +// fn data_layout(&self) -> &TargetDataLayout { +// self +// } +// } + +/// Endianness of the target +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Endian { + Little, + Big, +} + +/// A pair of alignments, ABI-mandated and preferred. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct AbiAndPrefAlign { + pub abi: Align, + pub pref: Align, +} + +impl AbiAndPrefAlign { + pub fn new(align: Align) -> AbiAndPrefAlign { + AbiAndPrefAlign { + abi: align, + pref: align, + } + } + + // pub fn min(self, other: AbiAndPrefAlign) -> AbiAndPrefAlign { + // AbiAndPrefAlign { + // abi: self.abi.min(other.abi), + // pref: self.pref.min(other.pref), + // } + // } + // + // pub fn max(self, other: AbiAndPrefAlign) -> AbiAndPrefAlign { + // AbiAndPrefAlign { + // abi: self.abi.max(other.abi), + // pref: self.pref.max(other.pref), + // } + // } +} diff --git a/crates/mun_target/src/abi/size.rs b/crates/mun_target/src/abi/size.rs new file mode 100644 index 000000000..79dd351ea --- /dev/null +++ b/crates/mun_target/src/abi/size.rs @@ -0,0 +1,148 @@ +///! Taken from the +///! [librustc_target](https://github.com/rust-lang/rust/tree/master/src/librustc_target) crate. +// use crate::abi::{Align, HasDataLayout}; +use std::convert::TryInto; +// use std::ops::{Add, AddAssign, Mul, Sub}; + +/// Size of a type in bytes. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Size { + raw: u64, +} + +impl Size { + pub const ZERO: Size = Size { raw: 0 }; + + #[inline] + pub fn from_bits(bits: impl TryInto) -> Size { + let bits = bits.try_into().ok().unwrap(); + // Avoid potential overflow from `bits + 7`. + Size::from_bytes(bits / 8 + ((bits % 8) + 7) / 8) + } + + #[inline] + pub fn from_bytes(bytes: impl TryInto) -> Size { + Size { + raw: bytes.try_into().ok().unwrap(), + } + } + + #[inline] + pub fn bytes(self) -> u64 { + self.raw + } + + // #[inline] + // pub fn bytes_usize(self) -> usize { + // self.bytes().try_into().unwrap() + // } + + #[inline] + pub fn bits(self) -> u64 { + self.bytes().checked_mul(8).unwrap_or_else(|| { + panic!( + "Size::bits: {} bytes in bits doesn't fit in u64", + self.bytes() + ) + }) + } + + // #[inline] + // pub fn bits_usize(self) -> usize { + // self.bits().try_into().unwrap() + // } + // + // #[inline] + // pub fn align_to(self, align: Align) -> Size { + // let mask = align.bytes() - 1; + // Size::from_bytes((self.bytes() + mask) & !mask) + // } + // + // #[inline] + // pub fn is_aligned(self, align: Align) -> bool { + // let mask = align.bytes() - 1; + // self.bytes() & mask == 0 + // } + // + // #[inline] + // pub fn checked_add(self, offset: Size, cx: &C) -> Option { + // let dl = cx.data_layout(); + // + // let bytes = self.bytes().checked_add(offset.bytes())?; + // + // if bytes < dl.obj_size_bound() { + // Some(Size::from_bytes(bytes)) + // } else { + // None + // } + // } + // + // #[inline] + // pub fn checked_mul(self, count: u64, cx: &C) -> Option { + // let dl = cx.data_layout(); + // + // let bytes = self.bytes().checked_mul(count)?; + // if bytes < dl.obj_size_bound() { + // Some(Size::from_bytes(bytes)) + // } else { + // None + // } + // } +} + +// // Panicking addition, subtraction and multiplication for convenience. +// // Avoid during layout computation, return `LayoutError` instead. +// +// impl Add for Size { +// type Output = Size; +// #[inline] +// fn add(self, other: Size) -> Size { +// Size::from_bytes(self.bytes().checked_add(other.bytes()).unwrap_or_else(|| { +// panic!( +// "Size::add: {} + {} doesn't fit in u64", +// self.bytes(), +// other.bytes() +// ) +// })) +// } +// } +// +// impl Sub for Size { +// type Output = Size; +// #[inline] +// fn sub(self, other: Size) -> Size { +// Size::from_bytes(self.bytes().checked_sub(other.bytes()).unwrap_or_else(|| { +// panic!( +// "Size::sub: {} - {} would result in negative size", +// self.bytes(), +// other.bytes() +// ) +// })) +// } +// } +// +// impl Mul for u64 { +// type Output = Size; +// #[inline] +// fn mul(self, size: Size) -> Size { +// size * self +// } +// } +// +// impl Mul for Size { +// type Output = Size; +// #[inline] +// fn mul(self, count: u64) -> Size { +// match self.bytes().checked_mul(count) { +// Some(bytes) => Size::from_bytes(bytes), +// None => panic!("Size::mul: {} * {} doesn't fit in u64", self.bytes(), count), +// } +// } +// } +// +// impl AddAssign for Size { +// #[inline] +// fn add_assign(&mut self, other: Size) { +// *self = *self + other; +// } +// } diff --git a/crates/mun_target/src/lib.rs b/crates/mun_target/src/lib.rs index d18a4d295..535e5eedb 100644 --- a/crates/mun_target/src/lib.rs +++ b/crates/mun_target/src/lib.rs @@ -4,6 +4,7 @@ //! It is heavily inspired by the //! [librustc_target](https://github.com/rust-lang/rust/tree/master/src/librustc_target) crate. +pub mod abi; pub mod spec; /// Returns the target triple of the host machine. This can be used as a default target. diff --git a/crates/mun_target/src/spec.rs b/crates/mun_target/src/spec.rs index 979b6d77e..8d77ee870 100644 --- a/crates/mun_target/src/spec.rs +++ b/crates/mun_target/src/spec.rs @@ -18,6 +18,15 @@ pub struct Target { /// Target triple to pass to LLVM pub llvm_target: String, + /// String to use as the `target_endian` `cfg` variable. + pub target_endian: String, + + /// String to use as the `target_pointer_width` `cfg` variable. + pub target_pointer_width: String, + + /// Width of c_int type + pub target_c_int_width: String, + /// The name of the OS pub target_os: String, diff --git a/crates/mun_target/src/spec/x86_64_apple_darwin.rs b/crates/mun_target/src/spec/x86_64_apple_darwin.rs index ae9714c44..901886281 100644 --- a/crates/mun_target/src/spec/x86_64_apple_darwin.rs +++ b/crates/mun_target/src/spec/x86_64_apple_darwin.rs @@ -13,6 +13,9 @@ pub fn target() -> TargetResult { Ok(Target { llvm_target, target_os: "macos".to_string(), + target_endian: "little".to_string(), + target_pointer_width: "64".to_string(), + target_c_int_width: "32".to_string(), target_env: String::new(), target_vendor: "apple".to_string(), arch: arch.to_string(), diff --git a/crates/mun_target/src/spec/x86_64_pc_windows_msvc.rs b/crates/mun_target/src/spec/x86_64_pc_windows_msvc.rs index fa846d2d4..014f2332c 100644 --- a/crates/mun_target/src/spec/x86_64_pc_windows_msvc.rs +++ b/crates/mun_target/src/spec/x86_64_pc_windows_msvc.rs @@ -6,6 +6,9 @@ pub fn target() -> TargetResult { Ok(Target { llvm_target: "x86_64-pc-windows-msvc".to_string(), + target_endian: "little".to_string(), + target_pointer_width: "64".to_string(), + target_c_int_width: "32".to_string(), target_os: "windows".to_string(), target_env: "msvc".to_string(), target_vendor: "pc".to_string(), diff --git a/crates/mun_target/src/spec/x86_64_unknown_linux_gnu.rs b/crates/mun_target/src/spec/x86_64_unknown_linux_gnu.rs index 9b2760c16..dcb550825 100644 --- a/crates/mun_target/src/spec/x86_64_unknown_linux_gnu.rs +++ b/crates/mun_target/src/spec/x86_64_unknown_linux_gnu.rs @@ -6,6 +6,9 @@ pub fn target() -> TargetResult { Ok(Target { llvm_target: "x86_64-unknown-linux-gnu".to_string(), + target_endian: "little".to_string(), + target_pointer_width: "64".to_string(), + target_c_int_width: "32".to_string(), target_os: "linux".to_string(), target_env: "gnu".to_string(), target_vendor: "unknown".to_string(), diff --git a/crates/mun_target/tests/data_layout.rs b/crates/mun_target/tests/data_layout.rs new file mode 100644 index 000000000..297604a61 --- /dev/null +++ b/crates/mun_target/tests/data_layout.rs @@ -0,0 +1,25 @@ +use mun_target::abi::TargetDataLayout; +use mun_target::spec::Target; + +#[test] +fn data_layout_windows() { + let layout = + TargetDataLayout::parse(&Target::search("x86_64-pc-windows-msvc").unwrap()).unwrap(); + + insta::assert_debug_snapshot!(layout); +} + +#[test] +fn data_layout_darwin() { + let layout = TargetDataLayout::parse(&Target::search("x86_64-apple-darwin").unwrap()).unwrap(); + + insta::assert_debug_snapshot!(layout); +} + +#[test] +fn data_layout_linux() { + let layout = + TargetDataLayout::parse(&Target::search("x86_64-unknown-linux-gnu").unwrap()).unwrap(); + + insta::assert_debug_snapshot!(layout); +} diff --git a/crates/mun_target/tests/snapshots/data_layout__data_layout_darwin.snap b/crates/mun_target/tests/snapshots/data_layout__data_layout_darwin.snap new file mode 100644 index 000000000..45cbe4299 --- /dev/null +++ b/crates/mun_target/tests/snapshots/data_layout__data_layout_darwin.snap @@ -0,0 +1,119 @@ +--- +source: crates/mun_target/tests/data_layout.rs +expression: layout +--- +TargetDataLayout { + endian: Little, + i1_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i8_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i16_align: AbiAndPrefAlign { + abi: Align { + pow2: 1, + }, + pref: Align { + pow2: 1, + }, + }, + i32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + i64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + i128_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + f32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + f64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + pointer_size: Size { + raw: 8, + }, + pointer_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + aggregate_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 3, + }, + }, + vector_align: [ + ( + Size { + raw: 8, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + ), + ( + Size { + raw: 16, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 4, + }, + pref: Align { + pow2: 4, + }, + }, + ), + ], + instruction_address_space: 0, +} diff --git a/crates/mun_target/tests/snapshots/data_layout__data_layout_linux.snap b/crates/mun_target/tests/snapshots/data_layout__data_layout_linux.snap new file mode 100644 index 000000000..45cbe4299 --- /dev/null +++ b/crates/mun_target/tests/snapshots/data_layout__data_layout_linux.snap @@ -0,0 +1,119 @@ +--- +source: crates/mun_target/tests/data_layout.rs +expression: layout +--- +TargetDataLayout { + endian: Little, + i1_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i8_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i16_align: AbiAndPrefAlign { + abi: Align { + pow2: 1, + }, + pref: Align { + pow2: 1, + }, + }, + i32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + i64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + i128_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + f32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + f64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + pointer_size: Size { + raw: 8, + }, + pointer_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + aggregate_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 3, + }, + }, + vector_align: [ + ( + Size { + raw: 8, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + ), + ( + Size { + raw: 16, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 4, + }, + pref: Align { + pow2: 4, + }, + }, + ), + ], + instruction_address_space: 0, +} diff --git a/crates/mun_target/tests/snapshots/data_layout__data_layout_windows.snap b/crates/mun_target/tests/snapshots/data_layout__data_layout_windows.snap new file mode 100644 index 000000000..45cbe4299 --- /dev/null +++ b/crates/mun_target/tests/snapshots/data_layout__data_layout_windows.snap @@ -0,0 +1,119 @@ +--- +source: crates/mun_target/tests/data_layout.rs +expression: layout +--- +TargetDataLayout { + endian: Little, + i1_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i8_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 0, + }, + }, + i16_align: AbiAndPrefAlign { + abi: Align { + pow2: 1, + }, + pref: Align { + pow2: 1, + }, + }, + i32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + i64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + i128_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + f32_align: AbiAndPrefAlign { + abi: Align { + pow2: 2, + }, + pref: Align { + pow2: 2, + }, + }, + f64_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + pointer_size: Size { + raw: 8, + }, + pointer_align: AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + aggregate_align: AbiAndPrefAlign { + abi: Align { + pow2: 0, + }, + pref: Align { + pow2: 3, + }, + }, + vector_align: [ + ( + Size { + raw: 8, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 3, + }, + pref: Align { + pow2: 3, + }, + }, + ), + ( + Size { + raw: 16, + }, + AbiAndPrefAlign { + abi: Align { + pow2: 4, + }, + pref: Align { + pow2: 4, + }, + }, + ), + ], + instruction_address_space: 0, +}