diff --git a/sway-core/src/concurrent_slab.rs b/sway-core/src/concurrent_slab.rs index 1c3301ff59e..eeb479bcb2c 100644 --- a/sway-core/src/concurrent_slab.rs +++ b/sway-core/src/concurrent_slab.rs @@ -61,19 +61,13 @@ where } } -impl ConcurrentSlab -where - T: Clone, -{ - #[allow(dead_code)] - pub fn len(&self) -> usize { - let inner = self.inner.read(); - inner.items.len() - } - - pub fn values(&self) -> Vec> { +impl ConcurrentSlab { + pub fn get(&self, index: usize) -> Arc { let inner = self.inner.read(); - inner.items.iter().filter_map(|x| x.clone()).collect() + inner.items[index] + .as_ref() + .expect("invalid slab index for ConcurrentSlab::get") + .clone() } pub fn insert(&self, value: T) -> usize { @@ -92,6 +86,22 @@ where inner.items.len() - 1 } } +} + +impl ConcurrentSlab +where + T: Clone, +{ + #[allow(dead_code)] + pub fn len(&self) -> usize { + let inner = self.inner.read(); + inner.items.len() + } + + pub fn values(&self) -> Vec> { + let inner = self.inner.read(); + inner.items.iter().filter_map(|x| x.clone()).collect() + } pub fn replace(&self, index: usize, new_value: T) -> Option { let mut inner = self.inner.write(); @@ -107,14 +117,6 @@ where Arc::into_inner(old) } - pub fn get(&self, index: usize) -> Arc { - let inner = self.inner.read(); - inner.items[index] - .as_ref() - .expect("invalid slab index for ConcurrentSlab::get") - .clone() - } - /// Improve performance by avoiding `Arc::clone`. /// The slab is kept locked while running `f`. pub fn map(&self, index: usize, f: impl FnOnce(&T) -> R) -> R { diff --git a/sway-core/src/engine_threading.rs b/sway-core/src/engine_threading.rs index fe06da106aa..cc24991a34f 100644 --- a/sway-core/src/engine_threading.rs +++ b/sway-core/src/engine_threading.rs @@ -2,6 +2,7 @@ use crate::{ decl_engine::{parsed_engine::ParsedDeclEngine, DeclEngine}, language::CallPath, query_engine::QueryEngine, + semantic_analysis::semantic_definition::SemanticDefinitionEngine, type_system::TypeEngine, ObservabilityEngine, }; @@ -21,6 +22,7 @@ pub struct Engines { query_engine: QueryEngine, source_engine: SourceEngine, obs_engine: Arc, + semantic_definition_engine: Arc, } impl Engines { @@ -91,6 +93,10 @@ impl Engines { engines: self, } } + + pub(crate) fn sde(&self) -> &SemanticDefinitionEngine { + self.semantic_definition_engine.as_ref() + } } #[derive(Clone, Copy)] diff --git a/sway-core/src/language/literal.rs b/sway-core/src/language/literal.rs index 974b77e3018..7e8ea29dde5 100644 --- a/sway-core/src/language/literal.rs +++ b/sway-core/src/language/literal.rs @@ -1,11 +1,9 @@ -use crate::{type_system::*, Engines}; +use crate::type_system::*; use serde::{Deserialize, Serialize}; use std::{ fmt, hash::{Hash, Hasher}, - num::{IntErrorKind, ParseIntError}, }; -use sway_error::error::CompileError; use sway_types::{integer_bits::IntegerBits, span, u256::U256}; #[derive(Debug, Clone, Eq, Serialize, Deserialize)] @@ -147,32 +145,6 @@ impl fmt::Display for Literal { } impl Literal { - #[allow(clippy::wildcard_in_or_patterns)] - pub(crate) fn handle_parse_int_error( - engines: &Engines, - e: ParseIntError, - ty: TypeInfo, - span: sway_types::Span, - ) -> CompileError { - match e.kind() { - IntErrorKind::PosOverflow => CompileError::IntegerTooLarge { - ty: engines.help_out(ty).to_string(), - span, - }, - IntErrorKind::NegOverflow => CompileError::IntegerTooSmall { - ty: engines.help_out(ty).to_string(), - span, - }, - IntErrorKind::InvalidDigit => CompileError::IntegerContainsInvalidDigit { - ty: engines.help_out(ty).to_string(), - span, - }, - IntErrorKind::Zero | IntErrorKind::Empty | _ => { - CompileError::Internal("Called incorrect internal sway-core on literal type.", span) - } - } - } - pub(crate) fn to_typeinfo(&self) -> TypeInfo { match self { Literal::String(_) => TypeInfo::StringSlice, diff --git a/sway-core/src/language/ty/declaration/function.rs b/sway-core/src/language/ty/declaration/function.rs index 553851cbec3..d6dbdc2b3a9 100644 --- a/sway-core/src/language/ty/declaration/function.rs +++ b/sway-core/src/language/ty/declaration/function.rs @@ -8,13 +8,14 @@ use crate::{ ty::*, CallPath, Inline, Purity, Trace, Visibility, }, - semantic_analysis::TypeCheckContext, + semantic_analysis::{semantic_definition::SemanticDefinitionId, TypeCheckContext}, transform::{self, AttributeKind}, type_system::*, types::*, }; use ast_elements::type_parameter::ConstGenericExpr; use either::Either; +use hashbrown::HashMap; use monomorphization::MonomorphizeHelper; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -95,6 +96,9 @@ pub struct TyFunctionDecl { /// TODO: See: https://github.com/FuelLabs/sway/issues/7371 /// !!! WARNING !!! pub kind: TyFunctionDeclKind, + + pub sdid: Option, + pub tid_map: HashMap, } impl TyDeclParsedType for TyFunctionDecl { @@ -338,6 +342,8 @@ impl DeclRefFunction { let mut method = original.clone(); if let Some(method_implementing_for) = method.implementing_for { + engines.te().start_capturing_duplicates(); + let mut type_id_type_subst_map = TypeSubstMap::new(); if let Some(TyDecl::ImplSelfOrTrait(t)) = method.implementing_type.clone() { @@ -413,34 +419,33 @@ impl DeclRefFunction { true, )); - let r = engines + method.tid_map = engines.te().end_capturing_duplicates().unwrap(); + + let decl_ref = engines .de() - .insert( - method.clone(), - engines.de().get_parsed_decl_id(self.id()).as_ref(), - ) + .insert(method, engines.de().get_parsed_decl_id(self.id()).as_ref()) .with_parent(decl_engine, self.id().into()); engines.obs().trace(|| { format!( " after get_method_safe_to_unify: {:?}; {:?}", engines.help_out(type_id), - engines.help_out(r.id()) + engines.help_out(decl_ref.id()) ) }); - return r; - } - - engines.obs().trace(|| { - format!( - " after get_method_safe_to_unify: {:?}; {:?}", - engines.help_out(type_id), - engines.help_out(self.id()) - ) - }); + decl_ref + } else { + engines.obs().trace(|| { + format!( + " after get_method_safe_to_unify: {:?}; {:?}", + engines.help_out(type_id), + engines.help_out(self.id()) + ) + }); - self.clone() + self.clone() + } } } @@ -514,6 +519,8 @@ impl HashWithEngines for TyFunctionDecl { is_trait_method_dummy: _, is_type_check_finalized: _, kind: _, + sdid: _, + tid_map: _, } = self; name.hash(state); body.hash(state, engines); @@ -667,6 +674,8 @@ impl TyFunctionDecl { FunctionDeclarationKind::Test => TyFunctionDeclKind::Test, FunctionDeclarationKind::Main => TyFunctionDeclKind::Main, }, + sdid: None, + tid_map: Default::default(), } } diff --git a/sway-core/src/language/ty/expression/expression.rs b/sway-core/src/language/ty/expression/expression.rs index fd1367f5ece..0a768b12cf2 100644 --- a/sway-core/src/language/ty/expression/expression.rs +++ b/sway-core/src/language/ty/expression/expression.rs @@ -109,23 +109,8 @@ impl TypeCheckAnalysis for TyExpression { handler: &Handler, ctx: &mut TypeCheckAnalysisContext, ) -> Result<(), ErrorEmitted> { - match &self.expression { - // Check literal "fits" into assigned typed. - TyExpressionVariant::Literal(Literal::Numeric(literal_value)) => { - let t = ctx.engines.te().get(self.return_type); - if let TypeInfo::UnsignedInteger(bits) = &*t { - if bits.would_overflow(*literal_value) { - handler.emit_err(CompileError::TypeError(TypeError::LiteralOverflow { - expected: format!("{:?}", ctx.engines.help_out(t)), - span: self.span.clone(), - })); - } - } - } - TyExpressionVariant::ArrayExplicit { .. } => { - self.as_array_unify_elements(handler, ctx.engines); - } - _ => {} + if let TyExpressionVariant::ArrayExplicit { .. } = &self.expression { + self.as_array_unify_elements(handler, ctx.engines); } self.expression.type_check_analyze(handler, ctx) } diff --git a/sway-core/src/semantic_analysis.rs b/sway-core/src/semantic_analysis.rs index c1c6ca9ae3a..3edfc0aab4f 100644 --- a/sway-core/src/semantic_analysis.rs +++ b/sway-core/src/semantic_analysis.rs @@ -19,3 +19,4 @@ pub use namespace::Namespace; pub(crate) use type_check_analysis::*; pub use type_check_context::TypeCheckContext; pub(crate) use type_check_finalization::*; +pub mod semantic_definition; diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs index 9547ee526ac..76c1d3e98b9 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function.rs @@ -85,6 +85,15 @@ impl ty::TyFunctionDecl { is_in_impl_self: bool, implementing_for: Option, ) -> Result { + // If functions aren't allowed in this location, return an error. + if ctx.functions_disallowed() { + return Err(handler.emit_err(CompileError::Unimplemented { + feature: "Declaring nested functions".to_string(), + help: vec![], + span: fn_decl.span.clone(), + })); + } + let FunctionDeclaration { name, body: _, @@ -103,15 +112,6 @@ impl ty::TyFunctionDecl { let type_engine = ctx.engines.te(); - // If functions aren't allowed in this location, return an error. - if ctx.functions_disallowed() { - return Err(handler.emit_err(CompileError::Unimplemented { - feature: "Declaring nested functions".to_string(), - help: vec![], - span: span.clone(), - })); - } - // Warn against non-snake case function names. if !is_snake_case(name.as_str()) { handler.emit_warn(CompileWarning { @@ -120,9 +120,12 @@ impl ty::TyFunctionDecl { }) } + let sdid = ctx.engines.sde().new_semantic_definition(); + // create a namespace for the function ctx.by_ref() .with_const_shadowing_mode(ConstShadowingMode::Sequential) + .with_semantic_definition(sdid) .disallow_functions() .scoped(handler, Some(span.clone()), |ctx| { // Type check the type parameters. @@ -247,6 +250,8 @@ impl ty::TyFunctionDecl { FunctionDeclarationKind::Test => ty::TyFunctionDeclKind::Test, FunctionDeclarationKind::Main => ty::TyFunctionDeclKind::Main, }, + sdid: Some(sdid), + tid_map: HashMap::default(), }; Ok(function_decl) @@ -262,6 +267,7 @@ impl ty::TyFunctionDecl { // create a namespace for the function ctx.by_ref() .with_const_shadowing_mode(ConstShadowingMode::Sequential) + .with_semantic_definition(*ty_fn_decl.sdid.as_ref().expect("missing semantic definition")) .disallow_functions() .scoped(handler, Some(fn_decl.span.clone()), |ctx| { let FunctionDeclaration { body, .. } = fn_decl; @@ -300,8 +306,16 @@ impl ty::TyFunctionDecl { .with_type_annotation(return_type.type_id) .with_function_type_annotation(return_type.type_id); + ctx.engines.te().start_capturing_duplicates(); let body = ty::TyCodeBlock::type_check(handler, ctx.by_ref(), body, true) .unwrap_or_else(|_err| ty::TyCodeBlock::default()); + let tid_map = ctx.engines.te().end_capturing_duplicates().unwrap(); + assert!(tid_map.is_empty()); + + let sdid = ctx.get_current_semantic_definition().expect("expected semantic definition"); + let sd = ctx.engines.sde().get(sdid); + let solver = sd.solver(ctx.engines, handler); + let _ = solver.solve(); ty_fn_decl.body = body; ty_fn_decl.is_type_check_finalized = true; @@ -410,6 +424,8 @@ fn test_function_selector_behavior() { is_trait_method_dummy: false, is_type_check_finalized: true, kind: ty::TyFunctionDeclKind::Default, + sdid: None, + tid_map: HashMap::default(), }; let selector_text = decl @@ -461,6 +477,8 @@ fn test_function_selector_behavior() { is_trait_method_dummy: false, is_type_check_finalized: true, kind: ty::TyFunctionDeclKind::Default, + sdid: None, + tid_map: HashMap::default(), }; let selector_text = decl diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs index d65544212cc..4635b88f344 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/trait_fn.rs @@ -1,5 +1,3 @@ -use sway_types::Spanned; - use crate::{ decl_engine::{parsed_id::ParsedDeclId, DeclId}, language::{ @@ -9,12 +7,13 @@ use crate::{ semantic_analysis::symbol_collection_context::SymbolCollectionContext, Engines, }; -use sway_error::handler::{ErrorEmitted, Handler}; - use crate::{ semantic_analysis::{AbiMode, TypeCheckContext}, type_system::*, }; +use hashbrown::HashMap; +use sway_error::handler::{ErrorEmitted, Handler}; +use sway_types::Spanned; impl ty::TyTraitFn { pub(crate) fn collect( @@ -129,6 +128,8 @@ impl ty::TyTraitFn { is_trait_method_dummy: true, is_type_check_finalized: true, kind: ty::TyFunctionDeclKind::Default, + sdid: None, + tid_map: HashMap::default(), } } } diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index dc3565a0d00..16a2c07b54d 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -47,6 +47,7 @@ use sway_error::{ convert_parse_tree_error::ConvertParseTreeError, error::{CompileError, StructFieldUsageContext}, handler::{ErrorEmitted, Handler}, + type_error::TypeError, warning::{CompileWarning, Warning}, }; use sway_types::{integer_bits::IntegerBits, u256::U256, BaseIdent, Ident, Named, Span, Spanned}; @@ -304,7 +305,7 @@ impl ty::TyExpression { // We've already emitted an error for the `::Error` case. ExpressionKind::Error(_, err) => Ok(ty::TyExpression::error(*err, span, engines)), ExpressionKind::Literal(lit) => { - Ok(Self::type_check_literal(engines, lit.clone(), span)) + Ok(Self::type_check_literal(&ctx, engines, lit.clone(), span)) } ExpressionKind::AmbiguousVariableExpression(name) => { if matches!( @@ -601,19 +602,33 @@ impl ty::TyExpression { ) .unwrap_or_else(|err| type_engine.id_of_error_recovery(err)); + // TODO: We should be able to remove this after the SemanticDefinition + // unification solver is working. + // Literals of type Numeric can now be resolved if typed_expression.return_type is // an UnsignedInteger or a Numeric if let ty::TyExpressionVariant::Literal(lit) = typed_expression.clone().expression { if let Literal::Numeric(_) = lit { match &*type_engine.get(typed_expression.return_type) { TypeInfo::UnsignedInteger(_) | TypeInfo::Numeric => { - typed_expression = Self::resolve_numeric_literal( + let expr = Self::resolve_numeric_literal( handler, ctx, lit, expr_span, typed_expression.return_type, - )? + )?; + typed_expression.expression = expr.expression; + let old_value = engines.te().get(typed_expression.return_type); + let new_value = engines.te().get(expr.return_type); + + if old_value.is_numeric() { + engines.te().replace( + engines, + typed_expression.return_type, + TypeInfo::clone(new_value.as_ref()), + ); + } } _ => {} } @@ -623,7 +638,12 @@ impl ty::TyExpression { Ok(typed_expression) } - fn type_check_literal(engines: &Engines, lit: Literal, span: Span) -> ty::TyExpression { + fn type_check_literal( + ctx: &TypeCheckContext, + engines: &Engines, + lit: Literal, + span: Span, + ) -> ty::TyExpression { let type_engine = engines.te(); let return_type = match &lit { Literal::String(_) => type_engine.id_of_string_slice(), @@ -637,6 +657,49 @@ impl ty::TyExpression { Literal::B256(_) => type_engine.id_of_b256(), Literal::Binary(_) => type_engine.id_of_raw_slice(), }; + + let s = span.clone(); + let l = lit.clone(); + + if let Some(sdid) = ctx.get_current_semantic_definition() { + let sd = engines.sde().get(sdid); + sd.when_type_reaches_non_changeable_state( + return_type, + move |engines, handler, return_type| { + let return_type_info = engines.te().get(return_type); + let bits = match return_type_info.as_ref() { + TypeInfo::UnsignedInteger(bits @ IntegerBits::Eight) => bits, + TypeInfo::UnsignedInteger(bits @ IntegerBits::Sixteen) => bits, + TypeInfo::UnsignedInteger(bits @ IntegerBits::ThirtyTwo) => bits, + TypeInfo::UnsignedInteger(bits @ IntegerBits::SixtyFour) => bits, + _ => return, + }; + + let literal_value = match &l { + Literal::U8(v) => *v as u64, + Literal::U16(v) => *v as u64, + Literal::U32(v) => *v as u64, + Literal::U64(v) => *v, + Literal::Numeric(v) => *v, + x => { + handler.emit_err(CompileError::InternalOwned( + format!("Invalid final type `{x:?}`"), + Span::dummy(), + )); + return; + } + }; + + if bits.would_overflow(literal_value) { + handler.emit_err(CompileError::TypeError(TypeError::LiteralOverflow { + expected: format!("{:?}", engines.help_out(return_type_info)), + span: s.clone(), + })); + } + }, + ); + } + ty::TyExpression { expression: ty::TyExpressionVariant::Literal(lit), return_type, @@ -3031,46 +3094,38 @@ impl ty::TyExpression { Literal::Numeric(num) => match &*type_engine.get(new_type) { TypeInfo::UnsignedInteger(n) => match n { IntegerBits::Eight => ( - num.to_string().parse().map(Literal::U8).map_err(|e| { - Literal::handle_parse_int_error( - engines, - e, - TypeInfo::UnsignedInteger(IntegerBits::Eight), - span.clone(), - ) + num.to_string().parse().map(Literal::U8).map_err(|_| { + CompileError::TypeError(TypeError::LiteralOverflow { + expected: "u8".to_string(), + span: span.clone(), + }) }), new_type, ), IntegerBits::Sixteen => ( - num.to_string().parse().map(Literal::U16).map_err(|e| { - Literal::handle_parse_int_error( - engines, - e, - TypeInfo::UnsignedInteger(IntegerBits::Sixteen), - span.clone(), - ) + num.to_string().parse().map(Literal::U16).map_err(|_| { + CompileError::TypeError(TypeError::LiteralOverflow { + expected: "u16".to_string(), + span: span.clone(), + }) }), new_type, ), IntegerBits::ThirtyTwo => ( - num.to_string().parse().map(Literal::U32).map_err(|e| { - Literal::handle_parse_int_error( - engines, - e, - TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo), - span.clone(), - ) + num.to_string().parse().map(Literal::U32).map_err(|_| { + CompileError::TypeError(TypeError::LiteralOverflow { + expected: "u32".to_string(), + span: span.clone(), + }) }), new_type, ), IntegerBits::SixtyFour => ( - num.to_string().parse().map(Literal::U64).map_err(|e| { - Literal::handle_parse_int_error( - engines, - e, - TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), - span.clone(), - ) + num.to_string().parse().map(Literal::U64).map_err(|_| { + CompileError::TypeError(TypeError::LiteralOverflow { + expected: "u64".to_string(), + span: span.clone(), + }) }), new_type, ), @@ -3078,8 +3133,11 @@ impl ty::TyExpression { IntegerBits::V256 => (Ok(Literal::U256(U256::from(num))), new_type), }, TypeInfo::Numeric => ( - num.to_string().parse().map(Literal::Numeric).map_err(|e| { - Literal::handle_parse_int_error(engines, e, TypeInfo::Numeric, span.clone()) + num.to_string().parse().map(Literal::Numeric).map_err(|_| { + CompileError::TypeError(TypeError::LiteralOverflow { + expected: "numeric".to_string(), + span: span.clone(), + }) }), type_engine.new_numeric(), ), diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index b15c620e47c..7265a3bc426 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -1034,6 +1034,14 @@ pub(crate) fn monomorphize_method( .update_constant_expression(engines, implementing_type); } + // solve semantic definition + if let Some(sdid) = func_decl.sdid.as_ref() { + let sd = ctx.engines().sde().get(*sdid); + let mut solver = sd.solver(ctx.engines, handler); + solver.push_tid_map(func_decl.tid_map.clone()); + let _ = solver.solve(); + } + let decl_ref = decl_engine .insert( func_decl, diff --git a/sway-core/src/semantic_analysis/semantic_definition.rs b/sway-core/src/semantic_analysis/semantic_definition.rs new file mode 100644 index 00000000000..19777e88b38 --- /dev/null +++ b/sway-core/src/semantic_analysis/semantic_definition.rs @@ -0,0 +1,488 @@ +use crate::{concurrent_slab::ConcurrentSlab, Engines, IncludeSelf, TypeId, TypeInfo}; +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; +use sway_error::handler::Handler; +use sway_types::integer_bits::IntegerBits; + +pub enum TypeEvent { + // When a type reachs its non changeable state. It is 100% concrete as it cannot change anymore + OnNonChangeableState, +} + +#[derive(Default, Debug)] +pub struct SemanticDefinitionEngine { + defs: ConcurrentSlab, +} + +impl SemanticDefinitionEngine { + pub fn new_semantic_definition(&self) -> SemanticDefinitionId { + let id = self.defs.insert(SemanticDefinition { + inner: RefCell::new(SemanticDefinitionInner { + unifications: vec![], + events: BTreeMap::new(), + registry: CallbackRegistry { callbacks: vec![] }, + }), + }); + SemanticDefinitionId(id) + } + + pub fn get(&self, id: SemanticDefinitionId) -> Arc { + self.defs.get(id.0) + } +} + +pub struct SemanticDefinitionSolver<'a> { + engines: &'a Engines, + handler: &'a Handler, + def: &'a SemanticDefinition, + replacements: HashMap, + tid_map: HashMap, + log: bool, +} + +enum UnificationChange { + LeftChanged, + RightChanged, +} + +fn unify(engines: &Engines, left_tid: TypeId, right_tid: TypeId) -> Option { + let left = engines.te().get(left_tid); + let right = engines.te().get(right_tid); + match (left.as_ref(), right.as_ref()) { + // (TypeInfo::Unknown, TypeInfo::Unknown) => None, + ( + TypeInfo::UnsignedInteger(IntegerBits::Eight), + TypeInfo::UnsignedInteger(IntegerBits::Eight), + ) => None, + (TypeInfo::UnsignedInteger(IntegerBits::Eight), TypeInfo::Numeric) => { + engines + .te() + .replace(engines, right_tid, TypeInfo::clone(&left)); + Some(UnificationChange::RightChanged) + } + (TypeInfo::Unknown, right) => { + engines.te().replace(engines, left_tid, right.clone()); + Some(UnificationChange::LeftChanged) + } + _ => todo!("{:?} {:?}", engines.help_out(left), engines.help_out(right)), + } +} + +/// Is changeable every type that has: +/// UnknownGeneric, TypeInfo::Placeholder, TypeInfo::TraitType, TypeInfo::Numeric and TypeInfo::Unknown +// TODO: SemanticDefinition should not suppor TypeInfo::Custom { .. } +// as all types should have already be resolved +// TODO: TypeInfo::TypeParam? +fn is_changeable(engines: &Engines, tid: TypeId) -> bool { + tid.extract_inner_types(engines, IncludeSelf::Yes) + .into_iter() + .any(|tid| { + let type_info = engines.te().get(tid); + matches!( + type_info.as_ref(), + TypeInfo::UnknownGeneric { .. } + | TypeInfo::Placeholder(..) + | TypeInfo::TraitType { .. } + | TypeInfo::Numeric + | TypeInfo::Unknown + ) + }) +} + +impl<'a> SemanticDefinitionSolver<'a> { + pub fn push_replacement(&mut self, tid: TypeId, new_tid: TypeId) -> &mut Self { + self.replacements.insert(tid, new_tid); + self + } + + pub fn solve(self) -> SolveResult { + let SemanticDefinitionSolver { + engines, + handler, + def, + mut replacements, + tid_map, + log, + } = self; + + let mut non_changeable_anymore_worklist = vec![]; + + // Adjust the semantic definition usind tid_map + // We cannot change the original SemanticDefinition, as it is shared, + // but is ok to access the callbacks as mut, as they are FnMut. + let mut def_inner = def.inner.borrow_mut(); + let mut events = def_inner.events.clone(); + let mut unifications = def_inner.unifications.clone(); + let registry = &mut def_inner.registry; + + for (k, v) in tid_map { + if let Some(actions) = events.remove(&k) { + events.insert(v, actions); + } + + unifications.retain_mut(|(l, r)| { + if *l == k { + *l = v; + } + + if *r == k { + *r = v; + } + + true + }); + } + + // will call OnNonChangeableState on stashed types + let call_events = |non_changeable_anymore_stash: &mut Vec, + events: &BTreeMap>, + registry: &mut CallbackRegistry| { + for not_changeable_tid in non_changeable_anymore_stash.drain(..) { + if let Some(actions) = events.get(¬_changeable_tid).cloned() { + for action in actions.iter() { + match action { + InnerTypeEvent::OnNonChangeableState { callback_id } => { + if log { + eprintln!( + " calling OnNonChangeableState callback for {:?}({:?})", + not_changeable_tid, + engines.help_out(not_changeable_tid) + ); + } + let cb = &mut registry.callbacks[*callback_id]; + match cb { + TypedCallbacks::TypeNonChangeableState { f } => { + (f)(engines, handler, not_changeable_tid); + } + } + } + } + } + } + } + }; + + for (tid, _) in events.iter() { + let is_changeable = is_changeable(engines, *tid); + if !is_changeable { + non_changeable_anymore_worklist.push(*tid); + } + } + + // Call because tid_map can concretize types + // others types can be concrete already also + call_events(&mut non_changeable_anymore_worklist, &events, registry); + + // Start solving + let mut steps = 10; + let mut worklist = replacements.iter().map(|x| *x.0).collect::>(); + while let Some(tid) = worklist.pop() { + call_events(&mut non_changeable_anymore_worklist, &events, registry); + + // try to solve for tid + steps -= 1; + if steps <= 0 { + return SolveResult::Incomplete; + } + + if log { + eprintln!( + "current: {tid:?}({:?}); worklist: {worklist:?}", + engines.help_out(tid) + ); + } + let tid_is_changeable = is_changeable(engines, tid); + + if log { + eprintln!(" is_changeable: {:?}", tid_is_changeable); + } + + if let Some(replace_tid) = replacements.remove(&tid) { + if log { + eprintln!( + " replacement: \n {tid:?}({:?}) <- {replace_tid:?}({:?})", + engines.help_out(tid), + engines.help_out(replace_tid) + ); + } + + let replace_type_info = engines.te().get(replace_tid); + let replace_tid_is_changeable = is_changeable(engines, replace_tid); + engines + .te() + .replace(engines, tid, replace_type_info.as_ref().clone()); + + if tid_is_changeable && !replace_tid_is_changeable { + if log { + eprintln!(" {:?} is not changeable anymore", tid); + } + non_changeable_anymore_worklist.push(tid); + } + } + + for (left, right) in unifications.iter().filter(|(a, b)| *a == tid || *b == tid) { + if log { + eprintln!( + " unification:\n {left:?}({:?}) with {right:?}({:?})", + engines.help_out(left), + engines.help_out(right) + ); + } + + let left_is_changeable_before = is_changeable(engines, *left); + let right_is_changeable_before = is_changeable(engines, *right); + + match unify(engines, *left, *right) { + Some(UnificationChange::LeftChanged) => { + if log { + eprintln!( + " left changed to {:?}({:?})", + left, + engines.help_out(left) + ); + } + worklist.push(*left); + } + Some(UnificationChange::RightChanged) => { + if log { + eprintln!( + " right changed to {:?}({:?})", + right, + engines.help_out(right) + ); + } + worklist.push(*right); + } + None => { + if log { + eprintln!(" no work needed"); + } + } + } + + let left_is_changeable_after = is_changeable(engines, *left); + let right_is_changeable_after = is_changeable(engines, *right); + + if left_is_changeable_before && !left_is_changeable_after { + non_changeable_anymore_worklist.push(*left); + if log { + eprintln!(" {:?} is not changeable anymore", left); + } + } + + if right_is_changeable_before && !right_is_changeable_after { + non_changeable_anymore_worklist.push(*right); + if log { + eprintln!(" {:?} is not changeable anymore", right); + } + } + } + } + + assert!(non_changeable_anymore_worklist.is_empty()); + assert!(worklist.is_empty()); + + // check solving state + if log { + eprintln!("checking solving state"); + } + let missing_replacements = !replacements.is_empty(); + let mut still_changeable_types = BTreeSet::new(); + for (l, r) in unifications.iter() { + if is_changeable(engines, *l) { + still_changeable_types.insert(l); + } + + if is_changeable(engines, *r) { + still_changeable_types.insert(r); + } + } + for (tid, _) in events.iter() { + if is_changeable(engines, *tid) { + still_changeable_types.insert(tid); + } + } + + if log { + for t in still_changeable_types.iter() { + eprintln!(" {:?}({:?}) still changeable", t, engines.help_out(t)); + } + } + + let r = if !missing_replacements && still_changeable_types.is_empty() { + SolveResult::Solved + } else { + SolveResult::Incomplete + }; + + if log { + eprintln!(" {r:?}"); + } + + r + } + + /// Modify the `SemanticDefinition` in place (do not change anything inside the SemanticDefinitionEngine) to + /// accomodate `get_method_safe_for_unify` and others mechanisms that change TypeId inside decls + pub fn push_tid_map(&mut self, tid_map: HashMap) { + self.tid_map = tid_map; + } +} + +#[derive(Debug)] +pub enum SolveResult { + Solved, + Incomplete, +} + +#[derive(Clone, Debug)] +enum InnerTypeEvent { + OnNonChangeableState { callback_id: usize }, +} + +enum TypedCallbacks { + TypeNonChangeableState { + #[allow(clippy::type_complexity)] + f: Box, + }, +} + +struct CallbackRegistry { + callbacks: Vec, +} + +pub struct SemanticDefinitionInner { + unifications: Vec<(TypeId, TypeId)>, + events: BTreeMap>, + registry: CallbackRegistry, +} + +impl std::fmt::Debug for SemanticDefinition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = self.inner.borrow(); + + f.debug_struct("SemanticDefinition") + .field("unifications", &inner.unifications) + .field("events", &inner.events) + .finish() + } +} + +pub struct SemanticDefinition { + inner: RefCell, +} + +impl SemanticDefinition { + pub fn solver<'a>( + &'a self, + engines: &'a Engines, + handler: &'a Handler, + ) -> SemanticDefinitionSolver<'a> { + SemanticDefinitionSolver { + engines, + handler, + def: self, + replacements: HashMap::default(), + tid_map: HashMap::new(), + log: false, + } + } + + pub fn when_type_reaches_non_changeable_state( + &self, + tid: TypeId, + callback: impl 'static + FnMut(&Engines, &Handler, TypeId), + ) { + let mut inner = self.inner.borrow_mut(); + + let callback_id = { + inner + .registry + .callbacks + .push(TypedCallbacks::TypeNonChangeableState { + f: Box::new(callback), + }); + inner.registry.callbacks.len() - 1 + }; + + let v = inner.events.entry(tid).or_default(); + v.push(InnerTypeEvent::OnNonChangeableState { callback_id }); + } + + pub fn push_type_unify(&self, a: TypeId, b: TypeId) { + let mut inner = self.inner.borrow_mut(); + inner.unifications.push((a, b)); + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct SemanticDefinitionId(usize); + +#[test] +fn semantic_definition_solve() { + use sway_error::error::CompileError; + use sway_types::Span; + + let engines = Engines::default(); + let handler = Handler::default(); + + // this will be done on TyFunctionDecl::type_check + let sdid = engines.sde().new_semantic_definition(); + + // let a = 0x100; + // Vec::::new().push(a); + + // this will be on type_check_literal for the RHS + let sd = engines.sde().get(sdid); + let value = 0x100_u64; + let tid0 = engines.te().insert(&engines, TypeInfo::Numeric, None); + sd.when_type_reaches_non_changeable_state(tid0, move |engines, handler, tid| { + let final_type_info = engines.te().get(tid); + let max_value = match final_type_info.as_ref() { + TypeInfo::UnsignedInteger(IntegerBits::Eight) => u8::MAX as u64, + TypeInfo::UnsignedInteger(IntegerBits::Sixteen) => u16::MAX as u64, + TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo) => u32::MAX as u64, + TypeInfo::UnsignedInteger(IntegerBits::SixtyFour) => u64::MAX, + x => { + handler.emit_err(CompileError::InternalOwned( + format!("Invalid final type `{x:?}`"), + Span::dummy(), + )); + return; + } + }; + + if value > max_value { + handler.emit_err(CompileError::Internal("Do not fit", Span::dummy())); + } + }); + + // this will be on type_check_variable_declaration for the LHS + let tid1 = engines.te().insert(&engines, TypeInfo::Unknown, None); + sd.push_type_unify(tid1, tid0); + + // Simulate get_method_safe_for_unify + engines.te().start_capturing_duplicates(); + engines.te().duplicate(&engines, tid0); + let tid1_new = engines.te().duplicate(&engines, tid1); + let tid_map = engines.te().end_capturing_duplicates().unwrap(); + + // This will be done when monomorphizing + let tid2 = engines.te().insert( + &engines, + TypeInfo::UnsignedInteger(IntegerBits::Eight), + None, + ); + let mut solver = sd.solver(&engines, &handler); + solver.push_tid_map(tid_map); + solver.push_replacement(tid1_new, tid2); + let r = solver.solve(); + assert!(matches!(r, SolveResult::Solved)); + + dbg!(handler.consume()); +} diff --git a/sway-core/src/semantic_analysis/type_check_context.rs b/sway-core/src/semantic_analysis/type_check_context.rs index d835cd6b4cb..c8cc709e930 100644 --- a/sway-core/src/semantic_analysis/type_check_context.rs +++ b/sway-core/src/semantic_analysis/type_check_context.rs @@ -16,6 +16,7 @@ use crate::{ }, semantic_analysis::{ ast_node::{AbiMode, ConstShadowingMode}, + semantic_definition::SemanticDefinitionId, Namespace, }, type_system::{GenericArgument, SubstTypes, TypeId, TypeInfo}, @@ -108,6 +109,9 @@ pub struct TypeCheckContext<'a> { // In some nested places of the first pass we want to disable the first pass optimizations // To disable those optimizations we can set this to false. code_block_first_pass: bool, + + // Associated SemanticDefinition + sdid: Option, } impl<'a> TypeCheckContext<'a> { @@ -137,6 +141,7 @@ impl<'a> TypeCheckContext<'a> { experimental, collecting_unifications: false, code_block_first_pass: false, + sdid: None, } } @@ -181,6 +186,7 @@ impl<'a> TypeCheckContext<'a> { experimental, collecting_unifications: false, code_block_first_pass: false, + sdid: None, } } @@ -212,6 +218,7 @@ impl<'a> TypeCheckContext<'a> { experimental: self.experimental, collecting_unifications: self.collecting_unifications, code_block_first_pass: self.code_block_first_pass, + sdid: self.sdid, } } @@ -262,6 +269,7 @@ impl<'a> TypeCheckContext<'a> { experimental: ctx.experimental, collecting_unifications: ctx.collecting_unifications, code_block_first_pass: ctx.code_block_first_pass, + sdid: ctx.sdid, }; with_scoped_ctx(&mut ctx) }, @@ -398,6 +406,14 @@ impl<'a> TypeCheckContext<'a> { } } + /// Map this `TypeCheckContext` instance to a new one with a associated semantic definition + pub(crate) fn with_semantic_definition(self, sdid: SemanticDefinitionId) -> Self { + Self { + sdid: Some(sdid), + ..self + } + } + /// Map this `TypeCheckContext` instance to a new one with the given generic shadowing `mode`. pub(crate) fn with_generic_shadowing_mode( self, @@ -933,4 +949,8 @@ impl<'a> TypeCheckContext<'a> { ) .is_ok() } + + pub(crate) fn get_current_semantic_definition(&self) -> Option { + self.sdid + } } diff --git a/sway-core/src/type_system/engine.rs b/sway-core/src/type_system/engine.rs index ddfc5184a8e..b3e718daf1d 100644 --- a/sway-core/src/type_system/engine.rs +++ b/sway-core/src/type_system/engine.rs @@ -15,7 +15,7 @@ use hashbrown::{hash_map::RawEntryMut, HashMap}; use parking_lot::RwLock; use std::{ hash::{BuildHasher, Hash, Hasher}, - sync::Arc, + sync::{Arc, Mutex}, time::Instant, }; use sway_error::{ @@ -141,6 +141,7 @@ pub struct TypeEngine { singleton_types: RwLock, unifications: ConcurrentSlab, last_replace: RwLock, + duplicates: Mutex>>, } pub trait IsConcrete { @@ -177,6 +178,7 @@ impl Default for TypeEngine { singleton_types: RwLock::new(singleton_types), unifications: Default::default(), last_replace: RwLock::new(Instant::now()), + duplicates: Mutex::new(vec![]), }; te.insert_shareable_built_in_types(); te @@ -185,12 +187,15 @@ impl Default for TypeEngine { impl Clone for TypeEngine { fn clone(&self) -> Self { + let duplicates = self.duplicates.lock().unwrap(); + let duplicates = duplicates.clone(); TypeEngine { slab: self.slab.clone(), shareable_types: RwLock::new(self.shareable_types.read().clone()), singleton_types: RwLock::new(self.singleton_types.read().clone()), unifications: self.unifications.clone(), last_replace: RwLock::new(*self.last_replace.read()), + duplicates: Mutex::new(duplicates), } } } @@ -219,7 +224,7 @@ macro_rules! type_engine_shareable_built_in_types { // The actual recursion step that generates the `id_of_` functions. (@step $idx:expr, ($ty_name:ident, $ti:expr, $ti_pat:pat), $(($tail_ty_name:ident, $tail_ti:expr, $tail_ti_pat:pat),)*) => { paste::paste! { - pub const fn [](&self) -> TypeId { + pub fn [](&self) -> TypeId { TypeId::new($idx) } } @@ -230,7 +235,7 @@ macro_rules! type_engine_shareable_built_in_types { // The entry point. Invoking the macro matches this arm. ($(($ty_name:ident, $ti:expr, $ti_pat:pat),)*) => { // The `unit` type is a special case. It will be inserted in the slab as the first type. - pub(crate) const fn id_of_unit(&self) -> TypeId { + pub(crate) fn id_of_unit(&self) -> TypeId { TypeId::new(0) } @@ -239,7 +244,7 @@ macro_rules! type_engine_shareable_built_in_types { // providing the proof of the error being emitted, although that proof is actually // not needed to obtain the type id, nor is used within this method at all. #[allow(unused_variables)] - pub(crate) const fn id_of_error_recovery(&self, error_emitted: ErrorEmitted) -> TypeId { + pub(crate) fn id_of_error_recovery(&self, error_emitted: ErrorEmitted) -> TypeId { TypeId::new(1) } @@ -881,7 +886,14 @@ impl TypeEngine { pub fn duplicate(&self, engines: &Engines, id: TypeId) -> TypeId { let type_source_info = self.slab.get(id.index()); let duplicate = TypeInfo::clone(&type_source_info.type_info); - self.insert(engines, duplicate, type_source_info.source_id.as_ref()) + let new_tid = self.insert(engines, duplicate, type_source_info.source_id.as_ref()); + + let mut duplicates = self.duplicates.lock().unwrap(); + if let Some(duplicates) = duplicates.last_mut() { + let _ = duplicates.insert(id, new_tid); + } + + new_tid } /// This method performs two actions, depending on the `replace_at_type_id`. @@ -2385,4 +2397,14 @@ impl TypeEngine { write!(builder, "TypeEngine {{\n{list}\n}}").unwrap(); builder } + + pub(crate) fn start_capturing_duplicates(&self) { + let mut duplicates = self.duplicates.lock().unwrap(); + duplicates.push(HashMap::new()); + } + + pub(crate) fn end_capturing_duplicates(&self) -> Option> { + let mut duplicates = self.duplicates.lock().unwrap(); + duplicates.pop() + } } diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index 32e2683b799..09aba7d56e0 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -848,6 +848,10 @@ impl TypeInfo { matches!(self, TypeInfo::Boolean) } + pub(crate) fn is_numeric(&self) -> bool { + matches!(self, TypeInfo::Numeric) + } + /// maps a type to a name that is used when constructing function selectors pub(crate) fn to_selector_name( &self, diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/array_wrong_elements_types/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/array_wrong_elements_types/stdout.snap index d24cab1f159..1d45bc5abf4 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/array_wrong_elements_types/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/array_wrong_elements_types/stdout.snap @@ -43,7 +43,7 @@ error 49 | 50 | // Literal too big 51 | let mut a = [8u8, 8, 18446744073709551615]; - | ^^^^^^^^^^^^^^^^^^^^ Literal value is too large for type u8. + | ^^^^^^^^^^^^^^^^^^^^ Literal would overflow because its value does not fit into "u8" 52 | } | ____ diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/literal_too_large_for_type/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/literal_too_large_for_type/test.toml index a0694b0703d..e6d2499995f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/literal_too_large_for_type/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_fail/literal_too_large_for_type/test.toml @@ -1,4 +1,4 @@ category = "fail" # check: let _x:u8 = 256; -# nextln: $()Literal value is too large for type u8. +# nextln: $()Literal would overflow because its value does not fit into "u8" diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw index 6fae9c87c7e..08bae140c6f 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw @@ -12,8 +12,6 @@ fn main() { // 0x100000000 does not fit into a u32 let _a = 0x100000000; Vec::::new().push(_a); - - } fn insufficient_type_check(arg: u64) -> [u32;2] { diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/stdout.snap b/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/stdout.snap index 2ca3d542a21..c7f1e247c93 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/stdout.snap +++ b/test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/stdout.snap @@ -39,22 +39,22 @@ error 13 | let _a = 0x100000000; | ^^^^^^^^^^^ Literal would overflow because its value does not fit into "u32" 14 | Vec::::new().push(_a); -15 | +15 | } | ____ error - --> test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw:20:22 + --> test/src/e2e_vm_tests/test_programs/should_fail/type_check_analyze_errors/src/main.sw:18:22 | -18 | -19 | fn insufficient_type_check(arg: u64) -> [u32;2] { -20 | let res = [1u32, arg]; +16 | +17 | fn insufficient_type_check(arg: u64) -> [u32;2] { +18 | let res = [1u32, arg]; | ^^^ Mismatched types. expected: u32 found: u64. -21 | res -22 | } +19 | res +20 | } | ____