From a2966e3bf333255730f62634b1077b35748ec92f Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 29 Sep 2024 17:36:14 +0100 Subject: [PATCH 01/24] Fixes for disjoint stuff - Change over intersection to use disjoint function - Also add valgrind to performance and size (WIP improvements to be made in the future) --- .github/workflows/performance-and-size.yml | 28 ++++++++++++++++++ README.md | 2 +- checker/specification/staging.md | 14 +++++++++ checker/src/context/mod.rs | 4 +-- checker/src/features/functions.rs | 2 +- checker/src/features/narrowing.rs | 3 ++ checker/src/synthesis/functions.rs | 2 +- checker/src/synthesis/hoisting.rs | 6 ++-- checker/src/synthesis/interfaces.rs | 2 +- checker/src/synthesis/type_annotations.rs | 18 ++++++++---- checker/src/types/disjoint.rs | 12 ++++---- checker/src/types/generics/substitution.rs | 2 +- checker/src/types/store.rs | 34 +++++++--------------- 13 files changed, 83 insertions(+), 46 deletions(-) diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index e58e86c5..97b11ee2 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -28,12 +28,23 @@ jobs: - uses: brndnmtthws/rust-action-cargo-binstall@v1 with: packages: hyperfine + + - name: Install valgrind + run: sudo apt-get install valgrind - name: Build Ezno run: cargo build --release env: CARGO_PROFILE_RELEASE_DEBUG: true + - name: Get base ezno + if: github.ref_name != 'main' + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: latest-checker + path: previous-ezno + - name: Run checker performance shell: bash run: | @@ -83,6 +94,8 @@ jobs: " >> $GITHUB_STEP_SUMMARY + # Get just statistics: `| rg "Diagnostics:" -A 100` + - name: Run checker performance w/staging shell: bash if: github.ref_name != 'main' @@ -112,6 +125,13 @@ jobs: hyperfine -i './target/release/ezno check large.tsx' echo "::endgroup::" + - name: Valgrind + shell: bash + run: | + valgrind --log-file=out-mem2.txt ./target/release/ezno check demo.tsx | true + + echo "TODO callgrind" + - name: Run parsing & stringing (minfied) benchmarks shell: bash run: | @@ -129,3 +149,11 @@ jobs: hyperfine "./target/debug/examples/parse input.js" "./target/release/examples/parse input.js" done + + - name: Upload checker + if: github.ref == 'main' + uses: actions/upload-artifact@v4 + with: + name: latest-checker + path: target/release/ezno + retention-days: 90 diff --git a/README.md b/README.md index 1a174c96..364aca7b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ What Ezno is: What Ezno is not - **eNZo, the Z is in front of the N** (pronounce as 'Fresno' without the 'fr') 😀 -- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC +- Be on parity with TSC or 1:1, it has some different behaviors **but** should work in existing projects using TSC. [You can see a full comparison of emitted errors and warnings compared with TSC here](https://kaleidawave.github.io/ezno/comparison) - Faster as a means to serve large codebases. Cut out bloat and complex code first! - Smarter as a means to allow more *dynamic patterns*. Keep things simple! - A binary executable compiler. It takes in JavaScript (or a TypeScript or Ezno superset) and does similar processes to traditional compilers, but at the end emits JavaScript. However, in the future, it *could* generate a lower level format using its event (side-effect) representation. diff --git a/checker/specification/staging.md b/checker/specification/staging.md index 4739e695..b8c459ae 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -1,3 +1,17 @@ Currently implementing: > This file is for work-in-progress and can help separating features that are being implemented to regressions + +### Fixes + +#### Disjoint not + +```ts +function a(b: Not) { + if ("hi" === b) { + console + } +} +``` + +- ??? diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index e8b11f28..2a748d54 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -919,9 +919,7 @@ impl Context { for (on, constraint) in object_constraints { match self.info.object_constraints.entry(on) { Entry::Occupied(mut existing) => { - let new = types - .new_and_type(*existing.get(), constraint) - .expect("creating impossible restriction"); + let new = types.new_and_type(*existing.get(), constraint); existing.insert(new); } Entry::Vacant(v) => { diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index 849e5bf8..b3570c83 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -1112,7 +1112,7 @@ pub fn new_name_expected_object( &mut environment.info, ); - types.new_and_type(expected, name_object.build_object()).unwrap() + types.new_and_type(expected, name_object.build_object()) // .unwrap() } /// Reverse of the above diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index 1720ff17..34ec0fb9 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -460,6 +460,9 @@ pub(crate) fn build_union_from_filter( build_union_from_filter(rhs, filter, found, information, types); } else if let Some(constraint) = crate::types::get_constraint(on, types) { build_union_from_filter(constraint, filter, found, information, types); + } else if let TypeId::BOOLEAN_TYPE = on { + build_union_from_filter(TypeId::TRUE, filter, found, information, types); + build_union_from_filter(TypeId::FALSE, filter, found, information, types); } else { let not_already_added = !found.contains(&on); if not_already_added && filter.type_matches_filter(on, information, types, false) { diff --git a/checker/src/synthesis/functions.rs b/checker/src/synthesis/functions.rs index b0e47b11..c5c450b6 100644 --- a/checker/src/synthesis/functions.rs +++ b/checker/src/synthesis/functions.rs @@ -838,7 +838,7 @@ pub(super) fn build_overloaded_function( let func = types.new_hoisted_function_type(as_function); // IMPORTANT THAT RESULT IS ON THE RIGHT OF AND TYPE - result = types.new_and_type(func, result).unwrap(); + result = types.new_and_type(func, result); } result diff --git a/checker/src/synthesis/hoisting.rs b/checker/src/synthesis/hoisting.rs index 4f9a2ea3..0781f2bf 100644 --- a/checker/src/synthesis/hoisting.rs +++ b/checker/src/synthesis/hoisting.rs @@ -501,8 +501,7 @@ pub(crate) fn hoist_statements( &mut sub_environment, checking_data, ); - extends = - checking_data.types.new_and_type(extends, new).unwrap(); + extends = checking_data.types.new_and_type(extends, new); } checking_data.types.set_extends_on_interface(ty, extends); } @@ -539,8 +538,7 @@ pub(crate) fn hoist_statements( environment, checking_data, ); - extends = - checking_data.types.new_and_type(extends, new).unwrap(); + extends = checking_data.types.new_and_type(extends, new); } checking_data.types.set_extends_on_interface(ty, extends); } diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index 12863cd4..6c7d99ec 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -448,7 +448,7 @@ pub(super) fn synthesise_signatures( let mut acc = iterator.next().expect("Empty intersection"); for right in iterator { - if let Ok(new_ty) = checking_data.types.new_and_type(acc, right) { - acc = new_ty; - } else { - checking_data.diagnostics_container.add_error( + let is_disjoint = crate::types::disjoint::types_are_disjoint( + acc, + right, + &mut Vec::new(), + environment, + &checking_data.types, + ); + if is_disjoint { + checking_data.diagnostics_container.add_warning( TypeCheckWarning::TypesDoNotIntersect { left: TypeStringRepresentation::from_type_id( acc, @@ -374,7 +379,10 @@ pub fn synthesise_type_annotation( position: position.with_source(environment.get_source()), }, ); - return TypeId::ERROR_TYPE; + return TypeId::NEVER_TYPE; + // return TypeId::ERROR_TYPE; + } else { + acc = checking_data.types.new_and_type(acc, right); } } acc diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index a49c5f59..40fdedc8 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -74,7 +74,7 @@ pub fn types_are_disjoint( }) = lhs_ty { use super::subtyping; - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + let lhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); let mut state = subtyping::State { // TODO already_checked: already_checked.clone(), @@ -84,16 +84,16 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, inner)); + crate::utilities::notify!("{:?}", (lhs, lhs_inner)); - subtyping::type_is_subtype(rhs, inner, &mut state, information, types).is_subtype() + subtyping::type_is_subtype(lhs_inner, rhs, &mut state, information, types).is_subtype() } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, arguments, }) = rhs_ty { use super::subtyping; - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + let rhs_inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); let mut state = subtyping::State { // TODO already_checked: already_checked.clone(), @@ -103,9 +103,9 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, inner)); + crate::utilities::notify!("{:?}", (lhs, rhs_inner)); - subtyping::type_is_subtype(lhs, inner, &mut state, information, types).is_subtype() + subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype() } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, arguments: _, diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index dacfe81a..4e2434e1 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -195,7 +195,7 @@ pub(crate) fn substitute( let rhs = *rhs; let lhs = substitute(*lhs, arguments, environment, types); let rhs = substitute(rhs, arguments, environment, types); - types.new_and_type(lhs, rhs).unwrap_or(TypeId::NEVER_TYPE) + types.new_and_type(lhs, rhs) // .unwrap_or(TypeId::NEVER_TYPE) } Type::Or(lhs, rhs) => { let rhs = *rhs; diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index f6bcb3bb..6df13325 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -302,40 +302,28 @@ impl TypeStore { iter.into_iter().reduce(|acc, n| self.new_or_type(acc, n)).unwrap_or(TypeId::NEVER_TYPE) } - pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> Result { + // intersection. Does not calculate disjoint + pub fn new_and_type(&mut self, lhs: TypeId, rhs: TypeId) -> TypeId { + // string & string = string if lhs == rhs { - return Ok(lhs); - } - - let left_ty = self.get_type_by_id(lhs); - let right_ty = self.get_type_by_id(rhs); - - // TODO more cases - if let (Type::Constant(l), Type::Constant(r)) = (left_ty, right_ty) { - if l != r { - return Err(()); - } - } else if left_ty.is_nominal() && right_ty.is_nominal() { - return Err(()); + return lhs; } // (left and right) distributivity. - let result = if let Type::Or(or_lhs, or_rhs) = left_ty { + if let Type::Or(or_lhs, or_rhs) = self.get_type_by_id(lhs) { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); - let new_lhs = self.new_and_type(or_lhs, rhs)?; - let new_rhs = self.new_and_type(or_rhs, rhs)?; + let new_lhs = self.new_and_type(or_lhs, rhs); + let new_rhs = self.new_and_type(or_rhs, rhs); self.new_or_type(new_lhs, new_rhs) - } else if let Type::Or(or_lhs, or_rhs) = right_ty { + } else if let Type::Or(or_lhs, or_rhs) = self.get_type_by_id(rhs) { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); - let new_lhs = self.new_and_type(lhs, or_lhs)?; - let new_rhs = self.new_and_type(lhs, or_rhs)?; + let new_lhs = self.new_and_type(lhs, or_lhs); + let new_rhs = self.new_and_type(lhs, or_rhs); self.new_or_type(new_lhs, new_rhs) } else { let ty = Type::And(lhs, rhs); self.register_type(ty) - }; - - Ok(result) + } } /// TODO temp From b4313fdfef4a18be53f7c66bf3655dca15ee0e3a Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 10:48:01 +0100 Subject: [PATCH 02/24] More fixes to Not type and narrowing --- checker/specification/staging.md | 29 +++++++++++++-- checker/src/features/narrowing.rs | 46 ++++++++++++++++------- checker/src/features/operations.rs | 18 ++++++++- checker/src/synthesis/block.rs | 5 ++- checker/src/types/calling.rs | 3 +- checker/src/types/disjoint.rs | 55 ++++++++++++++++++++++++---- checker/src/types/subtyping.rs | 44 +++++++++------------- checker/src/utilities/float_range.rs | 11 ++++++ 8 files changed, 157 insertions(+), 54 deletions(-) diff --git a/checker/specification/staging.md b/checker/specification/staging.md index b8c459ae..023fccd9 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -7,11 +7,32 @@ Currently implementing: #### Disjoint not ```ts -function a(b: Not) { - if ("hi" === b) { - console +function func1(param: Not) { + return "hi" === param; +} + +function func2(param: Not) { + return 4 === param; +} + +function func3(p1: Not, p2: Not) { + return p1 === p2; +} +``` + +- This equality is always false as "hi" and Not\ have no overlap + +#### Disjoint multiple of with range + +> TODO need to redo range to use interesection of less than and greater than + +```ts +function func(a: number, b: number) { + if (a % 15 === 0 && 31 < b && b < 37) { + print_type(a, b) + print_type(a === b) } } ``` -- ??? +- ? diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index 34ec0fb9..b398693c 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -41,22 +41,34 @@ pub fn narrow_based_on_expression( { let from = get_origin(*on, types); if let Type::Constant(Constant::String(c)) = types.get_type_by_id(*rhs) { - let narrowed_to = crate::features::string_name_to_type(c); - if let Some(narrowed_to) = narrowed_to { + let type_from_name = crate::features::string_name_to_type(c); + if let Some(type_from_name) = type_from_name { if negate { - let mut result = Vec::new(); - build_union_from_filter( - from, - Filter::Not(&Filter::IsType(narrowed_to)), - &mut result, - information, - types, - ); - let narrowed_to = types.new_or_type_from_iterator(result); + crate::utilities::notify!("{:?}", from); + // TODO temp fix + let narrowed_to = if let Some(TypeId::ANY_TYPE) = + crate::types::get_constraint(from, types) + { + crate::types::intrinsics::new_intrinsic( + &crate::types::intrinsics::Intrinsic::Not, + type_from_name, + types, + ) + } else { + let mut result = Vec::new(); + build_union_from_filter( + from, + Filter::Not(&Filter::IsType(type_from_name)), + &mut result, + information, + types, + ); + types.new_or_type_from_iterator(result) + }; let narrowed = types.new_narrowed(from, narrowed_to); into.insert(from, narrowed); } else { - let narrowed = types.new_narrowed(from, narrowed_to); + let narrowed = types.new_narrowed(from, type_from_name); into.insert(from, narrowed); } } else { @@ -364,10 +376,11 @@ pub(crate) enum Filter<'a> { static NULL_OR_UNDEFINED: Filter<'static> = Filter::NullOrUndefined; pub(crate) static NOT_NULL_OR_UNDEFINED: Filter<'static> = Filter::Not(&NULL_OR_UNDEFINED); -static FASLY: Filter<'static> = Filter::Falsy; +pub(crate) static FASLY: Filter<'static> = Filter::Falsy; pub(crate) static NOT_FASLY: Filter<'static> = Filter::Not(&FASLY); impl<'a> Filter<'a> { + // Returns `true` if `value` passes filter pub(crate) fn type_matches_filter( &self, value: TypeId, @@ -432,6 +445,12 @@ impl<'a> Filter<'a> { (allowed_match && is_null_or_undefined) || (!allowed_match && !is_null_or_undefined) } Filter::Falsy => { + let can_be_falsy = [TypeId::NUMBER_TYPE, TypeId::STRING_TYPE, TypeId::BOOLEAN_TYPE]; + + if can_be_falsy.contains(&value) { + return true; + } + let is_falsy = [ TypeId::NULL_TYPE, TypeId::UNDEFINED_TYPE, @@ -447,6 +466,7 @@ impl<'a> Filter<'a> { } } +/// Important that this does not handle `any` well with negated filters. It needs to generate negated types but only has non-mutable access to `TypeStore` #[allow(clippy::used_underscore_binding)] pub(crate) fn build_union_from_filter( on: TypeId, diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs index 78a88f83..1e4b1c4a 100644 --- a/checker/src/features/operations.rs +++ b/checker/src/features/operations.rs @@ -669,7 +669,23 @@ pub fn evaluate_logical_operation_with_expression< |env: &mut Environment, data: &mut CheckingData| { A::synthesise_expression(rhs, expecting, env, data) }, - Some(|_env: &mut Environment, _data: &mut CheckingData| lhs.0), + Some(|env: &mut Environment, checking_data: &mut CheckingData| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + super::narrowing::build_union_from_filter( + constraint, + super::narrowing::FASLY, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }), checking_data, )), LogicalOperator::Or => Ok(new_conditional_context( diff --git a/checker/src/synthesis/block.rs b/checker/src/synthesis/block.rs index 9f0bf7ea..ee9b8af5 100644 --- a/checker/src/synthesis/block.rs +++ b/checker/src/synthesis/block.rs @@ -42,7 +42,10 @@ pub(super) fn synthesise_block( !matches!( e, StatementOrDeclaration::Statement( - Statement::Comment(..) | Statement::MultiLineComment(..) | Statement::Empty(..) + Statement::Comment(..) + | Statement::MultiLineComment(..) + | Statement::Empty(..) + | Statement::AestheticSemiColon(..) ) | StatementOrDeclaration::Declaration(Declaration::Function(..)) ) }) { diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 63f46696..9a92236a 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -474,7 +474,8 @@ fn get_logical_callable_from_type( // } if ty == TypeId::ANY_TYPE { crate::utilities::notify!("Calling ANY"); - return Ok(NeedsCalculation::Infer { on: from.unwrap() }.into()); + // TODO temp + return Ok(NeedsCalculation::Infer { on: from.unwrap_or(TypeId::ERROR_TYPE) }.into()); } let le_ty = types.get_type_by_id(ty); diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 40fdedc8..10a14d67 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -84,7 +84,7 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, lhs_inner)); + // crate::utilities::notify!("{:?}", (lhs, lhs_inner)); subtyping::type_is_subtype(lhs_inner, rhs, &mut state, information, types).is_subtype() } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { @@ -103,7 +103,7 @@ pub fn types_are_disjoint( object_constraints: None, }; - crate::utilities::notify!("{:?}", (lhs, rhs_inner)); + crate::utilities::notify!("{:?}", (rhs, rhs_inner)); subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype() } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { @@ -116,6 +116,20 @@ pub fn types_are_disjoint( let overlap = range.overlaps(rhs_range); crate::utilities::notify!("{:?}", overlap); !overlap + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF, + arguments, + }) = rhs_ty + { + let multiple_of = types.get_type_by_id( + arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), + ); + if let Type::Constant(Constant::Number(multiple_of)) = multiple_of { + !range.contains_multiple_of(*multiple_of) + } else { + crate::utilities::notify!("Here"); + true + } } else { crate::utilities::notify!("Here"); true @@ -130,6 +144,20 @@ pub fn types_are_disjoint( let overlap = range.overlaps(lhs_range); crate::utilities::notify!("{:?}", overlap); !overlap + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF, + arguments, + }) = lhs_ty + { + let multiple_of = types.get_type_by_id( + arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), + ); + if let Type::Constant(Constant::Number(multiple_of)) = multiple_of { + !range.contains_multiple_of(*multiple_of) + } else { + crate::utilities::notify!("Here"); + true + } } else { crate::utilities::notify!("Here"); true @@ -144,23 +172,24 @@ pub fn types_are_disjoint( types.get_type_by_id( arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), ), - types.get_type_by_id(rhs), + rhs_ty, ) { let result = rhs % lhs != 0.; crate::utilities::notify!("{:?} {:?}", rhs, lhs); result } else { crate::utilities::notify!("Here"); - false + true } } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments, }) = rhs_ty { + let lhs = types.get_type_by_id(lhs); // Little bit complex here because dealing with decimal types, not integers if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( - types.get_type_by_id(lhs), + lhs, types.get_type_by_id( arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), ), @@ -169,8 +198,8 @@ pub fn types_are_disjoint( crate::utilities::notify!("{:?} {:?}", lhs, rhs); result } else { - crate::utilities::notify!("Here"); - false + crate::utilities::notify!("Here {:?}", lhs); + true } } else if let Some(lhs) = super::get_constraint(lhs, types) { // TODO not sure whether these should be here? @@ -192,6 +221,18 @@ pub fn types_are_disjoint( } } else if let Type::Constant(rhs_cst) = rhs_ty { types_are_disjoint(rhs_cst.get_backing_type(), lhs, already_checked, information, types) + } else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation( + _properties, + )) = lhs_ty + { + // TODO check properties + false + } else if let Type::Object(crate::types::ObjectNature::AnonymousTypeAnnotation( + _properties, + )) = rhs_ty + { + // TODO check properties + false } else { crate::utilities::notify!( "{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint", diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index eb274d06..ec7960da 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -267,7 +267,7 @@ pub(crate) fn type_is_subtype_with_generics( types, ); // Temp fix for narrowing constants - crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types)); + // crate::utilities::notify!("{:?}", super::helpers::is_not_of_constant(*right, types)); // SubTypeResult::IsNotSubType(_) return if let (Type::Narrowed { from, .. }, _, true) = (subtype, &result, super::helpers::is_not_of_constant(*right, types)) @@ -343,26 +343,13 @@ pub(crate) fn type_is_subtype_with_generics( // TODO others Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on @ TypeId::NOT_RESTRICTION, - arguments, + arguments: _, }) => { match *on { TypeId::NOT_RESTRICTION => { - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - // https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_comm -> https://leanprover-community.github.io/mathlib4_docs/Mathlib/Data/Set/Basic.html#Set.subset_compl_iff_disjoint_left - - let result = super::disjoint::types_are_disjoint( - base_type, - inner, - &mut state.already_checked, - information, - types, - ); - // crate::utilities::notify!("Here {:?}", (&result, inner)); - return if result { - SubTypeResult::IsSubType - } else { - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - }; + // This only happens when subtype ∪ supertype = `any`. This is only true when + // one is `any`. `Not` is already `never` and `supertype = any` is handled above + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); } _ => unreachable!(), } @@ -440,14 +427,14 @@ pub(crate) fn type_is_subtype_with_generics( // if !T::INFER_GENERICS && ty_structure_arguments.is_none() { let right_arg = get_constraint(ty, types).unwrap(); - crate::utilities::notify!( - "edge case {:?}", - ( - types.get_type_by_id(ty), - types.get_type_by_id(right_arg), - types.get_type_by_id(right_arg).is_operator() - ) - ); + // crate::utilities::notify!( + // "RHS is parameter, edge case results to {:?}", + // ( + // types.get_type_by_id(ty), + // types.get_type_by_id(right_arg), + // types.get_type_by_id(right_arg).is_operator() + // ) + // ); // This is important that LHS is not operator let left_is_operator_right_is_not = @@ -2586,7 +2573,10 @@ pub(crate) fn slice_matches_type( allow_casts, ) } else if let Some(contributions) = contributions { - assert!(rpt.is_substitutable()); + if !rpt.is_substitutable() { + eprintln!("{:?}", rpt); + } + // assert!(rpt.is_substitutable(), "{:?}", rpt); let constraint = rpt.get_constraint(); let res = slice_matches_type( (constraint, base_type_arguments), diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index 133e226e..a2f8e36d 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -141,6 +141,17 @@ impl FloatRange { } } + pub fn contains_multiple_of(self, multiple_of: BetterF64) -> bool { + let (Self::Inclusive { floor, ceiling } | Self::Exclusive { floor, ceiling }) = self; + + // (2, 4) + let floor = floor / multiple_of; + let ceiling = ceiling / multiple_of; + + // TODO >= ? + ceiling.floor() > *floor + } + // TODO more :) } From e0aa234ffd55e70247c3af64897be20e1038dd78 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 10:48:28 +0100 Subject: [PATCH 03/24] Improvements to benchmarking (inc some parser fixes) --- .github/workflows/performance-and-size.yml | 19 +- parser/examples/code_blocks_to_script.rs | 260 ++++++++++++++------- parser/src/expressions/arrow_function.rs | 18 +- 3 files changed, 197 insertions(+), 100 deletions(-) diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index 97b11ee2..73115d45 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -49,7 +49,10 @@ jobs: shell: bash run: | # Generate a file which contains everything that Ezno currently implements - cargo run -p ezno-parser --example code_blocks_to_script ./checker/specification/specification.md --comment-headers --out ./demo.tsx + cargo run -p ezno-parser \ + --example code_blocks_to_script ./checker/specification/specification.md \ + --comment-headers \ + --out ./demo.tsx echo "### Checking \`\`\`shell @@ -69,8 +72,8 @@ jobs: echo "::info::Wrote code to summary" command_output=$(./target/release/ezno check demo.tsx --timings --max-diagnostics all 2>&1 || true) + diagnostics=""; statistics=""; found_splitter=false; - while IFS= read -r line; do if [[ "$line" == "---"* ]]; then found_splitter=true; elif [[ "$found_splitter" == false ]]; then diagnostics+="$line"$'\n'; @@ -128,9 +131,17 @@ jobs: - name: Valgrind shell: bash run: | - valgrind --log-file=out-mem2.txt ./target/release/ezno check demo.tsx | true + echo "::group::Callgrind" + valgrind --tool=callgrind --callgrind-out-file=cpu-out ./target/release/ezno check demo.tsx | true + echo "CPU usage:" + head -n100 cpu-out + echo "::endgroup::" - echo "TODO callgrind" + echo "::group::Valgrind" + valgrind --log-file=memory-out ./target/release/ezno check demo.tsx | true + echo "Memory usage:" + cat memory-out + echo "::endgroup::" - name: Run parsing & stringing (minfied) benchmarks shell: bash diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs index 4f0a4fea..be696ec6 100644 --- a/parser/examples/code_blocks_to_script.rs +++ b/parser/examples/code_blocks_to_script.rs @@ -13,6 +13,7 @@ fn main() -> Result<(), Box> { let replace_satisfies_with_as = args.iter().any(|item| item == "--satisfies-with-as"); let add_headers_as_comments = args.iter().any(|item| item == "--comment-headers"); + // let declare_to_function = args.iter().any(|item| item == "--declare-to-function"); let into_files_directory_and_extension = args.windows(3).find_map(|item| { matches!(item[0].as_str(), "--into-files").then_some((item[1].clone(), item[2].clone())) @@ -35,7 +36,7 @@ fn main() -> Result<(), Box> { let content = std::fs::read_to_string(&path)?; - let filters: Vec<&str> = vec!["import", "export", "declare"]; + let filters: Vec<&str> = vec!["import", "export"]; let blocks = if path.ends_with(".md") { let mut blocks = Vec::new(); @@ -98,104 +99,187 @@ fn main() -> Result<(), Box> { } } } - return Ok(()); - } + } else { + // Else bundle into one, bound in arrow functions to prevent namespace collision + let mut final_blocks: Vec<(HashSet, String)> = Vec::new(); + for (header, mut code) in blocks { + // TODO clone + let module = Module::from_string(code.clone(), Default::default()).map_err(Box::new)?; - // Else bundle into one, bound in arrow functions to prevent namespace collision - let mut final_blocks: Vec<(HashSet, String)> = Vec::new(); - for (header, code) in blocks { - let module = Module::from_string(code.clone(), Default::default()).map_err(Box::new)?; - - let mut names = HashSet::new(); - - let mut visitors = Visitors { - expression_visitors: Default::default(), - statement_visitors: Default::default(), - variable_visitors: vec![Box::new(NameFinder)], - block_visitors: Default::default(), - }; - - module.visit::>( - &mut visitors, - &mut names, - &VisitOptions { visit_nested_blocks: false, reverse_statements: false }, - source_map::Nullable::NULL, - ); - - // TODO quick fix to also register interface and type alias names to prevent conflicts - for item in module.items { - match item { - StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { - name: StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. }, - .. - })) => { - names.insert(s.clone()); - } - StatementOrDeclaration::Declaration(Declaration::Interface(Decorated { - on: - InterfaceDeclaration { - name: - StatementPosition { - identifier: VariableIdentifier::Standard(s, _), .. - }, - .. - }, - .. - })) => { - names.insert(s.clone()); + let mut names = HashSet::new(); + + let mut visitors = Visitors { + expression_visitors: Default::default(), + statement_visitors: Default::default(), + variable_visitors: vec![Box::new(NameFinder)], + block_visitors: Default::default(), + }; + + module.visit::>( + &mut visitors, + &mut names, + &VisitOptions { visit_nested_blocks: false, reverse_statements: false }, + source_map::Nullable::NULL, + ); + + let mut declare_lets = Vec::new(); + + // TODO quick fix to also register interface and type alias names to prevent conflicts + for item in &module.items { + match item { + StatementOrDeclaration::Declaration(Declaration::TypeAlias(TypeAlias { + name: + StatementPosition { identifier: VariableIdentifier::Standard(s, _), .. }, + .. + })) => { + names.insert(s.clone()); + } + StatementOrDeclaration::Declaration(Declaration::Interface(Decorated { + on: + InterfaceDeclaration { + name: + StatementPosition { + identifier: VariableIdentifier::Standard(s, _), + .. + }, + .. + }, + .. + })) => { + names.insert(s.clone()); + } + StatementOrDeclaration::Declaration(Declaration::DeclareVariable( + declare_variable, + )) => { + for declaration in &declare_variable.declarations { + declare_lets.push(( + declaration.name.clone(), + declaration.type_annotation.clone(), + )); + } + } + _ => {} } - _ => {} } - } - // If available block add to that, otherwise create a new one - if let Some((items, block)) = - final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names)) - { - items.extend(names.into_iter()); - if add_headers_as_comments { - block.push_str("\n\t// "); - block.push_str(&header); - } - for line in code.lines() { - block.push_str("\n\t"); - block.push_str(&line); - } - // If the block is not terminated, it can change the parsing of the next one - if block.ends_with(')') { - block.push(';'); - } - block.push('\n'); - } else { - let mut block = String::new(); - if add_headers_as_comments { - block.push_str("\t// "); - block.push_str(&header); + if !declare_lets.is_empty() { + use source_map::{Nullable, Span}; + let (mut top_level, mut inside) = (Vec::new(), Vec::new()); + for item in module.items { + match item { + StatementOrDeclaration::Declaration(Declaration::TypeAlias( + TypeAlias { .. }, + )) => { + top_level.push(item); + } + StatementOrDeclaration::Declaration(Declaration::Interface( + Decorated { .. }, + )) => { + top_level.push(item); + } + StatementOrDeclaration::Declaration(Declaration::DeclareVariable(..)) => {} + item => { + inside.push(item); + } + } + } + + use ezno_parser::{ast, functions, expressions::operators, Expression, Statement}; + + let parameters = declare_lets + .into_iter() + .map(|(name, type_annotation)| functions::Parameter { + visibility: (), + name, + type_annotation, + additionally: None, + position: Span::NULL, + }) + .collect(); + let function = Expression::ArrowFunction(ast::ArrowFunction { + // TODO maybe async + header: false, + name: (), + parameters: functions::FunctionParameters { + parameters, + rest_parameter: Default::default(), + position: Span::NULL, + leading: (), + }, + return_type: None, + type_parameters: None, + position: Span::NULL, + body: ast::ExpressionOrBlock::Block(ast::Block(inside, Span::NULL)), + }); + + // void is temp fix + top_level.push( + Statement::Expression( + Expression::UnaryOperation { + operator: operators::UnaryOperator::Void, + operand: Box::new(function), + position: Span::NULL, + } + .into(), + ) + .into(), + ); + + let module = Module { hashbang_comment: None, items: top_level, span: Span::NULL }; + + code = module.to_string(&ezno_parser::ToStringOptions::typescript()); } - for line in code.lines() { - block.push_str("\n\t"); - block.push_str(&line); + + // If available block add to that, otherwise create a new one + if let Some((items, block)) = + final_blocks.iter_mut().find(|(uses, _)| uses.is_disjoint(&names)) + { + items.extend(names.into_iter()); + if add_headers_as_comments { + block.push_str("\n\t// "); + block.push_str(&header); + } + for line in code.lines() { + block.push_str("\n\t"); + block.push_str(&line); + } + // If the block is not terminated, it can change the parsing of the next one + if block.ends_with(')') { + block.push(';'); + } + block.push('\n'); + } else { + let mut block = String::new(); + if add_headers_as_comments { + block.push_str("\t// "); + block.push_str(&header); + } + for line in code.lines() { + block.push_str("\n\t"); + block.push_str(&line); + } + block.push('\n'); + final_blocks.push((names, block)); } - block.push('\n'); - final_blocks.push((names, block)); } - } - // eprintln!("Generated {:?} blocks", final_blocks.len()); + // eprintln!("Generated {:?} blocks", final_blocks.len()); - if let Some(out) = out_file { - let mut out = std::fs::File::create(out)?; - for (_items, block) in final_blocks { - writeln!(out, "() => {{\n{block}}};\n")?; - } - } else { - let mut out = std::io::stdout(); - for (_items, block) in final_blocks { - // eprintln!("block includes: {items:?}\n{block}\n---"); - writeln!(out, "() => {{\n{block}}};\n")?; + eprintln!("Bundled into {} functions", final_blocks.len()); + + if let Some(out) = out_file { + let mut out = std::fs::File::create(out)?; + for (_items, block) in final_blocks { + writeln!(out, "() => {{\n{block}}};\n")?; + } + } else { + let mut out = std::io::stdout(); + for (_items, block) in final_blocks { + // eprintln!("block includes: {items:?}\n{block}\n---"); + writeln!(out, "() => {{\n{block}}};\n")?; + } } } - Ok(()) } diff --git a/parser/src/expressions/arrow_function.rs b/parser/src/expressions/arrow_function.rs index 043ebc85..e30a80b1 100644 --- a/parser/src/expressions/arrow_function.rs +++ b/parser/src/expressions/arrow_function.rs @@ -91,17 +91,19 @@ impl FunctionBased for ArrowFunctionBase { local: crate::LocalToStringInformation, ) { // Use shorthand if one parameter with no declared type - if let (true, [Parameter { name, .. }]) = - (parameters.rest_parameter.is_none(), parameters.parameters.as_slice()) + if let ([Parameter { name, type_annotation, .. }], None) = + (parameters.parameters.as_slice(), ¶meters.rest_parameter) { - if let VariableField::Name(name, ..) = name.get_ast_ref() { - name.to_string_from_buffer(buf, options, local); - } else { - parameters.to_string_from_buffer(buf, options, local); + let is_printing_type_annotation = + options.include_type_annotations && type_annotation.is_some(); + if !is_printing_type_annotation { + if let VariableField::Name(name, ..) = name.get_ast_ref() { + name.to_string_from_buffer(buf, options, local); + return; + } } - } else { - parameters.to_string_from_buffer(buf, options, local); } + parameters.to_string_from_buffer(buf, options, local); } fn parameter_body_boundary_token_to_string_from_buffer( From 1b04c6c659e163ba639a018c34524da450cff107 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 11:49:53 +0100 Subject: [PATCH 04/24] Update for regress --- checker/Cargo.toml | 2 +- checker/src/features/regexp.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/checker/Cargo.toml b/checker/Cargo.toml index 242c5608..9832588e 100644 --- a/checker/Cargo.toml +++ b/checker/Cargo.toml @@ -39,7 +39,7 @@ path-absolutize = { version = "3.0", features = ["use_unix_paths_on_wasm"] } either = "1.6" levenshtein = "1" ordered-float = "4.2" -regress = { version = "0.10.0", features = [] } +regress = { version = "0.10", features = [] } serde = { version = "1.0", features = ["derive"], optional = true } simple-json-parser = "0.0.2" diff --git a/checker/src/features/regexp.rs b/checker/src/features/regexp.rs index 7797cef9..13e5c248 100644 --- a/checker/src/features/regexp.rs +++ b/checker/src/features/regexp.rs @@ -15,7 +15,7 @@ pub struct RegExp { source: String, re: Regex, groups: u32, - named_group_indices: crate::Map, + group_names: Vec, flags_unsupported: bool, used: bool, } @@ -65,13 +65,12 @@ impl RegExp { // let start_pred = compiled_regex.start_pred; // let loops = compiled_regex.loops; let groups = compiled_regex.groups + 1; - let named_group_indices = - compiled_regex.named_group_indices.iter().map(|(l, r)| (l.clone(), *r)).collect(); + let group_names = compiled_regex.group_names.iter().map(|l| l.into()).collect(); // let flags = compiled_regex.flags; let re = Regex::from(compiled_regex); - Ok(Self { source, re, groups, named_group_indices, flags_unsupported, used: false }) + Ok(Self { source, re, groups, group_names, flags_unsupported, used: false }) } #[must_use] @@ -262,7 +261,7 @@ impl RegExp { &mut environment.info, ); - for name in self.named_group_indices.keys() { + for name in &self.group_names { let key = PropertyKey::String(name.to_string().into()); named_groups_object.append( From 6d3f8f2959bb07f8dceaa675048eac072ef93e98 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 11:50:41 +0100 Subject: [PATCH 05/24] Formatting in example --- parser/examples/code_blocks_to_script.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs index be696ec6..311e55fb 100644 --- a/parser/examples/code_blocks_to_script.rs +++ b/parser/examples/code_blocks_to_script.rs @@ -184,18 +184,18 @@ fn main() -> Result<(), Box> { } } - use ezno_parser::{ast, functions, expressions::operators, Expression, Statement}; + use ezno_parser::{ast, expressions::operators, functions, Expression, Statement}; let parameters = declare_lets - .into_iter() - .map(|(name, type_annotation)| functions::Parameter { - visibility: (), - name, - type_annotation, - additionally: None, - position: Span::NULL, - }) - .collect(); + .into_iter() + .map(|(name, type_annotation)| functions::Parameter { + visibility: (), + name, + type_annotation, + additionally: None, + position: Span::NULL, + }) + .collect(); let function = Expression::ArrowFunction(ast::ArrowFunction { // TODO maybe async header: false, From 6f96aee899aa82e1ff9c5db507b83cc5dee519fd Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 13:56:48 +0100 Subject: [PATCH 06/24] Update deps + fix for regexp --- Cargo.lock | 151 ++++++++++++++++----------------- checker/src/features/regexp.rs | 4 +- 2 files changed, 73 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b461f75b..42ea9d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -53,9 +53,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -90,15 +90,15 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "cc" -version = "1.1.15" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8" dependencies = [ "shlex", ] @@ -190,7 +190,7 @@ checksum = "42e5ddace13a8459cb452b19e01f59f16d3e2049c8b808f338a13eeadc326e33" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -211,7 +211,7 @@ dependencies = [ "either_n", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -266,7 +266,7 @@ dependencies = [ "proc-macro2", "quote", "string-cases", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -383,15 +383,6 @@ dependencies = [ "syn-helpers", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.1.1" @@ -501,15 +492,6 @@ dependencies = [ "libc", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "iterator-endiate" version = "0.2.1" @@ -565,9 +547,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" @@ -693,9 +675,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "openssl" @@ -720,7 +705,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -743,9 +728,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.2.2" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" +checksum = "44d501f1a72f71d3c063a6bbc8f7271fa73aa09fe5d6283b6571e2ed176a2537" dependencies = [ "num-traits", ] @@ -776,15 +761,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "portable-atomic" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", @@ -810,18 +801,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regress" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16fe0a24af5daaae947294213d2fd2646fbf5e1fbacc1d4ba3e84b2393854842" +checksum = "1541daf4e4ed43a0922b7969bdc2170178bcacc5dabf7e39bc508a9fa3953a7a" dependencies = [ "hashbrown", "memchr", @@ -829,18 +820,18 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -866,11 +857,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -888,9 +879,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -898,13 +889,13 @@ dependencies = [ [[package]] name = "self-replace" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7828a58998685d8bf5a3c5e7a3379a5867289c20828c3ee436280b44b598515" +checksum = "03ec815b5eab420ab893f63393878d89c90fdd94c0bcc44c07abb8ad95552fb7" dependencies = [ - "fastrand 1.9.0", + "fastrand", "tempfile", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -929,9 +920,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -949,13 +940,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] @@ -966,14 +957,14 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1025,9 +1016,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1043,17 +1034,17 @@ dependencies = [ "either_n", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1105,20 +1096,20 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.76", + "syn 2.0.79", ] [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "vcpkg" @@ -1169,7 +1160,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -1213,7 +1204,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1426,9 +1417,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" @@ -1447,5 +1438,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.79", ] diff --git a/checker/src/features/regexp.rs b/checker/src/features/regexp.rs index 13e5c248..1dccc8f8 100644 --- a/checker/src/features/regexp.rs +++ b/checker/src/features/regexp.rs @@ -15,7 +15,7 @@ pub struct RegExp { source: String, re: Regex, groups: u32, - group_names: Vec, + group_names: Vec>, flags_unsupported: bool, used: bool, } @@ -65,7 +65,7 @@ impl RegExp { // let start_pred = compiled_regex.start_pred; // let loops = compiled_regex.loops; let groups = compiled_regex.groups + 1; - let group_names = compiled_regex.group_names.iter().map(|l| l.into()).collect(); + let group_names = compiled_regex.group_names.iter().cloned().collect(); // let flags = compiled_regex.flags; let re = Regex::from(compiled_regex); From 4192660a7c679f413b3087656bfd8c71fdefbcd8 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 5 Oct 2024 16:34:56 +0100 Subject: [PATCH 07/24] Change ranges back to LessThan & GreaterThan --- checker/definitions/internal.ts.d.bin | Bin 35199 -> 35442 bytes checker/definitions/overrides.d.ts | 13 +- checker/definitions/simple.d.ts | 5 +- checker/src/context/root.rs | 4 +- checker/src/synthesis/type_annotations.rs | 4 +- checker/src/types/disjoint.rs | 118 +++++----- checker/src/types/generics/substitution.rs | 2 +- checker/src/types/intrinsics.rs | 151 ++++++------ checker/src/types/mod.rs | 31 ++- checker/src/types/printing.rs | 37 ++- checker/src/types/properties/access.rs | 8 +- checker/src/types/store.rs | 21 +- checker/src/types/subtyping.rs | 99 ++++---- checker/src/utilities/float_range.rs | 257 +++++++++++---------- 14 files changed, 384 insertions(+), 366 deletions(-) diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index 720f630996b27a26f323280d9680158b32adbe5f..b85c2a79514da18cd72d6c7630936753f4c542a5 100644 GIT binary patch literal 35442 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 diff --git a/checker/definitions/overrides.d.ts b/checker/definitions/overrides.d.ts index 03781523..2cfb280f 100644 --- a/checker/definitions/overrides.d.ts +++ b/checker/definitions/overrides.d.ts @@ -127,8 +127,9 @@ declare class Array { type Record = { [P in K]: T } -type LessThan = ExclusiveRange; -type GreaterThan = ExclusiveRange; +type ExclusiveRange = GreaterThan & LessThan; +type InclusiveRange = (GreaterThan & LessThan) | (F | C); + type Integer = MultipleOf<1>; declare class Map { @@ -202,7 +203,7 @@ declare class Promise { } declare class RegExp { @Constant("regexp:constructor") - constructor(pattern: string, flags?: string); + constructor(pattern: string, flags?: string); @Constant("regexp:exec") exec(input: string): RegExpExecArray | null; @@ -222,10 +223,10 @@ interface RegExpExecArray extends Array { * The first match. This will always be present because `null` will be returned if there are no matches. */ 0: string; -} + // } -// es2018 -interface RegExpExecArray { + // // es2018 + // interface RegExpExecArray { groups?: { [key: string]: string; }; diff --git a/checker/definitions/simple.d.ts b/checker/definitions/simple.d.ts index c1b03c9d..7ef3ae1a 100644 --- a/checker/definitions/simple.d.ts +++ b/checker/definitions/simple.d.ts @@ -211,8 +211,9 @@ declare class Map { type Record = { [P in K]: T } -type LessThan = ExclusiveRange; -type GreaterThan = ExclusiveRange; +type ExclusiveRange = GreaterThan & LessThan; +type InclusiveRange = (GreaterThan & LessThan) | (F | C); + type Integer = MultipleOf<1>; /** diff --git a/checker/src/context/root.rs b/checker/src/context/root.rs index 69f0ffd7..2d57e70a 100644 --- a/checker/src/context/root.rs +++ b/checker/src/context/root.rs @@ -76,8 +76,8 @@ impl RootContext { ("Capitalize".to_owned(), TypeId::STRING_CAPITALIZE), ("Uncapitalize".to_owned(), TypeId::STRING_UNCAPITALIZE), ("NoInfer".to_owned(), TypeId::NO_INFER), - ("InclusiveRange".to_owned(), TypeId::INCLUSIVE_RANGE), - ("ExclusiveRange".to_owned(), TypeId::EXCLUSIVE_RANGE), + ("GreaterThan".to_owned(), TypeId::GREATER_THAN), + ("LessThan".to_owned(), TypeId::LESS_THAN), ("MultipleOf".to_owned(), TypeId::MULTIPLE_OF), ("NotNotANumber".to_owned(), TypeId::NUMBER_BUT_NOT_NOT_A_NUMBER), ("Not".to_owned(), TypeId::NOT_RESTRICTION), diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index 71c1c7da..7cb5fade 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -248,10 +248,10 @@ pub fn synthesise_type_annotation( // crate::utilities::notify!( // "{:?} and {:?}", // inner_type_alias_id, - // inner_type_alias_id.is_some_and(intrinsics::tsc_string_intrinsic) + // inner_type_alias_id.is_some_and(intrinsics::is_tsc_string_intrinsic) // ); - if intrinsics::tsc_string_intrinsic(inner_type_id) { + if intrinsics::is_tsc_string_intrinsic(inner_type_id) { distribute_tsc_string_intrinsic( inner_type_id, type_arguments.get(&TypeId::STRING_GENERIC).unwrap().0, diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 10a14d67..b3fcc168 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -30,15 +30,9 @@ pub fn types_are_disjoint( if let Type::Or(lhs_lhs, lhs_rhs) = lhs_ty { types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) && types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) - } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { - types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) - || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) } else if let Type::Or(rhs_lhs, rhs_rhs) = rhs_ty { types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) && types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) - } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { - types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) - || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) } else if let Type::AliasTo { to, parameters: None, name: _ } = lhs_ty { // TODO temp fix, need infer ANY if matches!(*to, TypeId::ANY_TYPE) { @@ -106,62 +100,6 @@ pub fn types_are_disjoint( crate::utilities::notify!("{:?}", (rhs, rhs_inner)); subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype() - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) = lhs_ty - { - let range = super::intrinsics::get_range(lhs, types).unwrap(); - if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) { - let overlap = range.overlaps(rhs_range); - crate::utilities::notify!("{:?}", overlap); - !overlap - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, - }) = rhs_ty - { - let multiple_of = types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), - ); - if let Type::Constant(Constant::Number(multiple_of)) = multiple_of { - !range.contains_multiple_of(*multiple_of) - } else { - crate::utilities::notify!("Here"); - true - } - } else { - crate::utilities::notify!("Here"); - true - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) = rhs_ty - { - let range = super::intrinsics::get_range(rhs, types).unwrap(); - if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) { - let overlap = range.overlaps(lhs_range); - crate::utilities::notify!("{:?}", overlap); - !overlap - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, - }) = lhs_ty - { - let multiple_of = types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), - ); - if let Type::Constant(Constant::Number(multiple_of)) = multiple_of { - !range.contains_multiple_of(*multiple_of) - } else { - crate::utilities::notify!("Here"); - true - } - } else { - crate::utilities::notify!("Here"); - true - } } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments, @@ -170,7 +108,7 @@ pub fn types_are_disjoint( // Little bit complex here because dealing with decimal types, not integers if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), ), rhs_ty, ) { @@ -191,7 +129,7 @@ pub fn types_are_disjoint( if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( lhs, types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(), + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), ), ) { let result = lhs % rhs != 0.; @@ -201,6 +139,12 @@ pub fn types_are_disjoint( crate::utilities::notify!("Here {:?}", lhs); true } + } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { + types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) + || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) + } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { + types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) + || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) } else if let Some(lhs) = super::get_constraint(lhs, types) { // TODO not sure whether these should be here? types_are_disjoint(lhs, rhs, already_checked, information, types) @@ -233,6 +177,20 @@ pub fn types_are_disjoint( { // TODO check properties false + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: _on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), + arguments: _, + }) = rhs_ty + { + crate::utilities::notify!("TODO"); + false + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: _on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), + arguments: _, + }) = lhs_ty + { + crate::utilities::notify!("TODO"); + false } else { crate::utilities::notify!( "{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint", @@ -243,3 +201,35 @@ pub fn types_are_disjoint( } } } + +// fn todo() { +// // else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { +// // on: TypeId::GREATER_THAN +// // arguments: _, +// // }) = lhs_ty +// // { +// // let range = super::intrinsics::get_range(lhs, types).unwrap(); +// // if let Some(rhs_range) = super::intrinsics::get_range(rhs, types) { +// // let overlap = range.overlaps(rhs_range); +// // crate::utilities::notify!("{:?}", overlap); +// // !overlap +// // } else { +// // crate::utilities::notify!("Here"); +// // true +// // } +// // } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { +// // on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, +// // arguments: _, +// // }) = rhs_ty +// // { +// // let range = super::intrinsics::get_range(rhs, types).unwrap(); +// // if let Some(lhs_range) = super::intrinsics::get_range(lhs, types) { +// // let overlap = range.overlaps(lhs_range); +// // crate::utilities::notify!("{:?}", overlap); +// // !overlap +// // } else { +// // crate::utilities::notify!("Here"); +// // true +// // } +// // } +// } diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index 4e2434e1..1013fa2d 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -146,7 +146,7 @@ pub(crate) fn substitute( let generic_arguments = structure_arguments.clone(); // Fold intrinsic type - if intrinsics::tsc_string_intrinsic(on) { + if intrinsics::is_tsc_string_intrinsic(on) { let arg = structure_arguments.get_structure_restriction(TypeId::STRING_GENERIC).unwrap(); let value = substitute(arg, arguments, environment, types); diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index 17581d27..f45a3dcc 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -93,7 +93,7 @@ pub(crate) fn apply_string_intrinsic(on: TypeId, s: &str) -> String { } #[must_use] -pub fn tsc_string_intrinsic(id: TypeId) -> bool { +pub fn is_tsc_string_intrinsic(id: TypeId) -> bool { matches!( id, TypeId::STRING_UPPERCASE @@ -105,8 +105,8 @@ pub fn tsc_string_intrinsic(id: TypeId) -> bool { #[must_use] pub fn is_intrinsic(id: TypeId) -> bool { - tsc_string_intrinsic(id) - || ezno_number_intrinsic(id) + is_tsc_string_intrinsic(id) + || is_ezno_number_intrinsic(id) || matches!( id, TypeId::LITERAL_RESTRICTION @@ -119,8 +119,8 @@ pub fn is_intrinsic(id: TypeId) -> bool { } #[must_use] -pub fn ezno_number_intrinsic(id: TypeId) -> bool { - matches!(id, TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE | TypeId::MULTIPLE_OF) +pub fn is_ezno_number_intrinsic(id: TypeId) -> bool { + matches!(id, TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF) } #[must_use] @@ -128,15 +128,17 @@ pub fn get_greater_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> let on = get_constraint(on, types).unwrap_or(on); let ty = types.get_type_by_id(on); if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), + on: TypeId::GREATER_THAN, arguments, }) = ty { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - Some((inclusive, floor)) + let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + Some((false, floor)) } else if let Type::And(lhs, rhs) = ty { get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types)) + } else if let Type::Or(lhs, rhs) = ty { + // TODO temp + get_greater_than(*lhs, types).or_else(|| get_greater_than(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { Some((true, on)) } else { @@ -149,17 +151,17 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { let on = get_constraint(on, types).unwrap_or(on); let ty = types.get_type_by_id(on); if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), + on: TypeId::LESS_THAN, arguments, }) = ty { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - Some(( - inclusive, - arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(), - )) + let floor = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + Some((false, floor)) } else if let Type::And(lhs, rhs) = ty { get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types)) + } else if let Type::Or(lhs, rhs) = ty { + // TODO temp + get_less_than(*lhs, types).or_else(|| get_less_than(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { Some((true, on)) } else { @@ -169,57 +171,66 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { #[must_use] pub fn get_range(on: TypeId, types: &TypeStore) -> Option { - let on = get_constraint(on, types).unwrap_or(on); - let ty = types.get_type_by_id(on); - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE), - arguments, - }) = ty - { - let inclusive = *on == TypeId::INCLUSIVE_RANGE; - crate::utilities::notify!("{:?} {:?}", on, arguments); - let floor = arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - let ceiling = arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); - if let ( - Type::Constant(crate::Constant::Number(floor)), - Type::Constant(crate::Constant::Number(ceiling)), - ) = (types.get_type_by_id(floor), types.get_type_by_id(ceiling)) + fn get_range2(range: &mut FloatRange, on: TypeId, types: &TypeStore) -> bool { + let on = get_constraint(on, types).unwrap_or(on); + let ty = types.get_type_by_id(on); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), + arguments, + }) = ty { - let (floor, ceiling) = (*floor, *ceiling); - Some(if inclusive { - FloatRange::Inclusive { floor, ceiling } + let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + if let Type::Constant(crate::Constant::Number(argument)) = + types.get_type_by_id(argument) + { + if let TypeId::GREATER_THAN = *on { + range.floor.1 = *argument; + } else { + range.ceiling.1 = *argument; + } + true } else { - FloatRange::Exclusive { floor, ceiling } - }) + false + } + } else if let Type::And(_l, _r) = ty { + // todo!("take intersection") + false + } else if let Type::Or(_l, _r) = ty { + // todo!("change inclusivity based on ==") + false + } else if let Type::Constant(crate::Constant::Number(number)) = ty { + *range = FloatRange::single(*number); + true } else { - crate::utilities::notify!("Not bottom top number"); - None + false } - } else if let Type::Constant(crate::Constant::Number(number)) = ty { - Some(FloatRange::single(*number)) - } else { - None } + + let mut range = FloatRange::default(); + let ok = get_range2(&mut range, on, types); + ok.then_some(range) } #[must_use] -pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { - use source_map::Nullable; +pub fn range_to_type(_range: FloatRange, _types: &mut TypeStore) -> TypeId { + // use source_map::Nullable; - let on = if let FloatRange::Inclusive { .. } = range { - TypeId::INCLUSIVE_RANGE - } else { - TypeId::EXCLUSIVE_RANGE - }; - let (FloatRange::Inclusive { floor, ceiling } | FloatRange::Exclusive { floor, ceiling }) = - range; - let floor = types.new_constant_type(crate::Constant::Number(floor)); - let ceiling = types.new_constant_type(crate::Constant::Number(ceiling)); - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (floor, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (ceiling, SpanWithSource::NULL)), - ])); - types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) + // let on = if let FloatRange::Inclusive { .. } = range { + // TypeId::INCLUSIVE_RANGE + // } else { + // TypeId::EXCLUSIVE_RANGE + // }; + // let (FloatRange::Inclusive { floor, ceiling } | FloatRange::Exclusive { floor, ceiling }) = + // range; + // let floor = types.new_constant_type(crate::Constant::Number(floor)); + // let ceiling = types.new_constant_type(crate::Constant::Number(ceiling)); + // let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ + // (TypeId::NUMBER_GENERIC, (floor, SpanWithSource::NULL)), + // (TypeId::NUMBER_CEILING_GENERIC, (ceiling, SpanWithSource::NULL)), + // ])); + // types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) + + TypeId::NUMBER_TYPE } #[must_use] @@ -231,7 +242,7 @@ pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option { arguments, }) = ty { - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC) + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC) } else if let Type::And(lhs, rhs) = ty { get_multiple(*lhs, types).or_else(|| get_multiple(*rhs, types)) } else if let Type::Constant(crate::Constant::Number(..)) = ty { @@ -255,7 +266,7 @@ pub fn is_not_not_a_number(on: TypeId, types: &TypeStore) -> bool { } else if let Type::Or(lhs, rhs) = ty { is_not_not_a_number(*lhs, types) && is_not_not_a_number(*rhs, types) } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, + on: TypeId::MULTIPLE_OF | TypeId::GREATER_THAN | TypeId::LESS_THAN, arguments: _, }) = ty { @@ -281,26 +292,26 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt Intrinsic::NoInfer => (TypeId::NO_INFER, TypeId::T_TYPE), Intrinsic::Literal => (TypeId::LITERAL_RESTRICTION, TypeId::T_TYPE), Intrinsic::LessThan => { - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (TypeId::NEG_INFINITY, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (argument, SpanWithSource::NULL)), - ])); + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::NUMBER_GENERIC, + (argument, SpanWithSource::NULL), + )])); return types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments }, + PartiallyAppliedGenerics { on: TypeId::LESS_THAN, arguments }, )); } Intrinsic::GreaterThan => { - let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - (TypeId::NUMBER_FLOOR_GENERIC, (argument, SpanWithSource::NULL)), - (TypeId::NUMBER_CEILING_GENERIC, (TypeId::INFINITY, SpanWithSource::NULL)), - ])); + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( + TypeId::NUMBER_GENERIC, + (argument, SpanWithSource::NULL), + )])); return types.register_type(Type::PartiallyAppliedGenerics( - PartiallyAppliedGenerics { on: TypeId::EXCLUSIVE_RANGE, arguments }, + PartiallyAppliedGenerics { on: TypeId::GREATER_THAN, arguments }, )); } - Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_FLOOR_GENERIC), + Intrinsic::MultipleOf => (TypeId::MULTIPLE_OF, TypeId::NUMBER_GENERIC), Intrinsic::Exclusive => (TypeId::EXCLUSIVE_RESTRICTION, TypeId::T_TYPE), Intrinsic::Not => { // Double negation diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index a4bd6d90..c48c9d59 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -143,32 +143,29 @@ impl TypeId { pub const WRITABLE_KEY_ARGUMENT: Self = Self(43); // Ezno intrinsics + pub const NUMBER_GENERIC: Self = Self(44); + pub const GREATER_THAN: Self = Self(45); + pub const LESS_THAN: Self = Self(46); + pub const MULTIPLE_OF: Self = Self(47); + pub const NOT_NOT_A_NUMBER: Self = Self(48); + pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(49); - /// Used in [`Self::INCLUSIVE_RANGE`], [`Self::EXCLUSIVE_RANGE`] and [`Self::MULTIPLE_OF`] - pub const NUMBER_FLOOR_GENERIC: Self = Self(44); - pub const NUMBER_CEILING_GENERIC: Self = Self(45); - pub const INCLUSIVE_RANGE: Self = Self(46); - pub const EXCLUSIVE_RANGE: Self = Self(47); - pub const MULTIPLE_OF: Self = Self(48); - pub const NOT_NOT_A_NUMBER: Self = Self(49); - pub const NUMBER_BUT_NOT_NOT_A_NUMBER: Self = Self(50); - - pub const LITERAL_RESTRICTION: Self = Self(51); - pub const EXCLUSIVE_RESTRICTION: Self = Self(52); - pub const NOT_RESTRICTION: Self = Self(53); + pub const LITERAL_RESTRICTION: Self = Self(50); + pub const EXCLUSIVE_RESTRICTION: Self = Self(51); + pub const NOT_RESTRICTION: Self = Self(52); /// This is needed for the TSC string intrinsics - pub const CASE_INSENSITIVE: Self = Self(54); + pub const CASE_INSENSITIVE: Self = Self(53); /// WIP - pub const OPEN_BOOLEAN_TYPE: Self = Self(55); - pub const OPEN_NUMBER_TYPE: Self = Self(56); + pub const OPEN_BOOLEAN_TYPE: Self = Self(54); + pub const OPEN_NUMBER_TYPE: Self = Self(55); /// For `+` operator - pub const STRING_OR_NUMBER: Self = Self(57); + pub const STRING_OR_NUMBER: Self = Self(56); /// Above add one (because [`TypeId`] starts at zero). Used to assert that the above is all correct - pub(crate) const INTERNAL_TYPE_COUNT: usize = 58; + pub(crate) const INTERNAL_TYPE_COUNT: usize = 57; } #[derive(Debug, binary_serialize_derive::BinarySerializable)] diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index a9688428..9f280fdc 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -200,25 +200,24 @@ pub fn print_type_into_buf( } }, Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments }) => { - // TypeId::INCLUSIVE_RANGE | - if let TypeId::EXCLUSIVE_RANGE = *on { - // let inclusive = *on == TypeId::INCLUSIVE_RANGE; - let floor = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); - let ceiling = - arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); - if let TypeId::NEG_INFINITY = floor { - buf.push_str("LessThan<"); - print_type_into_buf(ceiling, buf, cycles, args, types, info, debug); - buf.push('>'); - return; - } else if let TypeId::INFINITY = ceiling { - buf.push_str("GreaterThan<"); - print_type_into_buf(floor, buf, cycles, args, types, info, debug); - buf.push('>'); - return; - } - } + // if let TypeId::Great = *on { + // // let inclusive = *on == TypeId::INCLUSIVE_RANGE; + // let floor = + // arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // let ceiling = + // arguments.get_structure_restriction(TypeId::NUMBER_CEILING_GENERIC).unwrap(); + // if let TypeId::NEG_INFINITY = floor { + // buf.push_str("LessThan<"); + // print_type_into_buf(ceiling, buf, cycles, args, types, info, debug); + // buf.push('>'); + // return; + // } else if let TypeId::INFINITY = ceiling { + // buf.push_str("GreaterThan<"); + // print_type_into_buf(floor, buf, cycles, args, types, info, debug); + // buf.push('>'); + // return; + // } + // } if debug { write!(buf, "SG({:?})(", ty.0).unwrap(); diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 84d52df1..2ed46477 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -543,14 +543,12 @@ pub(crate) fn get_property_unbound( } } Type::Constant(Constant::String(s)) if under.is_equal_to("length") => { - // TODO temp TypeId::NUMBER_FLOOR_GENERIC for slice member + // TODO temp TypeId::NUMBER_GENERIC for slice member let count = s.chars().count(); Ok(Logical::BasedOnKey(BasedOnKey::Left { - value: Box::new(Logical::Pure(PropertyValue::Value( - TypeId::NUMBER_FLOOR_GENERIC, - ))), + value: Box::new(Logical::Pure(PropertyValue::Value(TypeId::NUMBER_GENERIC))), key_arguments: crate::Map::from_iter([( - TypeId::NUMBER_FLOOR_GENERIC, + TypeId::NUMBER_GENERIC, (CovariantContribution::Number(count as f64), 0), )]), }) diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 6df13325..2f39fc93 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -148,30 +148,20 @@ impl Default for TypeStore { name: "T".into(), extends: TypeId::NUMBER_TYPE, }), - Type::RootPolyType(PolyNature::StructureGeneric { - name: "U".into(), - extends: TypeId::NUMBER_TYPE, - }), Type::AliasTo { to: TypeId::NUMBER_TYPE, - name: "InclusiveRange".into(), - parameters: Some(vec![ - TypeId::NUMBER_FLOOR_GENERIC, - TypeId::NUMBER_CEILING_GENERIC, - ]), + name: "GreaterThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), }, Type::AliasTo { to: TypeId::NUMBER_TYPE, - name: "ExclusiveRange".into(), - parameters: Some(vec![ - TypeId::NUMBER_FLOOR_GENERIC, - TypeId::NUMBER_CEILING_GENERIC, - ]), + name: "LessThan".into(), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), }, Type::AliasTo { to: TypeId::NUMBER_TYPE, name: "MultipleOf".into(), - parameters: Some(vec![TypeId::NUMBER_FLOOR_GENERIC]), + parameters: Some(vec![TypeId::NUMBER_GENERIC]), }, // Intermediate for the below Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { @@ -182,7 +172,6 @@ impl Default for TypeStore { )])), }), Type::And(TypeId::NUMBER_TYPE, TypeId::NOT_NOT_A_NUMBER), - // TODO WIP Type::AliasTo { name: "Literal".into(), to: TypeId::T_TYPE, diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index ec7960da..bc08768d 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -866,7 +866,7 @@ pub(crate) fn type_is_subtype_with_generics( } TypeId::MULTIPLE_OF => { let argument = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); let right_multiple = crate::types::intrinsics::get_multiple(ty, types); return if let ( @@ -886,20 +886,35 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) }; } - TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE => { - return if let (super_range, Some(sub_range)) = ( - intrinsics::get_range(base_type, types).unwrap(), - intrinsics::get_range(ty, types), - ) { - if sub_range.contained_in(super_range) { - SubTypeResult::IsSubType - } else { - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - } - } else { - crate::utilities::notify!("TODO"); - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - }; + TypeId::GREATER_THAN => { + // return if let (super_range, Some(sub_range)) = ( + // intrinsics::get_range(base_type, types).unwrap(), + // intrinsics::get_range(ty, types), + // ) { + // if sub_range.contained_in(super_range) { + // SubTypeResult::IsSubType + // } else { + // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + // } + // } else { + // }; + crate::utilities::notify!("TODO"); + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); + } + TypeId::LESS_THAN => { + // return if let (super_range, Some(sub_range)) = ( + // intrinsics::get_range(base_type, types).unwrap(), + // intrinsics::get_range(ty, types), + // ) { + // if sub_range.contained_in(super_range) { + // SubTypeResult::IsSubType + // } else { + // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + // } + // } else { + // }; + crate::utilities::notify!("TODO"); + return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); } TypeId::CASE_INSENSITIVE => { if let Type::Constant(Constant::String(rs)) = subtype { @@ -2573,9 +2588,6 @@ pub(crate) fn slice_matches_type( allow_casts, ) } else if let Some(contributions) = contributions { - if !rpt.is_substitutable() { - eprintln!("{:?}", rpt); - } // assert!(rpt.is_substitutable(), "{:?}", rpt); let constraint = rpt.get_constraint(); let res = slice_matches_type( @@ -2723,23 +2735,6 @@ pub(crate) fn slice_matches_type( allow_casts, ) } - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF | TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, - arguments: _, - }) if allow_casts => { - // Special behavior here to treat numerical property keys (which are strings) as numbers - if let Ok(value) = slice.parse::() { - number_matches_type( - (base, base_type_arguments), - value, - contributions, - information, - types, - ) - } else { - false - } - } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, arguments, @@ -2758,6 +2753,22 @@ pub(crate) fn slice_matches_type( // crate::utilities::notify!("negated slice arguments={:?}", _k); !matches } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments: _ }) + if allow_casts && intrinsics::is_ezno_number_intrinsic(*on) => + { + // Special behavior here to treat numerical property keys (which are strings) as numbers + if let Ok(value) = slice.parse::() { + number_matches_type( + (base, base_type_arguments), + value, + contributions, + information, + types, + ) + } else { + false + } + } Type::Constructor(super::Constructor::KeyOf(on)) => { let argument = (Publicity::Public, &PropertyKey::String(std::borrow::Cow::Borrowed(slice)), None); @@ -2910,8 +2921,7 @@ pub(crate) fn number_matches_type( on: TypeId::MULTIPLE_OF, arguments, }) => { - let argument = - arguments.get_structure_restriction(TypeId::NUMBER_FLOOR_GENERIC).unwrap(); + let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); if let Type::Constant(Constant::Number(argument)) = types.get_type_by_id(argument) { let number: ordered_float::NotNan = number.try_into().unwrap(); (number % argument) == 0. @@ -2921,11 +2931,20 @@ pub(crate) fn number_matches_type( } } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::INCLUSIVE_RANGE | TypeId::EXCLUSIVE_RANGE, + on: TypeId::LESS_THAN, + arguments: _, + }) => { + todo!() + // let lhs_range = intrinsics::get_range(base, types).unwrap(); + // intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) + } + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, arguments: _, }) => { - let lhs_range = intrinsics::get_range(base, types).unwrap(); - intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) + todo!() + // let lhs_range = intrinsics::get_range(base, types).unwrap(); + // intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index a2f8e36d..a00c6a2e 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -1,18 +1,43 @@ type BetterF64 = ordered_float::NotNan; -// TODO #[derive(Debug, Clone, Copy, PartialEq)] -pub enum FloatRange { - /// yes or `===` - Inclusive { floor: BetterF64, ceiling: BetterF64 }, - /// but not necessarily `===` - Exclusive { floor: BetterF64, ceiling: BetterF64 }, +pub enum InclusiveExclusive { + Inclusive, + Exclusive, } +use InclusiveExclusive::{Exclusive, Inclusive}; + +impl InclusiveExclusive { + pub fn mix(self, other: Self) -> Self { + if let (Inclusive, Inclusive) = (self, other) { + Inclusive + } else { + Exclusive + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FloatRange { + pub floor: (InclusiveExclusive, BetterF64), + pub ceiling: (InclusiveExclusive, BetterF64), +} + +impl Default for FloatRange { + fn default() -> Self { + Self { + floor: (Exclusive, f64::MIN.try_into().unwrap()), + ceiling: (Exclusive, f64::MAX.try_into().unwrap()), + } + } +} + +// TODO try_from (assert ceiling > floor etc) impl FloatRange { #[must_use] pub fn single(on: BetterF64) -> Self { - Self::Inclusive { floor: on, ceiling: on } + Self { floor: (Inclusive, on), ceiling: (Inclusive, on) } } /// For disjointness. TODO Think this is correct @@ -20,30 +45,24 @@ impl FloatRange { pub fn overlaps(self, other: Self) -> bool { crate::utilities::notify!("{:?} ∩ {:?} != ∅", self, other); - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - if l_floor <= r_floor { - l_ceiling >= r_floor - } else if l_ceiling >= r_ceiling { - l_floor <= r_ceiling - } else { - false - } + // TODO more with inclusivity etc + other.floor.1 <= self.ceiling.1 || other.ceiling.1 <= self.floor.1 + } + + #[must_use] + pub fn intersection(self, other: Self) -> Result { + crate::utilities::notify!("{:?} ∩ {:?}", self, other); + + let max_floor = self.floor.1.max(other.floor.1); + let min_ceiling = self.ceiling.1.min(other.ceiling.1); + + if max_floor <= min_ceiling { + Ok(Self { + floor: (self.floor.0.mix(other.floor.0), max_floor), + ceiling: (self.ceiling.0.mix(other.ceiling.0), min_ceiling), + }) } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - if l_floor < r_floor { - l_ceiling > r_floor - } else if l_ceiling > r_ceiling { - l_floor < r_ceiling - } else { - false - } + Err(()) } } @@ -52,37 +71,27 @@ impl FloatRange { pub fn contained_in(self, other: Self) -> bool { crate::utilities::notify!("{:?} ⊆ {:?}", self, other); // Edge case - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Exclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - l_floor > r_floor && l_ceiling < r_ceiling + let lhs = if let (Inclusive, Exclusive) = (self.floor.0, other.floor.0) { + self.floor.1 > other.floor.1 } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - l_floor >= r_floor && l_ceiling <= r_ceiling - } + self.floor.1 >= other.floor.1 + }; + let rhs = if let (Inclusive, Exclusive) = (self.ceiling.0, other.ceiling.0) { + self.ceiling.1 < other.ceiling.1 + } else { + self.ceiling.1 <= other.ceiling.1 + }; + lhs && rhs } /// ∀ a in self, ∀ b in other: a > b #[must_use] pub fn above(self, other: Self) -> bool { crate::utilities::notify!("{:?} > {:?}", self, other); - if let ( - Self::Inclusive { floor: l_floor, ceiling: _ }, - Self::Inclusive { floor: _, ceiling: r_ceiling }, - ) = (self, other) - { - l_floor > r_ceiling + if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) { + self.floor.1 > other.ceiling.1 } else { - let (Self::Inclusive { floor: l_floor, ceiling: _ } - | Self::Exclusive { floor: l_floor, ceiling: _ }) = self; - let (Self::Inclusive { floor: _, ceiling: r_ceiling } - | Self::Exclusive { floor: _, ceiling: r_ceiling }) = other; - l_floor >= r_ceiling + self.floor.1 >= other.ceiling.1 } } @@ -90,61 +99,42 @@ impl FloatRange { #[must_use] pub fn below(self, other: Self) -> bool { crate::utilities::notify!("{:?} < {:?}", self, other); - if let ( - Self::Inclusive { floor: _, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: _ }, - ) = (self, other) - { - l_ceiling < r_floor + if let (Inclusive, Inclusive) = (self.ceiling.0, other.floor.0) { + self.ceiling.1 < other.floor.1 } else { - let (Self::Inclusive { floor: _, ceiling: l_ceiling } - | Self::Exclusive { floor: _, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: _ } - | Self::Exclusive { floor: r_floor, ceiling: _ }) = other; - l_ceiling <= r_floor + self.ceiling.1 <= other.floor.1 } } #[must_use] pub fn space_addition(self, other: Self) -> Self { - if let ( - Self::Inclusive { floor: l_floor, ceiling: l_ceiling }, - Self::Inclusive { floor: r_floor, ceiling: r_ceiling }, - ) = (self, other) - { - Self::Inclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling } - } else { - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - Self::Exclusive { floor: l_floor + r_floor, ceiling: l_ceiling + r_ceiling } + let floor_bound = self.floor.0.mix(other.floor.0); + let ceiling_bound = self.ceiling.0.mix(other.ceiling.0); + Self { + floor: (floor_bound, self.floor.1 + other.floor.1), + ceiling: (ceiling_bound, self.ceiling.1 + other.ceiling.1), } } #[must_use] pub fn space_multiplication(self, other: Self) -> Self { - let inclusive = matches!((self, other), (Self::Inclusive { .. }, Self::Inclusive { .. })); - let (Self::Inclusive { floor: l_floor, ceiling: l_ceiling } - | Self::Exclusive { floor: l_floor, ceiling: l_ceiling }) = self; - let (Self::Inclusive { floor: r_floor, ceiling: r_ceiling } - | Self::Exclusive { floor: r_floor, ceiling: r_ceiling }) = other; - // being lazy + let (l_floor, l_ceiling, r_floor, r_ceiling) = + (self.floor.1, self.ceiling.1, other.floor.1, other.ceiling.1); + // there may be a faster way but being lazy let corners = [l_floor * r_floor, l_floor * r_ceiling, r_floor * l_ceiling, l_ceiling * r_ceiling]; let floor = *corners.iter().min().unwrap(); let ceiling = *corners.iter().max().unwrap(); - if inclusive { - Self::Inclusive { floor, ceiling } - } else { - Self::Exclusive { floor, ceiling } - } + + let floor_bound = self.floor.0.mix(other.floor.0); + let ceiling_bound = self.ceiling.0.mix(other.ceiling.0); + Self { floor: (floor_bound, floor), ceiling: (ceiling_bound, ceiling) } } + #[must_use] pub fn contains_multiple_of(self, multiple_of: BetterF64) -> bool { - let (Self::Inclusive { floor, ceiling } | Self::Exclusive { floor, ceiling }) = self; + let (floor, ceiling) = (self.floor.1, self.ceiling.1); - // (2, 4) let floor = floor / multiple_of; let ceiling = ceiling / multiple_of; @@ -155,64 +145,87 @@ impl FloatRange { // TODO more :) } +impl From> for FloatRange { + fn from(range: std::ops::Range) -> FloatRange { + FloatRange { floor: (Exclusive, range.start), ceiling: (Exclusive, range.end) } + } +} +impl TryFrom> for FloatRange { + type Error = ordered_float::FloatIsNan; + + fn try_from(range: std::ops::Range) -> Result { + let floor = ordered_float::NotNan::new(range.start)?; + let ceiling = ordered_float::NotNan::new(range.end)?; + Ok(FloatRange { floor: (Exclusive, floor), ceiling: (Exclusive, ceiling) }) + } +} + // TODO more #[cfg(test)] mod tests { - use super::{BetterF64, FloatRange}; + use super::{BetterF64, FloatRange, InclusiveExclusive}; + + fn e(a: f64) -> (InclusiveExclusive, BetterF64) { + (InclusiveExclusive::Exclusive, a.try_into().unwrap()) + } + + fn i(a: f64) -> (InclusiveExclusive, BetterF64) { + (InclusiveExclusive::Inclusive, a.try_into().unwrap()) + } #[test] fn contained_in() { - assert!(FloatRange::single(2.into()) - .contained_in(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + let zero_to_four: FloatRange = FloatRange::try_from(0f64..4f64).unwrap(); + assert!(FloatRange::single(2.into()).contained_in(zero_to_four)); } #[test] fn overlaps() { - assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .overlaps(FloatRange::Exclusive { floor: 2.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(0.), ceiling: e(4.) } + .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) })); + + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .overlaps(FloatRange { floor: e(2.), ceiling: e(5.) })); } #[test] fn above() { - assert!(FloatRange::Exclusive { floor: 8.into(), ceiling: 10.into() } - .above(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .above(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(8.), ceiling: e(10.) } + .above(FloatRange { floor: e(6.), ceiling: e(7.) })); + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .above(FloatRange { floor: e(0.), ceiling: e(5.) })); } #[test] fn below() { - assert!(FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .below(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() })); - assert!(!FloatRange::Exclusive { floor: 0.into(), ceiling: 1.into() } - .below(FloatRange::Exclusive { floor: 0.into(), ceiling: 5.into() })); + assert!(FloatRange { floor: e(0.), ceiling: e(4.) } + .below(FloatRange { floor: e(6.), ceiling: e(7.) })); + assert!(!FloatRange { floor: e(0.), ceiling: e(1.) } + .below(FloatRange { floor: e(0.), ceiling: e(5.) })); } #[test] fn space_addition() { - assert_eq!( - FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .space_addition(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }), - FloatRange::Exclusive { floor: 6.into(), ceiling: 11.into() } - ); + let lhs = FloatRange { floor: e(0.), ceiling: e(4.) } + .space_addition(FloatRange { floor: e(6.), ceiling: e(7.) }); + assert_eq!(lhs, FloatRange { floor: e(6.), ceiling: e(11.) }); } #[test] fn space_multiplication() { - assert_eq!( - FloatRange::Exclusive { floor: 0.into(), ceiling: 4.into() } - .space_multiplication(FloatRange::Exclusive { floor: 6.into(), ceiling: 7.into() }), - FloatRange::Exclusive { floor: 0.into(), ceiling: 28.into() } - ); - assert_eq!( - FloatRange::Exclusive { floor: BetterF64::from(-2i32), ceiling: 4.into() } - .space_multiplication(FloatRange::Exclusive { - floor: BetterF64::from(-10i32), - ceiling: 1.into() - }), - FloatRange::Exclusive { floor: BetterF64::from(-40i32), ceiling: 20.into() } - ); + let lhs = FloatRange { floor: e(0.), ceiling: e(4.) } + .space_multiplication(FloatRange { floor: e(6.), ceiling: e(7.) }); + assert_eq!(lhs, FloatRange { floor: e(0.), ceiling: e(28.) }); + + let lhs = FloatRange { floor: e(-2.), ceiling: e(4.) } + .space_multiplication(FloatRange { floor: e(-10.), ceiling: e(1.) }); + assert_eq!(lhs, FloatRange { floor: e(-40.), ceiling: e(20.) }); + } + + #[test] + fn multiple_of() { + let lhs = FloatRange { floor: e(30.), ceiling: e(34.) }; + assert!(lhs.contains_multiple_of(4.into())); // 32 + assert!(!lhs.contains_multiple_of(7.into())); // 28 -- 35 } } From de9113a979757e9badda747eefd6a7ba061064ca Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 21 Oct 2024 20:09:35 +0100 Subject: [PATCH 08/24] Fix for tree shaking of known functions inside of callbacks --- checker/src/events/application.rs | 5 +++ checker/src/events/mod.rs | 4 +- checker/src/events/printing.rs | 3 ++ checker/src/types/calling.rs | 7 ++++ src/build.rs | 61 ++++++++++++++++++++++++++++++- src/cli.rs | 16 +------- src/wasm_bindings.rs | 5 +-- 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index d3ddba9f..8a0bdc9b 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -778,6 +778,11 @@ pub(crate) fn apply_events( }; type_arguments.set_during_application(*referenced_in_scope_as, new_function_ty); } + super::MiscellaneousEvents::MarkFunctionAsCalled(id) => { + if top_environment.is_always_run() { + types.called_functions.insert(*id); + } + } }, Event::FinalEvent(final_event) => { // I think this is okay diff --git a/checker/src/events/mod.rs b/checker/src/events/mod.rs index a376db6f..7bf999d8 100644 --- a/checker/src/events/mod.rs +++ b/checker/src/events/mod.rs @@ -185,6 +185,9 @@ pub enum MiscellaneousEvents { }, /// Creates a new function or class CreateConstructor { referenced_in_scope_as: TypeId, function: FunctionId }, + /// While [`Event::CallsType`] marks callbacks (and parameters) as being called, it does not mark regular function as being called + /// this event has no effect but does add it to the global `HashSet` of called types + MarkFunctionAsCalled(FunctionId), } /// A break in application @@ -252,7 +255,6 @@ pub enum ApplicationResult { position: SpanWithSource, }, /// From a `throw ***` statement (or expression). - /// Throw { thrown: TypeId, position: SpanWithSource, diff --git a/checker/src/events/printing.rs b/checker/src/events/printing.rs index 9e128329..72f86321 100644 --- a/checker/src/events/printing.rs +++ b/checker/src/events/printing.rs @@ -162,6 +162,9 @@ pub fn debug_effects( buf.push_str("end"); } Event::Miscellaneous(misc) => match misc { + super::MiscellaneousEvents::MarkFunctionAsCalled(_) => { + buf.push_str("Calls inlined function"); + } super::MiscellaneousEvents::Has { .. } => { buf.push_str("Has"); } diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 9a92236a..2beda63b 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -863,6 +863,13 @@ fn call_logical( call_site: input.call_site, possibly_thrown, }); + } else { + // This fixes tree shaking of functions that are called within callbacks + // TODO this could be done under an option + let value = Event::Miscellaneous( + crate::events::MiscellaneousEvents::MarkFunctionAsCalled(function.function), + ); + behavior.get_latest_info(top_environment).events.push(value); } Ok(result) diff --git a/src/build.rs b/src/build.rs index 0bc7f94f..14b6753c 100644 --- a/src/build.rs +++ b/src/build.rs @@ -42,6 +42,8 @@ pub struct BuildConfig { pub strip_whitespace: bool, #[cfg_attr(target_family = "wasm", serde(default))] pub source_maps: bool, + #[cfg_attr(target_family = "wasm", serde(default))] + pub optimise: bool, } pub type EznoParsePostCheckVisitors = @@ -71,7 +73,6 @@ pub fn build( type_definition_module: Option<&Path>, output_path: &Path, config: &BuildConfig, - transformers: Option, ) -> Result { // TODO parse options + non_standard_library & non_standard_syntax let type_check_options = TypeCheckOptions { store_type_mappings: true, ..Default::default() }; @@ -97,7 +98,17 @@ pub fn build( let mut outputs = Vec::new(); - let mut transformers = transformers.unwrap_or_default(); + let mut transformers = EznoParsePostCheckVisitors::default(); + + if config.optimise { + transformers + .expression_visitors_mut + .push(Box::new(crate::transformers::optimisations::ExpressionOptimiser)); + + transformers + .statement_visitors_mut + .push(Box::new(crate::transformers::optimisations::StatementOptimiser)); + } for source in keys { // Remove the module @@ -138,3 +149,49 @@ pub fn build( Err(FailedBuildOutput { diagnostics: result.diagnostics, fs: data.module_contents }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tree_shaking() { + let source = r#" + function make_observable(obj) { + return new Proxy(obj, { + get(on, prop: string, _rec) { + return on[prop] + }, + }) + } + + function get_a() { + return 1 + } + + function get_b() { + return 1 + } + + const obj = { + a() { return get_a() }, + b() { return get_b() }, + c: 2 + } + + const value = make_observable(obj); + const a_value = value.a(); + const c_value = value.c; + "#; + + let cfg = BuildConfig { optimise: true, ..Default::default() }; + + let output = + build(vec!["index.tsx"], |_| Some(source.to_owned()), None, "out.tsx").unwrap(); + + let first_source = output.outputs.content; + + // TODO assert equal + panic!("{first_source:?}"); + } +} diff --git a/src/cli.rs b/src/cli.rs index d0118798..e9467d97 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,7 @@ use std::{ }; use crate::{ - build::{build, BuildConfig, BuildOutput, EznoParsePostCheckVisitors, FailedBuildOutput}, + build::{build, BuildConfig, BuildOutput, FailedBuildOutput}, check::check, reporting::report_diagnostics_to_cli, utilities::{self, print_to_cli, MaxDiagnostics}, @@ -272,18 +272,6 @@ pub fn run_cli { let output_path = build_config.output.unwrap_or("ezno_output.js".into()); - let mut default_builders = EznoParsePostCheckVisitors::default(); - - if build_config.optimise { - default_builders - .expression_visitors_mut - .push(Box::new(crate::transformers::optimisations::ExpressionOptimiser)); - - default_builders - .statement_visitors_mut - .push(Box::new(crate::transformers::optimisations::StatementOptimiser)); - } - let entry_points = match get_entry_points(build_config.input) { Ok(entry_points) => entry_points, Err(_) => return ExitCode::FAILURE, @@ -300,8 +288,8 @@ pub fn run_cli JsValue { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); @@ -37,8 +37,7 @@ pub fn experimental_build_wasm( &fs_resolver, None, Path::new("out.js"), - &crate::build::BuildConfig { strip_whitespace: minify, source_maps: false }, - None, + &config, ); serde_wasm_bindgen::to_value(&result).unwrap() From b78844c293f30f455cdedd733b2f26470815db40 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 21 Oct 2024 20:11:20 +0100 Subject: [PATCH 09/24] Update CI tasks - Run performance-and-size automatically on PRs with compiler-performance label - Change "features" around on checker-specification --- .github/workflows/rust.yml | 28 +++++++++++++++++++----- checker/examples/run_checker.rs | 7 ++++-- checker/specification/Cargo.toml | 6 +++-- checker/specification/build.rs | 4 ++-- checker/specification/specification.md | 13 +++++++---- checker/specification/staging.md | 26 ++++++++++++++++++++++ checker/src/types/store.rs | 15 ++++++++----- parser/examples/code_blocks_to_script.rs | 1 + 8 files changed, 78 insertions(+), 22 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9a2efc66..ed32d810 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -88,6 +88,7 @@ jobs: run: | cargo test + # TODO more curl https://esm.sh/v128/react-dom@18.2.0/es2022/react-dom.mjs > react.js cargo run -p ezno-parser --example parse react.js working-directory: parser @@ -95,12 +96,19 @@ jobs: - name: Run checker specification if: (steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request') || github.ref_name == 'main' run: cargo test - working-directory: checker/specification - name: Run checker specification (w/ staging) if: steps.changes.outputs.checker == 'true' && github.event_name == 'pull_request' - run: cargo test -F staging - working-directory: checker/specification + run: cargo test -F staging -p ezno-checker-specification + env: + EZNO_DEBUG: 1 + + - name: Run checker specification (just to implement) + continue-on-error: true + if: steps.changes.outputs.checker == 'true' && github.event_name == 'pull_request' + run: | + # Aim of this test is to catch anything that may have been fixed in this next commit or any bad regressions (stack overflows) + cargo test --no-default-features -F to_implement -p ezno-checker-specification env: EZNO_DEBUG: 1 @@ -108,8 +116,7 @@ jobs: if: steps.changes.outputs.checker == 'true' || github.ref_name == 'main' run: | # Test checker with the parser features - cargo test -F ezno-parser - working-directory: checker + cargo test -F ezno-parser -p ezno-checker - name: Run base tests run: cargo test @@ -126,7 +133,7 @@ jobs: with: path: ${{ env.CACHE_PATHS }} key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - + - uses: dorny/paths-filter@v3 id: changes with: @@ -190,6 +197,7 @@ jobs: working-directory: src/js-cli-and-library shell: bash + # WIP - uses: actions/upload-artifact@v4 if: steps.changes.outputs.src == 'true' || github.ref_name == 'main' with: @@ -347,3 +355,11 @@ jobs: fi done shell: bash + + performance-and-size: + # WIP + runs-on: ubuntu-latest + steps: + - name: Kick off other workflow if the PR has a label + if: contains(github.event.pull_request.labels.*.name, 'compiler-performance') + run: gh workflow run performance-and-size.yml --ref ${{ github.event.base_ref }} \ No newline at end of file diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs index aab40341..d7991149 100644 --- a/checker/examples/run_checker.rs +++ b/checker/examples/run_checker.rs @@ -60,8 +60,7 @@ fn main() { if args.iter().any(|arg| arg == "--types") { eprintln!("Types:"); - let types = result.types.into_vec_temp(); - for (type_id, item) in &types[types.len().saturating_sub(60)..] { + for (type_id, item) in result.types.user_types() { eprintln!("\t{type_id:?}: {item:?}"); } } @@ -74,6 +73,10 @@ fn main() { } } + if args.iter().any(|arg| arg == "--called-functions") { + eprintln!("Called function: {:?}", result.types.called_functions); + } + if args.iter().any(|arg| arg == "--time") { let end = now.elapsed(); let count = result.diagnostics.into_iter().len(); diff --git a/checker/specification/Cargo.toml b/checker/specification/Cargo.toml index 51591c22..81dbbd2b 100644 --- a/checker/specification/Cargo.toml +++ b/checker/specification/Cargo.toml @@ -7,9 +7,11 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -just-staging = [] +default = ["base"] +base = [] staging = [] -all = ["staging"] +to_implement = [] +all = ["base", "staging", "to_implement"] [[test]] name = "specification_test" diff --git a/checker/specification/build.rs b/checker/specification/build.rs index e31236a4..75514b98 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), Box> { let out_path = Path::new(&std::env::var("OUT_DIR")?).join("specification.rs"); let mut out = File::create(out_path)?; - if cfg!(not(feature = "just-staging")) { + if cfg!(feature = "base") { let specification = read_to_string("./specification.md")?; markdown_lines_append_test_to_rust(specification.lines().enumerate(), &mut out)?; } @@ -24,7 +24,7 @@ fn main() -> Result<(), Box> { writeln!(&mut out, "}}").unwrap(); } - if cfg!(feature = "all") { + if cfg!(feature = "to_implement") { let to_implement = read_to_string("./to_implement.md")?; writeln!(&mut out, "mod to_implement {{ use super::check_errors; ").unwrap(); markdown_lines_append_test_to_rust(to_implement.lines().enumerate(), &mut out)?; diff --git a/checker/specification/specification.md b/checker/specification/specification.md index f218a2b4..3d5fb480 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -1267,7 +1267,7 @@ new MyClass("hi").value satisfies "hello" - Expected "hello", found "hi" -#### `new` on function prototype +#### `new` on function with assigned prototype ```ts function MyClass(value) { @@ -1778,13 +1778,13 @@ kestrel(3)(2) satisfies 4 ```ts function kestrel2(a) { - return _b => _c => a + return b => c => (a * b) + c } -kestrel2(3)(2)(6) satisfies 4 +kestrel2(3)(2)(1) satisfies 4 ``` -- Expected 4, found 3 +- Expected 4, found 7 #### Carry across objects @@ -2646,6 +2646,9 @@ declare var x: number; declare var x: number; (x < 4) satisfies string; (x === 4) satisfies Math; +(x !== 4) satisfies boolean; +(x > 4) satisfies boolean; +(x >= 4) satisfies boolean; ``` - Expected string, found boolean @@ -4011,6 +4014,8 @@ x.a = "hi"; x.a = 4; ``` + + - Type 4 does not meet property constraint "hi" #### `as` rewrite diff --git a/checker/specification/staging.md b/checker/specification/staging.md index 023fccd9..466295bd 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -36,3 +36,29 @@ function func(a: number, b: number) { ``` - ? + +#### Multiplication changes modulo + +```ts +// Integer = MultipleOf<1> +function func(param: Integer) { + if (param * 8 === 2) {} +} +``` + +- The equality is always false as MultipleOf<8> and 2 have no overlap + +#### TODO + +```ts +function func(param: Integer) { + print_and_debug_type(param - 0.2); + print_and_debug_type(param / 2); + print_and_debug_type(2 / param); +} + +function func2(param: number) { + print_and_debug_type((param - 5) + 5); + print_and_debug_type((param / 5) * 5); +} +``` diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 2f39fc93..27a4f174 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -315,12 +315,6 @@ impl TypeStore { } } - /// TODO temp - #[must_use] - pub fn into_vec_temp(self) -> Vec<(TypeId, Type)> { - self.types.into_iter().enumerate().map(|(idx, ty)| (TypeId(idx as u16), ty)).collect() - } - /// From something like: let a: number => string. Rather than a actual function pub fn new_function_type_annotation( &mut self, @@ -610,4 +604,13 @@ impl TypeStore { pub(crate) fn new_key_of(&mut self, of: TypeId) -> TypeId { self.register_type(Type::Constructor(Constructor::KeyOf(of))) } + + /// TODO temp for debugging + pub fn user_types(&self) -> impl Iterator + '_ { + self.types + .iter() + .enumerate() + .skip(TypeId::INTERNAL_TYPE_COUNT) + .map(|(idx, ty)| (TypeId(idx as u16), ty)) + } } diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs index 311e55fb..58e9a179 100644 --- a/parser/examples/code_blocks_to_script.rs +++ b/parser/examples/code_blocks_to_script.rs @@ -196,6 +196,7 @@ fn main() -> Result<(), Box> { position: Span::NULL, }) .collect(); + let function = Expression::ArrowFunction(ast::ArrowFunction { // TODO maybe async header: false, From c936ef124f95545c461de35b303e43a896814f75 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 21 Oct 2024 20:11:42 +0100 Subject: [PATCH 10/24] Clippy and cargo fmt fixes inline with latest Rust --- checker/src/context/invocation.rs | 22 +++++++-------- checker/src/features/functions.rs | 1 + checker/src/features/regexp.rs | 2 +- checker/src/synthesis/interfaces.rs | 10 +++---- checker/src/synthesis/mod.rs | 2 ++ checker/src/synthesis/type_annotations.rs | 5 ++-- checker/src/utilities/float_range.rs | 2 +- parser/src/block.rs | 3 ++- parser/src/declarations/mod.rs | 9 ++++--- parser/src/extensions/jsx.rs | 4 +-- parser/src/functions/mod.rs | 33 ++++++++++++----------- parser/src/lexer.rs | 20 ++++++++------ parser/src/tokens.rs | 15 +++++++---- parser/src/types/type_annotations.rs | 3 ++- 14 files changed, 73 insertions(+), 58 deletions(-) diff --git a/checker/src/context/invocation.rs b/checker/src/context/invocation.rs index b7218ade..4435e4a8 100644 --- a/checker/src/context/invocation.rs +++ b/checker/src/context/invocation.rs @@ -69,7 +69,7 @@ pub struct InvocationContext(Vec); /// TODO want to have type arguments on each of these pub(crate) enum InvocationKind { - Conditional(LocalInformation), + Conditional(Box), /// *Unconditional* /// /// TODO does this need [`LocalInformation`]?? @@ -89,15 +89,13 @@ impl CallCheckingBehavior for InvocationContext { self.0 .iter_mut() .rev() - .find_map( - |kind| { - if let InvocationKind::Conditional(info) = kind { - Some(info) - } else { - None - } - }, - ) + .find_map(|kind| { + if let InvocationKind::Conditional(info) = kind { + Some(&mut **info) + } else { + None + } + }) .unwrap_or(&mut environment.info) } @@ -142,10 +140,10 @@ impl InvocationContext { &mut self, cb: impl for<'a> FnOnce(&'a mut InvocationContext) -> T, ) -> (LocalInformation, T) { - self.0.push(InvocationKind::Conditional(LocalInformation::default())); + self.0.push(InvocationKind::Conditional(Box::default())); let result = cb(self); if let Some(InvocationKind::Conditional(info)) = self.0.pop() { - (info, result) + (*info, result) } else { unreachable!() } diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index b3570c83..96950fa5 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -1137,6 +1137,7 @@ pub fn extract_name(expecting: TypeId, types: &TypeStore, environment: &Environm } } +#[must_use] pub fn class_generics_to_function_generics( prototype: TypeId, types: &TypeStore, diff --git a/checker/src/features/regexp.rs b/checker/src/features/regexp.rs index 1dccc8f8..297d0334 100644 --- a/checker/src/features/regexp.rs +++ b/checker/src/features/regexp.rs @@ -65,7 +65,7 @@ impl RegExp { // let start_pred = compiled_regex.start_pred; // let loops = compiled_regex.loops; let groups = compiled_regex.groups + 1; - let group_names = compiled_regex.group_names.iter().cloned().collect(); + let group_names = compiled_regex.group_names.to_vec(); // let flags = compiled_regex.flags; let re = Regex::from(compiled_regex); diff --git a/checker/src/synthesis/interfaces.rs b/checker/src/synthesis/interfaces.rs index 6c7d99ec..edd068e5 100644 --- a/checker/src/synthesis/interfaces.rs +++ b/checker/src/synthesis/interfaces.rs @@ -58,7 +58,7 @@ pub(crate) enum InterfaceKey<'a> { } pub(crate) enum InterfaceValue { - Function(FunctionType, Option), + Function(Box, Option), Value(TypeId), } @@ -90,16 +90,16 @@ fn register( let value = match value { InterfaceValue::Function(function, getter_setter) => match getter_setter { Some(GetterSetter::Getter) => PropertyValue::Getter(Callable::new_from_function( - function, + *function, &mut checking_data.types, )), Some(GetterSetter::Setter) => PropertyValue::Setter(Callable::new_from_function( - function, + *function, &mut checking_data.types, )), None => { let function_id = function.id; - checking_data.types.functions.insert(function.id, function); + checking_data.types.functions.insert(function.id, *function); let ty = Type::FunctionReference(function_id); PropertyValue::Value(checking_data.types.register_type(ty)) } @@ -232,7 +232,7 @@ pub(super) fn synthesise_signatures( }, ); return TypeId::NEVER_TYPE; - // return TypeId::ERROR_TYPE; - } else { - acc = checking_data.types.new_and_type(acc, right); } + + acc = checking_data.types.new_and_type(acc, right); } acc } diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index a00c6a2e..7e36b855 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -9,6 +9,7 @@ pub enum InclusiveExclusive { use InclusiveExclusive::{Exclusive, Inclusive}; impl InclusiveExclusive { + #[must_use] pub fn mix(self, other: Self) -> Self { if let (Inclusive, Inclusive) = (self, other) { Inclusive @@ -49,7 +50,6 @@ impl FloatRange { other.floor.1 <= self.ceiling.1 || other.ceiling.1 <= self.floor.1 } - #[must_use] pub fn intersection(self, other: Self) -> Result { crate::utilities::notify!("{:?} ∩ {:?}", self, other); diff --git a/parser/src/block.rs b/parser/src/block.rs index 3070dde8..79739b59 100644 --- a/parser/src/block.rs +++ b/parser/src/block.rs @@ -43,7 +43,8 @@ impl StatementOrDeclaration { on: ExportDeclaration::Default { .. } | ExportDeclaration::Item { exported: Exportable::ImportAll { .. } - | Exportable::ImportParts { .. } | Exportable::Parts { .. }, + | Exportable::ImportParts { .. } + | Exportable::Parts { .. }, .. }, .. diff --git a/parser/src/declarations/mod.rs b/parser/src/declarations/mod.rs index 53d3344a..080f7dc6 100644 --- a/parser/src/declarations/mod.rs +++ b/parser/src/declarations/mod.rs @@ -78,8 +78,10 @@ impl Declaration { token, TSXToken::Keyword( TSXKeyword::Let - | TSXKeyword::Const | TSXKeyword::Function - | TSXKeyword::Class | TSXKeyword::Export + | TSXKeyword::Const + | TSXKeyword::Function + | TSXKeyword::Class + | TSXKeyword::Export ) | TSXToken::At, ); @@ -123,7 +125,8 @@ impl Declaration { reader.peek_n(1), Some(Token( TSXToken::OpenBrace - | TSXToken::Keyword(..) | TSXToken::Identifier(..) + | TSXToken::Keyword(..) + | TSXToken::Identifier(..) | TSXToken::StringLiteral(..) | TSXToken::Multiply, _ diff --git a/parser/src/extensions/jsx.rs b/parser/src/extensions/jsx.rs index 88f58aac..ebec2d33 100644 --- a/parser/src/extensions/jsx.rs +++ b/parser/src/extensions/jsx.rs @@ -485,7 +485,7 @@ pub fn html_tag_is_self_closing(tag_name: &str) -> bool { | "hr" | "img" | "input" | "link" | "meta" | "param" - | "source" | "track" - | "wbr" + | "source" + | "track" | "wbr" ) } diff --git a/parser/src/functions/mod.rs b/parser/src/functions/mod.rs index 7d86a4bb..84bb66a1 100644 --- a/parser/src/functions/mod.rs +++ b/parser/src/functions/mod.rs @@ -644,22 +644,23 @@ pub(crate) fn get_method_name( state: &mut crate::ParsingState, options: &ParseOptions, ) -> Result<(MethodHeader, WithComment>), crate::ParseError> { - let is_named_get_set_or_async = - matches!( - reader.peek(), - Some(Token(TSXToken::Keyword(kw), _)) - if kw.is_in_method_header() - ) && matches!( - reader.peek_n(1), - Some(Token( - TSXToken::OpenParentheses - | TSXToken::Colon | TSXToken::OpenChevron - | TSXToken::CloseBrace - | TSXToken::Comma | TSXToken::QuestionMark - | TSXToken::OptionalMember, - _ - )) - ); + let is_named_get_set_or_async = matches!( + reader.peek(), + Some(Token(TSXToken::Keyword(kw), _)) + if kw.is_in_method_header() + ) && matches!( + reader.peek_n(1), + Some(Token( + TSXToken::OpenParentheses + | TSXToken::Colon + | TSXToken::OpenChevron + | TSXToken::CloseBrace + | TSXToken::Comma + | TSXToken::QuestionMark + | TSXToken::OptionalMember, + _ + )) + ); let (function_header, key) = if is_named_get_set_or_async { let token = reader.next().unwrap(); diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 49c202ed..e6d68467 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -47,14 +47,18 @@ fn is_number_delimiter(chr: char) -> bool { chr, ' ' | ',' | '\n' | '\r' - | ';' | '+' | '-' - | '*' | '/' | '&' - | '|' | '!' | '^' - | '(' | '{' | '[' - | ')' | '}' | ']' - | '%' | '=' | ':' - | '<' | '>' | '?' - | '"' | '\'' | '`' + | ';' | '+' + | '-' | '*' + | '/' | '&' + | '|' | '!' + | '^' | '(' + | '{' | '[' + | ')' | '}' + | ']' | '%' + | '=' | ':' + | '<' | '>' + | '?' | '"' + | '\'' | '`' | '#' ) } diff --git a/parser/src/tokens.rs b/parser/src/tokens.rs index 9c61e8b9..1cedf7ed 100644 --- a/parser/src/tokens.rs +++ b/parser/src/tokens.rs @@ -456,7 +456,8 @@ impl TSXToken { | TSXToken::LogicalAnd | TSXToken::LogicalOr | TSXToken::Multiply - | TSXToken::Add | TSXToken::Subtract + | TSXToken::Add + | TSXToken::Subtract | TSXToken::Divide ) || self.is_assignment() } @@ -479,10 +480,14 @@ impl TSXToken { self, TSXToken::Keyword( TSXKeyword::Function - | TSXKeyword::If | TSXKeyword::For - | TSXKeyword::While | TSXKeyword::Const - | TSXKeyword::Let | TSXKeyword::Break - | TSXKeyword::Import | TSXKeyword::Export + | TSXKeyword::If + | TSXKeyword::For + | TSXKeyword::While + | TSXKeyword::Const + | TSXKeyword::Let + | TSXKeyword::Break + | TSXKeyword::Import + | TSXKeyword::Export ) ) } diff --git a/parser/src/types/type_annotations.rs b/parser/src/types/type_annotations.rs index 271e2b52..68009ab0 100644 --- a/parser/src/types/type_annotations.rs +++ b/parser/src/types/type_annotations.rs @@ -479,7 +479,8 @@ impl TypeAnnotation { TSXToken::CloseParentheses | TSXToken::CloseBracket | TSXToken::CloseBrace - | TSXToken::Comma | TSXToken::OpenChevron + | TSXToken::Comma + | TSXToken::OpenChevron ) || peek.is_assignment() || (start.map_or(false, |start| { peek.is_statement_or_declaration_start() From d9349ffef42e69041918bb47af583708d74f86cf Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 26 Oct 2024 12:27:08 +0100 Subject: [PATCH 11/24] Checker changes - Narrowing fix --- .github/workflows/rust.yml | 6 +- checker/specification/build.rs | 2 +- checker/specification/specification.md | 2 +- checker/specification/staging.md | 45 ++++-- checker/src/features/narrowing.rs | 50 +++--- checker/src/types/disjoint.rs | 90 +++++++---- checker/src/types/mod.rs | 21 ++- checker/src/types/properties/assignment.rs | 2 +- checker/src/types/store.rs | 38 +++-- checker/src/types/subtyping.rs | 172 +++++++++++++-------- checker/src/utilities/debugging.rs | 2 +- checker/src/utilities/float_range.rs | 41 +++++ 12 files changed, 320 insertions(+), 151 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ed32d810..b2f348df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -118,7 +118,7 @@ jobs: # Test checker with the parser features cargo test -F ezno-parser -p ezno-checker - - name: Run base tests + - name: Run CLI and base tests run: cargo test extras: @@ -362,4 +362,6 @@ jobs: steps: - name: Kick off other workflow if the PR has a label if: contains(github.event.pull_request.labels.*.name, 'compiler-performance') - run: gh workflow run performance-and-size.yml --ref ${{ github.event.base_ref }} \ No newline at end of file + run: | + echo ${{ github.event.after }} + gh workflow run performance-and-size.yml --ref ${{ github.event.after }} \ No newline at end of file diff --git a/checker/specification/build.rs b/checker/specification/build.rs index 75514b98..1c1bfc06 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -135,7 +135,7 @@ fn markdown_lines_append_test_to_rust( fn heading_to_rust_identifier(heading: &str) -> String { heading .replace("...", "") - .replace([' ', '-', '/', '.', '+'], "_") + .replace([' ', '-', '/', '.', '+', ':'], "_") .replace(['*', '\'', '`', '"', '&', '!', '(', ')', ','], "") .to_lowercase() } diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 3d5fb480..7ec5a11f 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -2496,7 +2496,7 @@ function func(a: string, b: number) { - Expected string, found true - Expected null, found boolean -#### Ranges for interal types +#### Ranges for internal types ```ts function func(a: number) { diff --git a/checker/specification/staging.md b/checker/specification/staging.md index 466295bd..9c197fb1 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -27,38 +27,49 @@ function func3(p1: Not, p2: Not) { > TODO need to redo range to use interesection of less than and greater than ```ts +// function func(a: number, b: number) { +// if (a % 15 === 0 && 31 < b && b < 37) { +// print_type(a, b) +// print_type(a === b) +// } +// } function func(a: number, b: number) { - if (a % 15 === 0 && 31 < b && b < 37) { - print_type(a, b) - print_type(a === b) + if (31 < b && b < 37) { + print_type(b) } } ``` - ? -#### Multiplication changes modulo +- The equality is always false as MultipleOf<8> and 2 have no overlap + +#### Modulo offsets ```ts -// Integer = MultipleOf<1> function func(param: Integer) { - if (param * 8 === 2) {} + print_type(param - 0.2); + print_type(param / 2); + print_type(2 / param); +} + +function func2(param: number) { + print_type((param - 5) + 5); + print_type((param / 5) * 5); } ``` -- The equality is always false as MultipleOf<8> and 2 have no overlap +- Hi -#### TODO +#### Narrowing: Implication by ```ts -function func(param: Integer) { - print_and_debug_type(param - 0.2); - print_and_debug_type(param / 2); - print_and_debug_type(2 / param); -} - -function func2(param: number) { - print_and_debug_type((param - 5) + 5); - print_and_debug_type((param / 5) * 5); +function func(a: boolean) { + const x = a ? 1 : 2; + if (x === 1) { + a satisfies "hi" + } } ``` + +- Expected "hi", found true diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index b398693c..40c757e3 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -36,10 +36,10 @@ pub fn narrow_based_on_expression( operator: CanonicalEqualityAndInequality::StrictEqual, rhs, } => { - if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = - types.get_type_by_id(*lhs) - { - let from = get_origin(*on, types); + let lhs_type = types.get_type_by_id(*lhs); + if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = lhs_type { + // let from = get_origin(*on, types); + let from = *on; //, types); if let Type::Constant(Constant::String(c)) = types.get_type_by_id(*rhs) { let type_from_name = crate::features::string_name_to_type(c); if let Some(type_from_name) = type_from_name { @@ -82,7 +82,7 @@ pub fn narrow_based_on_expression( operator: MathematicalAndBitwise::Modulo, rhs: modulo, result: _, - }) = types.get_type_by_id(*lhs) + }) = lhs_type { if *rhs == TypeId::ZERO { crate::utilities::notify!("TODO only if sensible"); @@ -103,14 +103,8 @@ pub fn narrow_based_on_expression( crate::utilities::notify!("maybe subtract LHS"); } } else { - if let Type::RootPolyType(PolyNature::Parameter { .. }) = - types.get_type_by_id(*lhs) - { - crate::utilities::notify!( - "lhs is {:?} with {:?}", - lhs, - types.get_type_by_id(*rhs) - ); + if let Type::RootPolyType(PolyNature::Parameter { .. }) = lhs_type { + crate::utilities::notify!( "lhs is {:?} with {:?}", lhs_type, rhs); } if negate && lhs == rhs { @@ -118,7 +112,9 @@ pub fn narrow_based_on_expression( return; } - let lhs = get_origin(*lhs, types); + // let lhs = get_origin(*lhs, types); + let lhs = *lhs; + let rhs = *rhs; let result = if negate { // TODO wip @@ -126,7 +122,7 @@ pub fn narrow_based_on_expression( let mut result = Vec::new(); build_union_from_filter( lhs, - Filter::Not(&Filter::IsType(*rhs)), + Filter::Not(&Filter::IsType(rhs)), &mut result, information, types, @@ -138,19 +134,35 @@ pub fn narrow_based_on_expression( } else { crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::Not, - *rhs, + rhs, types, ) }; types.new_narrowed(lhs, narrowed_to) } else { - *rhs + rhs }; into.insert(lhs, result); - // PROPERTY HERE - if let Type::Constructor(Constructor::Property { + + // CONDITION NARROWING HERE ((x ? 1 : 2) = 1 => x) + // There are missed conditons around things like `typeof` etc (oh well) + // it should be done higher up + if let Type::Constructor(Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) = types.get_type_by_id(lhs) { + if crate::types::helpers::type_equal(*truthy_result, rhs, types) { + narrow_based_on_expression(*condition, false, into, information, types); + } else if crate::types::helpers::type_equal(*otherwise_result, rhs, types) { + narrow_based_on_expression(*condition, true, into, information, types); + } + } + // PROPERTY NARROWING HERE (x.a: b => x: {a: b}) + else if let Type::Constructor(Constructor::Property { on, under, result: _, diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index b3fcc168..3464c932 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -100,6 +100,18 @@ pub fn types_are_disjoint( crate::utilities::notify!("{:?}", (rhs, rhs_inner)); subtyping::type_is_subtype(rhs_inner, lhs, &mut state, information, types).is_subtype() + } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { + types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) + || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) + } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { + types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) + || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) + } else if let Some(lhs) = super::get_constraint(lhs, types) { + // TODO not sure whether these should be here? + types_are_disjoint(lhs, rhs, already_checked, information, types) + } else if let Some(rhs) = super::get_constraint(rhs, types) { + // TODO not sure whether these should be here? + types_are_disjoint(lhs, rhs, already_checked, information, types) } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments, @@ -133,24 +145,60 @@ pub fn types_are_disjoint( ), ) { let result = lhs % rhs != 0.; - crate::utilities::notify!("{:?} {:?}", lhs, rhs); + // crate::utilities::notify!("{:?} {:?}", lhs, rhs); result } else { - crate::utilities::notify!("Here {:?}", lhs); + // crate::utilities::notify!("Here {:?}", lhs); true } - } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_ty { - types_are_disjoint(lhs, *rhs_lhs, already_checked, information, types) - || types_are_disjoint(lhs, *rhs_rhs, already_checked, information, types) - } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { - types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) - || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) - } else if let Some(lhs) = super::get_constraint(lhs, types) { - // TODO not sure whether these should be here? - types_are_disjoint(lhs, rhs, already_checked, information, types) - } else if let Some(rhs) = super::get_constraint(rhs, types) { - // TODO not sure whether these should be here? - types_are_disjoint(lhs, rhs, already_checked, information, types) + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), + arguments, + }) = rhs_ty + { + crate::utilities::notify!("Here"); + if let Type::Constant(Constant::Number(rhs)) = types.get_type_by_id( + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), + ) { + if let Type::Constant(Constant::Number(lhs)) = lhs_ty { + crate::utilities::notify!("{:?} {} {}", on, lhs, rhs); + if *on == TypeId::GREATER_THAN { + lhs < rhs + } else { + lhs > rhs + } + } else { + crate::utilities::notify!("Unsure here {:?}", (lhs_ty, rhs_ty)); + false + } + } else { + crate::utilities::notify!("Unsure here"); + false + } + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), + arguments, + }) = lhs_ty + { + crate::utilities::notify!("Here"); + if let Type::Constant(Constant::Number(lhs)) = types.get_type_by_id( + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), + ) { + if let Type::Constant(Constant::Number(rhs)) = rhs_ty { + crate::utilities::notify!("{:?} {} {}", on, lhs, rhs); + if *on == TypeId::GREATER_THAN { + lhs > rhs + } else { + lhs < rhs + } + } else { + crate::utilities::notify!("Unsure here {:?}", (lhs_ty, rhs_ty)); + false + } + } else { + crate::utilities::notify!("Unsure here"); + false + } } else if let Type::Constant(lhs_cst) = lhs_ty { if let Type::Constant(rhs_cst) = rhs_ty { lhs_cst != rhs_cst @@ -177,20 +225,6 @@ pub fn types_are_disjoint( { // TODO check properties false - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: _on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), - arguments: _, - }) = rhs_ty - { - crate::utilities::notify!("TODO"); - false - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: _on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), - arguments: _, - }) = lhs_ty - { - crate::utilities::notify!("TODO"); - false } else { crate::utilities::notify!( "{:?} cap {:?} == empty ? cases. Might be missing, calling disjoint", diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index c48c9d59..8436a1f9 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -190,13 +190,11 @@ pub enum Type { /// - Can be used for subtypes (aka N aliases number then more types on top) /// - **Does not imply .prototype = ** AliasTo { - to: TypeId, name: String, parameters: Option>, + to: TypeId, }, - /// For number and other rooted types - /// - /// Although they all alias Object + /// Properties are in environment (for declaration merging) Interface { name: String, parameters: Option>, @@ -1059,7 +1057,7 @@ pub(crate) mod helpers { matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) } - /// For quick checking + /// For quick checking. Wraps [`subtyping::type_is_subtype`] #[must_use] pub fn simple_subtype( expr_ty: TypeId, @@ -1101,4 +1099,17 @@ pub(crate) mod helpers { false } } + + // TODO narrowed as well + pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool { + if lhs == rhs { + true + } else if let (Type::Constant(lhs), Type::Constant(rhs)) = + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + lhs == rhs + } else { + false + } + } } diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index c28315a1..6cc25c28 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -200,7 +200,7 @@ pub fn set_property( result_union: _, }) = types.get_type_by_id(on) { - crate::utilities::notify!("Here"); + crate::utilities::notify!("Cascading assigment bc of conditional result"); let truthy = *truthy_result; let otherwise_result = *otherwise_result; diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 27a4f174..8f4ecbad 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -149,19 +149,19 @@ impl Default for TypeStore { extends: TypeId::NUMBER_TYPE, }), Type::AliasTo { - to: TypeId::NUMBER_TYPE, name: "GreaterThan".into(), parameters: Some(vec![TypeId::NUMBER_GENERIC]), + to: TypeId::NUMBER_TYPE, }, Type::AliasTo { - to: TypeId::NUMBER_TYPE, name: "LessThan".into(), parameters: Some(vec![TypeId::NUMBER_GENERIC]), + to: TypeId::NUMBER_TYPE, }, Type::AliasTo { - to: TypeId::NUMBER_TYPE, name: "MultipleOf".into(), parameters: Some(vec![TypeId::NUMBER_GENERIC]), + to: TypeId::NUMBER_TYPE, }, // Intermediate for the below Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { @@ -174,23 +174,23 @@ impl Default for TypeStore { Type::And(TypeId::NUMBER_TYPE, TypeId::NOT_NOT_A_NUMBER), Type::AliasTo { name: "Literal".into(), - to: TypeId::T_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::T_TYPE, }, Type::AliasTo { name: "Exclusive".into(), - to: TypeId::T_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::T_TYPE, }, Type::AliasTo { name: "Not".into(), - to: TypeId::ANY_TYPE, parameters: Some(vec![TypeId::T_TYPE]), + to: TypeId::ANY_TYPE, }, Type::AliasTo { name: "CaseInsensitive".into(), - to: TypeId::STRING_TYPE, parameters: Some(vec![TypeId::STRING_GENERIC]), + to: TypeId::STRING_TYPE, }, Type::RootPolyType(PolyNature::Open(TypeId::BOOLEAN_TYPE)), Type::RootPolyType(PolyNature::Open(TypeId::NUMBER_TYPE)), @@ -298,17 +298,28 @@ impl TypeStore { return lhs; } - // (left and right) distributivity. - if let Type::Or(or_lhs, or_rhs) = self.get_type_by_id(lhs) { + // (left and right) distributivity & some other reductions on singleton types bc why not + // TODO sort intrinsics? + let lhs_type = self.get_type_by_id(lhs); + let rhs_type = self.get_type_by_id(rhs); + if let Type::Or(or_lhs, or_rhs) = lhs_type { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); let new_lhs = self.new_and_type(or_lhs, rhs); let new_rhs = self.new_and_type(or_rhs, rhs); self.new_or_type(new_lhs, new_rhs) - } else if let Type::Or(or_lhs, or_rhs) = self.get_type_by_id(rhs) { + } else if let Type::Or(or_lhs, or_rhs) = rhs_type { let (or_lhs, or_rhs) = (*or_lhs, *or_rhs); let new_lhs = self.new_and_type(lhs, or_lhs); let new_rhs = self.new_and_type(lhs, or_rhs); self.new_or_type(new_lhs, new_rhs) + } else if let Type::Constant(_) = lhs_type { + lhs + } else if let Type::Constant(_) = rhs_type { + rhs + } else if let Type::And(rhs_lhs, rhs_rhs) = rhs_type { + let (rhs_lhs, rhs_rhs) = (*rhs_lhs, *rhs_rhs); + let lhs = self.new_and_type(lhs, rhs_lhs); + self.new_and_type(lhs, rhs_rhs) } else { let ty = Type::And(lhs, rhs); self.register_type(ty) @@ -525,7 +536,14 @@ impl TypeStore { self.register_type(Type::RootPolyType(PolyNature::Open(base))) } + /// Will provide origin rewriting as well pub fn new_narrowed(&mut self, from: TypeId, narrowed_to: TypeId) -> TypeId { + // TODO more. Fixes stuff + let narrowed_to = if let Type::Narrowed { from: _, narrowed_to: existing } = self.get_type_by_id(from) { + self.new_and_type(narrowed_to, *existing) + } else { + narrowed_to + }; self.register_type(Type::Narrowed { from, narrowed_to }) } diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index bc08768d..f02037f1 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -308,39 +308,6 @@ pub(crate) fn type_is_subtype_with_generics( left_result }; } - // Type::And(left, right) => { - // let left_is_operator_right_is_not = - // supertype.is_operator(); - - // // edge cases on edge cases - // // If any of these are true. Then do not perform constraint argument lookup - // let edge_case = left_is_operator_right_is_not; - - // if !edge_case { - - // let right = *right; - // let left_result = type_is_subtype_with_generics( - // (base_type, base_type_arguments), - // (*left, ty_structure_arguments), - // state, - // information, - // types, - // ); - - // return if let SubTypeResult::IsSubType = left_result { - // left_result - // } else { - // type_is_subtype_with_generics( - // (base_type, base_type_arguments), - // (right, ty_structure_arguments), - // state, - // information, - // types, - // ) - // }; - // } - // } - // TODO others Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on @ TypeId::NOT_RESTRICTION, arguments: _, @@ -887,34 +854,94 @@ pub(crate) fn type_is_subtype_with_generics( }; } TypeId::GREATER_THAN => { - // return if let (super_range, Some(sub_range)) = ( - // intrinsics::get_range(base_type, types).unwrap(), - // intrinsics::get_range(ty, types), - // ) { - // if sub_range.contained_in(super_range) { - // SubTypeResult::IsSubType - // } else { - // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - // } - // } else { - // }; - crate::utilities::notify!("TODO"); - return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); + let argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + let argument_type = types.get_type_by_id(argument); + return if let ( + Type::Constant(Constant::Number(value)), + Type::Constant(Constant::Number(subtype_number)), + ) = (argument_type, subtype) + { + if subtype_number > value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments, + }) = subtype + { + let subtype_argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // Transitivity + if argument == subtype_argument { + SubTypeResult::IsSubType + } else { + let subtype_argument = types.get_type_by_id(subtype_argument); + if let ( + Type::Constant(Constant::Number(subtype_number)), + Type::Constant(Constant::Number(value)), + ) = (argument_type, subtype_argument) + { + if subtype_number > value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!("Here"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; } TypeId::LESS_THAN => { - // return if let (super_range, Some(sub_range)) = ( - // intrinsics::get_range(base_type, types).unwrap(), - // intrinsics::get_range(ty, types), - // ) { - // if sub_range.contained_in(super_range) { - // SubTypeResult::IsSubType - // } else { - // SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) - // } - // } else { - // }; - crate::utilities::notify!("TODO"); - return SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch); + let argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + let argument_type = types.get_type_by_id(argument); + return if let ( + Type::Constant(Constant::Number(value)), + Type::Constant(Constant::Number(subtype_number)), + ) = (argument_type, subtype) + { + if subtype_number < value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments, + }) = subtype + { + let subtype_argument = + arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + // Transitivity + if argument == subtype_argument { + SubTypeResult::IsSubType + } else { + let subtype_argument = types.get_type_by_id(subtype_argument); + if let ( + Type::Constant(Constant::Number(subtype_number)), + Type::Constant(Constant::Number(value)), + ) = (argument_type, subtype_argument) + { + if subtype_number < value { + SubTypeResult::IsSubType + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } else { + crate::utilities::notify!("Here"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } + } + } else { + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + }; } TypeId::CASE_INSENSITIVE => { if let Type::Constant(Constant::String(rs)) = subtype { @@ -1433,13 +1460,26 @@ pub(crate) fn type_is_subtype_with_generics( types, ) } - Type::And(a, b) => { - // TODO more - crate::utilities::notify!("Here LHS class, RHS and"); - if *a == base_type || *b == base_type { - SubTypeResult::IsSubType + Type::And(left, right) => { + // This only happens in predicate edge cases (with numbers) + let left_result = type_is_subtype_with_generics( + (base_type, base_type_arguments), + (*left, ty_structure_arguments), + state, + information, + types, + ); + + if let SubTypeResult::IsSubType = left_result { + left_result } else { - SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + type_is_subtype_with_generics( + (base_type, base_type_arguments), + (*right, ty_structure_arguments), + state, + information, + types, + ) } } Type::SpecialObject(SpecialObject::Function(..)) | Type::FunctionReference(..) @@ -1448,7 +1488,7 @@ pub(crate) fn type_is_subtype_with_generics( SubTypeResult::IsSubType } ty => { - crate::utilities::notify!("Does {:?} not match class", ty); + crate::utilities::notify!("{:?} does not match class", ty); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } }, diff --git a/checker/src/utilities/debugging.rs b/checker/src/utilities/debugging.rs index 6ef42907..2a19cee2 100644 --- a/checker/src/utilities/debugging.rs +++ b/checker/src/utilities/debugging.rs @@ -17,7 +17,7 @@ pub(crate) fn is_debug_mode() -> bool { value } else { let new_value = std::env::var("EZNO_DEBUG") - .map(|value| !value.is_empty() || value == "0") + .map(|value| !(value.is_empty() || value == "0")) .unwrap_or_default(); IS_DEBUG_MODE.set(Some(new_value)); new_value diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index 7e36b855..15c56d25 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -41,6 +41,14 @@ impl FloatRange { Self { floor: (Inclusive, on), ceiling: (Inclusive, on) } } + pub fn as_single(self) -> Option { + if let FloatRange { floor: (Inclusive, floor), ceiling: (Inclusive, ceiling) } = self { + (floor == ceiling).then_some(floor) + } else { + None + } + } + /// For disjointness. TODO Think this is correct #[must_use] pub fn overlaps(self, other: Self) -> bool { @@ -142,6 +150,39 @@ impl FloatRange { ceiling.floor() > *floor } + // TODO double check + // TODO what happens when disjoint + #[must_use] + pub fn intersection(self, other: Self) -> Self { + let floor = if self.floor.0 < other.floor.0 { + other.floor + } else { + self.floor + }; + let ceiling = if self.ceiling.0 < other.ceiling.0 { + self.ceiling + } else { + other.ceiling + }; + Self { + floor, + ceiling, + } + } + + // This will try to get cover + // A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2 + // exists is in one of the ranges. Thus in this case it returns None + pub fn try_get_cover(self, other: Self) -> Option { + if self.contained_in(other) { + Some(other) + } else if other.contained_in(self) { + Some(self) + } else { + None + } + } + // TODO more :) } From 925cec9bd1cf03a7b845b84629c3b6f54b77fe5f Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 26 Oct 2024 12:31:07 +0100 Subject: [PATCH 12/24] CLI changes - Remove ast-explorer and REPL from WASM build - Change JS tests - Removing lexing from ast-explorer --- src/ast_explorer.rs | 37 +------ src/cli.rs | 7 +- src/js-cli-and-library/src/cli.js | 15 +-- src/js-cli-and-library/test.mjs | 38 ------- src/js-cli-and-library/tests/cli-test.mjs | 33 ++++++ src/js-cli-and-library/tests/cli.test.mjs | 1 + src/js-cli-and-library/tests/library.test.mjs | 71 ++++++++++++ src/lib.rs | 5 - src/main.rs | 23 +--- src/repl.rs | 104 ++++++++++-------- src/utilities.rs | 21 ++++ src/wasm_bindings.rs | 2 +- 12 files changed, 202 insertions(+), 155 deletions(-) delete mode 100644 src/js-cli-and-library/test.mjs create mode 100644 src/js-cli-and-library/tests/cli-test.mjs create mode 100644 src/js-cli-and-library/tests/cli.test.mjs create mode 100644 src/js-cli-and-library/tests/library.test.mjs diff --git a/src/ast_explorer.rs b/src/ast_explorer.rs index 7879d421..799cd9fd 100644 --- a/src/ast_explorer.rs +++ b/src/ast_explorer.rs @@ -9,7 +9,7 @@ use parser::{source_map::FileSystem, ASTNode, Expression, Module, ToStringOption use crate::{ reporting::report_diagnostics_to_cli, - utilities::{print_to_cli, print_to_cli_without_newline}, + utilities::{print_to_cli, print_to_cli_without_newline, cli_input_resolver}, }; /// REPL for printing out AST from user input @@ -28,7 +28,6 @@ impl ExplorerArguments { pub(crate) fn run( &mut self, fs_resolver: &T, - cli_input_resolver: U, ) { if let Some(ref file) = self.file { let content = fs_resolver.get_content_at_path(file); @@ -40,7 +39,7 @@ impl ExplorerArguments { } else { print_to_cli(format_args!("ezno ast-explorer\nUse #exit to leave. Also #switch-mode *mode name* and #load-file *path*")); loop { - let input = cli_input_resolver(self.nested.to_str()).unwrap_or_default(); + let input = cli_input_resolver(self.nested.to_str()); if input.is_empty() { continue; @@ -80,7 +79,6 @@ pub(crate) enum ExplorerSubCommand { FullAST(FullASTArgs), Prettifier(PrettyArgs), Uglifier(UglifierArgs), - Lexer(LexerArgs), } /// Prints AST for a given expression @@ -111,13 +109,12 @@ pub(crate) struct PrettyArgs {} #[argh(subcommand, name = "uglifier")] pub(crate) struct UglifierArgs {} -/// Prints sources with tokens over -#[derive(FromArgs, Debug, Default)] -#[argh(subcommand, name = "lexer")] -pub(crate) struct LexerArgs {} - impl ExplorerSubCommand { pub fn run(&self, input: String, path: Option) { + if cfg!(target_family = "wasm") { + panic!("Cannot run ast-explorer in WASM because of input callback. Consider reimplementing using library"); + } + match self { ExplorerSubCommand::AST(cfg) => { let mut fs = @@ -194,28 +191,6 @@ impl ExplorerSubCommand { .unwrap(), } } - ExplorerSubCommand::Lexer(_) => { - let mut color = console::Color::Red; - for (section, with) in parser::script_to_tokens(input) { - if with { - let value = style(section).bg(color); - // Cycle through colors - color = match color { - console::Color::Red => console::Color::Green, - console::Color::Green => console::Color::Yellow, - console::Color::Yellow => console::Color::Blue, - console::Color::Blue => console::Color::Magenta, - console::Color::Magenta => console::Color::Cyan, - console::Color::Cyan => console::Color::Red, - _ => unreachable!(), - }; - print_to_cli_without_newline(format_args!("{value}")); - } else { - print_to_cli_without_newline(format_args!("{section}")); - } - } - print_to_cli(format_args!("")); - } } } } diff --git a/src/cli.rs b/src/cli.rs index e9467d97..d7395733 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -173,11 +173,10 @@ fn file_system_resolver(path: &Path) -> Option { } } -pub fn run_cli( +pub fn run_cli( cli_arguments: &[&str], read_file: &T, write_file: U, - cli_input_resolver: V, ) -> ExitCode { let command = match FromArgs::from_args(&["ezno-cli"], cli_arguments) { Ok(TopLevel { nested }) => nested, @@ -398,12 +397,12 @@ pub fn run_cli { - repl.run(read_file, cli_input_resolver); + repl.run(read_file); // TODO not always true ExitCode::SUCCESS } CompilerSubCommand::Repl(argument) => { - crate::repl::run_repl(cli_input_resolver, argument); + crate::repl::run_repl(argument); // TODO not always true ExitCode::SUCCESS } // CompilerSubCommand::Run(run_arguments) => { diff --git a/src/js-cli-and-library/src/cli.js b/src/js-cli-and-library/src/cli.js index 863f1e5e..1bfea6a4 100644 --- a/src/js-cli-and-library/src/cli.js +++ b/src/js-cli-and-library/src/cli.js @@ -21,13 +21,10 @@ function writeFile(path, content) { writeFileSync(path, content) } -function readFromCLI(prompt_msg) { - if (typeof Deno !== "undefined") { - return prompt(`${prompt_msg}>`); - } else { - console.error("Prompt not supported in NodeJS (sync issue)"); - throw new Error("Prompt not supported in NodeJS"); - } +// Fix because REPL requires syncronous stdin input which isn't +// TODO also ast-explorer +if (cliArguments.length === 1 && (cliArguments[0] === "repl" || cliArguments[0] === "ast-explorer")) { + console.error("TODO") +} else { + run_cli(cliArguments, readFile, writeFile); } - -run_cli(cliArguments, readFile, writeFile, readFromCLI); diff --git a/src/js-cli-and-library/test.mjs b/src/js-cli-and-library/test.mjs deleted file mode 100644 index 42c21cba..00000000 --- a/src/js-cli-and-library/test.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import { check, parse_expression, get_version } from "./dist/initialised.mjs"; -import assert, { deepStrictEqual } from "node:assert"; - -function buildTest() { - // TODO -} - -buildTest() - -function checkTest() { - const example = "const x: 4 = 2;" - const output = check("input.ts", (_path) => example); - deepStrictEqual(output.diagnostics, [ - { - reason: "Type 2 is not assignable to type 4", - position: { start: 13, end: 14, source: 2 }, - labels: [ - [ - "Variable declared with type 4", - { - end: 10, - source: 2, - start: 9, - }, - ], - ], - kind: "error" - }, - ], - ); - console.log("WASM: check test passed") -} - -checkTest() - -console.log(parse_expression("x = 4 + 2")) - -console.log(get_version()) \ No newline at end of file diff --git a/src/js-cli-and-library/tests/cli-test.mjs b/src/js-cli-and-library/tests/cli-test.mjs new file mode 100644 index 00000000..17952cdb --- /dev/null +++ b/src/js-cli-and-library/tests/cli-test.mjs @@ -0,0 +1,33 @@ +import { equal } from "node:assert"; +import { test } from "node:test"; +import { spawn } from "node:child_process"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +function read(child) { + return new Promise((res, rej) => { + child.stdout.on("data", (d) => res(decoder.decode(d))); + child.stderr.on("data", (d) => res(decoder.decode(d))); + }) +} + +function write(child, command) { + const promise = new Promise((res, rej) => { child.stdin.addListener("finish", res) }); + child.stdin.write(command + "\n"); + return promise +} + +// Use Deno as nodejs repl doesn't work at the moment +const child = spawn("deno", ["run", "--allow-read", "./dist/cli.mjs", "repl"]); + +console.dir(await read(child)); +console.dir(await write(child, "print_type(4);")); +console.dir(await read(child)); +console.dir(await write(child, "close()")); + +// test("Parse from CLI", (t) => { +// t.test("temp", async () => { + +// }) +// }) \ No newline at end of file diff --git a/src/js-cli-and-library/tests/cli.test.mjs b/src/js-cli-and-library/tests/cli.test.mjs new file mode 100644 index 00000000..0ffdd02f --- /dev/null +++ b/src/js-cli-and-library/tests/cli.test.mjs @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/src/js-cli-and-library/tests/library.test.mjs b/src/js-cli-and-library/tests/library.test.mjs new file mode 100644 index 00000000..53c784a4 --- /dev/null +++ b/src/js-cli-and-library/tests/library.test.mjs @@ -0,0 +1,71 @@ +import { check, parse_expression, get_version, experimental_build } from "../dist/initialised.mjs"; +import { deepStrictEqual } from "node:assert"; +import { test } from "node:test"; +import { inspect } from "node:util"; + +console.log(`Running ezno@${get_version()}*`) + +test("Type checking on code diagnostics", (t) => { + t.test("type check", () => { + const example = "const x: 4 = 2;" + const output = check("input.ts", (_path) => example); + deepStrictEqual(output.diagnostics, [ + { + reason: "Type 2 is not assignable to type 4", + position: { start: 13, end: 14, source: 2 }, + labels: [ + [ + "Variable declared with type 4", + { + end: 10, + source: 2, + start: 9, + }, + ], + ], + kind: "error" + }, + ]); + }); +}); + +test("Compiling", (t) => { + t.test("Compile", () => { + const example = "const x: 4 = 2 + 2;" + const output = experimental_build("input.ts", (_path) => example, true); + deepStrictEqual(output, { + Ok: { + outputs: [{ output_path: 'out.js', content: 'const x=2+2', mappings: '' }], + diagnostics: [] + } + }); + // console.log(inspect(output, { depth: Infinity, colors: true })); + }) +}); + +test("Parsing", (t) => { + t.test("expressions", () => { + const expression = parse_expression("x = 4 + 2"); + + // console.log(inspect(expression, { depth: Infinity, colors: true })); + + deepStrictEqual(expression, { + Ok: { + Assignment: { + lhs: { + VariableOrPropertyAccess: { Variable: ['x', { start: 0, end: 1 }] } + }, + rhs: { + BinaryOperation: { + lhs: { NumberLiteral: [{ Number: 4 }, { start: 4, end: 5 }] }, + operator: 'Add', + rhs: { NumberLiteral: [{ Number: 2 }, { start: 8, end: 9 }] }, + position: { start: 4, end: 9 } + } + }, + position: { start: 0, end: 9 } + } + } + }); + }) +}) diff --git a/src/lib.rs b/src/lib.rs index a81c0908..b6f7999b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,11 +34,6 @@ where } } -/// prompt -> response -pub trait CLIInputResolver: Fn(&str) -> Option {} - -impl CLIInputResolver for T where T: Fn(&str) -> Option {} - pub trait WriteToFS: Fn(&std::path::Path, String) {} impl WriteToFS for T where T: Fn(&std::path::Path, String) {} diff --git a/src/main.rs b/src/main.rs index db171795..898f4d07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,27 +15,6 @@ use ezno_lib::cli::run_cli; use std::io; -#[cfg(target_family = "windows")] -pub(crate) fn cli_input_resolver(prompt: &str) -> String { - print!("{prompt}> "); - io::Write::flush(&mut io::stdout()).unwrap(); - let mut input = String::new(); - let std_in = &mut io::stdin(); - let _n = multiline_term_input::read_string(std_in, &mut input); - input -} - -#[cfg(target_family = "unix")] -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn cli_input_resolver(prompt: &str) -> String { - print!("{prompt}> "); - io::Write::flush(&mut io::stdout()).unwrap(); - let mut input = String::new(); - let std_in = &mut io::stdin(); - let _n = std_in.read_line(&mut input).unwrap(); - input -} - fn main() -> std::process::ExitCode { fn read_from_file(path: &std::path::Path) -> Option { std::fs::read_to_string(path).ok() @@ -48,5 +27,5 @@ fn main() -> std::process::ExitCode { let arguments = std::env::args().skip(1).collect::>(); let arguments = arguments.iter().map(String::as_str).collect::>(); - run_cli(&arguments, &read_from_file, write_to_file, |p| Some(cli_input_resolver(p))) + run_cli(&arguments, &read_from_file, write_to_file) } diff --git a/src/repl.rs b/src/repl.rs index 1978a1a1..0d644c6e 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -8,7 +8,7 @@ use parser::{Expression, Module, Statement}; use crate::reporting::report_diagnostics_to_cli; use crate::utilities::print_to_cli; -/// Run project repl using deno. (`deno` command must be in path) +/// Interactive type checking #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "repl")] pub(crate) struct ReplArguments { @@ -33,51 +33,33 @@ fn file_system_resolver(path: &Path) -> Option> { } } -pub(crate) fn run_repl( - cli_input_resolver: U, - ReplArguments { const_as_let, type_definition_module }: ReplArguments, -) { - print_to_cli(format_args!("Entering REPL. Exit with `close()`")); - - let definitions = if let Some(tdm) = type_definition_module { - std::iter::once(tdm).collect() - } else { - std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into()).collect() - }; - - let state = checker::synthesis::interactive::State::new(&file_system_resolver, definitions); - - let mut state = match state { - Ok(state) => state, - Err((diagnostics, fs)) => { - report_diagnostics_to_cli( - diagnostics, - &fs, - false, - crate::utilities::MaxDiagnostics::All, - ) - .unwrap(); - return; - } - }; - - let source = state.get_source_id(); - - loop { - let input = cli_input_resolver(""); - let input = if let Some(input) = input { - if input.is_empty() { - continue; - } else if input.trim() == "close()" { - break; - } +/// Wraps `checker::synthesis::interactive::State` +pub struct ReplSystem { + arguments: ReplArguments, + source: SourceId, + state: checker::synthesis::interactive::State +} - input +impl ReplSystem { + pub fn new(arguments: ReplArguments) -> Result)> { + let definitions = if let Some(tdm) = type_definition_module { + std::iter::once(tdm).collect() } else { - continue; + std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into()).collect() }; + + let state = checker::synthesis::interactive::State::new(&file_system_resolver, definitions)?; + let source = state.get_source_id(); + + ReplSystem { + arguments, + source, + state + } + } - let (from_index, _) = state.get_fs_mut().append_to_file(source, &input); + pub fn execute_statement(&mut self, input: String) { + let (from_index, _) = self.state.get_fs_mut().append_to_file(source, &input); let options = Default::default(); let offset = Some(from_index as u32); @@ -93,7 +75,7 @@ pub(crate) fn run_repl( Module::from_string(input, options) }; - let mut item = match result { + match result { Ok(item) => item, Err(err) => { report_diagnostics_to_cli( @@ -103,11 +85,10 @@ pub(crate) fn run_repl( crate::utilities::MaxDiagnostics::All, ) .unwrap(); - continue; } }; - if const_as_let { + if self.arguments.const_as_let { item.visit_mut( &mut VisitorsMut { statement_visitors_mut: vec![Box::new(crate::transformers::ConstToLet)], @@ -130,6 +111,7 @@ pub(crate) fn run_repl( crate::utilities::MaxDiagnostics::All, ) .unwrap(); + if let Some(last_ty) = last_ty { println!("{last_ty}"); } @@ -146,3 +128,35 @@ pub(crate) fn run_repl( } } } + +pub(crate) fn run_repl( arguments: ReplArguments) { + if cfg!(target_family = "wasm") { + panic!("Cannot run repl in WASM because of input callback. Consider reimplementing using library"); + } + + print_to_cli(format_args!("Entering REPL. Exit with `close()`")); + + let mut system = match ReplSystem::new(arguments) { + Ok(system) => system, + Err((diagnostics, fs)) => { + report_diagnostics_to_cli( + diagnostics, + &fs, + false, + crate::utilities::MaxDiagnostics::All, + ) + .unwrap(); + return; + } + }; + + loop { + let input = cli_input_resolver(""); + if input.is_empty() { + continue; + } else if input.trim() == "close()" { + break; + } + system.execute_statement(input) + } +} diff --git a/src/utilities.rs b/src/utilities.rs index c99aa8e5..b92c40cc 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -89,6 +89,27 @@ pub(crate) fn print_to_cli_without_newline(arguments: Arguments) { io::Write::flush(&mut io::stdout()).unwrap(); } +#[cfg(target_family = "windows")] +pub(crate) fn cli_input_resolver(prompt: &str) -> String { + print!("{prompt}> "); + io::Write::flush(&mut io::stdout()).unwrap(); + let mut input = String::new(); + let std_in = &mut io::stdin(); + let _n = multiline_term_input::read_string(std_in, &mut input); + input +} + +#[cfg(target_family = "unix")] +#[allow(clippy::unnecessary_wraps)] +pub(crate) fn cli_input_resolver(prompt: &str) -> String { + print!("{prompt}> "); + io::Write::flush(&mut io::stdout()).unwrap(); + let mut input = String::new(); + let std_in = &mut io::stdin(); + let _n = std_in.read_line(&mut input).unwrap(); + input +} + #[derive(Debug)] pub(crate) enum MaxDiagnostics { All, diff --git a/src/wasm_bindings.rs b/src/wasm_bindings.rs index b8390b0f..07b83e0a 100644 --- a/src/wasm_bindings.rs +++ b/src/wasm_bindings.rs @@ -23,7 +23,7 @@ export function experimental_build( pub fn experimental_build_wasm( entry_path: String, fs_resolver_js: &js_sys::Function, - config: BuildConfig, + config: crate::build::BuildConfig, ) -> JsValue { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); From 29de8851efbc2bd6a4a3d2e5f333917445a6ee62 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 7 Nov 2024 15:23:40 +0000 Subject: [PATCH 13/24] Add modulo class + 1/2 way through improving things --- checker/definitions/internal.ts.d.bin | Bin 35442 -> 35478 bytes checker/src/features/narrowing.rs | 85 +++++++++----- checker/src/types/disjoint.rs | 154 ++++++++++++-------------- checker/src/types/store.rs | 26 ++++- checker/src/utilities/float_range.rs | 22 +--- checker/src/utilities/mod.rs | 1 + checker/src/utilities/modulo_class.rs | 149 +++++++++++++++++++++++++ 7 files changed, 300 insertions(+), 137 deletions(-) create mode 100644 checker/src/utilities/modulo_class.rs diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index b85c2a79514da18cd72d6c7630936753f4c542a5..976c802f2f06ae5be2230ce7e5a60ede9b31c6af 100644 GIT binary patch delta 2803 zcmZ8jX>1$U5uUfpQzUhZv@T1EDC<5fQTIWKlqgXWsoN=fNh@+C)24Q5a%D?yUD|O{ z8EV|r9>*?HI7!{aNek3LtQJY@IB@}|aU1_IP}IGE+CUpOEgGO{k)kL9w6m1xNPgtZ z?!KKjZ{Ey&ANkL7-1%p@J>pSGt_=u*{bKDxC@h*E2PgwWP!23FiIM{FN*%y!b!UE4 zdS<;4@3T^TnJZ{d3CNNnt|+siAV5kZKsE`0emp8Kg<>K=VHSX$1VBr3E35QW22fds zf8{FD^;4@$^Kv)_@U9);wjI-0ei3<$$zfW0{4szv5&+&OL;yN106ms7vo_?pl1}YF zG%7~{zDE|XCalb%*lbXYh*B^S?-%w0ERs+(?xf3H8$huQhxt5wkrm?4I4kxr>zR?< zqOth{Vt`Kh-9279@1I1yKp? z2M{Sq2kLN~<)EF*!|Rl$2`(=~;}aBmdzMaco&*4kiwkg#+Z}J(+{X{vzGzffHG*MO zjK!w+3sN2Eq~)b~F{%OGq_{Y+5=l)Y!UO4kaZyl0hefZn5RyX5Y7V6MmcvRY5*Ei6 z>Ons*QvwBv2ji%GM2rT6m{QOtFa33NS8H`Y9?gr~^YX zJ}c-p^E(NtoBPtXlY+D;#+_i86k`!8kT?vQ!*VESfE4diM2;#xQ4w~7p?|9bry!BV zK}hvVYUoK9F3`mi_0WReOU<|_M%}7YAt+1X)k-jKdK-`qw?new#*#e|7e~OH48SD^ zSG{1Ii9|MYW(U*Ms-y@jiB|KeWeCX8VAobFk3*W9GV7(#rC3O7~aa&)O*P=sm7 zq<$wy%4)wD3q(T^MUGB@el$cS5WVM} zHHN@KOX%C@Pqf8e*o467#+zD$KCU*di&t^f$#)are$L%(0QeFcCq2bf7+&XoOW^z} zE3pFnf>XhIh&@ejA7|$XCI^`cOee4Q^y{1oloDR;R2@Xga)q5F&uzSlh8jKrEf%i_ z7+}{j0Di=E#>q%wDj3Un6}W$aoAi2wsh|yWYU9h?dM$v1<>lSufyo8B+S?N5eUtwF~vByYU|u8@_F6 zEbB~!W=OOIwFeYYV$>7pL$O53LF5kcxG$py@1#Zw%X#&{`X%BAVNs;QYEkn@OHlge?QfNV;!f>zcK*ib03q=1pKh$ z9yavW;YNE6-gG+7dOT?DXvdksCj50%8{TVg#Ro0{ueb^}{x}|Hc&?=iJ)U+f9jL>r zZaXeawqdJt7@wPN#Q!)8(9_n6w_B?)r_0WYHafc8%<^d!P?KFFeUdlhdiNjlO`5j| z;yatH-Yf#(#44(Jf>4*E8|4!~Z8le8BUq`sfu8@9@eLy-BGVrCbSD6ld<420N9O}q} znRtr4-e;GzL~tZD@M3@c*@060_h1Wt+HwakjuznOrabty7Aqd@>Bi}ydi=hl0oUBs zcwwdzf9Wc~+tV%h-{x|BuhWkY`YQ0s(0A}9XEB~?uf}h9t646-;e9jr1>(+3I6(Sk z_7&2XSp(@yEN$b|u!SWLnwALZ3ah2SJ4n=!EG`#LwAA9&!G8RoyAH=(vsltrhUwF_ zc&yFNEO@C+q~jUd4_0qGo)PBx8N*Fo=H66EX9(R2Hfkf!?&Be@YG-x4!Y~H-%*XfbJU>4 z(TdpQLT=_+yz8#SxW}sH!Nis0F_#Tr_gIZ4&4a`rG{7QC^a)Dx@MtRzPZr?9v;)0f z7aE;)I5F@V{$zA5qeM^Jb4N*M!WT(1obcVpKaDu>Tyqu5j@|hDR2SCvx8V;)D)6+U y0H2@yDSo;4+Zpc~0UEgM6o4#{Nq3^Y?;f5Us>T!jh4?Q|E!sw#Ha^*Rtm*%bh01dP delta 2904 zcmZ8jYitzP6+UgV~fGHsB z6gcSuI86p%2~8bPB09ji4FDI&036{3RhQ>|$)uJ9*j^2=s~X2x3AVAKCI1@qvb-wu zy|3R}5AYfpfO#yb%`X7d)B@DkE^Vmpcg9ltqDn$hql!u!>}>~_Ap@|@QD{1*DpAqP zHfwlcc1nw<0N$wq_;n3Br1C1$Ncamo0T!D9zC%X&2Ipp{6w2#LKESnnyu?cLthDuP zETsUfDWn7n@jbQiN-CkLV!3t2be7_!bLC5aslh3!Bu(jrf&gczCg;cia-v#fZq_*8 z1uF0*qsX?DtjAK*F@?_ZhYo;SWHfYmPL1d>O$9hc2mBTpKquJtBoaz8BBzuZ;6Y9I zWUpi#OzK=bZiO7bO2vxl3&m+#L`%+kWnJD4*;CuWnay}mi!S)VNHVyxp9^gM*@TwV zed?heu==S2GfHv@EaS?IZ$9A$n{Pf6pG(CKDV<;*NG9b48(2qUx{{RR9Z*2kr4022 zuY!Bc_B2h4XvydnaQoGJ)5c|WMj3<*C`Q3LrD<_RRynZUb&Ln(#4wl+X|ZSy*fW^~ z6kV=_oIp$+j%%`B0v64Hp%ZdOl~ii?epzjK045foOOd0R8egbSgFP@8*JFve5}IxW z7nO|u1~RZ{2ivH2I8)AA$Q`1p`spUsR7|JSZGtSuLJxsFK$*z%Mkb|@7gT2G`VP^= zOx0IqNV5ZSje4aMzIi2LRB0zTb|e*<>Le0w25V5GNlLO#q?^Ie2-t_@L`;|Cu_MZc zdn6eHOG;11)EP5aR0GZ?$no9-QW&g5ggz?ZE^zKqBll--1h=>+hjCFx#rv1vFy~m) zWei|a643vtB#_y}-)<$&Qk3Xo@UfrEc>u3Vg3I;%L>s{4LB zm=ki6NZHC6*fQmdO)o$P$4FsE$=4YTVaFL9V+<^DMV%p;dzN8KVI_v#ZC{O!Y(k%OFSqsdSq8U$rm4)yb)Uq%cVMw9~LH-SLOYU74#V+ zatsRTq@rs9$8syB>kQv4Y$|nuIFD&6rbgl8`)`{#zFeT9ntK6%=ci2%v5tl|fra>!)s1Z6 z0hKU9c8|D32Ncm|W}3qUZDQN3)ET={e?<;65y{Zy%)1$i9#s|kP>qu%b;?shQH3-T z7JH!&IAg;m;fG9MIhtYb3VCVJR@nCNpl>{?sWfOi41nd?`?8H6mvrTtAC~;(j#9ZC zdxL|xrF%2-U^o6S(1>3QSC;1zU4@BuRpNGXSENiAr@Za>`#=-!>VFuOZ`5dX9Fwwd>caiJjnJ)gIdkUWlwB|RG8VD0@k~EehdK zf70IHomhi)gDtq)QaIsvco0hl1Mb>Nr=r!T(a7+F_Nb7`&_9f$3{~h<> zhZCLn=TJ9Z3sqoxr~;2Y_FVoEt61vGPslKk83Hu(bh8Tsoh5ASS$jWDw`a5#e*XC5tLgAdrNgrL{W%j-8MSvk^Aj4;s# zI3Fjsoz5T{?(FTs*4=O7D?`J0IJ^aeo-Xu_G^2gA9^1w|_-=0pa$grd8GI4n^48<+ ttu@Sz_HFxIcL { let lhs_type = types.get_type_by_id(*lhs); - if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = lhs_type { - // let from = get_origin(*on, types); - let from = *on; //, types); + if let Type::Constructor(Constructor::TypeOperator(TypeOperator::TypeOf(on))) = + lhs_type + { + let from = *on; + let origin = get_origin(from, types); + if let Type::Constant(Constant::String(c)) = types.get_type_by_id(*rhs) { let type_from_name = crate::features::string_name_to_type(c); if let Some(type_from_name) = type_from_name { @@ -66,10 +69,10 @@ pub fn narrow_based_on_expression( types.new_or_type_from_iterator(result) }; let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(from, narrowed); + into.insert(origin, narrowed); } else { let narrowed = types.new_narrowed(from, type_from_name); - into.insert(from, narrowed); + into.insert(origin, narrowed); } } else { crate::utilities::notify!("Type name was (shouldn't be here)"); @@ -84,27 +87,37 @@ pub fn narrow_based_on_expression( result: _, }) = lhs_type { - if *rhs == TypeId::ZERO { - crate::utilities::notify!("TODO only if sensible"); + if negate { + crate::utilities::notify!("TODO do we not divisable by?"); + return; + } + let (from, rhs, modulo) = (*operand, *rhs, *modulo); - let (from, modulo) = (*operand, *modulo); - if negate { - crate::utilities::notify!("TODO do we not divisable by?"); - } else { - let narrowed_to = crate::types::intrinsics::new_intrinsic( - &crate::types::intrinsics::Intrinsic::MultipleOf, - modulo, - types, - ); - let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(from, narrowed); - } + let origin = get_origin(from, types); + let narrowed_to = crate::types::intrinsics::new_intrinsic( + &crate::types::intrinsics::Intrinsic::MultipleOf, + modulo, + types, + ); + + // TODO also from == x - 1 etc + let narrowed_to = if rhs != TypeId::ZERO { + types.register_type(Type::Constructor( + crate::types::Constructor::BinaryOperator { + lhs: narrowed_to, + operator: super::operations::MathematicalAndBitwise::Add, + rhs, + result: TypeId::NUMBER_TYPE, + }, + )) } else { - crate::utilities::notify!("maybe subtract LHS"); - } + narrowed_to + }; + let narrowed = types.new_narrowed(from, narrowed_to); + into.insert(origin, narrowed); } else { if let Type::RootPolyType(PolyNature::Parameter { .. }) = lhs_type { - crate::utilities::notify!( "lhs is {:?} with {:?}", lhs_type, rhs); + crate::utilities::notify!("lhs is {:?} with {:?}", lhs_type, rhs); } if negate && lhs == rhs { @@ -145,7 +158,6 @@ pub fn narrow_based_on_expression( into.insert(lhs, result); - // CONDITION NARROWING HERE ((x ? 1 : 2) = 1 => x) // There are missed conditons around things like `typeof` etc (oh well) // it should be done higher up @@ -154,20 +166,21 @@ pub fn narrow_based_on_expression( truthy_result, otherwise_result, result_union: _, - }) = types.get_type_by_id(lhs) { + }) = types.get_type_by_id(get_origin(lhs, types)) + { if crate::types::helpers::type_equal(*truthy_result, rhs, types) { narrow_based_on_expression(*condition, false, into, information, types); } else if crate::types::helpers::type_equal(*otherwise_result, rhs, types) { narrow_based_on_expression(*condition, true, into, information, types); } - } + } // PROPERTY NARROWING HERE (x.a: b => x: {a: b}) else if let Type::Constructor(Constructor::Property { on, under, result: _, mode: _, - }) = types.get_type_by_id(lhs) + }) = types.get_type_by_id(get_origin(lhs, types)) { let on = *on; let narrowed_to = if !negate @@ -214,6 +227,12 @@ pub fn narrow_based_on_expression( types, ); let narrowed = types.new_narrowed(lhs, narrowed_to); + // temp fix + let narrowed = if let Some(existing) = into.get(&lhs) { + types.new_and_type(*existing, narrowed) + } else { + narrowed + }; into.insert(lhs, narrowed); } else if types.get_type_by_id(rhs).is_dependent() { let narrowed_to = crate::types::intrinsics::new_intrinsic( @@ -222,6 +241,12 @@ pub fn narrow_based_on_expression( types, ); let narrowed = types.new_narrowed(rhs, narrowed_to); + // temp fix + let narrowed = if let Some(existing) = into.get(&rhs) { + types.new_and_type(*existing, narrowed) + } else { + narrowed + }; into.insert(rhs, narrowed); } } @@ -487,14 +512,14 @@ pub(crate) fn build_union_from_filter( information: &impl InformationChain, types: &TypeStore, ) { - if let Some((_condition, lhs, rhs)) = get_conditional(on, types) { - build_union_from_filter(lhs, filter, found, information, types); - build_union_from_filter(rhs, filter, found, information, types); - } else if let Some(constraint) = crate::types::get_constraint(on, types) { + if let Some(constraint) = crate::types::get_constraint(on, types) { build_union_from_filter(constraint, filter, found, information, types); } else if let TypeId::BOOLEAN_TYPE = on { build_union_from_filter(TypeId::TRUE, filter, found, information, types); build_union_from_filter(TypeId::FALSE, filter, found, information, types); + } else if let Some((_condition, lhs, rhs)) = get_conditional(on, types) { + build_union_from_filter(lhs, filter, found, information, types); + build_union_from_filter(rhs, filter, found, information, types); } else { let not_already_added = !found.contains(&on); if not_already_added && filter.type_matches_filter(on, information, types, false) { diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 3464c932..42e9c361 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -112,93 +112,32 @@ pub fn types_are_disjoint( } else if let Some(rhs) = super::get_constraint(rhs, types) { // TODO not sure whether these should be here? types_are_disjoint(lhs, rhs, already_checked, information, types) - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, - }) = lhs_ty + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, + ) = lhs_ty { - // Little bit complex here because dealing with decimal types, not integers - if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( - types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), - ), - rhs_ty, - ) { - let result = rhs % lhs != 0.; - crate::utilities::notify!("{:?} {:?}", rhs, lhs); - result - } else { - crate::utilities::notify!("Here"); - true - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::MULTIPLE_OF, - arguments, - }) = rhs_ty + number_modulo_disjoint(args, rhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, + ) = rhs_ty { - let lhs = types.get_type_by_id(lhs); - // Little bit complex here because dealing with decimal types, not integers - if let (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) = ( - lhs, - types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), - ), - ) { - let result = lhs % rhs != 0.; - // crate::utilities::notify!("{:?} {:?}", lhs, rhs); - result - } else { - // crate::utilities::notify!("Here {:?}", lhs); - true - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), - arguments, - }) = rhs_ty + number_modulo_disjoint(args, lhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN | TypeId::LESS_THAN, + arguments: _, + }, + ) = rhs_ty { - crate::utilities::notify!("Here"); - if let Type::Constant(Constant::Number(rhs)) = types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), - ) { - if let Type::Constant(Constant::Number(lhs)) = lhs_ty { - crate::utilities::notify!("{:?} {} {}", on, lhs, rhs); - if *on == TypeId::GREATER_THAN { - lhs < rhs - } else { - lhs > rhs - } - } else { - crate::utilities::notify!("Unsure here {:?}", (lhs_ty, rhs_ty)); - false - } - } else { - crate::utilities::notify!("Unsure here"); - false - } - } else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), - arguments, - }) = lhs_ty + number_range_disjoint(args, lhs, types) + } else if let Type::PartiallyAppliedGenerics( + args @ PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN | TypeId::LESS_THAN, + arguments: _, + }, + ) = lhs_ty { - crate::utilities::notify!("Here"); - if let Type::Constant(Constant::Number(lhs)) = types.get_type_by_id( - arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(), - ) { - if let Type::Constant(Constant::Number(rhs)) = rhs_ty { - crate::utilities::notify!("{:?} {} {}", on, lhs, rhs); - if *on == TypeId::GREATER_THAN { - lhs > rhs - } else { - lhs < rhs - } - } else { - crate::utilities::notify!("Unsure here {:?}", (lhs_ty, rhs_ty)); - false - } - } else { - crate::utilities::notify!("Unsure here"); - false - } + number_range_disjoint(args, rhs, types) } else if let Type::Constant(lhs_cst) = lhs_ty { if let Type::Constant(rhs_cst) = rhs_ty { lhs_cst != rhs_cst @@ -236,6 +175,55 @@ pub fn types_are_disjoint( } } +fn number_modulo_disjoint( + this: &PartiallyAppliedGenerics, + other: TypeId, + types: &TypeStore, +) -> bool { + let PartiallyAppliedGenerics { arguments, .. } = this; + let other = types.get_type_by_id(other); + // Little bit complex here because dealing with decimal types, not integers + if let (Type::Constant(Constant::Number(other)), Type::Constant(Constant::Number(this))) = ( + other, + types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()), + ) { + let result = other % this != 0.; + // crate::utilities::notify!("{:?} {:?}", lhs, rhs); + result + } else { + // crate::utilities::notify!("Here {:?}", lhs); + true + } +} + +fn number_range_disjoint( + this: &PartiallyAppliedGenerics, + other: TypeId, + types: &TypeStore, +) -> bool { + let PartiallyAppliedGenerics { on, arguments, .. } = this; + let greater_than = *on == TypeId::GREATER_THAN; + let this_ty = + types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); + if let Type::Constant(Constant::Number(this)) = this_ty { + let other_ty = types.get_type_by_id(other); + if let Type::Constant(Constant::Number(other)) = other_ty { + crate::utilities::notify!("{:?} {} {}", on, other, this); + if greater_than { + other < this + } else { + other > this + } + } else { + crate::utilities::notify!("Unsure here {:?}", (other_ty, this_ty)); + false + } + } else { + crate::utilities::notify!("Unsure here"); + false + } +} + // fn todo() { // // else if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { // // on: TypeId::GREATER_THAN diff --git a/checker/src/types/store.rs b/checker/src/types/store.rs index 8f4ecbad..263ced34 100644 --- a/checker/src/types/store.rs +++ b/checker/src/types/store.rs @@ -538,12 +538,32 @@ impl TypeStore { /// Will provide origin rewriting as well pub fn new_narrowed(&mut self, from: TypeId, narrowed_to: TypeId) -> TypeId { - // TODO more. Fixes stuff - let narrowed_to = if let Type::Narrowed { from: _, narrowed_to: existing } = self.get_type_by_id(from) { - self.new_and_type(narrowed_to, *existing) + let from_ty = self.get_type_by_id(from); + let new_constraint = self.get_type_by_id(narrowed_to); + let (from, existing) = if let Type::Narrowed { from, narrowed_to } = from_ty { + (*from, Some(*narrowed_to)) } else { + (from, None) + }; + // temp fix for adding things. + let narrowed_to = if let ( + Some(existing), + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: + TypeId::GREATER_THAN + | TypeId::LESS_THAN + | TypeId::MULTIPLE_OF + | TypeId::NOT_RESTRICTION, + arguments: _, + }), + ) = (existing, new_constraint) + { + self.new_and_type(existing, narrowed_to) + } else { + // crate::utilities::notify!("{:?}", from_ty); narrowed_to }; + self.register_type(Type::Narrowed { from, narrowed_to }) } diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index 15c56d25..5165bf12 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -150,28 +150,8 @@ impl FloatRange { ceiling.floor() > *floor } - // TODO double check - // TODO what happens when disjoint - #[must_use] - pub fn intersection(self, other: Self) -> Self { - let floor = if self.floor.0 < other.floor.0 { - other.floor - } else { - self.floor - }; - let ceiling = if self.ceiling.0 < other.ceiling.0 { - self.ceiling - } else { - other.ceiling - }; - Self { - floor, - ceiling, - } - } - // This will try to get cover - // A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2 + // A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2 // exists is in one of the ranges. Thus in this case it returns None pub fn try_get_cover(self, other: Self) -> Option { if self.contained_in(other) { diff --git a/checker/src/utilities/mod.rs b/checker/src/utilities/mod.rs index 222c487a..4e59012d 100644 --- a/checker/src/utilities/mod.rs +++ b/checker/src/utilities/mod.rs @@ -3,6 +3,7 @@ mod debugging; pub mod float_range; pub mod map; +pub mod modulo_class; pub mod range_map; pub mod serialization; diff --git a/checker/src/utilities/modulo_class.rs b/checker/src/utilities/modulo_class.rs new file mode 100644 index 00000000..7976e422 --- /dev/null +++ b/checker/src/utilities/modulo_class.rs @@ -0,0 +1,149 @@ +type BetterF64 = ordered_float::NotNan; + +/// x ≡ *class* [mod *modulo*] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ModuloClass { + pub modulo: BetterF64, + pub offset: BetterF64, +} + +// TODO more operations +impl ModuloClass { + pub fn new(modulo: BetterF64, offset: BetterF64) -> Self { + debug_assert!(modulo != 0.); + if modulo > 0f64.try_into().unwrap() { + Self { offset: offset % modulo, modulo } + } else { + // TODO think this is correct. [1]_{-3} = [2]_{3} + let modulo = -modulo; + Self { offset: modulo - (offset % modulo), modulo } + } + } + + pub fn contains(self, value: BetterF64) -> bool { + // Note -0. = 0. + (value - self.offset) % self.modulo == 0. + } + + /// WIP + pub fn disjoint(self, other: Self) -> bool { + if let Ok(gcd) = gcd_of_float(self.modulo, other.modulo) { + (self.offset - other.offset) % gcd == 0. + } else { + crate::utilities::notify!("Here"); + true + } + } + + pub fn intersection(self, _other: Self) -> Option { + todo!() + } + + pub fn get_cover(self, _other: Self) -> Option { + todo!() + } + + pub fn offset(self, offset: BetterF64) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo, self.offset + offset) + } + } + + pub fn multiply(self, multiple: BetterF64) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo * multiple, self.offset) + } + } + + pub fn negate(self) -> Self { + // TODO temp fix + if self.is_default() { + self + } else { + Self::new(self.modulo, self.modulo - self.offset) + } + } + + pub fn is_default(self) -> bool { + self.modulo == f64::EPSILON + } +} + +/// Using Farey algoirthm +/// TODO is there a faster way implemntation +/// Note that numerator and denominator are coprime +fn try_get_numerator_denominator(input: BetterF64) -> Result<(i32, i32), ()> { + const STEPS: usize = 50; + const MARGIN: f64 = 1e-4; + + let integer_part = input.trunc() as i32; + let fractional_part = input.fract(); + if fractional_part == 0. { + return Ok((integer_part, 1)); + } + + let (mut a, mut b, mut c, mut d) = (0, 1, 1, 1); + + for _ in 0..STEPS { + let mediant_float = (a as f64 + b as f64) / (c as f64 + d as f64); + if (fractional_part - mediant_float).abs() < MARGIN { + let numerator = a + b + integer_part * (c + d); + let denominator = c + d; + return Ok((numerator, denominator)); + } else if fractional_part > mediant_float { + a = a + b; + c = c + d; + } else { + b = a + b; + d = c + d; + } + } + + Err(()) +} + +fn gcd_of_float(n1: BetterF64, n2: BetterF64) -> Result { + fn gcd(mut n1: i32, mut n2: i32) -> i32 { + while n2 != 0 { + let t = n2; + n2 = n1 % n2; + n1 = t; + } + n1 + } + + /// n1*n2 = gcd(n1, n2)*lcm(n1, n2) + fn lcm(n1: i32, n2: i32) -> i32 { + (n1 * n2) / gcd(n1, n2) + } + + let (a, b) = try_get_numerator_denominator(n1)?; + let (c, d) = try_get_numerator_denominator(n2)?; + + // gcd(a / b, c / d) = gcd(a, c) / lcm(b, d) + Ok(BetterF64::new(gcd(a, c) as f64 / lcm(b, d) as f64).unwrap()) +} + +// hmmm +impl Default for ModuloClass { + fn default() -> Self { + Self { modulo: f64::EPSILON.try_into().unwrap(), offset: BetterF64::new(0.).unwrap() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO test negatives etc + #[test] + fn gcd() { + assert_eq!(gcd_of_float(1. / 3., 3. / 2.), Ok(3.)); + } +} From e5ac069aee9165b2a07403faf3137ea30acc3711 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 8 Nov 2024 09:45:25 +0000 Subject: [PATCH 14/24] Undo some CLI Stuff --- src/ast_explorer.rs | 37 +++++++++++++--- src/build.rs | 61 +------------------------- src/cli.rs | 23 +++++++--- src/lib.rs | 5 +++ src/main.rs | 23 +++++++++- src/repl.rs | 104 +++++++++++++++++++------------------------- src/utilities.rs | 21 --------- 7 files changed, 123 insertions(+), 151 deletions(-) diff --git a/src/ast_explorer.rs b/src/ast_explorer.rs index 799cd9fd..7879d421 100644 --- a/src/ast_explorer.rs +++ b/src/ast_explorer.rs @@ -9,7 +9,7 @@ use parser::{source_map::FileSystem, ASTNode, Expression, Module, ToStringOption use crate::{ reporting::report_diagnostics_to_cli, - utilities::{print_to_cli, print_to_cli_without_newline, cli_input_resolver}, + utilities::{print_to_cli, print_to_cli_without_newline}, }; /// REPL for printing out AST from user input @@ -28,6 +28,7 @@ impl ExplorerArguments { pub(crate) fn run( &mut self, fs_resolver: &T, + cli_input_resolver: U, ) { if let Some(ref file) = self.file { let content = fs_resolver.get_content_at_path(file); @@ -39,7 +40,7 @@ impl ExplorerArguments { } else { print_to_cli(format_args!("ezno ast-explorer\nUse #exit to leave. Also #switch-mode *mode name* and #load-file *path*")); loop { - let input = cli_input_resolver(self.nested.to_str()); + let input = cli_input_resolver(self.nested.to_str()).unwrap_or_default(); if input.is_empty() { continue; @@ -79,6 +80,7 @@ pub(crate) enum ExplorerSubCommand { FullAST(FullASTArgs), Prettifier(PrettyArgs), Uglifier(UglifierArgs), + Lexer(LexerArgs), } /// Prints AST for a given expression @@ -109,12 +111,13 @@ pub(crate) struct PrettyArgs {} #[argh(subcommand, name = "uglifier")] pub(crate) struct UglifierArgs {} +/// Prints sources with tokens over +#[derive(FromArgs, Debug, Default)] +#[argh(subcommand, name = "lexer")] +pub(crate) struct LexerArgs {} + impl ExplorerSubCommand { pub fn run(&self, input: String, path: Option) { - if cfg!(target_family = "wasm") { - panic!("Cannot run ast-explorer in WASM because of input callback. Consider reimplementing using library"); - } - match self { ExplorerSubCommand::AST(cfg) => { let mut fs = @@ -191,6 +194,28 @@ impl ExplorerSubCommand { .unwrap(), } } + ExplorerSubCommand::Lexer(_) => { + let mut color = console::Color::Red; + for (section, with) in parser::script_to_tokens(input) { + if with { + let value = style(section).bg(color); + // Cycle through colors + color = match color { + console::Color::Red => console::Color::Green, + console::Color::Green => console::Color::Yellow, + console::Color::Yellow => console::Color::Blue, + console::Color::Blue => console::Color::Magenta, + console::Color::Magenta => console::Color::Cyan, + console::Color::Cyan => console::Color::Red, + _ => unreachable!(), + }; + print_to_cli_without_newline(format_args!("{value}")); + } else { + print_to_cli_without_newline(format_args!("{section}")); + } + } + print_to_cli(format_args!("")); + } } } } diff --git a/src/build.rs b/src/build.rs index 14b6753c..0bc7f94f 100644 --- a/src/build.rs +++ b/src/build.rs @@ -42,8 +42,6 @@ pub struct BuildConfig { pub strip_whitespace: bool, #[cfg_attr(target_family = "wasm", serde(default))] pub source_maps: bool, - #[cfg_attr(target_family = "wasm", serde(default))] - pub optimise: bool, } pub type EznoParsePostCheckVisitors = @@ -73,6 +71,7 @@ pub fn build( type_definition_module: Option<&Path>, output_path: &Path, config: &BuildConfig, + transformers: Option, ) -> Result { // TODO parse options + non_standard_library & non_standard_syntax let type_check_options = TypeCheckOptions { store_type_mappings: true, ..Default::default() }; @@ -98,17 +97,7 @@ pub fn build( let mut outputs = Vec::new(); - let mut transformers = EznoParsePostCheckVisitors::default(); - - if config.optimise { - transformers - .expression_visitors_mut - .push(Box::new(crate::transformers::optimisations::ExpressionOptimiser)); - - transformers - .statement_visitors_mut - .push(Box::new(crate::transformers::optimisations::StatementOptimiser)); - } + let mut transformers = transformers.unwrap_or_default(); for source in keys { // Remove the module @@ -149,49 +138,3 @@ pub fn build( Err(FailedBuildOutput { diagnostics: result.diagnostics, fs: data.module_contents }) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tree_shaking() { - let source = r#" - function make_observable(obj) { - return new Proxy(obj, { - get(on, prop: string, _rec) { - return on[prop] - }, - }) - } - - function get_a() { - return 1 - } - - function get_b() { - return 1 - } - - const obj = { - a() { return get_a() }, - b() { return get_b() }, - c: 2 - } - - const value = make_observable(obj); - const a_value = value.a(); - const c_value = value.c; - "#; - - let cfg = BuildConfig { optimise: true, ..Default::default() }; - - let output = - build(vec!["index.tsx"], |_| Some(source.to_owned()), None, "out.tsx").unwrap(); - - let first_source = output.outputs.content; - - // TODO assert equal - panic!("{first_source:?}"); - } -} diff --git a/src/cli.rs b/src/cli.rs index d7395733..d0118798 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -9,7 +9,7 @@ use std::{ }; use crate::{ - build::{build, BuildConfig, BuildOutput, FailedBuildOutput}, + build::{build, BuildConfig, BuildOutput, EznoParsePostCheckVisitors, FailedBuildOutput}, check::check, reporting::report_diagnostics_to_cli, utilities::{self, print_to_cli, MaxDiagnostics}, @@ -173,10 +173,11 @@ fn file_system_resolver(path: &Path) -> Option { } } -pub fn run_cli( +pub fn run_cli( cli_arguments: &[&str], read_file: &T, write_file: U, + cli_input_resolver: V, ) -> ExitCode { let command = match FromArgs::from_args(&["ezno-cli"], cli_arguments) { Ok(TopLevel { nested }) => nested, @@ -271,6 +272,18 @@ pub fn run_cli( }) => { let output_path = build_config.output.unwrap_or("ezno_output.js".into()); + let mut default_builders = EznoParsePostCheckVisitors::default(); + + if build_config.optimise { + default_builders + .expression_visitors_mut + .push(Box::new(crate::transformers::optimisations::ExpressionOptimiser)); + + default_builders + .statement_visitors_mut + .push(Box::new(crate::transformers::optimisations::StatementOptimiser)); + } + let entry_points = match get_entry_points(build_config.input) { Ok(entry_points) => entry_points, Err(_) => return ExitCode::FAILURE, @@ -287,8 +300,8 @@ pub fn run_cli( &BuildConfig { strip_whitespace: build_config.minify, source_maps: build_config.source_maps, - optimise: build_config.optimise, }, + Some(default_builders), ); #[cfg(not(target_family = "wasm"))] @@ -397,12 +410,12 @@ pub fn run_cli( } }, CompilerSubCommand::ASTExplorer(mut repl) => { - repl.run(read_file); + repl.run(read_file, cli_input_resolver); // TODO not always true ExitCode::SUCCESS } CompilerSubCommand::Repl(argument) => { - crate::repl::run_repl(argument); + crate::repl::run_repl(cli_input_resolver, argument); // TODO not always true ExitCode::SUCCESS } // CompilerSubCommand::Run(run_arguments) => { diff --git a/src/lib.rs b/src/lib.rs index b6f7999b..a81c0908 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,11 @@ where } } +/// prompt -> response +pub trait CLIInputResolver: Fn(&str) -> Option {} + +impl CLIInputResolver for T where T: Fn(&str) -> Option {} + pub trait WriteToFS: Fn(&std::path::Path, String) {} impl WriteToFS for T where T: Fn(&std::path::Path, String) {} diff --git a/src/main.rs b/src/main.rs index 898f4d07..db171795 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,27 @@ use ezno_lib::cli::run_cli; use std::io; +#[cfg(target_family = "windows")] +pub(crate) fn cli_input_resolver(prompt: &str) -> String { + print!("{prompt}> "); + io::Write::flush(&mut io::stdout()).unwrap(); + let mut input = String::new(); + let std_in = &mut io::stdin(); + let _n = multiline_term_input::read_string(std_in, &mut input); + input +} + +#[cfg(target_family = "unix")] +#[allow(clippy::unnecessary_wraps)] +pub(crate) fn cli_input_resolver(prompt: &str) -> String { + print!("{prompt}> "); + io::Write::flush(&mut io::stdout()).unwrap(); + let mut input = String::new(); + let std_in = &mut io::stdin(); + let _n = std_in.read_line(&mut input).unwrap(); + input +} + fn main() -> std::process::ExitCode { fn read_from_file(path: &std::path::Path) -> Option { std::fs::read_to_string(path).ok() @@ -27,5 +48,5 @@ fn main() -> std::process::ExitCode { let arguments = std::env::args().skip(1).collect::>(); let arguments = arguments.iter().map(String::as_str).collect::>(); - run_cli(&arguments, &read_from_file, write_to_file) + run_cli(&arguments, &read_from_file, write_to_file, |p| Some(cli_input_resolver(p))) } diff --git a/src/repl.rs b/src/repl.rs index 0d644c6e..1978a1a1 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -8,7 +8,7 @@ use parser::{Expression, Module, Statement}; use crate::reporting::report_diagnostics_to_cli; use crate::utilities::print_to_cli; -/// Interactive type checking +/// Run project repl using deno. (`deno` command must be in path) #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "repl")] pub(crate) struct ReplArguments { @@ -33,33 +33,51 @@ fn file_system_resolver(path: &Path) -> Option> { } } -/// Wraps `checker::synthesis::interactive::State` -pub struct ReplSystem { - arguments: ReplArguments, - source: SourceId, - state: checker::synthesis::interactive::State -} +pub(crate) fn run_repl( + cli_input_resolver: U, + ReplArguments { const_as_let, type_definition_module }: ReplArguments, +) { + print_to_cli(format_args!("Entering REPL. Exit with `close()`")); + + let definitions = if let Some(tdm) = type_definition_module { + std::iter::once(tdm).collect() + } else { + std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into()).collect() + }; + + let state = checker::synthesis::interactive::State::new(&file_system_resolver, definitions); + + let mut state = match state { + Ok(state) => state, + Err((diagnostics, fs)) => { + report_diagnostics_to_cli( + diagnostics, + &fs, + false, + crate::utilities::MaxDiagnostics::All, + ) + .unwrap(); + return; + } + }; + + let source = state.get_source_id(); -impl ReplSystem { - pub fn new(arguments: ReplArguments) -> Result)> { - let definitions = if let Some(tdm) = type_definition_module { - std::iter::once(tdm).collect() + loop { + let input = cli_input_resolver(""); + let input = if let Some(input) = input { + if input.is_empty() { + continue; + } else if input.trim() == "close()" { + break; + } + + input } else { - std::iter::once(checker::INTERNAL_DEFINITION_FILE_PATH.into()).collect() + continue; }; - - let state = checker::synthesis::interactive::State::new(&file_system_resolver, definitions)?; - let source = state.get_source_id(); - - ReplSystem { - arguments, - source, - state - } - } - pub fn execute_statement(&mut self, input: String) { - let (from_index, _) = self.state.get_fs_mut().append_to_file(source, &input); + let (from_index, _) = state.get_fs_mut().append_to_file(source, &input); let options = Default::default(); let offset = Some(from_index as u32); @@ -75,7 +93,7 @@ impl ReplSystem { Module::from_string(input, options) }; - match result { + let mut item = match result { Ok(item) => item, Err(err) => { report_diagnostics_to_cli( @@ -85,10 +103,11 @@ impl ReplSystem { crate::utilities::MaxDiagnostics::All, ) .unwrap(); + continue; } }; - if self.arguments.const_as_let { + if const_as_let { item.visit_mut( &mut VisitorsMut { statement_visitors_mut: vec![Box::new(crate::transformers::ConstToLet)], @@ -111,7 +130,6 @@ impl ReplSystem { crate::utilities::MaxDiagnostics::All, ) .unwrap(); - if let Some(last_ty) = last_ty { println!("{last_ty}"); } @@ -128,35 +146,3 @@ impl ReplSystem { } } } - -pub(crate) fn run_repl( arguments: ReplArguments) { - if cfg!(target_family = "wasm") { - panic!("Cannot run repl in WASM because of input callback. Consider reimplementing using library"); - } - - print_to_cli(format_args!("Entering REPL. Exit with `close()`")); - - let mut system = match ReplSystem::new(arguments) { - Ok(system) => system, - Err((diagnostics, fs)) => { - report_diagnostics_to_cli( - diagnostics, - &fs, - false, - crate::utilities::MaxDiagnostics::All, - ) - .unwrap(); - return; - } - }; - - loop { - let input = cli_input_resolver(""); - if input.is_empty() { - continue; - } else if input.trim() == "close()" { - break; - } - system.execute_statement(input) - } -} diff --git a/src/utilities.rs b/src/utilities.rs index b92c40cc..c99aa8e5 100644 --- a/src/utilities.rs +++ b/src/utilities.rs @@ -89,27 +89,6 @@ pub(crate) fn print_to_cli_without_newline(arguments: Arguments) { io::Write::flush(&mut io::stdout()).unwrap(); } -#[cfg(target_family = "windows")] -pub(crate) fn cli_input_resolver(prompt: &str) -> String { - print!("{prompt}> "); - io::Write::flush(&mut io::stdout()).unwrap(); - let mut input = String::new(); - let std_in = &mut io::stdin(); - let _n = multiline_term_input::read_string(std_in, &mut input); - input -} - -#[cfg(target_family = "unix")] -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn cli_input_resolver(prompt: &str) -> String { - print!("{prompt}> "); - io::Write::flush(&mut io::stdout()).unwrap(); - let mut input = String::new(); - let std_in = &mut io::stdin(); - let _n = std_in.read_line(&mut input).unwrap(); - input -} - #[derive(Debug)] pub(crate) enum MaxDiagnostics { All, From 4d3309095ef2781a2022a146686b12a02c95e918 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 8 Nov 2024 10:38:54 +0000 Subject: [PATCH 15/24] WIP Improvements - Fixes a bit more intrinsics - Extract helpers and operator modules - Fix disjoint warning for !== - Remove intermediate operation --- checker/specification/specification.md | 3 +- checker/specification/staging.md | 34 +- checker/src/context/environment.rs | 140 ++- checker/src/diagnostics.rs | 17 +- checker/src/features/assignments.rs | 4 +- checker/src/features/constant_functions.rs | 7 + checker/src/features/narrowing.rs | 16 +- checker/src/features/operations.rs | 865 ------------------ checker/src/features/operations/equality.rs | 347 +++++++ checker/src/features/operations/logical.rs | 113 +++ .../operations/mathematical_bitwise.rs | 185 ++++ checker/src/features/operations/mod.rs | 12 + checker/src/features/operations/unary.rs | 84 ++ checker/src/features/template_literal.rs | 6 +- checker/src/options.rs | 6 + checker/src/synthesis/expressions.rs | 256 ++++-- checker/src/synthesis/type_annotations.rs | 34 +- checker/src/synthesis/variables.rs | 3 +- checker/src/types/calling.rs | 2 +- checker/src/types/disjoint.rs | 55 +- checker/src/types/helpers.rs | 373 ++++++++ checker/src/types/intrinsics.rs | 249 +++-- checker/src/types/mod.rs | 402 +------- checker/src/types/printing.rs | 21 +- checker/src/types/properties/access.rs | 2 +- checker/src/types/properties/assignment.rs | 3 +- checker/src/types/properties/mod.rs | 2 +- checker/src/types/subtyping.rs | 40 +- checker/src/utilities/float_range.rs | 46 +- 29 files changed, 1829 insertions(+), 1498 deletions(-) delete mode 100644 checker/src/features/operations.rs create mode 100644 checker/src/features/operations/equality.rs create mode 100644 checker/src/features/operations/logical.rs create mode 100644 checker/src/features/operations/mathematical_bitwise.rs create mode 100644 checker/src/features/operations/mod.rs create mode 100644 checker/src/features/operations/unary.rs create mode 100644 checker/src/types/helpers.rs diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 7ec5a11f..a0a11463 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -750,7 +750,7 @@ const z: false = true || 4 ```ts (4 === 2) satisfies true; -(4 !== 2) satisfies string; +(4 !== 5) satisfies string; ``` - Expected true, found false @@ -1889,6 +1889,7 @@ function conditional(v: string) { a++ } } + conditional("x") a satisfies 2 conditional("value") diff --git a/checker/specification/staging.md b/checker/specification/staging.md index 9c197fb1..8a6fa799 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -27,15 +27,11 @@ function func3(p1: Not, p2: Not) { > TODO need to redo range to use interesection of less than and greater than ```ts -// function func(a: number, b: number) { -// if (a % 15 === 0 && 31 < b && b < 37) { -// print_type(a, b) -// print_type(a === b) -// } -// } function func(a: number, b: number) { - if (31 < b && b < 37) { - print_type(b) + const condition = 31 < b && b < 37; + debug_type(condition); + if (condition) { + debug_type(b) } } ``` @@ -73,3 +69,25 @@ function func(a: boolean) { ``` - Expected "hi", found true + +#### Modulo range + +```ts +function func(x: number) { + return x % 5 === 6; +} +``` + +- This equality is always false + +### Broken + +#### Template literal edge cases + +```ts +const invalidNum1: `${1}` = 1; +const invalidNum2: `${1}` = "1"; +const invalidNum3: `${1}` = "2"; +``` + +- ? diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index a4a2dece..d939ec6f 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -15,10 +15,7 @@ use crate::{ }, modules::Exported, objects::SpecialObject, - operations::{ - evaluate_logical_operation_with_expression, - evaluate_pure_binary_operation_handle_errors, MathematicalAndBitwise, - }, + operations::{evaluate_logical_operation_with_expression, MathematicalOrBitwiseOperation}, variables::{VariableMutability, VariableOrImport, VariableWithValue}, }, subtyping::{type_is_subtype, type_is_subtype_object, State, SubTypeResult, SubTypingOptions}, @@ -278,7 +275,7 @@ impl<'a> Environment<'a> { } AssignmentKind::PureUpdate(operator) => { // Order matters here - let reference_position = reference.get_position(); + // let reference_position = reference.get_position(); let existing = self.get_reference( reference.clone(), checking_data, @@ -286,8 +283,9 @@ impl<'a> Environment<'a> { ); let expression = expression.unwrap(); - let expression_pos = - A::expression_position(expression).with_source(self.get_source()); + // let expression_pos = + // A::expression_position(expression).with_source(self.get_source()); + let rhs = A::synthesise_expression( expression, TypeId::ANY_TYPE, @@ -295,22 +293,50 @@ impl<'a> Environment<'a> { checking_data, ); - let new = evaluate_pure_binary_operation_handle_errors( - (existing, reference_position), - operator.into(), - (rhs, expression_pos), - checking_data, + let result = crate::features::operations::evaluate_mathematical_operation( + existing, + operator, + rhs, self, + &mut checking_data.types, + checking_data.options.strict_casts, ); - let assignment_position = - assignment_position.with_source(self.get_source()); - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, - ); - new + match result { + Ok(new) => { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + + new + } + Err(()) => { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + rhs, + self, + &checking_data.types, + false, + ), + position: assignment_position.with_source(self.get_source()), + }, + ); + TypeId::ERROR_TYPE + } + } } AssignmentKind::IncrementOrDecrement(direction, return_kind) => { // let value = @@ -323,31 +349,59 @@ impl<'a> Environment<'a> { ); // TODO existing needs to be cast to number!! - - let new = evaluate_pure_binary_operation_handle_errors( - (existing, position), - match direction { - IncrementOrDecrement::Increment => MathematicalAndBitwise::Add, - IncrementOrDecrement::Decrement => MathematicalAndBitwise::Subtract, + let operator = match direction { + IncrementOrDecrement::Increment => MathematicalOrBitwiseOperation::Add, + IncrementOrDecrement::Decrement => { + MathematicalOrBitwiseOperation::Subtract } - .into(), - (TypeId::ONE, source_map::Nullable::NULL), - checking_data, - self, - ); + }; - let assignment_position = - assignment_position.with_source(self.get_source()); - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, + let result = crate::features::operations::evaluate_mathematical_operation( + existing, + operator, + TypeId::ONE, + self, + &mut checking_data.types, + checking_data.options.strict_casts, ); - - match return_kind { - AssignmentReturnStatus::Previous => existing, - AssignmentReturnStatus::New => new, + match result { + Ok(new) => { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + + match return_kind { + AssignmentReturnStatus::Previous => existing, + AssignmentReturnStatus::New => new, + } + } + Err(()) => { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + TypeId::ONE, + self, + &checking_data.types, + false, + ), + position, + }, + ); + TypeId::ERROR_TYPE + } } } AssignmentKind::ConditionalUpdate(operator) => { diff --git a/checker/src/diagnostics.rs b/checker/src/diagnostics.rs index 94cecf0a..149b4c62 100644 --- a/checker/src/diagnostics.rs +++ b/checker/src/diagnostics.rs @@ -423,7 +423,7 @@ pub(crate) enum TypeCheckError<'a> { #[allow(clippy::upper_case_acronyms)] VariableUsedInTDZ(VariableUsedInTDZ), InvalidMathematicalOrBitwiseOperation { - operator: crate::features::operations::MathematicalAndBitwise, + operator: crate::features::operations::MathematicalOrBitwiseOperation, lhs: TypeStringRepresentation, rhs: TypeStringRepresentation, position: SpanWithSource, @@ -958,6 +958,7 @@ pub enum TypeCheckWarning { DisjointEquality { lhs: TypeStringRepresentation, rhs: TypeStringRepresentation, + result: bool, position: SpanWithSource, }, ItemMustBeUsedWithFlag { @@ -1039,11 +1040,15 @@ impl From for Diagnostic { kind, } } - TypeCheckWarning::DisjointEquality { lhs, rhs, position } => Diagnostic::Position { - reason: format!("This equality is always false as {lhs} and {rhs} have no overlap"), - position, - kind, - }, + TypeCheckWarning::DisjointEquality { lhs, rhs, position, result } => { + Diagnostic::Position { + reason: format!( + "This equality is always {result} as {lhs} and {rhs} have no overlap" + ), + position, + kind, + } + } } } } diff --git a/checker/src/features/assignments.rs b/checker/src/features/assignments.rs index 486aab57..2813e7b3 100644 --- a/checker/src/features/assignments.rs +++ b/checker/src/features/assignments.rs @@ -5,7 +5,7 @@ use crate::{ TypeId, }; -use super::operations::{LogicalOperator, MathematicalAndBitwise}; +use super::operations::{LogicalOperator, MathematicalOrBitwiseOperation}; /// A single or multiple items to assign to pub enum Assignable { @@ -50,7 +50,7 @@ pub enum AssignableArrayDestructuringField { /// Increment and decrement are are not binary add subtract as they cast their lhs to number pub enum AssignmentKind { Assign, - PureUpdate(MathematicalAndBitwise), + PureUpdate(MathematicalOrBitwiseOperation), ConditionalUpdate(LogicalOperator), IncrementOrDecrement(IncrementOrDecrement, AssignmentReturnStatus), } diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index 630f5a51..ee09d9ce 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -142,6 +142,13 @@ pub(crate) fn call_constant_function( Err(ConstantFunctionError::CannotComputeConstant) } } + "debug_number" => { + let arg = arguments.iter().next().unwrap(); + Ok(ConstantOutput::Diagnostic(format!( + "number: {:?}", + crate::types::intrinsics::get_range_and_mod_class(arg.value, types) + ))) + } "print_type" | "debug_type" | "print_and_debug_type" | "debug_type_independent" => { fn to_string( print: bool, diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index ce1e4944..9df7a326 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -8,7 +8,7 @@ use crate::{ Map, Type, TypeId, }; -use super::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise}; +use super::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation}; pub fn narrow_based_on_expression_into_vec( condition: TypeId, @@ -82,7 +82,7 @@ pub fn narrow_based_on_expression( } } else if let Type::Constructor(Constructor::BinaryOperator { lhs: operand, - operator: MathematicalAndBitwise::Modulo, + operator: MathematicalOrBitwiseOperation::Modulo, rhs: modulo, result: _, }) = lhs_type @@ -105,7 +105,7 @@ pub fn narrow_based_on_expression( types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: narrowed_to, - operator: super::operations::MathematicalAndBitwise::Add, + operator: super::operations::MathematicalOrBitwiseOperation::Add, rhs, result: TypeId::NUMBER_TYPE, }, @@ -229,6 +229,7 @@ pub fn narrow_based_on_expression( let narrowed = types.new_narrowed(lhs, narrowed_to); // temp fix let narrowed = if let Some(existing) = into.get(&lhs) { + crate::utilities::notify!("Here"); types.new_and_type(*existing, narrowed) } else { narrowed @@ -243,6 +244,7 @@ pub fn narrow_based_on_expression( let narrowed = types.new_narrowed(rhs, narrowed_to); // temp fix let narrowed = if let Some(existing) = into.get(&rhs) { + crate::utilities::notify!("Here"); types.new_and_type(*existing, narrowed) } else { narrowed @@ -465,8 +467,12 @@ impl<'a> Filter<'a> { } } Filter::HasProperty { property, filter } => { - let value = - types::properties::get_simple_value(information, value, property, types); + let value = types::properties::get_simple_property_value( + information, + value, + property, + types, + ); if let Some(value) = value { let matches = filter.type_matches_filter(value, information, types, negate); crate::utilities::notify!("Value {:?}", (value, negate, matches)); diff --git a/checker/src/features/operations.rs b/checker/src/features/operations.rs deleted file mode 100644 index 1e4b1c4a..00000000 --- a/checker/src/features/operations.rs +++ /dev/null @@ -1,865 +0,0 @@ -//! Contains logic for mathematical, bitwise and logical operators - -use derive_enum_from_into::EnumFrom; -use source_map::{Span, SpanWithSource}; - -use crate::{ - diagnostics::{TypeCheckError, TypeStringRepresentation}, - features::conditional::new_conditional_context, - types::{ - cast_as_number, cast_as_string, helpers::simple_subtype, intrinsics, is_type_truthy_falsy, - Constructor, PartiallyAppliedGenerics, TypeStore, - }, - CheckingData, Constant, Decidable, Environment, Type, TypeId, -}; - -use super::objects::SpecialObject; - -/// For these **binary** operations both operands are synthesised -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum MathematicalAndBitwise { - Add, - Subtract, - Multiply, - Divide, - Modulo, - Exponent, - BitwiseShiftLeft, - BitwiseShiftRight, - BitwiseShiftRightUnsigned, - BitwiseAnd, - BitwiseXOr, - BitwiseOr, -} - -#[derive(Clone, Copy, Debug, EnumFrom)] -pub enum PureBinaryOperation { - MathematicalAndBitwise(MathematicalAndBitwise), - // Some of these can be reduced - EqualityAndInequality(EqualityAndInequality), -} - -/// TODO report errors better here -pub fn evaluate_pure_binary_operation_handle_errors< - T: crate::ReadFromFS, - A: crate::ASTImplementation, ->( - (lhs, lhs_pos): (TypeId, SpanWithSource), - operator: PureBinaryOperation, - (rhs, rhs_pos): (TypeId, SpanWithSource), - checking_data: &mut CheckingData, - environment: &mut Environment, -) -> TypeId { - match operator { - PureBinaryOperation::MathematicalAndBitwise(operator) => { - if let (MathematicalAndBitwise::Exponent, TypeId::ZERO) = (operator, rhs) { - // This holds for NaN. Thus can do in every case - return TypeId::ONE; - } - - let result = evaluate_mathematical_operation( - lhs, - operator, - rhs, - environment, - &mut checking_data.types, - checking_data.options.strict_casts, - ); - - match result { - Ok(result) => result, - Err(_err) => { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_error( - TypeCheckError::InvalidMathematicalOrBitwiseOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - TypeId::ERROR_TYPE - } - } - } - PureBinaryOperation::EqualityAndInequality(operator) => { - // Cannot error, but can be always true or false - let result = evaluate_equality_inequality_operation( - lhs, - &operator, - rhs, - environment, - &mut checking_data.types, - checking_data.options.strict_casts, - ); - - if let Ok((result, warning)) = result { - if let EqualityAndInequalityResultKind::Disjoint = warning { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_warning( - crate::TypeCheckWarning::DisjointEquality { - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - } - - result - } else { - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - - checking_data.diagnostics_container.add_error( - crate::TypeCheckError::InvalidEqualityOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - - TypeId::ERROR_TYPE - } - } - } -} - -/// TODO proper error type -pub fn evaluate_mathematical_operation( - lhs: TypeId, - operator: MathematicalAndBitwise, - rhs: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result { - fn attempt_constant_math_operator( - lhs: TypeId, - operator: MathematicalAndBitwise, - rhs: TypeId, - types: &mut TypeStore, - strict_casts: bool, - ) -> Result { - if let MathematicalAndBitwise::Add = operator { - let constant = match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => { - Constant::Number(lhs + rhs) - } - (Type::Constant(lhs), Type::Constant(rhs)) => { - let mut first = cast_as_string(lhs, strict_casts)?; - let second = cast_as_string(rhs, strict_casts)?; - // Concatenate strings - first.push_str(&second); - Constant::String(first) - } - _ => return Err(()), - }; - Ok(types.new_constant_type(constant)) - } else { - match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - (Type::Constant(c1), Type::Constant(c2)) => { - let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN); - let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN); - // TODO hopefully Rust implementation is the same as JS - #[allow(clippy::cast_possible_truncation)] - let value = match operator { - MathematicalAndBitwise::Add => unreachable!(), - MathematicalAndBitwise::Subtract => lhs - rhs, - MathematicalAndBitwise::Multiply => lhs * rhs, - MathematicalAndBitwise::Divide => lhs / rhs, - MathematicalAndBitwise::Modulo => lhs % rhs, - MathematicalAndBitwise::Exponent => lhs.powf(rhs), - MathematicalAndBitwise::BitwiseShiftLeft => { - f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0)) - } - MathematicalAndBitwise::BitwiseShiftRight => { - f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0)) - } - MathematicalAndBitwise::BitwiseShiftRightUnsigned => { - (lhs as i32).wrapping_shr(rhs as u32).into() - } - MathematicalAndBitwise::BitwiseAnd => { - f64::from((lhs as i32) & (rhs as i32)) - } - MathematicalAndBitwise::BitwiseXOr => { - f64::from((lhs as i32) ^ (rhs as i32)) - } - MathematicalAndBitwise::BitwiseOr => f64::from((lhs as i32) | (rhs as i32)), - }; - let value = ordered_float::NotNan::try_from(value); - let ty = match value { - Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN, - }; - Ok(ty) - } - _ => Err(()), - } - } - } - - if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE { - return Ok(TypeId::ERROR_TYPE); - } - - let is_dependent = - types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - let can_be_string = if let MathematicalAndBitwise::Add = operator { - let left_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types); - let right_is_string = simple_subtype(lhs, TypeId::STRING_TYPE, info, types); - let left_is_string_or_number = - left_is_string || simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); - let right_is_string_or_number = - right_is_string || simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types); - if !left_is_string_or_number || !right_is_string_or_number { - return Err(()); - } - left_is_string || right_is_string - } else { - let left_is_number = simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); - if !left_is_number || !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) { - return Err(()); - } - false - }; - - // :) - if let (MathematicalAndBitwise::Exponent, TypeId::ONE, true) = - (operator, rhs, intrinsics::is_not_not_a_number(lhs, types)) - { - return Ok(lhs); - } else if let (MathematicalAndBitwise::Add, TypeId::ZERO) - | (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, rhs) - { - return Ok(lhs); - } else if let (MathematicalAndBitwise::Add, TypeId::ZERO) - | (MathematicalAndBitwise::Multiply, TypeId::ONE) = (operator, lhs) - { - return Ok(rhs); - } - - let result = if can_be_string { - TypeId::STRING_TYPE - } else if let ( - MathematicalAndBitwise::Add | MathematicalAndBitwise::Multiply, - Some(lhs_range), - Some(rhs_range), - ) = (operator, intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types)) - { - match operator { - MathematicalAndBitwise::Add => { - intrinsics::range_to_type(lhs_range.space_addition(rhs_range), types) - } - MathematicalAndBitwise::Multiply => { - intrinsics::range_to_type(lhs_range.space_multiplication(rhs_range), types) - } - _ => unreachable!(), - } - } else { - TypeId::NUMBER_TYPE - }; - - let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; - Ok(types.register_type(crate::Type::Constructor(constructor))) - } else { - attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) - } -} - -/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. -/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`) -#[derive(Clone, Copy, Debug)] -pub enum EqualityAndInequality { - StrictEqual, - StrictNotEqual, - Equal, - NotEqual, - GreaterThan, - LessThan, - LessThanOrEqual, - GreaterThanOrEqual, -} - -/// Canonical / irreducible form of [`EqualityAndInequality`]. -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum CanonicalEqualityAndInequality { - StrictEqual, - LessThan, -} - -pub enum EqualityAndInequalityResultKind { - Constant, - Disjoint, - Condition, -} - -pub fn evaluate_equality_inequality_operation( - mut lhs: TypeId, - operator: &EqualityAndInequality, - mut rhs: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> { - // `NaN == t` is always true - if lhs == TypeId::NAN || rhs == TypeId::NAN { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant)); - } - - match operator { - EqualityAndInequality::StrictEqual => { - // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); - - let left_dependent = types.get_type_by_id(lhs).is_dependent(); - let is_dependent = left_dependent || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - if lhs == rhs - && intrinsics::is_not_not_a_number(lhs, types) - && intrinsics::is_not_not_a_number(rhs, types) - { - // I think this is okay - return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); - } - - // Checks lhs and rhs type to see if they overlap - if crate::types::disjoint::types_are_disjoint( - lhs, - rhs, - &mut Vec::new(), - info, - types, - ) { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); - } - - // Sort if `*constant* == ...`. Ideally want constant type on the RHS - let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) }; - let constructor = crate::types::Constructor::CanonicalRelationOperator { - lhs, - operator: CanonicalEqualityAndInequality::StrictEqual, - rhs, - }; - - Ok(( - types.register_type(crate::Type::Constructor(constructor)), - EqualityAndInequalityResultKind::Condition, - )) - } else { - match attempt_constant_equality(lhs, rhs, types) { - Ok(ty) => Ok(( - if ty { TypeId::TRUE } else { TypeId::FALSE }, - EqualityAndInequalityResultKind::Constant, - )), - Err(()) => { - unreachable!( - "should have been caught `is_dependent` above, {:?} === {:?}", - types.get_type_by_id(lhs), - types.get_type_by_id(rhs) - ) - } - } - } - } - EqualityAndInequality::LessThan => { - fn attempt_less_than( - lhs: TypeId, - rhs: TypeId, - types: &mut TypeStore, - strict_casts: bool, - ) -> Result { - // Similar but reversed semantics to add - match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { - ( - Type::Constant(Constant::String(string1)), - Type::Constant(Constant::String(string2)), - ) => { - // Yah rust includes string alphanumerical equivalence of strings - Ok(string1 < string2) - } - (Type::Constant(c1), Type::Constant(c2)) => { - let lhs = cast_as_number(c1, strict_casts)?; - let rhs = cast_as_number(c2, strict_casts)?; - Ok(lhs < rhs) - } - (lhs, rhs) => { - crate::utilities::notify!("{:?}", (lhs, rhs)); - // Ok(TypeId::OPEN_BOOLEAN_TYPE) - Err(()) - } - } - } - - let is_dependent = types.get_type_by_id(lhs).is_dependent() - || types.get_type_by_id(rhs).is_dependent(); - - if is_dependent { - { - if let Type::Constructor(Constructor::BinaryOperator { - lhs: op_lhs, - operator, - rhs: op_rhs, - result: _, - }) = types.get_type_by_id(lhs) - { - if let ( - Type::Constant(Constant::Number(add)), - MathematicalAndBitwise::Add, - Type::Constant(Constant::Number(lt)), - ) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs)) - { - crate::utilities::notify!("Shifted LT"); - lhs = *op_lhs; - rhs = types.register_type(Type::Constant(Constant::Number(lt - add))); - } - } - } - - { - // let lhs = get_constraint(lhs, types).unwrap_or(lhs); - // let rhs = get_constraint(rhs, types).unwrap_or(rhs); - - if !simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) - || !simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) - { - return Err(()); - } - - // Tidies some things for counting loop iterations - - // Checking disjoint-ness for inequalities (TODO under option) via distribution - if let (Some(lhs_range), Some(rhs_range)) = - (intrinsics::get_range(lhs, types), intrinsics::get_range(rhs, types)) - { - if lhs_range.below(rhs_range) { - return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); - } - if lhs_range.above(rhs_range) { - return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); - } - } - } - - let constructor = Constructor::CanonicalRelationOperator { - lhs, - operator: CanonicalEqualityAndInequality::LessThan, - rhs, - }; - Ok(( - types.register_type(crate::Type::Constructor(constructor)), - EqualityAndInequalityResultKind::Condition, - )) - } else { - attempt_less_than(lhs, rhs, types, strict_casts).map(|value| { - ( - if value { TypeId::TRUE } else { TypeId::FALSE }, - EqualityAndInequalityResultKind::Constant, - ) - }) - } - } - // equal OR less than - EqualityAndInequality::LessThanOrEqual => { - let (equality_result, warning) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::StrictEqual, - rhs, - info, - types, - strict_casts, - )?; - - if equality_result == TypeId::TRUE { - Ok((equality_result, warning)) - } else if equality_result == TypeId::FALSE { - evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::LessThan, - rhs, - info, - types, - strict_casts, - ) - } else { - let (less_than_result, warning) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::LessThan, - rhs, - info, - types, - strict_casts, - )?; - Ok((types.new_logical_or_type(equality_result, less_than_result), warning)) - } - } - EqualityAndInequality::StrictNotEqual => { - let (equality_result, kind) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::StrictEqual, - rhs, - info, - types, - strict_casts, - )?; - if let EqualityAndInequalityResultKind::Condition = kind { - Ok((types.new_logical_negation_type(equality_result), kind)) - } else { - let negated = if let TypeId::TRUE = equality_result { - TypeId::FALSE - } else if let TypeId::FALSE = equality_result { - TypeId::TRUE - } else { - todo!() - }; - Ok((negated, kind)) - } - } - EqualityAndInequality::Equal => { - crate::utilities::notify!("TODO equal operator"); - Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition)) - } - EqualityAndInequality::NotEqual => { - let (equality_result, kind) = evaluate_equality_inequality_operation( - lhs, - &EqualityAndInequality::Equal, - rhs, - info, - types, - strict_casts, - )?; - if let EqualityAndInequalityResultKind::Condition = kind { - Ok((types.new_logical_negation_type(equality_result), kind)) - } else { - let negated = if let TypeId::TRUE = equality_result { - TypeId::FALSE - } else if let TypeId::FALSE = equality_result { - TypeId::TRUE - } else { - todo!() - }; - Ok((negated, kind)) - } - } - // Swapping operands! - EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation( - rhs, - &EqualityAndInequality::LessThan, - lhs, - info, - types, - strict_casts, - ), - // Swapping operands! - EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation( - rhs, - &EqualityAndInequality::LessThanOrEqual, - lhs, - info, - types, - strict_casts, - ), - } -} - -#[allow(clippy::let_and_return)] -pub fn is_null_or_undefined( - ty: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, -) -> TypeId { - let is_null = evaluate_equality_inequality_operation( - ty, - &EqualityAndInequality::StrictEqual, - TypeId::NULL_TYPE, - info, - types, - false, - ) - .map_or(TypeId::ERROR_TYPE, |(left, _)| left); - - if let TypeId::TRUE = is_null { - is_null - } else { - let is_undefined = evaluate_equality_inequality_operation( - ty, - &EqualityAndInequality::StrictEqual, - TypeId::UNDEFINED_TYPE, - info, - types, - false, - ) - .map_or(TypeId::ERROR_TYPE, |(left, _)| left); - - if let TypeId::FALSE = is_null { - is_undefined - } else { - types.new_logical_or_type(is_null, is_undefined) - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum LogicalOperator { - And, - Or, - /// TODO is this canonical? - NullCoalescing, -} - -/// TODO strict casts! -pub fn evaluate_logical_operation_with_expression< - 'a, - T: crate::ReadFromFS, - A: crate::ASTImplementation, ->( - lhs: (TypeId, Span), - operator: LogicalOperator, - rhs: &'a A::Expression<'a>, - checking_data: &mut CheckingData, - environment: &mut Environment, - expecting: TypeId, -) -> Result { - match operator { - LogicalOperator::And => Ok(new_conditional_context( - environment, - lhs, - |env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, expecting, env, data) - }, - Some(|env: &mut Environment, checking_data: &mut CheckingData| { - if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) - { - let mut result = Vec::new(); - super::narrowing::build_union_from_filter( - constraint, - super::narrowing::FASLY, - &mut result, - env, - &checking_data.types, - ); - let narrowed_to = checking_data.types.new_or_type_from_iterator(result); - checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) - } else { - lhs.0 - } - }), - checking_data, - )), - LogicalOperator::Or => Ok(new_conditional_context( - environment, - lhs, - |env: &mut Environment, checking_data: &mut CheckingData| { - if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) - { - let mut result = Vec::new(); - super::narrowing::build_union_from_filter( - constraint, - super::narrowing::NOT_FASLY, - &mut result, - env, - &checking_data.types, - ); - let narrowed_to = checking_data.types.new_or_type_from_iterator(result); - checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) - } else { - lhs.0 - } - }, - Some(|env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, expecting, env, data) - }), - checking_data, - )), - LogicalOperator::NullCoalescing => { - let is_lhs_null_or_undefined = - is_null_or_undefined(lhs.0, environment, &mut checking_data.types); - // Equivalent to: `(lhs is null or undefined) ? lhs : rhs` - Ok(new_conditional_context( - environment, - (is_lhs_null_or_undefined, lhs.1), - |env: &mut Environment, checking_data: &mut CheckingData| { - if let Some(constraint) = - crate::types::get_constraint(lhs.0, &checking_data.types) - { - let mut result = Vec::new(); - super::narrowing::build_union_from_filter( - constraint, - super::narrowing::NOT_NULL_OR_UNDEFINED, - &mut result, - env, - &checking_data.types, - ); - let narrowed_to = checking_data.types.new_or_type_from_iterator(result); - checking_data - .types - .register_type(Type::Narrowed { from: lhs.0, narrowed_to }) - } else { - lhs.0 - } - }, - Some(|env: &mut Environment, data: &mut CheckingData| { - A::synthesise_expression(rhs, expecting, env, data) - }), - checking_data, - )) - } - } -} - -/// `typeof` and some others done elsewhere -#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] -pub enum UnaryOperation { - /// Treated as `(value ? false : true)` - LogicalNot, - /// Treated as `0 - value` (could also do -1 * value?) - Negation, - /// Treated as `value ^ 0xFFFF_FFFF` - BitwiseNot, -} - -/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing -pub fn evaluate_unary_operator( - operator: UnaryOperation, - operand: TypeId, - info: &impl crate::context::InformationChain, - types: &mut TypeStore, - strict_casts: bool, -) -> Result { - if operand == TypeId::ERROR_TYPE { - return Ok(operand); - } - - match operator { - UnaryOperation::LogicalNot => { - if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) { - if value { - Ok(TypeId::FALSE) - } else { - Ok(TypeId::TRUE) - } - } else { - let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types); - if is_boolean { - Ok(types.new_logical_negation_type(operand)) - } else { - Err(()) - } - } - } - UnaryOperation::Negation | UnaryOperation::BitwiseNot => { - if let Type::Constant(cst) = types.get_type_by_id(operand) { - let value = cast_as_number(cst, strict_casts).expect("hmm"); - let value = match operator { - UnaryOperation::BitwiseNot => f64::from(!(value as i32)), - UnaryOperation::Negation => -value, - UnaryOperation::LogicalNot => unreachable!(), - }; - let value = ordered_float::NotNan::try_from(value); - Ok(match value { - Ok(value) => types.new_constant_type(Constant::Number(value)), - Err(_) => TypeId::NAN, - }) - } else { - match operator { - UnaryOperation::BitwiseNot => evaluate_mathematical_operation( - TypeId::MAX_U32, - MathematicalAndBitwise::BitwiseXOr, - operand, - info, - types, - strict_casts, - ), - UnaryOperation::Negation => evaluate_mathematical_operation( - TypeId::ZERO, - MathematicalAndBitwise::Subtract, - operand, - info, - types, - strict_casts, - ), - UnaryOperation::LogicalNot => unreachable!("handled above"), - } - } - } - } -} - -/// Returns whether lhs and rhs are always equal or never equal. TODO more -/// -/// TODO return decidable. -fn attempt_constant_equality(lhs: TypeId, rhs: TypeId, types: &mut TypeStore) -> Result { - if lhs == rhs { - Ok(true) - } else if matches!(lhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE) - || matches!(rhs, TypeId::NULL_TYPE | TypeId::UNDEFINED_TYPE) - { - // If above `==`` failed => false (as always have same `TypeId`) - Ok(false) - } else { - let lhs = types.get_type_by_id(lhs); - let rhs = types.get_type_by_id(rhs); - if let (Type::Constant(cst1), Type::Constant(cst2)) = (lhs, rhs) { - Ok(cst1 == cst2) - } else if let (Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..)), _) - | (_, Type::Object(..) | Type::SpecialObject(SpecialObject::Function(..))) = (lhs, rhs) - { - // Same objects and functions always have same type id. Poly case doesn't occur here - Ok(false) - } - // Temp fix for closures - else if let ( - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_lhs, .. }), - Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: on_rhs, .. }), - ) = (lhs, rhs) - { - // TODO does this work? - attempt_constant_equality(*on_lhs, *on_rhs, types) - } else { - crate::utilities::notify!("{:?} === {:?} is apparently false", lhs, rhs); - Err(()) - } - } -} diff --git a/checker/src/features/operations/equality.rs b/checker/src/features/operations/equality.rs new file mode 100644 index 00000000..d71337c1 --- /dev/null +++ b/checker/src/features/operations/equality.rs @@ -0,0 +1,347 @@ +use super::MathematicalOrBitwiseOperation; +use crate::types::{ + cast_as_number, disjoint, helpers, intrinsics, Constant, Constructor, Type, TypeId, +}; + +/// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. +/// (for examples `a > b` is equivalent to `b < a` (after side effects) and `a !== b` is equivalent to `!(a === b)`) +#[derive(Clone, Copy, Debug)] +pub enum EqualityAndInequality { + StrictEqual, + StrictNotEqual, + Equal, + NotEqual, + GreaterThan, + LessThan, + LessThanOrEqual, + GreaterThanOrEqual, +} + +/// Canonical / irreducible form of [`EqualityAndInequality`]. +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum CanonicalEqualityAndInequality { + StrictEqual, + LessThan, +} + +pub enum EqualityAndInequalityResultKind { + Constant, + Disjoint, + Condition, +} + +pub fn evaluate_equality_inequality_operation( + mut lhs: TypeId, + operator: &EqualityAndInequality, + mut rhs: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::types::TypeStore, + strict_casts: bool, +) -> Result<(TypeId, EqualityAndInequalityResultKind), ()> { + // `NaN == t` is always true + if lhs == TypeId::NAN || rhs == TypeId::NAN { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Constant)); + } + + match operator { + EqualityAndInequality::StrictEqual => { + // crate::utilities::notify!("{:?} === {:?}", lhs, rhs); + + if let (Type::Constant(lhs), Type::Constant(rhs)) = + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE }; + return Ok((result, EqualityAndInequalityResultKind::Constant)); + } + + if let ( + Type::Object(_) | Type::SpecialObject(_), + Type::Object(_) | Type::SpecialObject(_), + ) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + let result = if lhs == rhs { TypeId::TRUE } else { TypeId::FALSE }; + return Ok((result, EqualityAndInequalityResultKind::Constant)); + } + + // if is_dependent { + if lhs == rhs + && intrinsics::is_not_not_a_number(lhs, types) + && intrinsics::is_not_not_a_number(rhs, types) + { + // I think this is okay + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + + // Checks lhs and rhs type to see if they overlap + if disjoint::types_are_disjoint(lhs, rhs, &mut Vec::new(), info, types) { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); + } + + let left_dependent = types.get_type_by_id(lhs).is_dependent(); + + // Sort if `*constant* == ...`. Ideally want constant type on the RHS + let (lhs, rhs) = if left_dependent { (lhs, rhs) } else { (rhs, rhs) }; + let operator = CanonicalEqualityAndInequality::StrictEqual; + let result_ty = + types.register_type(Type::Constructor(Constructor::CanonicalRelationOperator { + lhs, + operator, + rhs, + })); + + Ok((result_ty, EqualityAndInequalityResultKind::Condition)) + + // } else { + // match attempt_constant_equality(lhs, rhs, types) { + // Ok(ty) => Ok(( + // if ty { TypeId::TRUE } else { TypeId::FALSE }, + // EqualityAndInequalityResultKind::Constant, + // )), + // Err(()) => { + // unreachable!( + // "should have been caught `is_dependent` above, {:?} === {:?}", + // types.get_type_by_id(lhs), + // types.get_type_by_id(rhs) + // ) + // } + // } + // } + } + EqualityAndInequality::LessThan => { + fn attempt_less_than( + lhs: TypeId, + rhs: TypeId, + types: &mut crate::types::TypeStore, + strict_casts: bool, + ) -> Result { + // Similar but reversed semantics to add + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + ( + Type::Constant(Constant::String(string1)), + Type::Constant(Constant::String(string2)), + ) => { + // Yah rust includes string alphanumerical equivalence of strings + Ok(string1 < string2) + } + (Type::Constant(c1), Type::Constant(c2)) => { + let lhs = cast_as_number(c1, strict_casts)?; + let rhs = cast_as_number(c2, strict_casts)?; + Ok(lhs < rhs) + } + (lhs, rhs) => { + crate::utilities::notify!("{:?}", (lhs, rhs)); + // Ok(TypeId::OPEN_BOOLEAN_TYPE) + Err(()) + } + } + } + + let is_dependent = types.get_type_by_id(lhs).is_dependent() + || types.get_type_by_id(rhs).is_dependent(); + + if is_dependent { + { + if let Type::Constructor(Constructor::BinaryOperator { + lhs: op_lhs, + operator, + rhs: op_rhs, + result: _, + }) = types.get_type_by_id(lhs) + { + if let ( + Type::Constant(Constant::Number(add)), + MathematicalOrBitwiseOperation::Add, + Type::Constant(Constant::Number(lt)), + ) = (types.get_type_by_id(*op_rhs), operator, types.get_type_by_id(rhs)) + { + crate::utilities::notify!("Shifted LT"); + lhs = *op_lhs; + rhs = types.register_type(Type::Constant(Constant::Number(lt - add))); + } + } + } + + { + // let lhs = get_constraint(lhs, types).unwrap_or(lhs); + // let rhs = get_constraint(rhs, types).unwrap_or(rhs); + + if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) + || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) + { + return Err(()); + } + + // Tidies some things for counting loop iterations + + // Checking disjoint-ness for inequalities (TODO under option) via distribution + if let ((Some(lhs_range), _), (Some(rhs_range), _)) = ( + intrinsics::get_range_and_mod_class(lhs, types), + intrinsics::get_range_and_mod_class(rhs, types), + ) { + crate::utilities::notify!("{:?}", (lhs_range, rhs_range)); + if lhs_range.below(rhs_range) { + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + if lhs_range.above(rhs_range) { + return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); + } + } + } + + let constructor = Constructor::CanonicalRelationOperator { + lhs, + operator: CanonicalEqualityAndInequality::LessThan, + rhs, + }; + Ok(( + types.register_type(crate::Type::Constructor(constructor)), + EqualityAndInequalityResultKind::Condition, + )) + } else { + attempt_less_than(lhs, rhs, types, strict_casts).map(|value| { + ( + if value { TypeId::TRUE } else { TypeId::FALSE }, + EqualityAndInequalityResultKind::Constant, + ) + }) + } + } + // equal OR less than + EqualityAndInequality::LessThanOrEqual => { + let (equality_result, warning) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::StrictEqual, + rhs, + info, + types, + strict_casts, + )?; + + if equality_result == TypeId::TRUE { + Ok((equality_result, warning)) + } else if equality_result == TypeId::FALSE { + evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::LessThan, + rhs, + info, + types, + strict_casts, + ) + } else { + let (less_than_result, warning) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::LessThan, + rhs, + info, + types, + strict_casts, + )?; + Ok((types.new_logical_or_type(equality_result, less_than_result), warning)) + } + } + EqualityAndInequality::StrictNotEqual => { + let (equality_result, kind) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::StrictEqual, + rhs, + info, + types, + strict_casts, + )?; + if let EqualityAndInequalityResultKind::Condition = kind { + Ok((types.new_logical_negation_type(equality_result), kind)) + } else { + let negated = if let TypeId::TRUE = equality_result { + TypeId::FALSE + } else if let TypeId::FALSE = equality_result { + TypeId::TRUE + } else { + todo!() + }; + Ok((negated, kind)) + } + } + EqualityAndInequality::Equal => { + crate::utilities::notify!("TODO equal operator"); + Ok((TypeId::OPEN_BOOLEAN_TYPE, EqualityAndInequalityResultKind::Condition)) + } + EqualityAndInequality::NotEqual => { + let (equality_result, kind) = evaluate_equality_inequality_operation( + lhs, + &EqualityAndInequality::Equal, + rhs, + info, + types, + strict_casts, + )?; + if let EqualityAndInequalityResultKind::Condition = kind { + Ok((types.new_logical_negation_type(equality_result), kind)) + } else { + let negated = if let TypeId::TRUE = equality_result { + TypeId::FALSE + } else if let TypeId::FALSE = equality_result { + TypeId::TRUE + } else { + todo!() + }; + Ok((negated, kind)) + } + } + // Swapping operands! + EqualityAndInequality::GreaterThan => evaluate_equality_inequality_operation( + rhs, + &EqualityAndInequality::LessThan, + lhs, + info, + types, + strict_casts, + ), + // Swapping operands! + EqualityAndInequality::GreaterThanOrEqual => evaluate_equality_inequality_operation( + rhs, + &EqualityAndInequality::LessThanOrEqual, + lhs, + info, + types, + strict_casts, + ), + } +} + +#[allow(clippy::let_and_return)] +pub fn is_null_or_undefined( + ty: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::types::TypeStore, +) -> TypeId { + let is_null = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::NULL_TYPE, + info, + types, + false, + ) + .map_or(TypeId::ERROR_TYPE, |(left, _)| left); + + if let TypeId::TRUE = is_null { + is_null + } else { + let is_undefined = evaluate_equality_inequality_operation( + ty, + &EqualityAndInequality::StrictEqual, + TypeId::UNDEFINED_TYPE, + info, + types, + false, + ) + .map_or(TypeId::ERROR_TYPE, |(left, _)| left); + + if let TypeId::FALSE = is_null { + is_undefined + } else { + types.new_logical_or_type(is_null, is_undefined) + } + } +} diff --git a/checker/src/features/operations/logical.rs b/checker/src/features/operations/logical.rs new file mode 100644 index 00000000..46493153 --- /dev/null +++ b/checker/src/features/operations/logical.rs @@ -0,0 +1,113 @@ +use super::{ + super::{conditional, narrowing}, + equality::is_null_or_undefined, +}; +use crate::{Type, TypeId}; + +#[derive(Copy, Clone, Debug)] +pub enum LogicalOperator { + And, + Or, + /// TODO is this canonical? + NullCoalescing, +} + +/// TODO strict casts! +pub fn evaluate_logical_operation_with_expression< + 'a, + T: crate::ReadFromFS, + A: crate::ASTImplementation, +>( + lhs: (TypeId, source_map::Span), + operator: LogicalOperator, + rhs: &'a A::Expression<'a>, + checking_data: &mut crate::CheckingData, + environment: &mut crate::Environment, + expecting: TypeId, +) -> Result { + match operator { + LogicalOperator::And => Ok(conditional::new_conditional_context( + environment, + lhs, + |env: &mut crate::Environment, data: &mut crate::CheckingData| { + A::synthesise_expression(rhs, expecting, env, data) + }, + Some(|env: &mut crate::Environment, checking_data: &mut crate::CheckingData| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + narrowing::FASLY, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }), + checking_data, + )), + LogicalOperator::Or => Ok(conditional::new_conditional_context( + environment, + lhs, + |env: &mut crate::Environment, checking_data: &mut crate::CheckingData| { + if let Some(constraint) = crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + narrowing::NOT_FASLY, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data.types.register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }, + Some(|env: &mut crate::Environment, data: &mut crate::CheckingData| { + A::synthesise_expression(rhs, expecting, env, data) + }), + checking_data, + )), + LogicalOperator::NullCoalescing => { + let is_lhs_null_or_undefined = + is_null_or_undefined(lhs.0, environment, &mut checking_data.types); + // Equivalent to: `(lhs is null or undefined) ? lhs : rhs` + Ok(conditional::new_conditional_context( + environment, + (is_lhs_null_or_undefined, lhs.1), + |env: &mut crate::Environment, checking_data: &mut crate::CheckingData| { + if let Some(constraint) = + crate::types::get_constraint(lhs.0, &checking_data.types) + { + let mut result = Vec::new(); + narrowing::build_union_from_filter( + constraint, + narrowing::NOT_NULL_OR_UNDEFINED, + &mut result, + env, + &checking_data.types, + ); + let narrowed_to = checking_data.types.new_or_type_from_iterator(result); + checking_data + .types + .register_type(Type::Narrowed { from: lhs.0, narrowed_to }) + } else { + lhs.0 + } + }, + Some(|env: &mut crate::Environment, data: &mut crate::CheckingData| { + A::synthesise_expression(rhs, expecting, env, data) + }), + checking_data, + )) + } + } +} diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs new file mode 100644 index 00000000..1caec5f4 --- /dev/null +++ b/checker/src/features/operations/mathematical_bitwise.rs @@ -0,0 +1,185 @@ +use crate::{ + types::{cast_as_number, cast_as_string, helpers, intrinsics, Constant, Type}, + TypeId, +}; + +/// For these **binary** operations both operands are synthesised +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum MathematicalOrBitwiseOperation { + Add, + Subtract, + Multiply, + Divide, + Modulo, + Exponent, + BitwiseShiftLeft, + BitwiseShiftRight, + BitwiseShiftRightUnsigned, + BitwiseAnd, + BitwiseXOr, + BitwiseOr, +} + +/// TODO proper error type +pub fn evaluate_mathematical_operation( + lhs: TypeId, + operator: MathematicalOrBitwiseOperation, + rhs: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::TypeStore, + strict_casts: bool, +) -> Result { + fn attempt_constant_math_operator( + lhs: TypeId, + operator: MathematicalOrBitwiseOperation, + rhs: TypeId, + types: &mut crate::TypeStore, + strict_casts: bool, + ) -> Result { + if let MathematicalOrBitwiseOperation::Add = operator { + let constant = match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => { + Constant::Number(lhs + rhs) + } + (Type::Constant(lhs), Type::Constant(rhs)) => { + let mut first = cast_as_string(lhs, strict_casts)?; + let second = cast_as_string(rhs, strict_casts)?; + // Concatenate strings + first.push_str(&second); + Constant::String(first) + } + _ => return Err(()), + }; + Ok(types.new_constant_type(constant)) + } else { + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + (Type::Constant(c1), Type::Constant(c2)) => { + let lhs = cast_as_number(c1, strict_casts).unwrap_or(f64::NAN); + let rhs = cast_as_number(c2, strict_casts).unwrap_or(f64::NAN); + // TODO hopefully Rust implementation is the same as JS + #[allow(clippy::cast_possible_truncation)] + let value = match operator { + MathematicalOrBitwiseOperation::Add => unreachable!(), + MathematicalOrBitwiseOperation::Subtract => lhs - rhs, + MathematicalOrBitwiseOperation::Multiply => lhs * rhs, + MathematicalOrBitwiseOperation::Divide => lhs / rhs, + MathematicalOrBitwiseOperation::Modulo => lhs % rhs, + MathematicalOrBitwiseOperation::Exponent => lhs.powf(rhs), + MathematicalOrBitwiseOperation::BitwiseShiftLeft => { + f64::from((lhs as i32).checked_shl(rhs as u32).unwrap_or(0)) + } + MathematicalOrBitwiseOperation::BitwiseShiftRight => { + f64::from((lhs as i32).checked_shr(rhs as u32).unwrap_or(0)) + } + MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned => { + (lhs as i32).wrapping_shr(rhs as u32).into() + } + MathematicalOrBitwiseOperation::BitwiseAnd => { + f64::from((lhs as i32) & (rhs as i32)) + } + MathematicalOrBitwiseOperation::BitwiseXOr => { + f64::from((lhs as i32) ^ (rhs as i32)) + } + MathematicalOrBitwiseOperation::BitwiseOr => { + f64::from((lhs as i32) | (rhs as i32)) + } + }; + let value = ordered_float::NotNan::try_from(value); + let ty = match value { + Ok(value) => types.new_constant_type(Constant::Number(value)), + Err(_) => TypeId::NAN, + }; + Ok(ty) + } + _ => Err(()), + } + } + } + + if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE { + return Ok(TypeId::ERROR_TYPE); + } + + let is_dependent = + types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent(); + + if is_dependent { + let can_be_string = if let MathematicalOrBitwiseOperation::Add = operator { + let left_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); + let right_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); + let left_is_string_or_number = + left_is_string || helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); + let right_is_string_or_number = + right_is_string || helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types); + + if !left_is_string_or_number || !right_is_string_or_number { + return Err(()); + } + + left_is_string || right_is_string + } else { + let left_is_number = helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); + + if !left_is_number || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) { + return Err(()); + } + + false + }; + + // :) + if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ZERO) = (operator, rhs) { + // This holds for NaN. Thus can do in every case + return Ok(TypeId::ONE); + } else if let (MathematicalOrBitwiseOperation::Exponent, TypeId::ONE, true) = + (operator, rhs, intrinsics::is_not_not_a_number(lhs, types)) + { + return Ok(lhs); + } else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO) + | (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, rhs) + { + return Ok(lhs); + } else if let (MathematicalOrBitwiseOperation::Add, TypeId::ZERO) + | (MathematicalOrBitwiseOperation::Multiply, TypeId::ONE) = (operator, lhs) + { + return Ok(rhs); + } + + let result = if can_be_string { + TypeId::STRING_TYPE + } else if let ( + MathematicalOrBitwiseOperation::Add | MathematicalOrBitwiseOperation::Multiply, + (lhs_range, _lhs_modulo), + (rhs_range, _rhs_modulo), + ) = ( + operator, + intrinsics::get_range_and_mod_class(lhs, types), + intrinsics::get_range_and_mod_class(rhs, types), + ) { + crate::utilities::notify!( + "{:?} with {:?}", + (lhs_range, _lhs_modulo), + (rhs_range, _rhs_modulo) + ); + if let (Some(lhs_range), Some(rhs_range)) = (lhs_range, rhs_range) { + let range = match operator { + MathematicalOrBitwiseOperation::Add => lhs_range.space_addition(rhs_range), + MathematicalOrBitwiseOperation::Multiply => { + lhs_range.space_multiplication(rhs_range) + } + _ => unreachable!(), + }; + intrinsics::range_to_type(range, types) + } else { + TypeId::NUMBER_TYPE + } + } else { + TypeId::NUMBER_TYPE + }; + + let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; + Ok(types.register_type(crate::Type::Constructor(constructor))) + } else { + attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) + } +} diff --git a/checker/src/features/operations/mod.rs b/checker/src/features/operations/mod.rs new file mode 100644 index 00000000..5164812f --- /dev/null +++ b/checker/src/features/operations/mod.rs @@ -0,0 +1,12 @@ +mod equality; +mod logical; +mod mathematical_bitwise; +mod unary; + +pub use equality::{ + evaluate_equality_inequality_operation, is_null_or_undefined, CanonicalEqualityAndInequality, + EqualityAndInequality, EqualityAndInequalityResultKind, +}; +pub use logical::{evaluate_logical_operation_with_expression, LogicalOperator}; +pub use mathematical_bitwise::{evaluate_mathematical_operation, MathematicalOrBitwiseOperation}; +pub use unary::{evaluate_unary_operator, UnaryOperation}; diff --git a/checker/src/features/operations/unary.rs b/checker/src/features/operations/unary.rs new file mode 100644 index 00000000..d51a3e8b --- /dev/null +++ b/checker/src/features/operations/unary.rs @@ -0,0 +1,84 @@ +use crate::{ + types::{ + cast_as_number, helpers::simple_subtype, is_type_truthy_falsy, Constant, Type, TypeId, + }, + Decidable, +}; + +/// `typeof` and some others done elsewhere +#[derive(Clone, Copy, Debug, binary_serialize_derive::BinarySerializable)] +pub enum UnaryOperation { + /// Treated as `(value ? false : true)` + LogicalNot, + /// Treated as `0 - value` (could also do -1 * value?) + Negation, + /// Treated as `value ^ 0xFFFF_FFFF` + BitwiseNot, +} + +/// Tries to evaluate unary operation for constant terms. Else delegates to binary operations that handle equivalent thing +pub fn evaluate_unary_operator( + operator: UnaryOperation, + operand: TypeId, + info: &impl crate::context::InformationChain, + types: &mut crate::TypeStore, + strict_casts: bool, +) -> Result { + if operand == TypeId::ERROR_TYPE { + return Ok(operand); + } + + match operator { + UnaryOperation::LogicalNot => { + if let Decidable::Known(value) = is_type_truthy_falsy(operand, types) { + if value { + Ok(TypeId::FALSE) + } else { + Ok(TypeId::TRUE) + } + } else { + let is_boolean = simple_subtype(operand, TypeId::BOOLEAN_TYPE, info, types); + if is_boolean { + Ok(types.new_logical_negation_type(operand)) + } else { + Err(()) + } + } + } + UnaryOperation::Negation | UnaryOperation::BitwiseNot => { + if let Type::Constant(cst) = types.get_type_by_id(operand) { + let value = cast_as_number(cst, strict_casts).expect("hmm"); + let value = match operator { + UnaryOperation::BitwiseNot => f64::from(!(value as i32)), + UnaryOperation::Negation => -value, + UnaryOperation::LogicalNot => unreachable!(), + }; + let value = ordered_float::NotNan::try_from(value); + Ok(match value { + Ok(value) => types.new_constant_type(Constant::Number(value)), + Err(_) => TypeId::NAN, + }) + } else { + match operator { + UnaryOperation::BitwiseNot => super::evaluate_mathematical_operation( + TypeId::MAX_U32, + super::MathematicalOrBitwiseOperation::BitwiseXOr, + operand, + info, + types, + strict_casts, + ), + UnaryOperation::Negation => super::evaluate_mathematical_operation( + TypeId::ZERO, + super::MathematicalOrBitwiseOperation::Subtract, + operand, + info, + types, + strict_casts, + ), + UnaryOperation::LogicalNot => unreachable!("handled above"), + } + } + } + } +} diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs index 747877b2..91b4691c 100644 --- a/checker/src/features/template_literal.rs +++ b/checker/src/features/template_literal.rs @@ -144,7 +144,7 @@ where checking_data.types.new_constant_type(Constant::String(static_part.to_owned())); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, lhs, environment, &mut checking_data.types, @@ -164,7 +164,7 @@ where ); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs, environment, &mut checking_data.types, @@ -184,7 +184,7 @@ where checking_data.types.new_constant_type(Constant::String(final_part.to_owned())); let result = super::operations::evaluate_mathematical_operation( acc, - crate::features::operations::MathematicalAndBitwise::Add, + crate::features::operations::MathematicalOrBitwiseOperation::Add, value, environment, &mut checking_data.types, diff --git a/checker/src/options.rs b/checker/src/options.rs index 00a39872..84069fd7 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -53,6 +53,11 @@ pub struct TypeCheckOptions { pub measure_time: bool, + /// Enables two things: + /// - range and modulo class inference + /// - modifications to ranges and classes based on operations + pub advanced_number_intrinsics: bool, + /// Printing internal diagnostics in dts pub debug_dts: bool, } @@ -78,6 +83,7 @@ impl Default for TypeCheckOptions { measure_time: false, debug_dts: false, extra_syntax: true, + advanced_number_intrinsics: false, } } } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 08e95b32..d9040422 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -32,9 +32,10 @@ use crate::{ objects::ObjectBuilder, operations::is_null_or_undefined, operations::{ - evaluate_logical_operation_with_expression, - evaluate_pure_binary_operation_handle_errors, evaluate_unary_operator, - EqualityAndInequality, MathematicalAndBitwise, UnaryOperation, + evaluate_equality_inequality_operation, evaluate_logical_operation_with_expression, + evaluate_mathematical_operation, evaluate_unary_operator, EqualityAndInequality, + EqualityAndInequalityResultKind, LogicalOperator, MathematicalOrBitwiseOperation, + UnaryOperation, }, template_literal::synthesise_template_literal_expression, variables::VariableWithValue, @@ -46,7 +47,7 @@ use crate::{ }, types::{ calling::{CallingInput, UnsynthesisedArgument}, - get_larger_type, + helpers::get_larger_type, logical::{Logical, LogicalOrValid}, printing::{print_property_key, print_type}, properties::{ @@ -251,11 +252,9 @@ pub(super) fn synthesise_expression( | BinaryOperator::NullCoalescing = operator { let operator = match operator { - BinaryOperator::LogicalAnd => crate::features::operations::LogicalOperator::And, - BinaryOperator::LogicalOr => crate::features::operations::LogicalOperator::Or, - BinaryOperator::NullCoalescing => { - crate::features::operations::LogicalOperator::NullCoalescing - } + BinaryOperator::LogicalAnd => LogicalOperator::And, + BinaryOperator::LogicalOr => LogicalOperator::Or, + BinaryOperator::NullCoalescing => LogicalOperator::NullCoalescing, _ => unreachable!(), }; return evaluate_logical_operation_with_expression( @@ -267,64 +266,175 @@ pub(super) fn synthesise_expression( expecting, // TODO unwrap ) .unwrap(); - } + } else if let BinaryOperator::StrictEqual + | BinaryOperator::StrictNotEqual + | BinaryOperator::Equal + | BinaryOperator::NotEqual + | BinaryOperator::GreaterThan + | BinaryOperator::LessThan + | BinaryOperator::LessThanEqual + | BinaryOperator::GreaterThanEqual = operator + { + let rhs_ty = + synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + let operator = match operator { + BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual, + BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual, + BinaryOperator::Equal => EqualityAndInequality::Equal, + BinaryOperator::NotEqual => EqualityAndInequality::NotEqual, + BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan, + BinaryOperator::LessThan => EqualityAndInequality::LessThan, + BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual, + BinaryOperator::GreaterThanEqual => EqualityAndInequality::GreaterThanOrEqual, + _ => unreachable!(), + }; + let result = evaluate_equality_inequality_operation( + lhs_ty, + &operator, + rhs_ty, + environment, + &mut checking_data.types, + checking_data.options.strict_casts, + ); - let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + if let Ok((result, warning)) = result { + if let EqualityAndInequalityResultKind::Disjoint = warning { + let lhs_pos = + ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = + ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_warning( + crate::TypeCheckWarning::DisjointEquality { + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + result: result == TypeId::TRUE, + position, + }, + ); + } - if lhs_ty == TypeId::ERROR_TYPE || rhs_ty == TypeId::ERROR_TYPE { - return TypeId::ERROR_TYPE; - } + return result; + } else { + let lhs_pos = + ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = + ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); - let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); - let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); - - let operator = match operator { - BinaryOperator::Add => MathematicalAndBitwise::Add.into(), - BinaryOperator::Subtract => MathematicalAndBitwise::Subtract.into(), - BinaryOperator::Multiply => MathematicalAndBitwise::Multiply.into(), - BinaryOperator::Divide => MathematicalAndBitwise::Divide.into(), - BinaryOperator::Modulo => MathematicalAndBitwise::Modulo.into(), - BinaryOperator::Exponent => MathematicalAndBitwise::Exponent.into(), - BinaryOperator::BitwiseShiftLeft => MathematicalAndBitwise::BitwiseShiftLeft.into(), - BinaryOperator::BitwiseShiftRight => { - MathematicalAndBitwise::BitwiseShiftRight.into() - } - BinaryOperator::BitwiseShiftRightUnsigned => { - MathematicalAndBitwise::BitwiseShiftRightUnsigned.into() - } - BinaryOperator::BitwiseAnd => MathematicalAndBitwise::BitwiseAnd.into(), - BinaryOperator::BitwiseXOr => MathematicalAndBitwise::BitwiseXOr.into(), - BinaryOperator::BitwiseOr => MathematicalAndBitwise::BitwiseOr.into(), - BinaryOperator::StrictEqual => EqualityAndInequality::StrictEqual.into(), - BinaryOperator::StrictNotEqual => EqualityAndInequality::StrictNotEqual.into(), - BinaryOperator::Equal => EqualityAndInequality::Equal.into(), - BinaryOperator::NotEqual => EqualityAndInequality::NotEqual.into(), - BinaryOperator::GreaterThan => EqualityAndInequality::GreaterThan.into(), - BinaryOperator::LessThan => EqualityAndInequality::LessThan.into(), - BinaryOperator::LessThanEqual => EqualityAndInequality::LessThanOrEqual.into(), - BinaryOperator::GreaterThanEqual => { - EqualityAndInequality::GreaterThanOrEqual.into() - } - BinaryOperator::LogicalAnd - | BinaryOperator::LogicalOr - | BinaryOperator::NullCoalescing => { - unreachable!() - } - BinaryOperator::Pipe | BinaryOperator::Compose => { - checking_data.raise_unimplemented_error( - "special operations", - position.with_source(environment.get_source()), + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidEqualityOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, ); - return TypeId::UNIMPLEMENTED_ERROR_TYPE; + + return TypeId::ERROR_TYPE; } - }; - Instance::RValue(evaluate_pure_binary_operation_handle_errors( - (lhs_ty, lhs_pos), - operator, - (rhs_ty, rhs_pos), - checking_data, - environment, - )) + } else { + let rhs_ty = + synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + let operator = match operator { + BinaryOperator::Add => MathematicalOrBitwiseOperation::Add, + BinaryOperator::Subtract => MathematicalOrBitwiseOperation::Subtract, + BinaryOperator::Multiply => MathematicalOrBitwiseOperation::Multiply, + BinaryOperator::Divide => MathematicalOrBitwiseOperation::Divide, + BinaryOperator::Modulo => MathematicalOrBitwiseOperation::Modulo, + BinaryOperator::Exponent => MathematicalOrBitwiseOperation::Exponent, + BinaryOperator::BitwiseShiftLeft => { + MathematicalOrBitwiseOperation::BitwiseShiftLeft + } + BinaryOperator::BitwiseShiftRight => { + MathematicalOrBitwiseOperation::BitwiseShiftRight + } + BinaryOperator::BitwiseShiftRightUnsigned => { + MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned + } + BinaryOperator::BitwiseAnd => MathematicalOrBitwiseOperation::BitwiseAnd, + BinaryOperator::BitwiseXOr => MathematicalOrBitwiseOperation::BitwiseXOr, + BinaryOperator::BitwiseOr => MathematicalOrBitwiseOperation::BitwiseOr, + BinaryOperator::Pipe | BinaryOperator::Compose => { + checking_data.raise_unimplemented_error( + "special operations", + position.with_source(environment.get_source()), + ); + return TypeId::UNIMPLEMENTED_ERROR_TYPE; + } + _ => { + unreachable!() + } + }; + let result = evaluate_mathematical_operation( + lhs_ty, + operator, + rhs_ty, + environment, + &mut checking_data.types, + checking_data.options.strict_casts, + ); + match result { + Ok(value) => Instance::RValue(value), + Err(()) => { + let lhs_pos = + ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = + ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_error( + TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, + ); + return TypeId::ERROR_TYPE; + } + } + } } Expression::UnaryOperation { operand, operator, position } => { match operator { @@ -1060,37 +1170,37 @@ fn operator_to_assignment_kind( ), BinaryAssignmentOperator::AddAssign | BinaryAssignmentOperator::BitwiseShiftRightUnsigned => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Add) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Add) } BinaryAssignmentOperator::SubtractAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Subtract) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Subtract) } BinaryAssignmentOperator::MultiplyAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Multiply) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Multiply) } BinaryAssignmentOperator::DivideAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Divide) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Divide) } BinaryAssignmentOperator::ModuloAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Modulo) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Modulo) } BinaryAssignmentOperator::ExponentAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::Exponent) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::Exponent) } BinaryAssignmentOperator::BitwiseShiftLeftAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftLeft) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftLeft) } BinaryAssignmentOperator::BitwiseShiftRightAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseShiftRight) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseShiftRight) } BinaryAssignmentOperator::BitwiseAndAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseAnd) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseAnd) } BinaryAssignmentOperator::BitwiseXOrAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseXOr) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseXOr) } BinaryAssignmentOperator::BitwiseOrAssign => { - AssignmentKind::PureUpdate(MathematicalAndBitwise::BitwiseOr) + AssignmentKind::PureUpdate(MathematicalOrBitwiseOperation::BitwiseOr) } } } diff --git a/checker/src/synthesis/type_annotations.rs b/checker/src/synthesis/type_annotations.rs index dea12315..304b3333 100644 --- a/checker/src/synthesis/type_annotations.rs +++ b/checker/src/synthesis/type_annotations.rs @@ -11,13 +11,11 @@ use crate::{ features::objects::ObjectBuilder, types::{ generics::generic_type_arguments::GenericArguments, - properties::{PropertyKey, PropertyValue, Publicity}, - Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId, - }, - types::{ generics::ExplicitTypeArguments, + helpers::{ArrayItem, Counter}, intrinsics::{self, distribute_tsc_string_intrinsic}, - ArrayItem, Counter, + properties::{PropertyKey, PropertyValue, Publicity}, + Constant, Constructor, PartiallyAppliedGenerics, PolyNature, Type, TypeId, }, CheckingData, Map, }; @@ -490,7 +488,7 @@ pub fn synthesise_type_annotation( items.push((pos, ArrayItem::Optional(annotation_ty))); } TupleElementKind::Spread => { - let slice = crate::types::as_slice( + let slice = crate::types::helpers::as_slice( annotation_ty, &checking_data.types, environment, @@ -521,15 +519,15 @@ pub fn synthesise_type_annotation( ); for (ty_position, item) in items { + use crate::types::helpers::ArrayItem; + let value = match item { - crate::types::ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty), - crate::types::ArrayItem::Optional(item_ty) => { - PropertyValue::ConditionallyExists { - condition: TypeId::OPEN_BOOLEAN_TYPE, - truthy: Box::new(PropertyValue::Value(item_ty)), - } - } - crate::types::ArrayItem::Wildcard(on) => { + ArrayItem::Member(item_ty) => PropertyValue::Value(item_ty), + ArrayItem::Optional(item_ty) => PropertyValue::ConditionallyExists { + condition: TypeId::OPEN_BOOLEAN_TYPE, + truthy: Box::new(PropertyValue::Value(item_ty)), + }, + ArrayItem::Wildcard(on) => { crate::utilities::notify!("found wildcard"); let after = idx.into_type(&mut checking_data.types); @@ -723,7 +721,8 @@ pub fn synthesise_type_annotation( checking_data.types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs: lhs, result: TypeId::STRING_TYPE, }, @@ -740,7 +739,7 @@ pub fn synthesise_type_annotation( }; let constructor = crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs, result: TypeId::STRING_TYPE, }; @@ -756,7 +755,8 @@ pub fn synthesise_type_annotation( checking_data.types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: acc, - operator: crate::features::operations::MathematicalAndBitwise::Add, + operator: + crate::features::operations::MathematicalOrBitwiseOperation::Add, rhs: lhs, result: TypeId::STRING_TYPE, }, diff --git a/checker/src/synthesis/variables.rs b/checker/src/synthesis/variables.rs index 236d890f..56286456 100644 --- a/checker/src/synthesis/variables.rs +++ b/checker/src/synthesis/variables.rs @@ -15,7 +15,8 @@ use crate::{ }, synthesis::parser_property_key_to_checker_property_key, types::{ - get_larger_type, printing, + helpers::get_larger_type, + printing, properties::{ get_properties_on_single_type, get_property_key_names_on_a_single_type, PropertyKey, Publicity, diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 2beda63b..09210f99 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -22,7 +22,7 @@ use crate::{ types::{ functions::{FunctionBehavior, FunctionEffect, FunctionType}, generics::substitution::SubstitutionArguments, - get_structure_arguments_based_on_object_constraint, + helpers::get_structure_arguments_based_on_object_constraint, logical::{Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical}, properties::AccessMode, substitute, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, Type, diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 42e9c361..309ca37a 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -1,4 +1,7 @@ -use super::{Constant, PartiallyAppliedGenerics, Type, TypeId, TypeStore}; +use super::{ + Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, Type, TypeId, + TypeStore, +}; use crate::context::InformationChain; /// For equality + [`crate::intrinsics::Intrinsics::Not`] @@ -106,12 +109,6 @@ pub fn types_are_disjoint( } else if let Type::And(lhs_lhs, lhs_rhs) = lhs_ty { types_are_disjoint(*lhs_lhs, rhs, already_checked, information, types) || types_are_disjoint(*lhs_rhs, rhs, already_checked, information, types) - } else if let Some(lhs) = super::get_constraint(lhs, types) { - // TODO not sure whether these should be here? - types_are_disjoint(lhs, rhs, already_checked, information, types) - } else if let Some(rhs) = super::get_constraint(rhs, types) { - // TODO not sure whether these should be here? - types_are_disjoint(lhs, rhs, already_checked, information, types) } else if let Type::PartiallyAppliedGenerics( args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, ) = lhs_ty @@ -138,6 +135,50 @@ pub fn types_are_disjoint( ) = lhs_ty { number_range_disjoint(args, rhs, types) + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs: _lhs, + operator: MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, + }) = lhs_ty + { + if let ( + Type::Constant(Constant::Number(lhs_mod)), + Type::Constant(Constant::Number(num)), + ) = (types.get_type_by_id(*rhs), rhs_ty) + { + crate::utilities::notify!("{:?}", (num, lhs_mod)); + // Modulos return negative for negative number :( + // Checking whether out of range here + num.abs() > **lhs_mod + } else { + false + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs: _lhs, + operator: MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, + }) = rhs_ty + { + if let ( + Type::Constant(Constant::Number(num)), + Type::Constant(Constant::Number(rhs_mod)), + ) = (types.get_type_by_id(*rhs), lhs_ty) + { + crate::utilities::notify!("{:?}", (num, rhs_mod)); + // Modulos return negative for negative number :( + // Checking whether out of range here + num.abs() > **rhs_mod + } else { + false + } + } else if let Some(lhs) = super::get_constraint(lhs, types) { + // TODO not sure whether these should be here? + types_are_disjoint(lhs, rhs, already_checked, information, types) + } else if let Some(rhs) = super::get_constraint(rhs, types) { + // TODO not sure whether these should be here? + types_are_disjoint(lhs, rhs, already_checked, information, types) } else if let Type::Constant(lhs_cst) = lhs_ty { if let Type::Constant(rhs_cst) = rhs_ty { lhs_cst != rhs_cst diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs new file mode 100644 index 00000000..f0c50040 --- /dev/null +++ b/checker/src/types/helpers.rs @@ -0,0 +1,373 @@ +use super::{ + get_constraint, properties, subtyping, Constant, Constructor, GenericArguments, GenericChain, + InformationChain, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, PolyNature, Type, + TypeId, TypeStore, +}; + +pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>( + object: TypeId, + info_chain: &C, + types: &'a TypeStore, +) -> Option<&'a GenericArguments> { + if let Some(object_constraint) = + info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied()) + { + let ty = types.get_type_by_id(object_constraint); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty { + Some(arguments) + } else { + crate::utilities::notify!("Generics might be missed here {:?}", ty); + None + } + } else { + None + } +} + +pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool { + // TODO should be `ObjectNature::AnonymousObjectType` or something else + let ty = types.get_type_by_id(id); + if let Type::Object(super::ObjectNature::RealDeal) = ty { + environment + .get_chain_of_info() + .any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE)) + } else if let Type::AliasTo { to, .. } = ty { + tuple_like(*to, types, environment) + } else { + false + } +} + +pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId { + // return Type::PropertyOf() + todo!() +} + +pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId { + todo!() + // if let PropertyKey::Type(slice) = +} + +pub fn get_array_length( + ctx: &impl InformationChain, + on: TypeId, + types: &TypeStore, +) -> Result, Option> { + let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length")); + let id = properties::get_simple_property_value(ctx, on, &length_property, types).ok_or(None)?; + if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) { + Ok(*n) + } else { + Err(Some(id)) + } +} + +/// TODO name? +#[derive(Clone, Copy, Debug)] +pub enum ArrayItem { + Member(TypeId), + Optional(TypeId), + Wildcard(TypeId), +} + +/// WIP +pub(crate) fn as_slice( + ty: TypeId, + types: &TypeStore, + environment: &crate::Environment, +) -> Result, ()> { + if tuple_like(ty, types, environment) { + let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty }; + let properties = + environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty)); + if let Some(properties) = properties { + Ok(properties + .iter() + .filter_map(|(_, key, value)| { + let not_length_value = !key.is_equal_to("length"); + not_length_value.then(|| { + crate::utilities::notify!("key (should be incremental) {:?}", key); + if key.as_number(types).is_some() { + if let crate::PropertyValue::ConditionallyExists { .. } = value { + ArrayItem::Optional(value.as_get_type(types)) + } else { + ArrayItem::Member(value.as_get_type(types)) + } + } else { + ArrayItem::Wildcard(value.as_get_type(types)) + } + }) + }) + .collect()) + } else { + crate::utilities::notify!("BAD"); + Err(()) + } + } else { + Err(()) + } +} + +/// WIP for counting slice indexes +#[derive(derive_enum_from_into::EnumFrom, Clone, Copy, Debug)] +pub enum Counter { + On(usize), + AddTo(TypeId), +} + +impl Counter { + /// TODO &mut or Self -> Self? + pub fn increment(&mut self, types: &mut TypeStore) { + match self { + Counter::On(value) => { + *value += 1; + } + Counter::AddTo(value) => { + *value = types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: *value, + operator: MathematicalOrBitwiseOperation::Add, + rhs: TypeId::ONE, + // TODO could be greater than + result: TypeId::NUMBER_TYPE, + })); + } + } + } + + pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) { + let current = self.into_type(types); + let new = types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: ty, + operator: MathematicalOrBitwiseOperation::Add, + rhs: current, + // TODO could be greater than + result: TypeId::NUMBER_TYPE, + })); + *self = Counter::AddTo(new); + } + + pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> { + match self { + Counter::On(value) => properties::PropertyKey::from_usize(value), + Counter::AddTo(ty) => properties::PropertyKey::Type(ty), + } + } + + pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { + match self { + Counter::On(value) => { + types.new_constant_type(Constant::Number((value as f64).try_into().unwrap())) + } + Counter::AddTo(ty) => ty, + } + } +} + +/// To fill in for TSC behavior for mapped types +#[must_use] +pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool { + match types.get_type_by_id(id) { + Type::AliasTo { to, .. } => references_key_of(*to, types), + Type::Or(lhs, rhs) | Type::And(lhs, rhs) => { + references_key_of(*lhs, types) || references_key_of(*rhs, types) + } + Type::RootPolyType(c) => references_key_of(c.get_constraint(), types), + Type::Constructor(c) => { + if let Constructor::KeyOf(..) = c { + true + } else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c { + references_key_of(*lhs, types) || references_key_of(*rhs, types) + } else { + crate::utilities::notify!("TODO might have missed keyof {:?}", c); + false + } + } + Type::PartiallyAppliedGenerics(a) => { + if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments { + e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types)) + } else { + false + } + } + Type::Interface { .. } + | Type::Class { .. } + | Type::Constant(_) + | Type::Narrowed { .. } + | Type::FunctionReference(_) + | Type::Object(_) + | Type::SpecialObject(_) => false, + } +} + +#[allow(clippy::match_like_matches_macro)] +#[must_use] +pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool { + if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE { + true + } else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) { + true + } else { + false + } +} + +/// TODO want to skip mapped generics because that would break subtyping +#[must_use] +pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { + match types.get_type_by_id(ty) { + Type::Constructor(crate::types::Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) => Some((*condition, *truthy_result, *otherwise_result)), + Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)), + // For reasons ! + Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None, + _ => { + if let Some(constraint) = get_constraint(ty, types) { + get_conditional(constraint, types) + } else { + None + } + } + } +} + +/// TODO wip +#[must_use] +pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool { + if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty { + true + } else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) { + is_pseudo_continous((arg, generics), types) + } else { + let ty = types.get_type_by_id(ty); + if let Type::Or(left, right) = ty { + is_pseudo_continous((*left, generics), types) + || is_pseudo_continous((*right, generics), types) + } else if let Type::And(left, right) = ty { + is_pseudo_continous((*left, generics), types) + && is_pseudo_continous((*right, generics), types) + } else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty { + is_pseudo_continous((*extends, generics), types) + } else { + false + } + } +} + +#[must_use] +pub fn is_inferrable_type(ty: TypeId) -> bool { + matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) +} + +/// For quick checking. Wraps [`subtyping::type_is_subtype`] +#[must_use] +pub fn simple_subtype( + expr_ty: TypeId, + to_satisfy: TypeId, + information: &impl InformationChain, + types: &TypeStore, +) -> bool { + let mut state = subtyping::State { + already_checked: Default::default(), + mode: Default::default(), + contributions: Default::default(), + others: subtyping::SubTypingOptions { allow_errors: true }, + object_constraints: None, + }; + + subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype() +} + +// unfolds narrowing +pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId { + if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) { + // Hopefully don't have a nested from + *from + } else { + ty + } +} + +/// Temp fix for equality of narrowing stuff +pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::NOT_RESTRICTION, + arguments, + }) = types.get_type_by_id(ty) + { + let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); + types.get_type_by_id(inner).is_constant() + } else { + false + } +} + +// TODO narrowed as well +pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool { + if lhs == rhs { + true + } else if let (Type::Constant(lhs), Type::Constant(rhs)) = + (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + lhs == rhs + } else { + false + } +} + +#[derive(Debug, Copy, Clone)] +pub struct AndCondition(pub TypeId); + +#[derive(Debug)] +pub struct OrCase(pub Vec); + +pub fn into_conditions(ty: TypeId, types: &TypeStore) -> Vec { + // TODO aliases and such + if let Type::And(lhs, rhs) = types.get_type_by_id(ty) { + let mut buf = into_conditions(*lhs, types); + buf.append(&mut into_conditions(*rhs, types)); + buf + } else if let Some(backing) = get_constraint_or_alias(ty, types) { + into_conditions(backing, types) + } else { + vec![AndCondition(ty)] + } +} + +pub fn into_cases(ty: TypeId, types: &TypeStore) -> Vec { + if let Type::Or(lhs, rhs) = types.get_type_by_id(ty) { + let mut buf = into_cases(*lhs, types); + buf.append(&mut into_cases(*rhs, types)); + buf + } else if let Some(backing) = get_constraint_or_alias(ty, types) { + into_cases(backing, types) + } else { + vec![OrCase(into_conditions(ty, types))] + } +} + +/// Returns the constraint or base of a constant for a type. Otherwise just return the type +#[must_use] +pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId { + if let Some(poly_base) = get_constraint(on, types) { + poly_base + } else if let Type::Constant(cst) = types.get_type_by_id(on) { + cst.get_backing_type() + } else { + on + } +} + +#[must_use] +pub fn get_constraint_or_alias(on: TypeId, types: &TypeStore) -> Option { + match types.get_type_by_id(on) { + Type::RootPolyType(rpt) => Some(rpt.get_constraint()), + Type::Constructor(constr) => Some(constr.get_constraint()), + Type::AliasTo { to, parameters: None, .. } => Some(*to), + Type::Narrowed { narrowed_to, .. } => Some(*narrowed_to), + _ => None, + } +} diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index f45a3dcc..da1d0ac8 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -2,15 +2,18 @@ use source_map::SpanWithSource; use crate::{ types::{ - generics::generic_type_arguments::GenericArguments, get_constraint, - PartiallyAppliedGenerics, TypeStore, + generics::generic_type_arguments::GenericArguments, get_constraint, helpers::into_cases, + Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, TypeStore, }, TypeId, }; use super::Type; -pub use crate::utilities::float_range::FloatRange; +pub use crate::utilities::{ + float_range::{FloatRange, InclusiveExclusive}, + modulo_class::ModuloClass, +}; /// These are special marker types (using [`Type::Alias`]) /// @@ -169,68 +172,31 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { } } +// TODO also take into account mod range #[must_use] -pub fn get_range(on: TypeId, types: &TypeStore) -> Option { - fn get_range2(range: &mut FloatRange, on: TypeId, types: &TypeStore) -> bool { - let on = get_constraint(on, types).unwrap_or(on); - let ty = types.get_type_by_id(on); - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN), - arguments, - }) = ty - { - let argument = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); - if let Type::Constant(crate::Constant::Number(argument)) = - types.get_type_by_id(argument) - { - if let TypeId::GREATER_THAN = *on { - range.floor.1 = *argument; - } else { - range.ceiling.1 = *argument; - } - true - } else { - false - } - } else if let Type::And(_l, _r) = ty { - // todo!("take intersection") - false - } else if let Type::Or(_l, _r) = ty { - // todo!("change inclusivity based on ==") - false - } else if let Type::Constant(crate::Constant::Number(number)) = ty { - *range = FloatRange::single(*number); - true - } else { - false - } - } - - let mut range = FloatRange::default(); - let ok = get_range2(&mut range, on, types); - ok.then_some(range) -} +pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { + // TODO skip if infinite + let floor_ty = types.new_constant_type(Constant::Number(range.floor.1)); + let ceiling_ty = types.new_constant_type(Constant::Number(range.ceiling.1)); + let floor = range + .get_greater_than() + .map(|_number| new_intrinsic(&Intrinsic::GreaterThan, floor_ty, types)); + let ceiling = + range.get_less_than().map(|_number| new_intrinsic(&Intrinsic::LessThan, ceiling_ty, types)); -#[must_use] -pub fn range_to_type(_range: FloatRange, _types: &mut TypeStore) -> TypeId { - // use source_map::Nullable; - - // let on = if let FloatRange::Inclusive { .. } = range { - // TypeId::INCLUSIVE_RANGE - // } else { - // TypeId::EXCLUSIVE_RANGE - // }; - // let (FloatRange::Inclusive { floor, ceiling } | FloatRange::Exclusive { floor, ceiling }) = - // range; - // let floor = types.new_constant_type(crate::Constant::Number(floor)); - // let ceiling = types.new_constant_type(crate::Constant::Number(ceiling)); - // let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([ - // (TypeId::NUMBER_GENERIC, (floor, SpanWithSource::NULL)), - // (TypeId::NUMBER_CEILING_GENERIC, (ceiling, SpanWithSource::NULL)), - // ])); - // types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) + let mut ty = if let (Some(f), Some(c)) = (floor, ceiling) { + types.new_and_type(f, c) + } else { + floor.or(ceiling).unwrap() + }; - TypeId::NUMBER_TYPE + if let InclusiveExclusive::Inclusive = range.floor.0 { + ty = types.new_or_type(ty, floor_ty); + } + if let InclusiveExclusive::Inclusive = range.ceiling.0 { + ty = types.new_or_type(ty, ceiling_ty); + } + ty } #[must_use] @@ -327,6 +293,7 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt } Intrinsic::CaseInsensitive => (TypeId::CASE_INSENSITIVE, TypeId::STRING_GENERIC), }; + let arguments = GenericArguments::ExplicitRestrictions(crate::Map::from_iter([( to_pair, (argument, SpanWithSource::NULL), @@ -334,3 +301,161 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) } + +pub fn get_range_and_mod_class( + ty: TypeId, + types: &TypeStore, +) -> (Option, Option) { + let cases = into_cases(ty, types); + let interesting = cases.iter().enumerate().find_map(|(idx, case)| { + case.0 + .iter() + .copied() + .try_fold(Vec::new(), |mut acc, ty| { + if let Ok(item) = PureNumberIntrinsic::try_from_type(ty.0, types) { + acc.push(item); + Some(acc) + } else { + None + } + }) + .map(|value| (idx, value)) + }); + + if let Some((idx, interesting)) = interesting { + let mut range: Option = None; + let mut modulo_class: Option = None; + for number_condition in interesting { + match number_condition { + PureNumberIntrinsic::GreaterThan(greater_than) => { + range = Some(match range { + None => FloatRange::new_greater_than(greater_than), + Some(mut range) => { + range.floor.1 = range.floor.1.max(greater_than); + range + } + }); + } + PureNumberIntrinsic::LessThan(less_than) => { + range = Some(match range { + None => FloatRange::new_less_than(less_than), + Some(mut range) => { + range.ceiling.1 = range.ceiling.1.max(less_than); + range + } + }); + } + PureNumberIntrinsic::Modulo { modulo, offset } => { + modulo_class = Some(match modulo_class { + None => ModuloClass::new(modulo, offset), + Some(_) => { + crate::utilities::notify!("TODO intersection"); + return (None, None); + } + }); + } + } + } + + for (other_idx, case) in cases.into_iter().enumerate() { + // Skip the interesting case + if idx == other_idx { + continue; + } + // Test whether all cases fit inside this new condition. Also modify ranges for inclusion if not + if let [value] = &case.0[..] { + if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(value.0) { + let num = *num; + if let Some(ref modulo_class) = modulo_class { + if !modulo_class.contains(num) { + return (None, None); + } + } + if let Some(ref mut range) = range { + if !range.contains(num) { + return (None, None); + } else if range.floor.1 == num { + range.floor.0 = InclusiveExclusive::Inclusive; + } else if range.ceiling.1 == num { + range.ceiling.0 = InclusiveExclusive::Inclusive; + } + } + } + } else { + return (None, None); + } + } + (range, modulo_class) + } else if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(ty) { + (Some(FloatRange::new_single(*num)), None) + } else { + crate::utilities::notify!("Not interesting or constant {:?}", ty); + (None, None) + } +} + +type BetterF64 = ordered_float::NotNan; + +/// Unit. No combinations at this point +pub enum PureNumberIntrinsic { + GreaterThan(BetterF64), + LessThan(BetterF64), + Modulo { modulo: BetterF64, offset: BetterF64 }, +} + +impl PureNumberIntrinsic { + pub fn try_from_type(ty: TypeId, types: &TypeStore) -> Result { + let ty = types.get_type_by_id(ty); + if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: on @ (TypeId::GREATER_THAN | TypeId::LESS_THAN | TypeId::MULTIPLE_OF), + arguments, + }) = ty + { + let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) { + match *on { + TypeId::GREATER_THAN => Ok(PureNumberIntrinsic::GreaterThan(*number)), + TypeId::LESS_THAN => Ok(PureNumberIntrinsic::LessThan(*number)), + TypeId::MULTIPLE_OF => Ok(PureNumberIntrinsic::Modulo { + modulo: *number, + offset: 0f64.try_into().unwrap(), + }), + _ => todo!(), + } + } else { + Err(()) + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs, + operator: MathematicalOrBitwiseOperation::Add, + rhs, + .. + }) = ty + { + let lhs_modulo = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::MULTIPLE_OF, + arguments, + }) = types.get_type_by_id(*lhs) + { + let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); + if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) { + Some(number) + } else { + None + } + } else { + None + }; + if let (Some(modulo), Type::Constant(Constant::Number(offset))) = + (lhs_modulo, types.get_type_by_id(*rhs)) + { + Ok(PureNumberIntrinsic::Modulo { modulo: *modulo, offset: *offset }) + } else { + Err(()) + } + } else { + crate::utilities::notify!("err here {:?}", ty); + Err(()) + } + } +} diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index 8436a1f9..849ac1df 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod classes; pub mod disjoint; pub mod functions; pub mod generics; +pub mod helpers; pub mod intrinsics; pub mod logical; pub mod printing; @@ -33,13 +34,12 @@ pub use crate::features::objects::SpecialObject; use crate::{ context::InformationChain, events::RootReference, - features::operations::{CanonicalEqualityAndInequality, MathematicalAndBitwise}, + features::operations::{CanonicalEqualityAndInequality, MathematicalOrBitwiseOperation}, subtyping::SliceArguments, Decidable, FunctionId, }; use derive_debug_extras::DebugExtras; -use derive_enum_from_into::EnumFrom; use source_map::SpanWithSource; pub type ExplicitTypeArgument = (TypeId, SpanWithSource); @@ -369,7 +369,7 @@ impl Type { pub enum Constructor { BinaryOperator { lhs: TypeId, - operator: MathematicalAndBitwise, + operator: MathematicalOrBitwiseOperation, rhs: TypeId, /// for add + number intrinsics result: TypeId, @@ -415,18 +415,32 @@ pub enum Constructor { } impl Constructor { - fn get_base(&self) -> Option { + pub fn get_constraint(&self) -> TypeId { match self { - Constructor::ConditionalResult { result_union: result, .. } - | Constructor::Awaited { result, .. } - | Constructor::Property { result, .. } - | Constructor::Image { result, .. } => Some(*result), - Constructor::BinaryOperator { .. } - | Constructor::CanonicalRelationOperator { .. } - | Constructor::TypeExtends(_) - | Constructor::TypeOperator(_) => None, - // TODO or symbol - Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE), + Constructor::BinaryOperator { result, .. } + | Constructor::Awaited { on: _, result } + | Constructor::Image { on: _, with: _, result } => *result, + Constructor::Property { on: _, under: _, result, mode: _ } => { + // crate::utilities::notify!("Here, result of a property get"); + *result + } + Constructor::ConditionalResult { result_union, .. } => { + // TODO dynamic and open poly + *result_union + } + Constructor::TypeOperator(op) => match op { + // TODO union of names + TypeOperator::TypeOf(_) => TypeId::STRING_TYPE, + TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => { + TypeId::BOOLEAN_TYPE + } + }, + Constructor::CanonicalRelationOperator { .. } => TypeId::BOOLEAN_TYPE, + Constructor::TypeExtends(op) => { + let crate::types::TypeExtends { .. } = op; + TypeId::BOOLEAN_TYPE + } + Constructor::KeyOf(_) => TypeId::STRING_TYPE, } } } @@ -666,32 +680,7 @@ pub(crate) fn is_explicit_generic(on: TypeId, types: &TypeStore) -> bool { pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { match types.get_type_by_id(on) { Type::RootPolyType(nature) => Some(nature.get_constraint()), - Type::Constructor(constructor) => match constructor.clone() { - Constructor::BinaryOperator { result, .. } - | Constructor::Awaited { on: _, result } - | Constructor::Image { on: _, with: _, result } => Some(result), - Constructor::Property { on: _, under: _, result, mode: _ } => { - // crate::utilities::notify!("Here, result of a property get"); - Some(result) - } - Constructor::ConditionalResult { result_union, .. } => { - // TODO dynamic and open poly - Some(result_union) - } - Constructor::TypeOperator(op) => match op { - // TODO union of names - TypeOperator::TypeOf(_) => Some(TypeId::STRING_TYPE), - TypeOperator::IsPrototype { .. } | TypeOperator::HasProperty(..) => { - Some(TypeId::BOOLEAN_TYPE) - } - }, - Constructor::CanonicalRelationOperator { .. } => Some(TypeId::BOOLEAN_TYPE), - Constructor::TypeExtends(op) => { - let crate::types::TypeExtends { .. } = op; - Some(TypeId::BOOLEAN_TYPE) - } - Constructor::KeyOf(_) => Some(TypeId::STRING_TYPE), - }, + Type::Constructor(constructor) => Some(constructor.get_constraint()), Type::Narrowed { from: _, narrowed_to } => Some(*narrowed_to), Type::Object(ObjectNature::RealDeal) => { // crate::utilities::notify!("Might be missing some mutations that are possible here"); @@ -701,18 +690,6 @@ pub(crate) fn get_constraint(on: TypeId, types: &TypeStore) -> Option { } } -/// Returns the constraint or base of a constant for a type. Otherwise just return the type -#[must_use] -pub fn get_larger_type(on: TypeId, types: &TypeStore) -> TypeId { - if let Some(poly_base) = get_constraint(on, types) { - poly_base - } else if let Type::Constant(cst) = types.get_type_by_id(on) { - cst.get_backing_type() - } else { - on - } -} - /// TODO T on `Array`, U, V on `Map` etc. Works around not having `&mut TypeStore` and mutations in-between #[derive(Clone, Debug, binary_serialize_derive::BinarySerializable)] pub enum LookUpGeneric { @@ -792,324 +769,3 @@ pub enum Confirmation { HasProperty { on: (), property: () }, IsType { on: (), ty: () }, } - -pub(crate) fn get_structure_arguments_based_on_object_constraint<'a, C: InformationChain>( - object: TypeId, - info_chain: &C, - types: &'a TypeStore, -) -> Option<&'a GenericArguments> { - if let Some(object_constraint) = - info_chain.get_chain_of_info().find_map(|c| c.object_constraints.get(&object).copied()) - { - let ty = types.get_type_by_id(object_constraint); - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { arguments, .. }) = ty { - Some(arguments) - } else { - crate::utilities::notify!("Generics might be missed here {:?}", ty); - None - } - } else { - None - } -} - -pub(crate) fn tuple_like(id: TypeId, types: &TypeStore, environment: &crate::Environment) -> bool { - // TODO should be `ObjectNature::AnonymousObjectType` or something else - let ty = types.get_type_by_id(id); - if let Type::Object(ObjectNature::RealDeal) = ty { - environment - .get_chain_of_info() - .any(|info| info.prototypes.get(&id).is_some_and(|p| *p == TypeId::ARRAY_TYPE)) - } else if let Type::AliasTo { to, .. } = ty { - tuple_like(*to, types, environment) - } else { - false - } -} - -pub(crate) fn _unfold_tuple(_ty: TypeId) -> TypeId { - // return Type::PropertyOf() - todo!() -} - -pub(crate) fn _assign_to_tuple(_ty: TypeId) -> TypeId { - todo!() - // if let PropertyKey::Type(slice) = -} - -fn get_array_length( - ctx: &impl InformationChain, - on: TypeId, - types: &TypeStore, -) -> Result, Option> { - let length_property = properties::PropertyKey::String(std::borrow::Cow::Borrowed("length")); - let id = get_simple_value(ctx, on, &length_property, types).ok_or(None)?; - if let Type::Constant(Constant::Number(n)) = types.get_type_by_id(id) { - Ok(*n) - } else { - Err(Some(id)) - } -} - -/// TODO name? -#[derive(Clone, Copy, Debug)] -pub enum ArrayItem { - Member(TypeId), - Optional(TypeId), - Wildcard(TypeId), -} - -/// WIP -pub(crate) fn as_slice( - ty: TypeId, - types: &TypeStore, - environment: &crate::Environment, -) -> Result, ()> { - if tuple_like(ty, types, environment) { - let ty = if let Type::AliasTo { to, .. } = types.get_type_by_id(ty) { *to } else { ty }; - let properties = - environment.get_chain_of_info().find_map(|info| info.current_properties.get(&ty)); - if let Some(properties) = properties { - Ok(properties - .iter() - .filter_map(|(_, key, value)| { - let not_length_value = !key.is_equal_to("length"); - not_length_value.then(|| { - crate::utilities::notify!("key (should be incremental) {:?}", key); - if key.as_number(types).is_some() { - if let crate::PropertyValue::ConditionallyExists { .. } = value { - ArrayItem::Optional(value.as_get_type(types)) - } else { - ArrayItem::Member(value.as_get_type(types)) - } - } else { - ArrayItem::Wildcard(value.as_get_type(types)) - } - }) - }) - .collect()) - } else { - crate::utilities::notify!("BAD"); - Err(()) - } - } else { - Err(()) - } -} - -/// WIP for counting slice indexes -#[derive(EnumFrom, Clone, Copy, Debug)] -pub enum Counter { - On(usize), - AddTo(TypeId), -} - -impl Counter { - /// TODO &mut or Self -> Self? - pub fn increment(&mut self, types: &mut TypeStore) { - match self { - Counter::On(value) => { - *value += 1; - } - Counter::AddTo(value) => { - *value = types.register_type(Type::Constructor(Constructor::BinaryOperator { - lhs: *value, - operator: MathematicalAndBitwise::Add, - rhs: TypeId::ONE, - // TODO could be greater than - result: TypeId::NUMBER_TYPE, - })); - } - } - } - - pub fn add_type(&mut self, ty: TypeId, types: &mut TypeStore) { - let current = self.into_type(types); - let new = types.register_type(Type::Constructor(Constructor::BinaryOperator { - lhs: ty, - operator: MathematicalAndBitwise::Add, - rhs: current, - // TODO could be greater than - result: TypeId::NUMBER_TYPE, - })); - *self = Counter::AddTo(new); - } - - pub(crate) fn into_property_key(self) -> properties::PropertyKey<'static> { - match self { - Counter::On(value) => properties::PropertyKey::from_usize(value), - Counter::AddTo(ty) => properties::PropertyKey::Type(ty), - } - } - - pub(crate) fn into_type(self, types: &mut TypeStore) -> TypeId { - match self { - Counter::On(value) => { - types.new_constant_type(Constant::Number((value as f64).try_into().unwrap())) - } - Counter::AddTo(ty) => ty, - } - } -} - -pub(crate) mod helpers { - use super::{ - get_constraint, subtyping, Constructor, GenericArguments, GenericChain, InformationChain, - PartiallyAppliedGenerics, PolyNature, Type, TypeId, TypeStore, - }; - - /// To fill in for TSC behavior for mapped types - #[must_use] - pub fn references_key_of(id: TypeId, types: &TypeStore) -> bool { - match types.get_type_by_id(id) { - Type::AliasTo { to, .. } => references_key_of(*to, types), - Type::Or(lhs, rhs) | Type::And(lhs, rhs) => { - references_key_of(*lhs, types) || references_key_of(*rhs, types) - } - Type::RootPolyType(c) => references_key_of(c.get_constraint(), types), - Type::Constructor(c) => { - if let Constructor::KeyOf(..) = c { - true - } else if let Constructor::BinaryOperator { lhs, rhs, operator: _, result: _ } = c { - references_key_of(*lhs, types) || references_key_of(*rhs, types) - } else { - crate::utilities::notify!("TODO might have missed keyof {:?}", c); - false - } - } - Type::PartiallyAppliedGenerics(a) => { - if let GenericArguments::ExplicitRestrictions(ref e) = a.arguments { - e.0.iter().any(|(_, (lhs, _))| references_key_of(*lhs, types)) - } else { - false - } - } - Type::Interface { .. } - | Type::Class { .. } - | Type::Constant(_) - | Type::Narrowed { .. } - | Type::FunctionReference(_) - | Type::Object(_) - | Type::SpecialObject(_) => false, - } - } - - #[allow(clippy::match_like_matches_macro)] - #[must_use] - pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool { - if ty == TypeId::UNIMPLEMENTED_ERROR_TYPE { - true - } else if let Type::RootPolyType(PolyNature::Error(_)) = types.get_type_by_id(ty) { - true - } else { - false - } - } - - /// TODO want to skip mapped generics because that would break subtyping - #[must_use] - pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { - match types.get_type_by_id(ty) { - Type::Constructor(crate::types::Constructor::ConditionalResult { - condition, - truthy_result, - otherwise_result, - result_union: _, - }) => Some((*condition, *truthy_result, *otherwise_result)), - Type::Or(left, right) => Some((TypeId::OPEN_BOOLEAN_TYPE, *left, *right)), - // For reasons ! - Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None, - _ => { - if let Some(constraint) = get_constraint(ty, types) { - get_conditional(constraint, types) - } else { - None - } - } - } - } - - /// TODO wip - #[must_use] - pub fn is_pseudo_continous((ty, generics): (TypeId, GenericChain), types: &TypeStore) -> bool { - if let TypeId::NUMBER_TYPE | TypeId::STRING_TYPE = ty { - true - } else if let Some(arg) = generics.as_ref().and_then(|args| args.get_single_argument(ty)) { - is_pseudo_continous((arg, generics), types) - } else { - let ty = types.get_type_by_id(ty); - if let Type::Or(left, right) = ty { - is_pseudo_continous((*left, generics), types) - || is_pseudo_continous((*right, generics), types) - } else if let Type::And(left, right) = ty { - is_pseudo_continous((*left, generics), types) - && is_pseudo_continous((*right, generics), types) - } else if let Type::RootPolyType(PolyNature::MappedGeneric { extends, .. }) = ty { - is_pseudo_continous((*extends, generics), types) - } else { - false - } - } - } - - #[must_use] - pub fn is_inferrable_type(ty: TypeId) -> bool { - matches!(ty, TypeId::ANY_TO_INFER_TYPE | TypeId::OBJECT_TYPE) - } - - /// For quick checking. Wraps [`subtyping::type_is_subtype`] - #[must_use] - pub fn simple_subtype( - expr_ty: TypeId, - to_satisfy: TypeId, - information: &impl InformationChain, - types: &TypeStore, - ) -> bool { - let mut state = subtyping::State { - already_checked: Default::default(), - mode: Default::default(), - contributions: Default::default(), - others: subtyping::SubTypingOptions { allow_errors: true }, - object_constraints: None, - }; - - subtyping::type_is_subtype(to_satisfy, expr_ty, &mut state, information, types).is_subtype() - } - - // unfolds narrowing - pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId { - if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) { - // Hopefully don't have a nested from - *from - } else { - ty - } - } - - /// Temp fix for equality of narrowing stuff - pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { - if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { - on: TypeId::NOT_RESTRICTION, - arguments, - }) = types.get_type_by_id(ty) - { - let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - types.get_type_by_id(inner).is_constant() - } else { - false - } - } - - // TODO narrowed as well - pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool { - if lhs == rhs { - true - } else if let (Type::Constant(lhs), Type::Constant(rhs)) = - (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) - { - lhs == rhs - } else { - false - } - } -} diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 9f280fdc..0b8eac28 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -9,7 +9,8 @@ use crate::{ calling::ThisValue, functions::{FunctionBehavior, FunctionEffect}, generics::generic_type_arguments::GenericArguments, - get_array_length, get_constraint, get_simple_value, + get_constraint, get_simple_property_value, + helpers::get_array_length, properties::{get_properties_on_single_type, AccessMode, PropertyKey, Publicity}, Constructor, GenericChainLink, ObjectNature, PartiallyAppliedGenerics, TypeExtends, }, @@ -78,11 +79,21 @@ pub fn print_type_into_buf( let r#type = types.get_type_by_id(ty); match r#type { Type::And(a, b) => { + let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + if value.0.is_some() || value.1.is_some() { + crate::utilities::notify!("{:?}", value); + } + print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" & "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); } Type::Or(a, b) => { + let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + if value.0.is_some() || value.1.is_some() { + crate::utilities::notify!("{:?}", value); + } + print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" | "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); @@ -618,8 +629,12 @@ pub fn print_type_into_buf( if i != 0 { buf.push_str(", "); } - let value = - get_simple_value(info, ty, &PropertyKey::from_usize(i), types); + let value = get_simple_property_value( + info, + ty, + &PropertyKey::from_usize(i), + types, + ); if let Some(value) = value { print_type_into_buf(value, buf, cycles, args, types, info, debug); diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 2ed46477..753333f9 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -421,7 +421,7 @@ pub(crate) fn get_property_unbound( } Type::Object(ObjectNature::RealDeal) => { let object_constraint_structure_generics = - crate::types::get_structure_arguments_based_on_object_constraint( + crate::types::helpers::get_structure_arguments_based_on_object_constraint( on, info_chain, types, ); diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index 6cc25c28..ba8750ae 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -9,8 +9,9 @@ use crate::{ types::{ calling::{CallingDiagnostics, CallingOutput, SynthesisedArgument}, get_constraint, + helpers::tuple_like, logical::{BasedOnKey, Logical, LogicalOrValid, NeedsCalculation}, - tuple_like, Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore, + Constructor, GenericChain, PartiallyAppliedGenerics, TypeStore, }, Environment, Type, TypeId, }; diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs index dd608419..7f1b6060 100644 --- a/checker/src/types/properties/mod.rs +++ b/checker/src/types/properties/mod.rs @@ -186,7 +186,7 @@ impl<'a> PropertyKey<'a> { } /// For getting `length` and stuff -pub(crate) fn get_simple_value( +pub(crate) fn get_simple_property_value( ctx: &impl InformationChain, on: TypeId, property: &PropertyKey, diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index f02037f1..81d458eb 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -6,7 +6,7 @@ use crate::{ context::{GeneralContext, InformationChain}, features::{ objects::{self, SpecialObject}, - operations::MathematicalAndBitwise, + operations::MathematicalOrBitwiseOperation, }, Constant, Environment, PropertyValue, TypeId, }; @@ -225,15 +225,15 @@ pub(crate) fn type_is_subtype_with_generics( information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { - { - let debug = true; - crate::utilities::notify!( - "Checking {} :>= {}, with {:?}", - print_type(base_type, types, information, debug), - print_type(ty, types, information, debug), - base_type_arguments - ); - } + // { + // let debug = true; + // crate::utilities::notify!( + // "Checking {} :>= {}, with {:?}", + // print_type(base_type, types, information, debug), + // print_type(ty, types, information, debug), + // base_type_arguments + // ); + // } if base_type == TypeId::ANY_TYPE || ty == TypeId::NEVER_TYPE { return SubTypeResult::IsSubType; @@ -332,7 +332,7 @@ pub(crate) fn type_is_subtype_with_generics( }; } - crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments); + // crate::utilities::notify!("Looking for {:?} with {:?}", ty, ty_structure_arguments); if let Some(arg) = ty_structure_arguments.and_then(|tas| tas.get_argument_covariant(ty)) { @@ -646,10 +646,8 @@ pub(crate) fn type_is_subtype_with_generics( // TODO temp fix if let Type::Constructor(c) = subtype { crate::utilities::notify!("TODO right hand side maybe okay"); - if let Some(to) = c.get_base() { - if to == base_type { - return SubTypeResult::IsSubType; - } + if c.get_constraint() == base_type { + return SubTypeResult::IsSubType; } } if let PolyNature::FunctionGeneric { .. } = nature { @@ -1140,7 +1138,7 @@ pub(crate) fn type_is_subtype_with_generics( Type::Constructor(cst) => match cst { // For template literal types Constructor::BinaryOperator { - operator: crate::types::MathematicalAndBitwise::Add, + operator: crate::types::MathematicalOrBitwiseOperation::Add, .. } => { if let Type::Constant(Constant::String(rs)) = subtype { @@ -1487,8 +1485,8 @@ pub(crate) fn type_is_subtype_with_generics( { SubTypeResult::IsSubType } - ty => { - crate::utilities::notify!("{:?} does not match class", ty); + _ty => { + // crate::utilities::notify!("{:?} does not match class", base_type); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } }, @@ -2878,7 +2876,7 @@ pub(crate) fn slice_matches_type( Type::Constructor(super::Constructor::BinaryOperator { lhs, rhs, - operator: MathematicalAndBitwise::Add, + operator: MathematicalOrBitwiseOperation::Add, result: _, }) => { let lhs = base_type_arguments @@ -2976,7 +2974,7 @@ pub(crate) fn number_matches_type( }) => { todo!() // let lhs_range = intrinsics::get_range(base, types).unwrap(); - // intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) + // intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range) } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::GREATER_THAN, @@ -2984,7 +2982,7 @@ pub(crate) fn number_matches_type( }) => { todo!() // let lhs_range = intrinsics::get_range(base, types).unwrap(); - // intrinsics::FloatRange::single(number.try_into().unwrap()).contained_in(lhs_range) + // intrinsics::FloatRange::new_single(number.try_into().unwrap()).contained_in(lhs_range) } Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index 5165bf12..bcc8b9f0 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -17,6 +17,10 @@ impl InclusiveExclusive { Exclusive } } + + pub fn is_inclusive(self) -> bool { + matches!(self, Inclusive) + } } #[derive(Debug, Clone, Copy, PartialEq)] @@ -28,8 +32,8 @@ pub struct FloatRange { impl Default for FloatRange { fn default() -> Self { Self { - floor: (Exclusive, f64::MIN.try_into().unwrap()), - ceiling: (Exclusive, f64::MAX.try_into().unwrap()), + floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()), + ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()), } } } @@ -37,7 +41,7 @@ impl Default for FloatRange { // TODO try_from (assert ceiling > floor etc) impl FloatRange { #[must_use] - pub fn single(on: BetterF64) -> Self { + pub fn new_single(on: BetterF64) -> Self { Self { floor: (Inclusive, on), ceiling: (Inclusive, on) } } @@ -49,6 +53,40 @@ impl FloatRange { } } + pub fn new_greater_than(greater_than: BetterF64) -> Self { + FloatRange { + floor: (Exclusive, greater_than), + ceiling: (Exclusive, f64::INFINITY.try_into().unwrap()), + } + } + + pub fn get_greater_than(self) -> Option { + (self.floor.1 != f64::NEG_INFINITY).then_some(self.floor.1) + } + + pub fn new_less_than(less_than: BetterF64) -> Self { + FloatRange { + floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()), + ceiling: (Exclusive, less_than), + } + } + + pub fn get_less_than(self) -> Option { + (self.ceiling.1 != f64::INFINITY).then_some(self.ceiling.1) + } + + pub fn contains(self, value: BetterF64) -> bool { + if self.floor.1 < value && value < self.ceiling.1 { + true + } else if self.floor.1 == value { + self.floor.0.is_inclusive() + } else if self.ceiling.1 == value { + self.ceiling.0.is_inclusive() + } else { + false + } + } + /// For disjointness. TODO Think this is correct #[must_use] pub fn overlaps(self, other: Self) -> bool { @@ -197,7 +235,7 @@ mod tests { #[test] fn contained_in() { let zero_to_four: FloatRange = FloatRange::try_from(0f64..4f64).unwrap(); - assert!(FloatRange::single(2.into()).contained_in(zero_to_four)); + assert!(FloatRange::new_single(2.into()).contained_in(zero_to_four)); } #[test] From 5215906b3b17ad96d2dda76d6015b7039d2f3344 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 10 Nov 2024 09:07:02 +0000 Subject: [PATCH 16/24] More checker improvements --- checker/examples/run_checker.rs | 2 + checker/specification/specification.md | 18 +++-- checker/specification/staging.md | 47 ++++++++++-- checker/src/context/environment.rs | 62 +++++++++------- checker/src/context/information.rs | 48 ++++++++++-- checker/src/context/mod.rs | 3 +- checker/src/events/application.rs | 1 + checker/src/features/conditional.rs | 2 + checker/src/features/functions.rs | 1 + checker/src/features/iteration.rs | 40 +++++----- checker/src/features/mod.rs | 7 +- checker/src/features/narrowing.rs | 74 +++++++++---------- checker/src/features/operations/logical.rs | 2 +- .../operations/mathematical_bitwise.rs | 27 +++++-- checker/src/features/operations/mod.rs | 8 +- .../operations/{equality.rs => relation.rs} | 29 +++++++- checker/src/features/operations/unary.rs | 3 + checker/src/features/template_literal.rs | 3 + checker/src/synthesis/expressions.rs | 1 + checker/src/synthesis/statements.rs | 4 + checker/src/types/disjoint.rs | 33 +++++++-- checker/src/types/generics/substitution.rs | 14 +++- checker/src/types/intrinsics.rs | 14 ++-- checker/src/types/mod.rs | 2 +- checker/src/types/printing.rs | 4 +- checker/src/types/properties/assignment.rs | 21 ++++-- checker/src/types/subtyping.rs | 52 ++++++++----- 27 files changed, 353 insertions(+), 169 deletions(-) rename checker/src/features/operations/{equality.rs => relation.rs} (90%) diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs index d7991149..f81081eb 100644 --- a/checker/examples/run_checker.rs +++ b/checker/examples/run_checker.rs @@ -20,6 +20,7 @@ fn main() { let no_lib = args.iter().any(|item| item == "--no-lib"); let debug_dts = args.iter().any(|item| item == "--debug-dts"); let extras = args.iter().any(|item| item == "--extras"); + let advanced_number_intrinsics = args.iter().any(|item| item == "--advanced-numbers"); let now = Instant::now(); @@ -46,6 +47,7 @@ fn main() { max_inline_count: 600, debug_dts, extra_syntax: extras, + advanced_number_intrinsics, ..Default::default() }; diff --git a/checker/specification/specification.md b/checker/specification/specification.md index a0a11463..39b3dfc2 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -2517,6 +2517,8 @@ function func(a: number) { } ``` +With advanced_number_intrinsics + - Expected null, found InclusiveRange\<-5, 5> - Expected string, found InclusiveRange\<18, 22> @@ -4306,22 +4308,22 @@ function buildObject(param: any) { ```ts function conditional(param: boolean) { - const obj1 = {}, obj2 = {}; - const sum = param ? obj1 : obj2; - if (sum === obj1) { - sum.a = 2; - } - [obj1, obj2] satisfies string; + const obj1 = { b: 2 }, obj2 = { c: 6 }; + const sum = param ? obj1 : obj2; + if (sum === obj1) { + sum.a = 3; + } + [obj1, obj2] satisfies string; } ``` -- Expected string, found [{ a: 2 }, {}] +- Expected string, found [{ b: 2, a: 3 }, { c: 6 }] #### From condition equality ```ts function conditional(param: boolean) { - const obj1 = { a: 1 }, obj2 = {}; + const obj1 = { a: 1 }, obj2 = { b: 2}; const sum = param ? obj1 : obj2; if (param) { sum satisfies string; diff --git a/checker/specification/staging.md b/checker/specification/staging.md index 8a6fa799..c6a392c8 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -27,18 +27,22 @@ function func3(p1: Not, p2: Not) { > TODO need to redo range to use interesection of less than and greater than ```ts -function func(a: number, b: number) { - const condition = 31 < b && b < 37; - debug_type(condition); - if (condition) { - debug_type(b) +function func1(a: number, b: number) { + if (a % 8 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 41) { + const x = a === b; } } ``` -- ? +With advanced_number_intrinsics -- The equality is always false as MultipleOf<8> and 2 have no overlap +- This equality is always false as MultipleOf<10> and LessThan<37> & GreaterThan<31> have no overlap #### Modulo offsets @@ -55,6 +59,8 @@ function func2(param: number) { } ``` +With advanced_number_intrinsics + - Hi #### Narrowing: Implication by @@ -80,6 +86,30 @@ function func(x: number) { - This equality is always false +#### Narrowing in for loop + +> Can't do modulo because post mutation + +```ts +for (let i = 0; i < 3; i++) { + const x = i === 50; +} +``` + +- This equality is always false as LessThan<3> and 50 have no overlap + +#### Transistivity + +```ts +function func(a: number, b: number, c: number) { + if (a < b && b < c) { + const cond = (a < c) satisfies 5; + } +} +``` + +- Expected 5, found true + ### Broken #### Template literal edge cases @@ -90,4 +120,5 @@ const invalidNum2: `${1}` = "1"; const invalidNum3: `${1}` = "2"; ``` -- ? +- Type 1 is not assignable to type "1" +- Type \"2\" is not assignable to type "1" diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index d939ec6f..6c10c4a2 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -14,7 +14,6 @@ use crate::{ AssignmentKind, AssignmentReturnStatus, IncrementOrDecrement, Reference, }, modules::Exported, - objects::SpecialObject, operations::{evaluate_logical_operation_with_expression, MathematicalOrBitwiseOperation}, variables::{VariableMutability, VariableOrImport, VariableWithValue}, }, @@ -300,6 +299,7 @@ impl<'a> Environment<'a> { self, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); match result { Ok(new) => { @@ -363,6 +363,7 @@ impl<'a> Environment<'a> { self, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); match result { Ok(new) => { @@ -944,37 +945,35 @@ impl<'a> Environment<'a> { info.variable_current_value.get(&og_var.get_origin_variable_id()) }) .copied(); - let narrowed = current_value.and_then(|cv| self.get_narrowed(cv)); - if let Some(precise) = narrowed.or(current_value) { - let ty = checking_data.types.get_type_by_id(precise); + // TODO WIP + let narrowed = current_value + .and_then(|cv| self.get_narrowed_or_object(cv, &checking_data.types)); - // TODO temp for function - if let Type::SpecialObject(SpecialObject::Function(..)) = ty { - return Ok(VariableWithValue(og_var.clone(), precise)); - } else if let Type::RootPolyType(PolyNature::Open(_)) = ty { - crate::utilities::notify!( - "Open poly type '{}' treated as immutable free variable", - name - ); - return Ok(VariableWithValue(og_var.clone(), precise)); - } else if let Type::Constant(_) = ty { - return Ok(VariableWithValue(og_var.clone(), precise)); - } - - crate::utilities::notify!("Free variable with value!"); + if let Some(precise) = narrowed.or(current_value) { + // let ty = checking_data.types.get_type_by_id(precise); + + // // TODO temp for function + // let value = if let Type::SpecialObject(SpecialObject::Function(..)) = ty { + // return Ok(VariableWithValue(og_var.clone(), precise)); + // } else if let Type::RootPolyType(PolyNature::Open(_)) = ty { + // crate::utilities::notify!( + // "Open poly type '{}' treated as immutable free variable", + // name + // ); + // return Ok(VariableWithValue(og_var.clone(), precise)); + // } else if let Type::Constant(_) = ty { + // }; + + return Ok(VariableWithValue(og_var.clone(), precise)); } else { crate::utilities::notify!("Free variable with no current value"); - } - - if let Some(narrowed) = narrowed { - narrowed - } else { let constraint = checking_data .local_type_mappings .variables_to_constraints .0 .get(&og_var.get_origin_variable_id()); + if let Some(constraint) = constraint { *constraint } else { @@ -994,12 +993,15 @@ impl<'a> Environment<'a> { for ctx in self.parents_iter() { if let GeneralContext::Syntax(s) = ctx { if s.possibly_mutated_variables.contains(&variable_id) { + crate::utilities::notify!("Possibly mutated variables"); break; } - if let Some(value) = + + if let Some(current_value) = get_on_ctx!(ctx.info.variable_current_value.get(&variable_id)) + .copied() { - return Ok(VariableWithValue(og_var.clone(), *value)); + return Ok(VariableWithValue(og_var.clone(), current_value)); } if s.context_type.scope.is_dynamic_boundary().is_some() { @@ -1040,8 +1042,11 @@ impl<'a> Environment<'a> { } } - let ty = if let Some(value) = reused_reference { - value + let ty = if let Some(reused_reference) = reused_reference { + // TODO temp. I believe this can break type contracts because of mutations + // but needed here because of for loop narrowing + let narrowed = self.get_narrowed_or_object(reused_reference, &checking_data.types); + narrowed.unwrap_or(reused_reference) } else { // TODO dynamic ? let ty = Type::RootPolyType(crate::types::PolyNature::FreeVariable { @@ -1076,6 +1081,7 @@ impl<'a> Environment<'a> { self, of, None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>, + &checking_data.types, ) .expect("import not assigned yet"); return Ok(VariableWithValue(og_var.clone(), current_value)); @@ -1085,7 +1091,9 @@ impl<'a> Environment<'a> { self, og_var.get_id(), None::<&crate::types::generics::substitution::SubstitutionArguments<'static>>, + &checking_data.types, ); + if let Some(current_value) = current_value { Ok(VariableWithValue(og_var.clone(), current_value)) } else { diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index e84835be..feec4618 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -245,6 +245,31 @@ pub trait InformationChain { fn get_narrowed(&self, for_ty: TypeId) -> Option { self.get_chain_of_info().find_map(|info| info.narrowed_values.get(&for_ty).copied()) } + + fn get_narrowed_or_object(&self, for_ty: TypeId, types: &TypeStore) -> Option { + let value = self.get_narrowed(for_ty); + if let Some(value) = value { + Some(value) + } else if let Type::Constructor(crate::types::Constructor::ConditionalResult { + condition, + truthy_result, + otherwise_result, + result_union: _, + }) = types.get_type_by_id(for_ty) + { + let narrowed_condition = self.get_narrowed(*condition)?; + if let crate::Decidable::Known(condition) = + crate::types::is_type_truthy_falsy(narrowed_condition, types) + { + let value = if condition { truthy_result } else { otherwise_result }; + Some(*value) + } else { + None + } + } else { + value + } + } } pub struct ModuleInformation<'a> { @@ -386,11 +411,24 @@ pub fn merge_info( onto.variable_current_value.insert(var, new); } - // TODO temp fix for `... ? { ... } : { ... }`. Breaks for the fact that property - // properties might be targeting something above the current condition (e.g. `x ? (y.a = 2) : false`); - onto.current_properties.extend(truthy.current_properties.drain()); - if let Some(ref mut otherwise) = otherwise { - onto.current_properties.extend(otherwise.current_properties.drain()); + // TODO temp fix for `... ? { ... } : { ... }`. + // TODO add undefineds to sides etc + for (on, properties) in truthy.current_properties.into_iter() { + if let Some(existing) = onto.current_properties.get_mut(&on) { + existing.extend(properties); + } else { + onto.current_properties.insert(on, properties); + } + } + + if let Some(otherwise) = otherwise { + for (on, properties) in otherwise.current_properties.into_iter() { + if let Some(existing) = onto.current_properties.get_mut(&on) { + existing.extend(properties); + } else { + onto.current_properties.insert(on, properties); + } + } } // TODO set more information? diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index 2a748d54..368de944 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -1014,6 +1014,7 @@ pub(crate) fn get_value_of_variable( info: &impl InformationChain, on: VariableId, closures: Option<&impl ClosureChain>, + types: &TypeStore, ) -> Option { for fact in info.get_chain_of_info() { let res = if let Some(closures) = closures { @@ -1029,7 +1030,7 @@ pub(crate) fn get_value_of_variable( // TODO in remaining info, don't loop again if let Some(res) = res { - let narrowed = info.get_narrowed(res); + let narrowed = info.get_narrowed_or_object(res, types); return Some(narrowed.unwrap_or(res)); } } diff --git a/checker/src/events/application.rs b/checker/src/events/application.rs index 8a0bdc9b..dc6db249 100644 --- a/checker/src/events/application.rs +++ b/checker/src/events/application.rs @@ -88,6 +88,7 @@ pub(crate) fn apply_events( top_environment, *id, Some(type_arguments), + types, ); if let Some(ty) = value { ty diff --git a/checker/src/features/conditional.rs b/checker/src/features/conditional.rs index caa7b332..d46db3a3 100644 --- a/checker/src/features/conditional.rs +++ b/checker/src/features/conditional.rs @@ -47,6 +47,7 @@ where &mut checking_data.types, ); + crate::utilities::notify!("Narrowed value {:?} in true branch", values); truthy_environment.info.narrowed_values = values; let result = then_evaluate(&mut truthy_environment, checking_data); @@ -81,6 +82,7 @@ where &mut checking_data.types, ); + crate::utilities::notify!("Narrowed value {:?} in false branch", values); falsy_environment.info.narrowed_values = values; let result = else_evaluate(&mut falsy_environment, checking_data); diff --git a/checker/src/features/functions.rs b/checker/src/features/functions.rs index 96950fa5..43081834 100644 --- a/checker/src/features/functions.rs +++ b/checker/src/features/functions.rs @@ -822,6 +822,7 @@ where let closes_over = create_closed_over_references( &function_environment.context_type.closed_over_references, &function_environment, + &checking_data.types, ); let Syntax { diff --git a/checker/src/features/iteration.rs b/checker/src/features/iteration.rs index d7b2c0cf..e638cbb1 100644 --- a/checker/src/features/iteration.rs +++ b/checker/src/features/iteration.rs @@ -138,8 +138,6 @@ pub fn synthesise_iteration( // } } IterationBehavior::DoWhile(condition) => { - // let is_do_while = matches!(behavior, IterationBehavior::DoWhile(..)); - // Same as above but condition is evaluated at end. Don't know whether events should be evaluated once...? let (condition, result, ..) = environment.new_lexical_environment_fold_into_parent( Scope::Iteration { label }, @@ -252,6 +250,20 @@ pub fn synthesise_iteration( TypeId::TRUE }; + let values = + super::narrowing::narrow_based_on_expression_into_vec( + condition, + false, + environment, + &mut checking_data.types, + ); + + crate::utilities::notify!( + "Narrowed values in loop {:?}", + values + ); + environment.info.narrowed_values = values; + // TODO not always needed add_loop_described_break_event( condition, @@ -494,8 +506,9 @@ pub(crate) fn run_iteration_block( let events_to_be_applied = iterations * events.len(); crate::utilities::notify!( - "count = {:?}. with", - (events_to_be_applied, input.max_inline) + "applying {:?} events (max {:?})", + events_to_be_applied, + input.max_inline ); (events_to_be_applied < input.max_inline as usize).then_some(iterations) }); @@ -508,20 +521,6 @@ pub(crate) fn run_iteration_block( crate::utilities::notify!("Running {} times", iterations); - // crate::utilities::notify!( - // "Evaluating a constant amount of iterations {:?}", - // iterations - // ); - - // if let InitialVariablesInput::Calculated(initial) = initial { - // for (variable_id, initial_value) in initial.iter() { - // invocation_context - // .get_latest_info(top_environment) - // .variable_current_value - // .insert(*variable_id, *initial_value); - // } - // } - run_iteration_loop( invocation_context, iterations, @@ -672,7 +671,7 @@ fn evaluate_unknown_iteration_for_loop( let initial = match initial { RunBehavior::Run => ClosedOverVariables(Default::default()), RunBehavior::References(v) => { - crate::features::create_closed_over_references(&v, top_environment) + crate::features::create_closed_over_references(&v, top_environment, types) } }; @@ -909,8 +908,9 @@ fn calculate_result_of_loop( result: _, }) = value_after_running_expressions_in_loop { + let assignment = crate::types::helpers::get_origin(*assignment, types); debug_assert!( - assignment == less_than_reference_type_id, + assignment == *less_than_reference_type_id, "incrementor free variable type not the same as condition free variable type?" ); diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index 572559fe..b69a2464 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -219,6 +219,7 @@ fn get_promise_value(constraint: TypeId, types: &TypeStore) -> Option { pub(crate) fn create_closed_over_references( closed_over_references: &ClosedOverReferencesInScope, current_environment: &Environment, + types: &TypeStore, ) -> ClosedOverVariables { ClosedOverVariables( closed_over_references @@ -229,9 +230,9 @@ pub(crate) fn create_closed_over_references( let c = None::< &crate::types::generics::substitution::SubstitutionArguments<'static>, >; - let get_value_of_variable = - get_value_of_variable(current_environment, *on, c); - let ty = if let Some(value) = get_value_of_variable { + let value = get_value_of_variable(current_environment, *on, c, types); + + let ty = if let Some(value) = value { value } else { // TODO think we are getting rid of this diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index 9df7a326..e8402c0f 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -18,6 +18,8 @@ pub fn narrow_based_on_expression_into_vec( ) -> Map { let mut into = Default::default(); narrow_based_on_expression(condition, negate, &mut into, information, types); + crate::utilities::notify!("{:?}", into); + into.iter_mut().for_each(|(on, value)| *value = types.new_narrowed(*on, *value)); into } @@ -68,11 +70,9 @@ pub fn narrow_based_on_expression( ); types.new_or_type_from_iterator(result) }; - let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(origin, narrowed); + into.insert(origin, narrowed_to); } else { - let narrowed = types.new_narrowed(from, type_from_name); - into.insert(origin, narrowed); + into.insert(origin, type_from_name); } } else { crate::utilities::notify!("Type name was (shouldn't be here)"); @@ -91,9 +91,9 @@ pub fn narrow_based_on_expression( crate::utilities::notify!("TODO do we not divisable by?"); return; } - let (from, rhs, modulo) = (*operand, *rhs, *modulo); - - let origin = get_origin(from, types); + let (operand, rhs, modulo) = (*operand, *rhs, *modulo); + let operand = get_origin(operand, types); + crate::utilities::notify!("Here {:?}", types.get_type_by_id(modulo)); let narrowed_to = crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::MultipleOf, modulo, @@ -113,19 +113,17 @@ pub fn narrow_based_on_expression( } else { narrowed_to }; - let narrowed = types.new_narrowed(from, narrowed_to); - into.insert(origin, narrowed); + into.insert(operand, narrowed_to); } else { if let Type::RootPolyType(PolyNature::Parameter { .. }) = lhs_type { crate::utilities::notify!("lhs is {:?} with {:?}", lhs_type, rhs); } if negate && lhs == rhs { - into.insert(*lhs, types.new_narrowed(*lhs, TypeId::NOT_NOT_A_NUMBER)); + into.insert(*lhs, TypeId::NOT_NOT_A_NUMBER); return; } - // let lhs = get_origin(*lhs, types); let lhs = *lhs; let rhs = *rhs; @@ -142,8 +140,7 @@ pub fn narrow_based_on_expression( ); // crate::utilities::notify!("Here {:?} {:?}", (filter, lhs), result); - let narrowed_to = types.new_or_type_from_iterator(result); - types.new_narrowed(lhs, narrowed_to) + types.new_or_type_from_iterator(result) } else { crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::Not, @@ -151,14 +148,15 @@ pub fn narrow_based_on_expression( types, ) }; - types.new_narrowed(lhs, narrowed_to) + narrowed_to } else { rhs }; + crate::utilities::notify!("Here {:?} {:?}", lhs, result); into.insert(lhs, result); - // CONDITION NARROWING HERE ((x ? 1 : 2) = 1 => x) + // CONDITION NARROWING HERE ((x ? 1 : 2) == 1 => x) // There are missed conditons around things like `typeof` etc (oh well) // it should be done higher up if let Type::Constructor(Constructor::ConditionalResult { @@ -207,7 +205,7 @@ pub fn narrow_based_on_expression( // on, // types.get_type_by_id(narrowed_to) // ); - into.insert(on, types.new_narrowed(on, narrowed_to)); + into.insert(on, narrowed_to); } } } @@ -216,40 +214,41 @@ pub fn narrow_based_on_expression( operator: CanonicalEqualityAndInequality::LessThan, rhs, } => { - let (lhs, rhs) = (*lhs, *rhs); if negate { + crate::utilities::notify!("Skipping negate on less"); return; } + let lhs = get_origin(*lhs, types); + let rhs = get_origin(*rhs, types); if types.get_type_by_id(lhs).is_dependent() { let narrowed_to = crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::LessThan, rhs, types, ); - let narrowed = types.new_narrowed(lhs, narrowed_to); - // temp fix - let narrowed = if let Some(existing) = into.get(&lhs) { + // TODO need to merge. This is very bad + let narrowed_to = if let Some(existing) = into.get(&lhs) { crate::utilities::notify!("Here"); - types.new_and_type(*existing, narrowed) + types.new_and_type(*existing, narrowed_to) } else { - narrowed + narrowed_to }; - into.insert(lhs, narrowed); - } else if types.get_type_by_id(rhs).is_dependent() { + into.insert(lhs, narrowed_to); + } + if types.get_type_by_id(rhs).is_dependent() { let narrowed_to = crate::types::intrinsics::new_intrinsic( &crate::types::intrinsics::Intrinsic::GreaterThan, lhs, types, ); - let narrowed = types.new_narrowed(rhs, narrowed_to); - // temp fix - let narrowed = if let Some(existing) = into.get(&rhs) { + // TODO need to merge. This is very bad + let narrowed_to = if let Some(existing) = into.get(&rhs) { crate::utilities::notify!("Here"); - types.new_and_type(*existing, narrowed) + types.new_and_type(*existing, narrowed_to) } else { - narrowed + narrowed_to }; - into.insert(rhs, narrowed); + into.insert(rhs, narrowed_to); } } Constructor::TypeOperator(TypeOperator::IsPrototype { lhs, rhs_prototype }) => { @@ -265,8 +264,7 @@ pub fn narrow_based_on_expression( build_union_from_filter(constraint, filter, &mut result, information, types); types.new_or_type_from_iterator(result) }; - let narrowed = types.new_narrowed(lhs, narrowed_to); - into.insert(lhs, narrowed); + into.insert(lhs, narrowed_to); } Constructor::TypeExtends(crate::types::TypeExtends { item, extends }) => { let (item, extends) = (*item, *extends); @@ -282,8 +280,7 @@ pub fn narrow_based_on_expression( build_union_from_filter(constraint, filter, &mut result, information, types); types.new_or_type_from_iterator(result) }; - let narrowed = types.new_narrowed(item, narrowed_to); - into.insert(item, narrowed); + into.insert(item, narrowed_to); } Constructor::TypeOperator(TypeOperator::HasProperty(on, under)) => { let on = *on; @@ -304,7 +301,7 @@ pub fn narrow_based_on_expression( ); types.new_or_type_from_iterator(items) }; - into.insert(on, types.new_narrowed(on, narrowed_to)); + into.insert(on, narrowed_to); } constructor => { if let Some(condition) = as_logical_not(constructor, types) { @@ -362,8 +359,6 @@ pub fn narrow_based_on_expression( crate::types::get_constraint(rhs_request, types) .unwrap_or(rhs_request), ); - // TODO - // let narrowed = types.new_narrowed(rhs, narrowed_to); into.insert(on, types.new_or_type(lhs_request, rhs_request)); } else { // Only when we have two results is it useful @@ -389,10 +384,7 @@ pub fn narrow_based_on_expression( types, ); let narrowed_to = types.new_or_type_from_iterator(result); - into.insert( - condition, - types.register_type(Type::Narrowed { from: condition, narrowed_to }), - ); + into.insert(condition, narrowed_to); } } } diff --git a/checker/src/features/operations/logical.rs b/checker/src/features/operations/logical.rs index 46493153..25ecfc72 100644 --- a/checker/src/features/operations/logical.rs +++ b/checker/src/features/operations/logical.rs @@ -1,6 +1,6 @@ use super::{ super::{conditional, narrowing}, - equality::is_null_or_undefined, + relation::is_null_or_undefined, }; use crate::{Type, TypeId}; diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs index 1caec5f4..dbbe6a9b 100644 --- a/checker/src/features/operations/mathematical_bitwise.rs +++ b/checker/src/features/operations/mathematical_bitwise.rs @@ -28,6 +28,7 @@ pub fn evaluate_mathematical_operation( info: &impl crate::context::InformationChain, types: &mut crate::TypeStore, strict_casts: bool, + operate_on_number_intrinsics: bool, ) -> Result { fn attempt_constant_math_operator( lhs: TypeId, @@ -48,7 +49,10 @@ pub fn evaluate_mathematical_operation( first.push_str(&second); Constant::String(first) } - _ => return Err(()), + _ => { + crate::utilities::notify!("here"); + return Err(()); + } }; Ok(types.new_constant_type(constant)) } else { @@ -96,14 +100,17 @@ pub fn evaluate_mathematical_operation( } } + let lhs_ty = types.get_type_by_id(lhs); + let rhs_ty = types.get_type_by_id(rhs); + if lhs == TypeId::ERROR_TYPE || rhs == TypeId::ERROR_TYPE { return Ok(TypeId::ERROR_TYPE); } - let is_dependent = - types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent(); + crate::utilities::notify!("lhs={:?}, rhs={:?}", lhs_ty, rhs_ty); + let either_dependent = lhs_ty.is_dependent() || rhs_ty.is_dependent(); - if is_dependent { + if either_dependent { let can_be_string = if let MathematicalOrBitwiseOperation::Add = operator { let left_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); let right_is_string = helpers::simple_subtype(lhs, TypeId::STRING_TYPE, info, types); @@ -111,16 +118,17 @@ pub fn evaluate_mathematical_operation( left_is_string || helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); let right_is_string_or_number = right_is_string || helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types); - if !left_is_string_or_number || !right_is_string_or_number { + crate::utilities::notify!("Here"); return Err(()); } left_is_string || right_is_string } else { - let left_is_number = helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types); - - if !left_is_number || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) { + if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) + || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) + { + crate::utilities::notify!("Here :/"); return Err(()); } @@ -151,10 +159,12 @@ pub fn evaluate_mathematical_operation( MathematicalOrBitwiseOperation::Add | MathematicalOrBitwiseOperation::Multiply, (lhs_range, _lhs_modulo), (rhs_range, _rhs_modulo), + true, ) = ( operator, intrinsics::get_range_and_mod_class(lhs, types), intrinsics::get_range_and_mod_class(rhs, types), + operate_on_number_intrinsics, ) { crate::utilities::notify!( "{:?} with {:?}", @@ -174,6 +184,7 @@ pub fn evaluate_mathematical_operation( TypeId::NUMBER_TYPE } } else { + // TODO if or of constant types TypeId::NUMBER_TYPE }; diff --git a/checker/src/features/operations/mod.rs b/checker/src/features/operations/mod.rs index 5164812f..a0e3855f 100644 --- a/checker/src/features/operations/mod.rs +++ b/checker/src/features/operations/mod.rs @@ -1,12 +1,12 @@ -mod equality; mod logical; mod mathematical_bitwise; +mod relation; mod unary; -pub use equality::{ +pub use logical::{evaluate_logical_operation_with_expression, LogicalOperator}; +pub use mathematical_bitwise::{evaluate_mathematical_operation, MathematicalOrBitwiseOperation}; +pub use relation::{ evaluate_equality_inequality_operation, is_null_or_undefined, CanonicalEqualityAndInequality, EqualityAndInequality, EqualityAndInequalityResultKind, }; -pub use logical::{evaluate_logical_operation_with_expression, LogicalOperator}; -pub use mathematical_bitwise::{evaluate_mathematical_operation, MathematicalOrBitwiseOperation}; pub use unary::{evaluate_unary_operator, UnaryOperation}; diff --git a/checker/src/features/operations/equality.rs b/checker/src/features/operations/relation.rs similarity index 90% rename from checker/src/features/operations/equality.rs rename to checker/src/features/operations/relation.rs index d71337c1..2ed50d73 100644 --- a/checker/src/features/operations/equality.rs +++ b/checker/src/features/operations/relation.rs @@ -1,6 +1,7 @@ use super::MathematicalOrBitwiseOperation; use crate::types::{ - cast_as_number, disjoint, helpers, intrinsics, Constant, Constructor, Type, TypeId, + cast_as_number, disjoint, get_constraint, helpers, intrinsics, Constant, Constructor, + PartiallyAppliedGenerics, Type, TypeId, }; /// Not canonical / reducible form of [`CanonicalEqualityAndInequality`]. @@ -162,8 +163,8 @@ pub fn evaluate_equality_inequality_operation( } { - // let lhs = get_constraint(lhs, types).unwrap_or(lhs); - // let rhs = get_constraint(rhs, types).unwrap_or(rhs); + let lhs = get_constraint(lhs, types).unwrap_or(lhs); + let rhs = get_constraint(rhs, types).unwrap_or(rhs); if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) @@ -186,6 +187,28 @@ pub fn evaluate_equality_inequality_operation( return Ok((TypeId::FALSE, EqualityAndInequalityResultKind::Disjoint)); } } + + // Transitivity + // TODO extra from & + if let ( + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::LESS_THAN, + arguments: larg, + }), + Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { + on: TypeId::GREATER_THAN, + arguments: rarg, + }), + ) = (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) + { + crate::utilities::notify!("{:?} {:?}", larg, rarg); + // Only one level + let transitivity = larg.get_structure_restriction(TypeId::NUMBER_GENERIC) + == rarg.get_structure_restriction(TypeId::NUMBER_GENERIC); + if transitivity { + return Ok((TypeId::TRUE, EqualityAndInequalityResultKind::Constant)); + } + } } let constructor = Constructor::CanonicalRelationOperator { diff --git a/checker/src/features/operations/unary.rs b/checker/src/features/operations/unary.rs index d51a3e8b..5c44e324 100644 --- a/checker/src/features/operations/unary.rs +++ b/checker/src/features/operations/unary.rs @@ -67,6 +67,7 @@ pub fn evaluate_unary_operator( info, types, strict_casts, + false, ), UnaryOperation::Negation => super::evaluate_mathematical_operation( TypeId::ZERO, @@ -75,6 +76,8 @@ pub fn evaluate_unary_operator( info, types, strict_casts, + // TODO + true, ), UnaryOperation::LogicalNot => unreachable!("handled above"), } diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs index 91b4691c..69a99485 100644 --- a/checker/src/features/template_literal.rs +++ b/checker/src/features/template_literal.rs @@ -149,6 +149,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); if let Ok(result) = result { acc = result; @@ -169,6 +170,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); if let Ok(result) = result { acc = result; @@ -189,6 +191,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); if let Ok(result) = result { result diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index d9040422..c8052bf8 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -400,6 +400,7 @@ pub(super) fn synthesise_expression( environment, &mut checking_data.types, checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, ); match result { Ok(value) => Instance::RValue(value), diff --git a/checker/src/synthesis/statements.rs b/checker/src/synthesis/statements.rs index cd291cd1..227555b7 100644 --- a/checker/src/synthesis/statements.rs +++ b/checker/src/synthesis/statements.rs @@ -183,6 +183,10 @@ pub(super) fn synthesise_statement( environment, checking_data, |environment, checking_data| { + crate::utilities::notify!( + "environment.info.narrowed_values={:?}", + environment.info.narrowed_values + ); synthesise_block_or_single_statement(&stmt.inner, environment, checking_data); }, position.with_source(environment.get_source()), diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 309ca37a..aac64274 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -113,11 +113,13 @@ pub fn types_are_disjoint( args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, ) = lhs_ty { + // TODO also offset number_modulo_disjoint(args, rhs, types) } else if let Type::PartiallyAppliedGenerics( args @ PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments: _ }, ) = rhs_ty { + // TODO also offset number_modulo_disjoint(args, lhs, types) } else if let Type::PartiallyAppliedGenerics( args @ PartiallyAppliedGenerics { @@ -221,18 +223,35 @@ fn number_modulo_disjoint( other: TypeId, types: &TypeStore, ) -> bool { + crate::utilities::notify!("Here number_modulo_disjoint"); let PartiallyAppliedGenerics { arguments, .. } = this; - let other = types.get_type_by_id(other); + let other_ty = types.get_type_by_id(other); + let argument = + types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); + + let argument = if let Type::Constant(Constant::Number(argument)) = argument { + argument + } else { + crate::utilities::notify!("Gets complex here"); + return false; + }; + // Little bit complex here because dealing with decimal types, not integers - if let (Type::Constant(Constant::Number(other)), Type::Constant(Constant::Number(this))) = ( - other, - types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()), - ) { - let result = other % this != 0.; + if let Type::Constant(Constant::Number(other)) = other_ty { + let result = other % argument != 0.; // crate::utilities::notify!("{:?} {:?}", lhs, rhs); result } else { - // crate::utilities::notify!("Here {:?}", lhs); + let (range, modulo_class) = super::intrinsics::get_range_and_mod_class(other, types); + crate::utilities::notify!("{:?}, {:?}", range, modulo_class); + if let Some(range) = range { + return !range.contains_multiple_of(*argument); + } + if let Some(_modulo_class) = modulo_class { + crate::utilities::notify!("todo"); + return false; + } + crate::utilities::notify!("Here {:?}", other_ty); true } } diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index 1013fa2d..62a2cd61 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -237,8 +237,18 @@ pub(crate) fn substitute( let lhs = substitute(lhs, arguments, environment, types); let rhs = substitute(rhs, arguments, environment, types); - match evaluate_mathematical_operation(lhs, operator, rhs, environment, types, false) - { + // TODO + let advanced_number_intrinsics = false; + + match evaluate_mathematical_operation( + lhs, + operator, + rhs, + environment, + types, + false, + advanced_number_intrinsics, + ) { Ok(result) => result, Err(()) => { unreachable!( diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index da1d0ac8..f25fb983 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -340,7 +340,7 @@ pub fn get_range_and_mod_class( range = Some(match range { None => FloatRange::new_less_than(less_than), Some(mut range) => { - range.ceiling.1 = range.ceiling.1.max(less_than); + range.ceiling.1 = range.ceiling.1.min(less_than); range } }); @@ -372,12 +372,15 @@ pub fn get_range_and_mod_class( } } if let Some(ref mut range) = range { - if !range.contains(num) { - return (None, None); - } else if range.floor.1 == num { + if range.floor.1 == num { range.floor.0 = InclusiveExclusive::Inclusive; } else if range.ceiling.1 == num { range.ceiling.0 = InclusiveExclusive::Inclusive; + } else if !range.contains(num) { + crate::utilities::notify!( + "Here, number not contained in current range" + ); + return (None, None); } } } @@ -397,6 +400,7 @@ pub fn get_range_and_mod_class( type BetterF64 = ordered_float::NotNan; /// Unit. No combinations at this point +#[derive(Debug)] pub enum PureNumberIntrinsic { GreaterThan(BetterF64), LessThan(BetterF64), @@ -454,7 +458,7 @@ impl PureNumberIntrinsic { Err(()) } } else { - crate::utilities::notify!("err here {:?}", ty); + // crate::utilities::notify!("err here {:?}", ty); Err(()) } } diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index 849ac1df..e8e4ae5d 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -614,7 +614,6 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable { | Type::Constructor(_) | Type::Interface { .. } | Type::PartiallyAppliedGenerics(..) - | Type::Narrowed { .. } | Type::Class { .. } => { // TODO some of these case are known Decidable::Unknown(id) @@ -626,6 +625,7 @@ pub fn is_type_truthy_falsy(id: TypeId, types: &TypeStore) -> Decidable { // TODO strict casts Decidable::Known(cast_as_boolean(cst, false).unwrap()) } + Type::Narrowed { narrowed_to, .. } => is_type_truthy_falsy(*narrowed_to, types), } } } diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 0b8eac28..0fa02987 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -100,9 +100,7 @@ pub fn print_type_into_buf( } Type::Narrowed { narrowed_to, from } => { if debug { - buf.push_str("(narrowed from "); - print_type_into_buf(*from, buf, cycles, args, types, info, debug); - buf.push_str(") "); + write!(buf, "(narrowed from {:?}) ", from).unwrap(); } print_type_into_buf(*narrowed_to, buf, cycles, args, types, info, debug); } diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index ba8750ae..ff545848 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -224,6 +224,13 @@ pub fn set_property( ); } + // Important that this goes below the condition above + let on = if let Type::Narrowed { narrowed_to, .. } = types.get_type_by_id(on) { + *narrowed_to + } else { + on + }; + // IMPORTANT: THIS ALSO CAPTURES POLY CONSTRAINTS let current_property = get_property_unbound((on, None), (publicity, under, None), false, environment, types); @@ -261,12 +268,16 @@ pub fn set_property( types, ), } - } else if get_constraint(on, types).is_some() { - Err(SetPropertyError::AssigningToNonExistent { - property: PropertyKeyRepresentation::new(under, environment, types), - position, - }) } else { + let on_type = types.get_type_by_id(on); + crate::utilities::notify!("{:?}", on_type); + if get_constraint(on, types).is_some() { + return Err(SetPropertyError::AssigningToNonExistent { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } + crate::utilities::notify!("No property on object, assigning anyway"); let info = behavior.get_latest_info(environment); info.register_property( diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index 81d458eb..d49592c3 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -225,15 +225,15 @@ pub(crate) fn type_is_subtype_with_generics( information: &impl InformationChain, types: &TypeStore, ) -> SubTypeResult { - // { - // let debug = true; - // crate::utilities::notify!( - // "Checking {} :>= {}, with {:?}", - // print_type(base_type, types, information, debug), - // print_type(ty, types, information, debug), - // base_type_arguments - // ); - // } + { + let debug = true; + crate::utilities::notify!( + "Checking {} :>= {}, with {:?}", + print_type(base_type, types, information, debug), + print_type(ty, types, information, debug), + base_type_arguments + ); + } if base_type == TypeId::ANY_TYPE || ty == TypeId::NEVER_TYPE { return SubTypeResult::IsSubType; @@ -1139,6 +1139,7 @@ pub(crate) fn type_is_subtype_with_generics( // For template literal types Constructor::BinaryOperator { operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, .. } => { if let Type::Constant(Constant::String(rs)) = subtype { @@ -1156,17 +1157,26 @@ pub(crate) fn type_is_subtype_with_generics( // TODO clear contributions SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = subtype + { + crate::utilities::notify!("TODO test prefixes"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } else { - crate::utilities::notify!("RHS not string"); - type_is_subtype_with_generics( - (TypeId::NUMBER_TYPE, base_type_arguments), - (ty, ty_structure_arguments), - state, - information, - types, - ) + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } + Constructor::BinaryOperator { + operator: crate::types::MathematicalOrBitwiseOperation::Add, + result: TypeId::NUMBER_TYPE, + .. + } => { + crate::utilities::notify!("TODO here!"); + SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) + } Constructor::BinaryOperator { .. } | Constructor::CanonicalRelationOperator { .. } => { unreachable!("invalid constructor on LHS") } @@ -2873,6 +2883,14 @@ pub(crate) fn slice_matches_type( false } } + Type::Constant(Constant::Number(base)) => { + crate::utilities::notify!("Here"); + if let Ok(slice_as_float) = slice.parse::() { + *base == slice_as_float + } else { + false + } + } Type::Constructor(super::Constructor::BinaryOperator { lhs, rhs, From 85309506fde41a809d64b53def3f3b701eaf6088 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 10 Nov 2024 09:07:56 +0000 Subject: [PATCH 17/24] Improve specification tests and parser parsing examples --- checker/specification/build.rs | 92 ++++++++++++++------ checker/specification/test.rs | 15 ++-- parser/examples/duplicate_block.rs | 135 +++++++++++++++++++++++++++++ parser/examples/parse.rs | 108 ++++++++++++++++------- 4 files changed, 284 insertions(+), 66 deletions(-) create mode 100644 parser/examples/duplicate_block.rs diff --git a/checker/specification/build.rs b/checker/specification/build.rs index 1c1bfc06..f49c6c9d 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -19,14 +19,18 @@ fn main() -> Result<(), Box> { if cfg!(feature = "staging") { let staging = read_to_string("./staging.md")?; - writeln!(&mut out, "mod staging {{ use super::check_errors; ").unwrap(); + writeln!(&mut out, "mod staging {{ ").unwrap(); + writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ") + .unwrap(); markdown_lines_append_test_to_rust(staging.lines().enumerate(), &mut out)?; writeln!(&mut out, "}}").unwrap(); } if cfg!(feature = "to_implement") { let to_implement = read_to_string("./to_implement.md")?; - writeln!(&mut out, "mod to_implement {{ use super::check_errors; ").unwrap(); + writeln!(&mut out, "mod to_implement {{ ").unwrap(); + writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ") + .unwrap(); markdown_lines_append_test_to_rust(to_implement.lines().enumerate(), &mut out)?; writeln!(&mut out, "}}").unwrap(); } @@ -60,8 +64,20 @@ fn markdown_lines_append_test_to_rust( let heading = line.strip_prefix("####").unwrap().trim_start(); let test_title = heading_to_rust_identifier(heading); - let blocks = { - let mut blocks = Vec::new(); + pub struct File<'a> { + path: &'a str, + code: String, + } + + // pub struct Block { + // /// Vec for FS tests + // files: Vec, + // expected_diagnostics: Vec, + // options: Vec + // } + + let files = { + let mut files = Vec::::new(); let mut current_filename = None; for (_, line) in lines.by_ref() { // Also handles TSX @@ -74,10 +90,10 @@ fn markdown_lines_append_test_to_rust( for (_, line) in lines.by_ref() { if let Some(path) = line.strip_prefix("// in ") { if !code.trim().is_empty() { - blocks.push(( - current_filename.unwrap_or(DEFAULT_FILE_PATH), - mem::take(&mut code), - )); + files.push(File { + path: current_filename.unwrap_or(DEFAULT_FILE_PATH), + code: mem::take(&mut code), + }); } current_filename = Some(path); continue; @@ -88,40 +104,64 @@ fn markdown_lines_append_test_to_rust( code.push_str(line); code.push('\n') } - blocks.push((current_filename.unwrap_or(DEFAULT_FILE_PATH), code)); - blocks + files.push(File { path: current_filename.unwrap_or(DEFAULT_FILE_PATH), code }); + files }; - let errors = { - let mut errors = Vec::new(); + + let (expected_diagnostics, options) = { + let mut expected_diagnostics = Vec::new(); + let mut options = None::>; for (_, line) in lines.by_ref() { - if line.starts_with("#") { + if let (Some(args), false) = (line.strip_prefix("With "), options.is_some()) { + options = Some(args.split(',').collect()); + } else if line.starts_with("#") { panic!("block with no diagnostics or break between in {test_title}") - } else if line.starts_with('-') { - let error = - line.strip_prefix("- ").unwrap().replace('\\', "").replace('"', "\\\""); - errors.push(format!("\"{}\"", error)) - } else if !errors.is_empty() { + } else if let Some(diagnostic) = line.strip_prefix("-") { + let error = diagnostic.trim().replace('\\', "").replace('"', "\\\""); + expected_diagnostics.push(format!("\"{}\"", error)) + } else if !expected_diagnostics.is_empty() { break; } } - errors + (expected_diagnostics, options) }; - let errors = errors.join(", "); + let expected_diagnostics = expected_diagnostics.join(", "); let heading_idx = heading_idx + 1; - let code = blocks + // TODO don't allocate + let code_as_list = files .into_iter() - .map(|(path, content)| format!("(\"{path}\",r#\"{content}\"#),")) - .fold(String::new(), |mut acc, cur| { - acc.push_str(&cur); + .map(|File { path, code }| format!("(\"{path}\",r#\"{code}\"#),")) + .reduce(|mut acc, slice| { + acc.push_str(&slice); acc - }); + }) + .unwrap(); + + let options = if let Some(options) = options { + let arguments = options + .into_iter() + .map(|value| format!("{value}: true")) + .reduce(|mut acc, slice| { + acc.push_str(&slice); + acc.push_str(", "); + acc + }) + .unwrap(); + format!("Some(super::TypeCheckOptions {{ {arguments}, ..super::TypeCheckOptions::default() }})") + } else { + format!("None") + }; writeln!( out, "#[test] fn {test_title}() {{ - super::check_errors(\"{heading}\", {heading_idx}, &[{code}], &[{errors}]) + super::check_expected_diagnostics( + \"{heading}\", {heading_idx}, + &[{code_as_list}], &[{expected_diagnostics}], + {options} + ) }}", )?; } diff --git a/checker/specification/test.rs b/checker/specification/test.rs index 85166a18..10a662d5 100644 --- a/checker/specification/test.rs +++ b/checker/specification/test.rs @@ -11,13 +11,14 @@ use checker::{ diagnostics, source_map::{Nullable, SourceId}, synthesis::EznoParser, + TypeCheckOptions, }; // This is here as it is used in the included `/specification.rs` use parser::ASTNode; mod specification { - use super::check_errors; + use super::{check_expected_diagnostics, TypeCheckOptions}; // from build.rs include!(concat!(env!("OUT_DIR"), "/specification.rs")); @@ -37,12 +38,13 @@ const SIMPLE_DTS: Option<&str> = None; const IN_CI: bool = option_env!("CI").is_some(); /// Called by each test -fn check_errors( +fn check_expected_diagnostics( heading: &'static str, - _line: usize, + line: usize, // (Path, Content) code: &[(&'static str, &'static str)], expected_diagnostics: &[&'static str], + type_check_options: Option, ) { // let global_buffer = Arc::new(Mutex::new(String::new())); // let old_panic_hook = panic::take_hook(); @@ -59,10 +61,7 @@ fn check_errors( // }) // }); - // TODO could test these - let type_check_options = Default::default(); - - // eprintln!("{:?}", code); + let type_check_options = type_check_options.unwrap_or_default(); // let result = panic::catch_unwind(|| { @@ -125,7 +124,7 @@ fn check_errors( if diagnostics != expected_diagnostics { panic!( - "{}", + "In '{heading}' on line {line}, found\n{}", pretty_assertions::Comparison::new(expected_diagnostics, &diagnostics).to_string() ) } diff --git a/parser/examples/duplicate_block.rs b/parser/examples/duplicate_block.rs new file mode 100644 index 00000000..21183a2f --- /dev/null +++ b/parser/examples/duplicate_block.rs @@ -0,0 +1,135 @@ +use ezno_parser::{ + declarations::VariableDeclaration, + visiting::{Chain, ImmutableVariableOrProperty, VisitOptions, Visitor, Visitors}, + ASTNode, Declaration, Expression, Module, StatementOrDeclaration, VariableField, +}; +use std::collections::{HashMap, HashSet}; + +struct Offsets { + pub offsets: Vec, + /// TODO use &str references + pub top_level_variables: HashSet, + pub top_level_types: HashSet, +} + +/// TODO this could use visting right? +/// TODO abstract to library +/// TODO do for funtions and types +fn get_top_level_identifiers(m: &Module) -> (HashSet, HashSet) { + let (mut variables, mut types): (HashSet<_>, HashSet<_>) = Default::default(); + for item in &m.items { + match item { + StatementOrDeclaration::Declaration(Declaration::Variable(variable)) => { + match variable { + VariableDeclaration::ConstDeclaration { declarations, position: _ } => { + for declaration in declarations { + if let VariableField::Name(identifier) = declaration.name.get_ast_ref() + { + variables.insert(identifier.as_option_str().unwrap().to_owned()); + } + } + } + VariableDeclaration::LetDeclaration { declarations, position: _ } => { + for declaration in declarations { + if let VariableField::Name(identifier) = declaration.name.get_ast_ref() + { + variables.insert(identifier.as_option_str().unwrap().to_owned()); + } + } + } + } + } + StatementOrDeclaration::Declaration(Declaration::Function(function)) => { + variables.insert(function.on.name.identifier.as_option_str().unwrap().to_owned()); + } + _ => {} + } + } + (variables, types) +} + +fn main() { + let code = " +let x = 2; +let y = x + 2; +let z = 6; +" + .trim(); + + // function func() {{ return [x, z] }} + let module = Module::from_string(code.into(), Default::default()).unwrap(); + + let (top_level_variables, top_level_types) = get_top_level_identifiers(&module); + + let mut visitors = Visitors { + expression_visitors: vec![Box::new(NameReferenceFinder)], + statement_visitors: Default::default(), + variable_visitors: vec![Box::new(NameIndexFinder)], + block_visitors: Default::default(), + }; + + // eprintln!("variables={:#?}", (&top_level_variables, &top_level_types)); + + let mut offsets: Offsets = + Offsets { offsets: Default::default(), top_level_variables, top_level_types }; + + module.visit::( + &mut visitors, + &mut offsets, + &VisitOptions { visit_nested_blocks: true, reverse_statements: false }, + source_map::Nullable::NULL, + ); + + // TODO why is this backwards + // eprintln!("offsets={:#?}", offsets); + + offsets.offsets.sort_unstable(); + let mut rest = code.to_owned(); + for (idx, offset) in offsets.offsets.iter_mut().enumerate().rev() { + let current_offset = *offset as usize; + rest.insert_str(current_offset, "000"); + // need to ammed offset now string has been changed + *offset += ("000".len() * idx) as u32; + } + rest.push('\n'); + + let mut total = rest.clone(); + const SIZE: usize = 10; + total.reserve(rest.len() * (SIZE - 1)); + + for i in 1..SIZE { + let name = format!("{:03}", i); + for offset in offsets.offsets.iter().copied() { + let range = offset as usize..(offset as usize + 3); + rest.replace_range(range, &name); + } + + total.push_str(&rest); + } + + eprintln!("{}", total); +} + +/// TODO this could be collected in the same process as above +struct NameIndexFinder; + +impl<'a> Visitor, Offsets> for NameIndexFinder { + fn visit(&mut self, item: &ImmutableVariableOrProperty<'a>, data: &mut Offsets, chain: &Chain) { + if chain.len() == 1 && item.get_variable_name().is_some() { + data.offsets.push(item.get_position().end); + // data.insert(name.to_owned()); + } + } +} + +struct NameReferenceFinder; + +impl Visitor for NameReferenceFinder { + fn visit(&mut self, item: &Expression, data: &mut Offsets, _chain: &Chain) { + if let Expression::VariableReference(name, position) = item { + if data.top_level_variables.contains(name) { + data.offsets.push(position.end); + } + } + } +} diff --git a/parser/examples/parse.rs b/parser/examples/parse.rs index cd189381..ccd5b852 100644 --- a/parser/examples/parse.rs +++ b/parser/examples/parse.rs @@ -1,8 +1,10 @@ -use std::{collections::VecDeque, time::Instant}; +use std::{collections::VecDeque, path::Path, time::Instant}; use ezno_parser::{ASTNode, Comments, Module, ParseOptions, ToStringOptions}; use source_map::FileSystem; +type Files = source_map::MapFileStore; + fn main() -> Result<(), Box> { let mut args: VecDeque<_> = std::env::args().skip(1).collect(); let path = args.pop_front().ok_or("expected argument")?; @@ -18,20 +20,18 @@ fn main() -> Result<(), Box> { let display_keywords = args.iter().any(|item| item == "--keywords"); let extras = args.iter().any(|item| item == "--extras"); let partial_syntax = args.iter().any(|item| item == "--partial"); - let source_maps = args.iter().any(|item| item == "--source-map"); + let print_source_maps = args.iter().any(|item| item == "--source-map"); let timings = args.iter().any(|item| item == "--timings"); - let render_timings = args.iter().any(|item| item == "--render-timings"); let type_definition_module = args.iter().any(|item| item == "--type-definition-module"); let type_annotations = !args.iter().any(|item| item == "--no-type-annotations"); let top_level_html = args.iter().any(|item| item == "--top-level-html"); + let parse_imports = args.iter().any(|item| item == "--parse-imports"); let print_ast = args.iter().any(|item| item == "--ast"); - let render_output = args.iter().any(|item| item == "--render"); + let to_string_output = args.iter().any(|item| item == "--to-string"); let pretty = args.iter().any(|item| item == "--pretty"); - let now = Instant::now(); - // TODO temp const STACK_SIZE_MB: usize = 32; let parse_options = ParseOptions { @@ -52,14 +52,48 @@ fn main() -> Result<(), Box> { ..ParseOptions::default() }; - let mut fs = source_map::MapFileStore::::default(); - - let source = std::fs::read_to_string(path.clone())?; + let mut fs = Files::default(); + + let to_string_options = to_string_output.then(|| ToStringOptions { + expect_markers: true, + include_type_annotations: type_annotations, + pretty, + comments: if pretty { Comments::All } else { Comments::None }, + // 60 is temp + max_line_length: if pretty { 60 } else { u8::MAX }, + ..Default::default() + }); + + parse_path( + path.as_ref(), + timings, + parse_imports, + &parse_options, + print_ast, + print_source_maps, + &to_string_options, + display_keywords, + &mut fs, + ) +} +fn parse_path( + path: &Path, + timings: bool, + parse_imports: bool, + parse_options: &ParseOptions, + print_ast: bool, + print_source_maps: bool, + to_string_options: &Option, + display_keywords: bool, + fs: &mut Files, +) -> Result<(), Box> { + let source = std::fs::read_to_string(path)?; let source_id = fs.new_source_id(path.into(), source.clone()); - eprintln!("parsing {:?} bytes", source.len()); - let result = Module::from_string_with_options(source.clone(), parse_options, None); + eprintln!("parsing {:?} ({:?} bytes)", path.display(), source.len()); + let now = Instant::now(); + let result = Module::from_string_with_options(source.clone(), parse_options.clone(), None); match result { Ok((module, state)) => { @@ -70,45 +104,55 @@ fn main() -> Result<(), Box> { if print_ast { println!("{module:#?}"); } - if source_maps || render_output || render_timings { - let now = Instant::now(); - let to_string_options = ToStringOptions { - expect_markers: true, - include_type_annotations: type_annotations, - pretty, - comments: if pretty { Comments::All } else { Comments::None }, - // 60 is temp - max_line_length: if pretty { 60 } else { u8::MAX }, - ..Default::default() - }; + if let Some(to_string_options) = to_string_options { + let now = Instant::now(); let (output, source_map) = - module.to_string_with_source_map(&to_string_options, source_id, &fs); + module.to_string_with_source_map(to_string_options, source_id, fs); - if timings || render_timings { + if timings { eprintln!("ToString'ed in: {:?}", now.elapsed()); } - if source_maps { - let sm = source_map.unwrap().to_json(&fs); - println!("{output}\n{sm}"); - } - if render_output { - println!("{output}"); + + println!("{output}"); + if print_source_maps { + let sm = source_map.unwrap().to_json(fs); + println!("{sm}"); } } if display_keywords { - println!("{:?}", state.keyword_positions.unwrap()); + println!("{:?}", state.keyword_positions.as_ref()); } + if parse_imports { + for import in state.constant_imports.iter() { + // Don't reparse files (+ catches cycles) + let resolved_path = path.parent().unwrap().join(import); + if fs.get_paths().contains_key(&resolved_path) { + continue; + } + let _ = parse_path( + &resolved_path, + timings, + parse_imports, + parse_options, + print_ast, + print_source_maps, + to_string_options, + display_keywords, + fs, + )?; + } + } Ok(()) } Err(parse_err) => { let mut line_column = parse_err .position .with_source(source_id) - .into_line_column_span::(&fs); + .into_line_column_span::(fs); { // Editor are one indexed line_column.line_start += 1; From 6e93bd9558693cb2f4597236b370a21cdd08a27e Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 12 Nov 2024 19:30:04 +0000 Subject: [PATCH 18/24] Fix for #207 --- checker/src/context/mod.rs | 6 +- checker/src/features/constant_functions.rs | 16 ++-- checker/src/features/mod.rs | 101 +++++++++++---------- checker/src/types/calling.rs | 5 +- checker/src/types/helpers.rs | 45 ++++++--- checker/src/types/mod.rs | 6 -- checker/src/types/properties/access.rs | 82 +++++++++-------- 7 files changed, 145 insertions(+), 116 deletions(-) diff --git a/checker/src/context/mod.rs b/checker/src/context/mod.rs index 368de944..e39c2482 100644 --- a/checker/src/context/mod.rs +++ b/checker/src/context/mod.rs @@ -977,11 +977,11 @@ impl Context { } pub(crate) fn get_prototype(&self, on: TypeId) -> TypeId { - if let Some(prototype) = self.info.prototypes.get(&on) { + if let Some(prototype) = self.get_chain_of_info().find_map(|info| info.prototypes.get(&on)) + { *prototype - } else if let Some(parent) = self.context_type.get_parent() { - get_on_ctx!(parent.get_prototype(on)) } else { + crate::utilities::notify!("Could not find prototype"); TypeId::OBJECT_TYPE } } diff --git a/checker/src/features/constant_functions.rs b/checker/src/features/constant_functions.rs index ee09d9ce..9c359f1b 100644 --- a/checker/src/features/constant_functions.rs +++ b/checker/src/features/constant_functions.rs @@ -294,12 +294,16 @@ pub(crate) fn call_constant_function( } "setPrototypeOf" => { if let [first, second] = arguments { - let _prototype = environment - .info - .prototypes - .insert(first.non_spread_type().unwrap(), second.non_spread_type().unwrap()); - // TODO - Ok(ConstantOutput::Value(TypeId::UNDEFINED_TYPE)) + if let (Ok(first), Ok(second)) = (first.non_spread_type(), second.non_spread_type()) + { + let _prototype = environment.info.prototypes.insert(first, second); + + crate::utilities::notify!("Set {:?} prototype to {:?}", first, second); + + Ok(ConstantOutput::Value(first)) + } else { + Err(ConstantFunctionError::CannotComputeConstant) + } } else { Err(ConstantFunctionError::CannotComputeConstant) } diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index b69a2464..a00d20e1 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -28,7 +28,7 @@ use crate::{ diagnostics::TypeStringRepresentation, events::RootReference, types::{ - get_constraint, + get_constraint, helpers, logical::{Logical, LogicalOrValid}, properties, PartiallyAppliedGenerics, TypeStore, }, @@ -431,58 +431,63 @@ pub(crate) fn has_property( information: &impl InformationChain, types: &mut TypeStore, ) -> TypeId { - match types.get_type_by_id(rhs) { - Type::Interface { .. } - | Type::Class { .. } - | Type::Constant(_) - | Type::FunctionReference(_) - | Type::Object(_) - | Type::PartiallyAppliedGenerics(_) - | Type::And(_, _) - | Type::SpecialObject(_) - | Type::Narrowed { .. } - | Type::AliasTo { .. } => { - let result = properties::get_property_unbound( - (rhs, None), - (publicity, key, None), - false, - information, - types, - ); - match result { - Ok(LogicalOrValid::Logical(result)) => match result { - Logical::Pure(_) => TypeId::TRUE, - Logical::Or { .. } => { - crate::utilities::notify!("or or implies `in`"); - TypeId::UNIMPLEMENTED_ERROR_TYPE - } - Logical::Implies { .. } => { - crate::utilities::notify!("or or implies `in`"); + if let Some((condition, truthy, falsy)) = helpers::get_type_as_conditional(rhs, types) { + let truthy_result = has_property((publicity, key), truthy, information, types); + let otherwise_result = has_property((publicity, key), falsy, information, types); + types.new_conditional_type(condition, truthy_result, otherwise_result) + } else { + match types.get_type_by_id(rhs) { + Type::Interface { .. } + | Type::Class { .. } + | Type::Constant(_) + | Type::FunctionReference(_) + | Type::Object(_) + | Type::PartiallyAppliedGenerics(_) + | Type::And(_, _) + | Type::SpecialObject(_) + | Type::Narrowed { .. } + | Type::AliasTo { .. } => { + let result = properties::get_property_unbound( + (rhs, None), + (publicity, key, None), + false, + information, + types, + ); + match result { + Ok(LogicalOrValid::Logical(result)) => match result { + Logical::Pure(_) => TypeId::TRUE, + Logical::Or { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::UNIMPLEMENTED_ERROR_TYPE + } + Logical::Implies { .. } => { + crate::utilities::notify!("or or implies `in`"); + TypeId::UNIMPLEMENTED_ERROR_TYPE + } + Logical::BasedOnKey { .. } => { + crate::utilities::notify!("mapped in"); + TypeId::UNIMPLEMENTED_ERROR_TYPE + } + }, + Ok(LogicalOrValid::NeedsCalculation(result)) => { + crate::utilities::notify!("TODO {:?}", result); TypeId::UNIMPLEMENTED_ERROR_TYPE } - Logical::BasedOnKey { .. } => { - crate::utilities::notify!("mapped in"); - TypeId::UNIMPLEMENTED_ERROR_TYPE + Err(err) => { + crate::utilities::notify!("TODO {:?}", err); + TypeId::FALSE } - }, - Ok(LogicalOrValid::NeedsCalculation(result)) => { - crate::utilities::notify!("TODO {:?}", result); - TypeId::UNIMPLEMENTED_ERROR_TYPE - } - Err(err) => { - crate::utilities::notify!("TODO {:?}", err); - TypeId::FALSE } } - } - Type::Or(_, _) => { - crate::utilities::notify!("Condtionally"); - TypeId::UNIMPLEMENTED_ERROR_TYPE - } - Type::RootPolyType(_) | Type::Constructor(_) => { - crate::utilities::notify!("Queue event / create dependent"); - let constraint = get_constraint(rhs, types).unwrap(); - has_property((publicity, key), constraint, information, types) + Type::Or(_, _) => { + unreachable!() + } + Type::RootPolyType(_) | Type::Constructor(_) => { + crate::utilities::notify!("Queue event / create dependent"); + let constraint = get_constraint(rhs, types).unwrap(); + has_property((publicity, key), constraint, information, types) + } } } } diff --git a/checker/src/types/calling.rs b/checker/src/types/calling.rs index 09210f99..9ea6c083 100644 --- a/checker/src/types/calling.rs +++ b/checker/src/types/calling.rs @@ -614,7 +614,10 @@ fn call_logical( || is_independent_function || matches!( const_fn_ident.as_str(), - "satisfies" | "is_dependent" | "bind" | "proxy:constructor" + "satisfies" + | "is_dependent" | "bind" + | "proxy:constructor" | "setPrototypeOf" + | "getPrototypeOf" ); // { diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs index f0c50040..27fa2038 100644 --- a/checker/src/types/helpers.rs +++ b/checker/src/types/helpers.rs @@ -213,7 +213,7 @@ pub fn _type_is_error(ty: TypeId, types: &TypeStore) -> bool { /// TODO want to skip mapped generics because that would break subtyping #[must_use] -pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { +pub fn get_type_as_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, TypeId)> { match types.get_type_by_id(ty) { Type::Constructor(crate::types::Constructor::ConditionalResult { condition, @@ -226,7 +226,7 @@ pub fn get_conditional(ty: TypeId, types: &TypeStore) -> Option<(TypeId, TypeId, Type::RootPolyType(PolyNature::MappedGeneric { .. }) => None, _ => { if let Some(constraint) = get_constraint(ty, types) { - get_conditional(constraint, types) + get_type_as_conditional(constraint, types) } else { None } @@ -324,28 +324,47 @@ pub struct AndCondition(pub TypeId); #[derive(Debug)] pub struct OrCase(pub Vec); -pub fn into_conditions(ty: TypeId, types: &TypeStore) -> Vec { - // TODO aliases and such - if let Type::And(lhs, rhs) = types.get_type_by_id(ty) { +pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec { + let ty = types.get_type_by_id(id); + if let Type::And(lhs, rhs) = ty { let mut buf = into_conditions(*lhs, types); buf.append(&mut into_conditions(*rhs, types)); buf - } else if let Some(backing) = get_constraint_or_alias(ty, types) { - into_conditions(backing, types) + } else if let Type::RootPolyType(rpt) = ty { + into_conditions(rpt.get_constraint(), types) + } else if let Type::Narrowed { narrowed_to, .. } = ty { + into_conditions(*narrowed_to, types) } else { - vec![AndCondition(ty)] + // Temp fix + if let Type::Constructor(Constructor::BinaryOperator { result, .. }) = ty { + if !matches!(*result, TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) { + return into_conditions(*result, types); + } + } + + vec![AndCondition(id)] } + + // else if let Some(backing) = get_constraint_or_alias(ty, types) { + // // TODO temp to keep information + // let mut buf = vec![ty]; + // buf.append(&mut into_conditions(*backing, types)); + // buf + // } } -pub fn into_cases(ty: TypeId, types: &TypeStore) -> Vec { - if let Type::Or(lhs, rhs) = types.get_type_by_id(ty) { +pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec { + let ty = types.get_type_by_id(id); + if let Type::Or(lhs, rhs) = ty { let mut buf = into_cases(*lhs, types); buf.append(&mut into_cases(*rhs, types)); buf - } else if let Some(backing) = get_constraint_or_alias(ty, types) { - into_cases(backing, types) + } else if let Type::RootPolyType(rpt) = ty { + into_cases(rpt.get_constraint(), types) + } else if let Type::Narrowed { narrowed_to, .. } = ty { + into_cases(*narrowed_to, types) } else { - vec![OrCase(into_conditions(ty, types))] + vec![OrCase(into_conditions(id, types))] } } diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index e8e4ae5d..ce22d99c 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -763,9 +763,3 @@ impl TypeCombinable for TypeId { TypeId::UNDEFINED_TYPE } } - -/// Used for **both** inference and narrowing -pub enum Confirmation { - HasProperty { on: (), property: () }, - IsType { on: (), ty: () }, -} diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 753333f9..405e6efe 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -10,7 +10,7 @@ use crate::{ contributions::CovariantContribution, generic_type_arguments::GenericArguments, }, get_constraint, - helpers::{get_conditional, is_inferrable_type, is_pseudo_continous}, + helpers::{get_type_as_conditional, is_inferrable_type, is_pseudo_continous}, logical::{ BasedOnKey, Invalid, Logical, LogicalOrValid, NeedsCalculation, PossibleLogical, PropertyOn, @@ -335,8 +335,11 @@ pub(crate) fn get_property_unbound( } Type::Constructor(crate::types::Constructor::ConditionalResult { .. }) | Type::Or(..) => { + crate::utilities::notify!("Here {:?}", on); + let (condition, truthy_result, otherwise_result) = - get_conditional(on, types).expect("case above != get_conditional"); + get_type_as_conditional(on, types) + .expect("case above != get_type_as_conditional"); if require_both_logical { let left = get_property_on_type_unbound( @@ -372,14 +375,16 @@ pub(crate) fn get_property_unbound( types, ); if left.is_err() && right.is_err() { + crate::utilities::notify!( + "One side invalid {:?}", + (left.is_err(), right.is_err()) + ); Err(Invalid(on)) } else { - let left = left.unwrap_or(LogicalOrValid::Logical(Logical::Pure( - PropertyValue::Deleted, - ))); - let right = right.unwrap_or(LogicalOrValid::Logical(Logical::Pure( - PropertyValue::Deleted, - ))); + const DELETED_PROPERTY: LogicalOrValid = + LogicalOrValid::Logical(Logical::Pure(PropertyValue::Deleted)); + let left = left.unwrap_or(DELETED_PROPERTY); + let right = right.unwrap_or(DELETED_PROPERTY); Ok(Logical::Or { condition, left: Box::new(left), right: Box::new(right) } .into()) } @@ -430,17 +435,6 @@ pub(crate) fn get_property_unbound( .find_map(|facts| facts.prototypes.get(&on)) .copied(); - let generics = if let Some(generics) = object_constraint_structure_generics { - // TODO clone - Some(generics.clone()) - } else if prototype - .is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype)) - { - Some(GenericArguments::LookUp { on }) - } else { - None - }; - let on_self = resolver( (on, on_type_arguments), (publicity, under, under_type_arguments), @@ -448,31 +442,39 @@ pub(crate) fn get_property_unbound( types, ); - let result = if let (Some(prototype), None) = (prototype, &on_self) { - resolver( + if let Some(property) = on_self { + let generics = if let Some(generics) = object_constraint_structure_generics { + // TODO clone + Some(generics.clone()) + } else if prototype + .is_some_and(|prototype| types.lookup_generic_map.contains_key(&prototype)) + { + Some(GenericArguments::LookUp { on }) + } else { + None + }; + + let property = wrap(property); + let property = if let Some(ref generics) = generics { + // TODO clone + Logical::Implies { on: Box::new(property), antecedent: generics.clone() } + } else { + property + }; + Ok(LogicalOrValid::Logical(property)) + } else if let Some(prototype) = prototype { + crate::utilities::notify!("{:?}", types.get_type_by_id(prototype)); + + get_property_on_type_unbound( (prototype, on_type_arguments), (publicity, under, under_type_arguments), + require_both_logical, info_chain, types, ) } else { - on_self - }; - - // crate::utilities::notify!("result={:?}", result); - - result - .map(wrap) - .map(|result| { - if let Some(ref generics) = generics { - // TODO clone - Logical::Implies { on: Box::new(result), antecedent: generics.clone() } - } else { - result - } - }) - .map(LogicalOrValid::Logical) - .ok_or(Invalid(on)) + Err(Invalid(on)) + } } Type::Interface { extends, .. } => resolver( (on, on_type_arguments), @@ -602,7 +604,9 @@ pub(crate) fn get_property_unbound( // if *key == TypeId::ERROR_TYPE { // return Err(MissingOrToCalculate::Error); // } else - if let Some((condition, truthy_result, otherwise_result)) = get_conditional(key, types) { + if let Some((condition, truthy_result, otherwise_result)) = + get_type_as_conditional(key, types) + { let left = get_property_unbound( (on, on_type_arguments), (publicity, &PropertyKey::Type(truthy_result), under_type_arguments), From a64bb4592c5d5c66181d9e014750ebaed902917b Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 11:05:20 +0000 Subject: [PATCH 19/24] More improvements - More range fixes - Literal on object - Indentation in specification --- checker/specification/build.rs | 4 +- checker/specification/specification.md | 125 +++++++++--------- checker/specification/staging.md | 56 ++++---- checker/specification/to_implement.md | 13 ++ checker/src/context/information.rs | 35 +++++ checker/src/features/mod.rs | 15 ++- checker/src/features/narrowing.rs | 9 +- .../operations/mathematical_bitwise.rs | 118 +++++++++++++---- checker/src/synthesis/expressions.rs | 1 + checker/src/types/disjoint.rs | 31 +++-- checker/src/types/helpers.rs | 36 ++--- checker/src/types/intrinsics.rs | 25 +++- checker/src/types/mod.rs | 6 +- checker/src/types/printing.rs | 28 ++-- checker/src/types/properties/access.rs | 8 +- checker/src/types/subtyping.rs | 7 +- checker/src/utilities/modulo_class.rs | 3 +- 17 files changed, 352 insertions(+), 168 deletions(-) diff --git a/checker/specification/build.rs b/checker/specification/build.rs index f49c6c9d..1c597410 100644 --- a/checker/specification/build.rs +++ b/checker/specification/build.rs @@ -175,7 +175,7 @@ fn markdown_lines_append_test_to_rust( fn heading_to_rust_identifier(heading: &str) -> String { heading .replace("...", "") - .replace([' ', '-', '/', '.', '+', ':'], "_") - .replace(['*', '\'', '`', '"', '&', '!', '(', ')', ','], "") + .replace([' ', '-', '/', '.', '+'], "_") + .replace(['*', '\'', '`', '"', '&', '!', '(', ')', ',', ':'], "") .to_lowercase() } diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 39b3dfc2..74c7194f 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -1993,13 +1993,13 @@ stringIsHi(string) satisfies number; ```ts function func(param: boolean) { let a = 2; - if (param) { + if (param) { a = 3; - return a; - } else { + return a; + } else { a = 7; } - a satisfies string; + a satisfies string; } ``` @@ -2480,8 +2480,8 @@ getNumberBetweenFive() === 2.2; getNumberBetweenFive() === 7; ``` -- This equality is always false as InclusiveRange<0, 5> & Integer and 2.2 have no overlap -- This equality is always false as InclusiveRange<0, 5> & Integer and 7 have no overlap +- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 2.2 have no overlap +- This equality is always false as GreaterThan<0> & LessThan<5> & Integer | 0 | 5 and 7 have no overlap #### Identity equality @@ -2490,7 +2490,7 @@ getNumberBetweenFive() === 7; ```ts function func(a: string, b: number) { (a === a) satisfies string; - (b === b) satisfies null; + (b === b) satisfies null; } ``` @@ -2519,8 +2519,8 @@ function func(a: number) { With advanced_number_intrinsics -- Expected null, found InclusiveRange\<-5, 5> -- Expected string, found InclusiveRange\<18, 22> +- Expected null, found GreaterThan<-5> & LessThan<5> | -5 | 5 +- Expected string, found GreaterThan<18> & LessThan<22> | 18 | 22 #### Not disjoint @@ -2904,8 +2904,8 @@ let x: BoxString; ```ts interface X { - a: string - b: string + a: string + b: string } function func(x: X | null) { @@ -3273,11 +3273,11 @@ doThingWithX(new Y()) ```ts class Box { - value: T; + value: T; - constructor(value: T) { - this.value = value; - } + constructor(value: T) { + this.value = value; + } } const myBox = new Box("hi"); @@ -4045,7 +4045,7 @@ x.property_b ```ts function x(p: { readonly a: string, b: string }) { - p.a = "hi"; + p.a = "hi"; p.b = "hi"; } ``` @@ -4231,10 +4231,10 @@ function logicNarrow(thing: any, other: any) { ```ts function func(param: boolean | string | number) { - if (typeof param === "boolean") { - return 5 - } - param satisfies null; + if (typeof param === "boolean") { + return 5 + } + param satisfies null; } ``` @@ -4244,9 +4244,9 @@ function func(param: boolean | string | number) { ```ts function func(param: Array | string) { - if (param instanceof Array) { + if (param instanceof Array) { param satisfies null; - } + } } ``` @@ -4256,9 +4256,9 @@ function func(param: Array | string) { ```ts function func(param: any) { - if (param instanceof Array) { + if (param instanceof Array) { param satisfies null; - } + } } ``` @@ -4268,10 +4268,10 @@ function func(param: any) { ```ts function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: number }) { - if (param.tag === "a") { - param.a satisfies string; - param satisfies null; - } + if (param.tag === "a") { + param.a satisfies string; + param satisfies null; + } } ``` @@ -4281,10 +4281,10 @@ function narrowPropertyEquals(param: { tag: "a", a: string } | { tag: "b", b: nu ```ts function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) { - if ("a" in param) { - param.a satisfies string; - param satisfies null; - } + if ("a" in param) { + param.a satisfies string; + param satisfies null; + } } ``` @@ -4296,9 +4296,9 @@ function narrowFromTag(param: { tag: "a", a: string } | { tag: "b", b: number }) ```ts function buildObject(param: any) { - if ("a" in param) { - param satisfies null; - } + if ("a" in param) { + param satisfies null; + } } ``` @@ -4308,12 +4308,12 @@ function buildObject(param: any) { ```ts function conditional(param: boolean) { - const obj1 = { b: 2 }, obj2 = { c: 6 }; - const sum = param ? obj1 : obj2; - if (sum === obj1) { - sum.a = 3; - } - [obj1, obj2] satisfies string; + const obj1 = { b: 2 }, obj2 = { c: 6 }; + const sum = param ? obj1 : obj2; + if (sum === obj1) { + sum.a = 3; + } + [obj1, obj2] satisfies string; } ``` @@ -4353,19 +4353,19 @@ function conditional(param: boolean) { ```ts function func1(param: string | number) { - if (typeof param === "number" && param > 0) { - param satisfies number; - } else { - param satisfies null; + if (typeof param === "number" && param > 0) { + param satisfies number; + } else { + param satisfies null; } } function func2(param: string | number | boolean) { - if (typeof param === "string" || !(typeof param === "number")) { - param satisfies undefined; - } else { - param satisfies number; - } + if (typeof param === "string" || !(typeof param === "number")) { + param satisfies undefined; + } else { + param satisfies number; + } } ``` @@ -4380,10 +4380,10 @@ function func2(param: string | number | boolean) { function func(param: boolean) { let a = param; const inner = (value: boolean) => a = value; - if (a) { + if (a) { inner(false); a satisfies null; - } + } } ``` @@ -4393,15 +4393,15 @@ function func(param: boolean) { ```ts function func1(param: any): asserts param is number { - if (typeof param !== "string") { - throw "bad" - } + if (typeof param !== "string") { + throw "bad" + } } function func2(param: any): asserts param is boolean { - if (typeof param !== "boolean") { - throw "bad" - } + if (typeof param !== "boolean") { + throw "bad" + } } ``` @@ -4986,9 +4986,16 @@ function register(a: Literal) { register("something") // `document.title` is an unknown string, non-literal -register(document.title) +register(document.title); + +function func(param: object) { + const obj = { a: 2 }; + const obj1: Literal = obj; + const obj2: Literal = param; +} ``` +- Type object is not assignable to type Literal\ - Argument of type string is not assignable to parameter of type Literal\ #### Number intrinsics diff --git a/checker/specification/staging.md b/checker/specification/staging.md index c6a392c8..c1f2567f 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -42,28 +42,9 @@ function func1(a: number, b: number) { With advanced_number_intrinsics -- This equality is always false as MultipleOf<10> and LessThan<37> & GreaterThan<31> have no overlap +- This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap -#### Modulo offsets - -```ts -function func(param: Integer) { - print_type(param - 0.2); - print_type(param / 2); - print_type(2 / param); -} - -function func2(param: number) { - print_type((param - 5) + 5); - print_type((param / 5) * 5); -} -``` - -With advanced_number_intrinsics - -- Hi - -#### Narrowing: Implication by +#### Narrowing: Implication from equality ```ts function func(a: boolean) { @@ -84,11 +65,11 @@ function func(x: number) { } ``` -- This equality is always false +- This equality is always false as ExclusiveRange<-5, 5> and 6 have no overlap #### Narrowing in for loop -> Can't do modulo because post mutation +> Can't do modulo because of post mutation ```ts for (let i = 0; i < 3; i++) { @@ -110,6 +91,21 @@ function func(a: number, b: number, c: number) { - Expected 5, found true +### Operators across conditions + +```ts +function func(param: boolean) { + const value = param ? 1 : 2; + return value + 1; +} + +func statisfies string; +``` + +With advanced_number_intrinsics + +- Expected string, found (param: boolean) => 2 | 3 + ### Broken #### Template literal edge cases @@ -122,3 +118,17 @@ const invalidNum3: `${1}` = "2"; - Type 1 is not assignable to type "1" - Type \"2\" is not assignable to type "1" + +#### Set prototype of conditional + +```ts +const obj = Object.setPrototypeOf( + {}, + Math.random() ? { a: 2 } : { get a() { return 0 } } +); + +const result = 'a' in obj; +result satisfies string; +``` + +- Expected string, found true diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md index 11cce36d..9fbf3af0 100644 --- a/checker/specification/to_implement.md +++ b/checker/specification/to_implement.md @@ -948,3 +948,16 @@ new RegExp("x").group.string ``` - ? + +### Properties + +#### Issue [#208](https://github.com/kaleidawave/ezno/issues/208) + +```ts +let d = {}; +let e = 0; +if (Math.random() > 0.2) { d.a = 0; } +print_type('a' in d); +``` + +- Hmmm diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index feec4618..9ec159c9 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -414,6 +414,24 @@ pub fn merge_info( // TODO temp fix for `... ? { ... } : { ... }`. // TODO add undefineds to sides etc for (on, properties) in truthy.current_properties.into_iter() { + // let properties = properties + // .into_iter() + // .map(|(publicity, key, value)| { + // let falsy_environment_property = otherwise + // .as_mut() + // .and_then(|otherwise| { + // pick_out_property(&mut otherwise.current_properties, (publicity, key), onto, types) + // }); + + // if let Some(existing) = falsy_environment_property { + // // Merging more complex properties has lots of issues + // todo!() + // } else { + // (publicity, key, PropertyValue::ConditionallyExists { condition, value }) + // } + // }) + // .collect(); + if let Some(existing) = onto.current_properties.get_mut(&on) { existing.extend(properties); } else { @@ -434,3 +452,20 @@ pub fn merge_info( // TODO set more information? } } + +// `info_chain` and `types` are a bit excess, but `key_matches` requires it +// TODO needs to delete afterwards, to block it out for subsequent +fn _pick_out_property( + from: &mut Properties, + (want_publicity, want_key): (Publicity, &PropertyKey<'static>), + info_chain: &impl InformationChain, + types: &TypeStore, +) -> Option<(Publicity, PropertyKey<'static>, PropertyValue)> { + from.iter() + .position(|(publicity, key, _)| { + *publicity == want_publicity + && crate::types::key_matches((key, None), (want_key, None), info_chain, types).0 + }) + // TODO replace with deleted? + .map(|idx| from.remove(idx)) +} diff --git a/checker/src/features/mod.rs b/checker/src/features/mod.rs index a00d20e1..0af0ae5f 100644 --- a/checker/src/features/mod.rs +++ b/checker/src/features/mod.rs @@ -457,9 +457,18 @@ pub(crate) fn has_property( match result { Ok(LogicalOrValid::Logical(result)) => match result { Logical::Pure(_) => TypeId::TRUE, - Logical::Or { .. } => { - crate::utilities::notify!("or or implies `in`"); - TypeId::UNIMPLEMENTED_ERROR_TYPE + Logical::Or { condition, left, right } => { + // TODO some problems here, need to recurse + let (left, right) = (*left, *right); + if let (LogicalOrValid::Logical(_), LogicalOrValid::Logical(_)) = + (&left, right) + { + TypeId::TRUE + } else if let LogicalOrValid::Logical(_) = left { + condition + } else { + types.new_logical_negation_type(condition) + } } Logical::Implies { .. } => { crate::utilities::notify!("or or implies `in`"); diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index e8402c0f..b13beaa9 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -2,7 +2,7 @@ use crate::{ context::InformationChain, types::{ self, as_logical_and, as_logical_not, as_logical_or, - helpers::{get_conditional, get_origin}, + helpers::{get_origin, get_type_as_conditional}, properties, Constant, Constructor, PolyNature, TypeOperator, TypeStore, }, Map, Type, TypeId, @@ -18,7 +18,6 @@ pub fn narrow_based_on_expression_into_vec( ) -> Map { let mut into = Default::default(); narrow_based_on_expression(condition, negate, &mut into, information, types); - crate::utilities::notify!("{:?}", into); into.iter_mut().for_each(|(on, value)| *value = types.new_narrowed(*on, *value)); into } @@ -129,7 +128,7 @@ pub fn narrow_based_on_expression( let result = if negate { // TODO wip - let narrowed_to = if get_conditional(lhs, types).is_some() { + let narrowed_to = if get_type_as_conditional(lhs, types).is_some() { let mut result = Vec::new(); build_union_from_filter( lhs, @@ -244,7 +243,7 @@ pub fn narrow_based_on_expression( // TODO need to merge. This is very bad let narrowed_to = if let Some(existing) = into.get(&rhs) { crate::utilities::notify!("Here"); - types.new_and_type(*existing, narrowed_to) + types.new_and_type(narrowed_to, *existing) } else { narrowed_to }; @@ -515,7 +514,7 @@ pub(crate) fn build_union_from_filter( } else if let TypeId::BOOLEAN_TYPE = on { build_union_from_filter(TypeId::TRUE, filter, found, information, types); build_union_from_filter(TypeId::FALSE, filter, found, information, types); - } else if let Some((_condition, lhs, rhs)) = get_conditional(on, types) { + } else if let Some((_condition, lhs, rhs)) = get_type_as_conditional(on, types) { build_union_from_filter(lhs, filter, found, information, types); build_union_from_filter(rhs, filter, found, information, types); } else { diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs index dbbe6a9b..eead3382 100644 --- a/checker/src/features/operations/mathematical_bitwise.rs +++ b/checker/src/features/operations/mathematical_bitwise.rs @@ -38,23 +38,22 @@ pub fn evaluate_mathematical_operation( strict_casts: bool, ) -> Result { if let MathematicalOrBitwiseOperation::Add = operator { - let constant = match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { + match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { (Type::Constant(Constant::Number(lhs)), Type::Constant(Constant::Number(rhs))) => { - Constant::Number(lhs + rhs) + Ok(types.new_constant_type(Constant::Number(lhs + rhs))) } (Type::Constant(lhs), Type::Constant(rhs)) => { let mut first = cast_as_string(lhs, strict_casts)?; let second = cast_as_string(rhs, strict_casts)?; // Concatenate strings first.push_str(&second); - Constant::String(first) + Ok(types.new_constant_type(Constant::String(first))) } - _ => { - crate::utilities::notify!("here"); - return Err(()); + (lhs, rhs) => { + crate::utilities::notify!("here {:?} + {:?}", lhs, rhs); + Err(()) } - }; - Ok(types.new_constant_type(constant)) + } } else { match (types.get_type_by_id(lhs), types.get_type_by_id(rhs)) { (Type::Constant(c1), Type::Constant(c2)) => { @@ -95,7 +94,10 @@ pub fn evaluate_mathematical_operation( }; Ok(ty) } - _ => Err(()), + (lhs, rhs) => { + crate::utilities::notify!("here {:?} @ {:?}", lhs, rhs); + Err(()) + } } } } @@ -153,12 +155,10 @@ pub fn evaluate_mathematical_operation( return Ok(rhs); } - let result = if can_be_string { - TypeId::STRING_TYPE - } else if let ( + if let ( MathematicalOrBitwiseOperation::Add | MathematicalOrBitwiseOperation::Multiply, - (lhs_range, _lhs_modulo), - (rhs_range, _rhs_modulo), + (lhs_range, lhs_modulo), + (rhs_range, rhs_modulo), true, ) = ( operator, @@ -167,9 +167,11 @@ pub fn evaluate_mathematical_operation( operate_on_number_intrinsics, ) { crate::utilities::notify!( - "{:?} with {:?}", - (lhs_range, _lhs_modulo), - (rhs_range, _rhs_modulo) + "{:?} with {:?}. {:?} & {:?}", + (lhs_range, lhs_modulo), + (rhs_range, rhs_modulo), + rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single), + lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single) ); if let (Some(lhs_range), Some(rhs_range)) = (lhs_range, rhs_range) { let range = match operator { @@ -179,17 +181,81 @@ pub fn evaluate_mathematical_operation( } _ => unreachable!(), }; - intrinsics::range_to_type(range, types) - } else { - TypeId::NUMBER_TYPE + // That is a lot types + let result = intrinsics::range_to_type(range, types); + let constructor = + crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; + + return Ok(types.register_type(crate::Type::Constructor(constructor))); } - } else { - // TODO if or of constant types - TypeId::NUMBER_TYPE - }; - let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; - Ok(types.register_type(crate::Type::Constructor(constructor))) + // else if let (Some(lhs_modulo), Some(offset)) = + // (lhs_modulo, rhs_range.and_then(crate::utilities::float_range::FloatRange::as_single)) + // { + // crate::utilities::notify!("Here"); + // let mod_class = lhs_modulo.offset(offset); + // intrinsics::modulo_to_type(mod_class, types) + // } else if let (Some(rhs_modulo), Some(offset)) = + // (rhs_modulo, lhs_range.and_then(crate::utilities::float_range::FloatRange::as_single)) + // { + // let mod_class = rhs_modulo.offset(offset); + // intrinsics::modulo_to_type(mod_class, types) + // } + } + + if let (Some((condition, lhs_truthy_result, lhs_falsy_result)), true) = + (helpers::get_type_as_conditional(lhs, types), operate_on_number_intrinsics) + { + crate::utilities::notify!("Here lhs dependent"); + let truthy_result = evaluate_mathematical_operation( + lhs_truthy_result, + operator, + rhs, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + let falsy_result = evaluate_mathematical_operation( + lhs_falsy_result, + operator, + rhs, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + + Ok(types.new_conditional_type(condition, truthy_result, falsy_result)) + } else if let (true, Some((condition, rhs_truthy_result, rhs_falsy_result))) = + (operate_on_number_intrinsics, helpers::get_type_as_conditional(rhs, types)) + { + let truthy_result = evaluate_mathematical_operation( + lhs, + operator, + rhs_truthy_result, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + let falsy_result = evaluate_mathematical_operation( + lhs, + operator, + rhs_falsy_result, + info, + types, + strict_casts, + operate_on_number_intrinsics, + )?; + + Ok(types.new_conditional_type(condition, truthy_result, falsy_result)) + } else { + let result = if can_be_string { TypeId::STRING_TYPE } else { TypeId::NUMBER_TYPE }; + let constructor = + crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; + Ok(types.register_type(crate::Type::Constructor(constructor))) + } } else { attempt_constant_math_operator(lhs, operator, rhs, types, strict_casts) } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index c8052bf8..4b805dea 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -1345,6 +1345,7 @@ pub(super) fn synthesise_object_literal( print_type(*truthy_result, &checking_data.types, environment, true), print_type(*otherwise_result, &checking_data.types, environment, true) ); + // Concatenate some types here if they all have the same keys // && matches!( // (lv, rv), diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index aac64274..f0dc4f6b 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -175,6 +175,20 @@ pub fn types_are_disjoint( } else { false } + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = rhs_ty + { + todo!() + } else if let Type::Constructor(Constructor::BinaryOperator { + operator: MathematicalOrBitwiseOperation::Add, + result: TypeId::STRING_TYPE, + .. + }) = lhs_ty + { + todo!() } else if let Some(lhs) = super::get_constraint(lhs, types) { // TODO not sure whether these should be here? types_are_disjoint(lhs, rhs, already_checked, information, types) @@ -236,23 +250,23 @@ fn number_modulo_disjoint( return false; }; + let offset = 0f64.try_into().unwrap(); + let this = crate::utilities::modulo_class::ModuloClass::new(*argument, offset); + // Little bit complex here because dealing with decimal types, not integers if let Type::Constant(Constant::Number(other)) = other_ty { - let result = other % argument != 0.; - // crate::utilities::notify!("{:?} {:?}", lhs, rhs); - result + !this.contains(*other) } else { let (range, modulo_class) = super::intrinsics::get_range_and_mod_class(other, types); - crate::utilities::notify!("{:?}, {:?}", range, modulo_class); + crate::utilities::notify!("Disjoint with modulo, {:?}, {:?}", range, modulo_class); if let Some(range) = range { return !range.contains_multiple_of(*argument); } - if let Some(_modulo_class) = modulo_class { - crate::utilities::notify!("todo"); - return false; + if let Some(modulo_class) = modulo_class { + return this.disjoint(modulo_class); } crate::utilities::notify!("Here {:?}", other_ty); - true + false } } @@ -265,6 +279,7 @@ fn number_range_disjoint( let greater_than = *on == TypeId::GREATER_THAN; let this_ty = types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); + if let Type::Constant(Constant::Number(this)) = this_ty { let other_ty = types.get_type_by_id(other); if let Type::Constant(Constant::Number(other)) = other_ty { diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs index 27fa2038..e4aa5166 100644 --- a/checker/src/types/helpers.rs +++ b/checker/src/types/helpers.rs @@ -326,31 +326,25 @@ pub struct OrCase(pub Vec); pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec { let ty = types.get_type_by_id(id); + + // Temp fix + if let Type::Constructor(Constructor::BinaryOperator { result, .. }) = ty { + return if matches!(*result, TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) { + vec![AndCondition(id)] + } else { + into_conditions(*result, types) + }; + } + if let Type::And(lhs, rhs) = ty { let mut buf = into_conditions(*lhs, types); buf.append(&mut into_conditions(*rhs, types)); buf - } else if let Type::RootPolyType(rpt) = ty { - into_conditions(rpt.get_constraint(), types) - } else if let Type::Narrowed { narrowed_to, .. } = ty { - into_conditions(*narrowed_to, types) + } else if let Some(backing) = get_constraint_or_alias(id, types) { + into_conditions(backing, types) } else { - // Temp fix - if let Type::Constructor(Constructor::BinaryOperator { result, .. }) = ty { - if !matches!(*result, TypeId::NUMBER_TYPE | TypeId::STRING_TYPE) { - return into_conditions(*result, types); - } - } - vec![AndCondition(id)] } - - // else if let Some(backing) = get_constraint_or_alias(ty, types) { - // // TODO temp to keep information - // let mut buf = vec![ty]; - // buf.append(&mut into_conditions(*backing, types)); - // buf - // } } pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec { @@ -359,10 +353,8 @@ pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec { let mut buf = into_cases(*lhs, types); buf.append(&mut into_cases(*rhs, types)); buf - } else if let Type::RootPolyType(rpt) = ty { - into_cases(rpt.get_constraint(), types) - } else if let Type::Narrowed { narrowed_to, .. } = ty { - into_cases(*narrowed_to, types) + } else if let Some(backing) = get_constraint_or_alias(id, types) { + into_cases(backing, types) } else { vec![OrCase(into_conditions(id, types))] } diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index f25fb983..52cec855 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -172,7 +172,6 @@ pub fn get_less_than(on: TypeId, types: &TypeStore) -> Option<(bool, TypeId)> { } } -// TODO also take into account mod range #[must_use] pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { // TODO skip if infinite @@ -181,6 +180,7 @@ pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { let floor = range .get_greater_than() .map(|_number| new_intrinsic(&Intrinsic::GreaterThan, floor_ty, types)); + let ceiling = range.get_less_than().map(|_number| new_intrinsic(&Intrinsic::LessThan, ceiling_ty, types)); @@ -199,6 +199,24 @@ pub fn range_to_type(range: FloatRange, types: &mut TypeStore) -> TypeId { ty } +pub fn modulo_to_type(mod_class: ModuloClass, types: &mut TypeStore) -> TypeId { + // TODO skip if infinite + let modulo = types.new_constant_type(Constant::Number(mod_class.modulo)); + let ty = new_intrinsic(&Intrinsic::MultipleOf, modulo, types); + if mod_class.offset != 0. { + let offset = types.new_constant_type(Constant::Number(mod_class.offset)); + types.register_type(Type::Constructor(Constructor::BinaryOperator { + lhs: ty, + operator: MathematicalOrBitwiseOperation::Add, + rhs: offset, + result: TypeId::NUMBER_TYPE, + })) + } else { + ty + } +} + +// TODO offsets as well #[must_use] pub fn get_multiple(on: TypeId, types: &TypeStore) -> Option { let on = get_constraint(on, types).unwrap_or(on); @@ -436,10 +454,12 @@ impl PureNumberIntrinsic { .. }) = ty { + let lhs_ty = types.get_type_by_id(super::get_constraint(*lhs, types).unwrap_or(*lhs)); + crate::utilities::notify!("{:?}", lhs_ty); let lhs_modulo = if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::MULTIPLE_OF, arguments, - }) = types.get_type_by_id(*lhs) + }) = lhs_ty { let arg = arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap(); if let Type::Constant(Constant::Number(number)) = types.get_type_by_id(arg) { @@ -450,6 +470,7 @@ impl PureNumberIntrinsic { } else { None }; + if let (Some(modulo), Type::Constant(Constant::Number(offset))) = (lhs_modulo, types.get_type_by_id(*rhs)) { diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index ce22d99c..7164c90c 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -329,9 +329,11 @@ impl Type { // Fine Type::Narrowed { .. } | Type::Constructor(_) | Type::RootPolyType(_) => true, // TODO what about if left or right - Type::And(_, _) | Type::Or(_, _) => false, + // TODO should not be true here + Type::And(_, _) | Type::Or(_, _) => true, // TODO what about if it aliases dependent? - Type::AliasTo { .. } => false, + // TODO should not be true here + Type::AliasTo { .. } => true, Type::Interface { .. } | Type::Class { .. } => false, Type::Constant(_) | Type::SpecialObject(_) diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 0fa02987..dc19a2f9 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -79,20 +79,20 @@ pub fn print_type_into_buf( let r#type = types.get_type_by_id(ty); match r#type { Type::And(a, b) => { - let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); - if value.0.is_some() || value.1.is_some() { - crate::utilities::notify!("{:?}", value); - } + // let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + // if value.0.is_some() || value.1.is_some() { + // crate::utilities::notify!("{:?}", value); + // } print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" & "); print_type_into_buf(*b, buf, cycles, args, types, info, debug); } Type::Or(a, b) => { - let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); - if value.0.is_some() || value.1.is_some() { - crate::utilities::notify!("{:?}", value); - } + // let value = crate::types::intrinsics::get_range_and_mod_class(ty, types); + // if value.0.is_some() || value.1.is_some() { + // crate::utilities::notify!("{:?}", value); + // } print_type_into_buf(*a, buf, cycles, args, types, info, debug); buf.push_str(" | "); @@ -418,6 +418,18 @@ pub fn print_type_into_buf( print_type_into_buf(*result, buf, cycles, args, types, info, debug); } } + Constructor::BinaryOperator { + lhs: _, + operator: crate::features::operations::MathematicalOrBitwiseOperation::Modulo, + rhs, + result: _, + } if matches!(types.get_type_by_id(*rhs), Type::Constant(_)) => { + if let Type::Constant(crate::Constant::Number(num)) = types.get_type_by_id(*rhs) { + write!(buf, "ExclusiveRange<-{num}, {num}>").unwrap(); + } else { + unreachable!() + } + } constructor if debug => match constructor { Constructor::BinaryOperator { lhs, operator, rhs, result: _ } => { print_type_into_buf(*lhs, buf, cycles, args, types, info, debug); diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 405e6efe..39df69c3 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -335,8 +335,6 @@ pub(crate) fn get_property_unbound( } Type::Constructor(crate::types::Constructor::ConditionalResult { .. }) | Type::Or(..) => { - crate::utilities::notify!("Here {:?}", on); - let (condition, truthy_result, otherwise_result) = get_type_as_conditional(on, types) .expect("case above != get_type_as_conditional"); @@ -738,9 +736,9 @@ pub(crate) fn get_property( types, ); - { - crate::utilities::notify!("Access {:?} result {:?}", under, result); - } + // { + // crate::utilities::notify!("Access {:?} result {:?}", under, result); + // } match result { Ok(LogicalOrValid::Logical(logical)) => { diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index d49592c3..037601bf 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -795,10 +795,13 @@ pub(crate) fn type_is_subtype_with_generics( // Ezno intrinsic TypeId::LITERAL_RESTRICTION => { let inner = arguments.get_structure_restriction(TypeId::T_TYPE).unwrap(); - return if let Type::Constant(rhs_constant) = subtype { + return if let Type::Constant(_) + | Type::Object(ObjectNature::RealDeal) + | Type::SpecialObject(..) = subtype + { type_is_subtype_with_generics( (inner, base_type_arguments), - (rhs_constant.get_backing_type(), ty_structure_arguments), + (ty, ty_structure_arguments), state, information, types, diff --git a/checker/src/utilities/modulo_class.rs b/checker/src/utilities/modulo_class.rs index 7976e422..96bba2fc 100644 --- a/checker/src/utilities/modulo_class.rs +++ b/checker/src/utilities/modulo_class.rs @@ -28,7 +28,8 @@ impl ModuloClass { /// WIP pub fn disjoint(self, other: Self) -> bool { if let Ok(gcd) = gcd_of_float(self.modulo, other.modulo) { - (self.offset - other.offset) % gcd == 0. + crate::utilities::notify!("{:?}", gcd); + (self.offset - other.offset) % gcd != 0. } else { crate::utilities::notify!("Here"); true From ebae1bba3c7e897bc77289ded2d719d04fdeac3c Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 17:01:14 +0000 Subject: [PATCH 20/24] Some more fixes - Disjoint on template literals - Move specification tests --- .github/workflows/clippy-rustfmt-fix.yml | 1 + checker/definitions/internal.ts.d.bin | Bin 35478 -> 34856 bytes checker/definitions/overrides.d.ts | 4 +- checker/specification/specification.md | 169 +++++++++++++----- checker/specification/staging.md | 131 -------------- checker/specification/to_implement.md | 41 +++++ checker/src/features/narrowing.rs | 2 - .../operations/mathematical_bitwise.rs | 2 +- checker/src/features/operations/relation.rs | 10 +- checker/src/synthesis/classes.rs | 52 ++++-- checker/src/types/disjoint.rs | 12 +- checker/src/types/helpers.rs | 81 +++++++++ checker/src/types/intrinsics.rs | 2 +- checker/src/types/printing.rs | 86 ++++----- checker/src/types/properties/access.rs | 2 +- checker/src/types/properties/assignment.rs | 9 + checker/src/types/properties/mod.rs | 8 +- checker/src/types/subtyping.rs | 25 ++- src/js-cli-and-library/tests/cli-test.mjs | 33 ---- 19 files changed, 367 insertions(+), 303 deletions(-) delete mode 100644 src/js-cli-and-library/tests/cli-test.mjs diff --git a/.github/workflows/clippy-rustfmt-fix.yml b/.github/workflows/clippy-rustfmt-fix.yml index fc2cfd76..16aa4d05 100644 --- a/.github/workflows/clippy-rustfmt-fix.yml +++ b/.github/workflows/clippy-rustfmt-fix.yml @@ -16,6 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - uses: actions/cache@v4 with: path: ${{ env.CACHE_PATHS }} diff --git a/checker/definitions/internal.ts.d.bin b/checker/definitions/internal.ts.d.bin index 976c802f2f06ae5be2230ce7e5a60ede9b31c6af..c2573ea25516655d3782a0b8598bb007c7ad600e 100644 GIT binary patch literal 34856 zcmdUY34mNhwf3p1?w+|blaPdc?*xGf36q6A5J=1dAuA*yV2GhJ(~~qzx`*zbBtt+1 zK?o>{;EMP}5fPvGKt&W4cLY=v1r<~jL~wf`E{OW%|Grx8y*<+zqCWWl|0bvB)~Qpc z&Z+g(sk-+T&ns6B?Jjh0C?48d>MP#1T;xD$>8_M(mA<0LrU@b!07Np23-t{mmyZ&8 z699;lmoHr+a?JrE?*xd9>nW}qSiiQ|+gt3e*Va}CYITvXOcQwsAQBCfdy2iKaw{5NS&|i#(5H%JR(4t5z#jk#|F@_gT%d%c_+vgCct!D6&65WH+Zt zeXzf{wuH=M3D$zefMwG_>H3 zzH951wH2@a-w-|aaPk|~P#YpGD1lu7_;n%`$~`Dt)|eCSHstp;n`iFS;`(`8`b7@g zS>zaih*?}fcp57Ha9jDg<#p(_US!o6ku?BhFefycopwhN0z`I7GVtwTCfdRx+a6j_ z>>Eop6wA)YVlSIb%ksgE>neRUk-Jc!U$%wn74fx+76m1~V-;!bsdNu)v`ypMDI)I$ zi0tmPE0#Bvs+BTh*VgKVIxKQKWl^bKtQPv9hwFQ%O6y!-AIp9Fj-+*drMhu$pR4?n15jDv1_VHoN-+rDJ8eJ5G79v@IU!tC#xw zic5Q^NGq6>%BA|?ZW5Z#QrXEh-+5b#-L{vVB;(N3z`H&;w^-}0mip_J>M4>b5A^l5 zNc)M^BAQ2)Wp}8AM%g9?WplWUj@3W4P+ng=L44mBEu$BdeZkn6SEt6eoU|@3mFM?W z3iaJ_Ti9Z6_i#h#i85wMaXmWqreY9&S90yHRC|t-9GV~cRTc&3$CpvE(;Sq^0yML7 zt;A$U$VzF~ktoUZ57aiuo7Cqpg6yotV@-dhUv5x)1CcW^aw9tMU1}l_Gcy61!;lZv z<@>4^xQ}N8K7&H-kzcEafcZ@>V6(+d#p&040OR44t#Z&cH5Si^D!5R7hj5qD(Onn~z z>N7!TYT#Vdw*c`#g6yu;q^y4n#Msd>WxY_Ab9EOGb7N$-x37W;psou*4aVq9?c8cz zHtTl)^RWcmy$;y(^nJkmJjR;7%6hp#{{x6!b_$tg>s3^lyk0K`su-g)rHuo9a*4hK zn44m(Uba9k)psC#XN*(xvKW<*FE^D;wp_dKAXkNq}~pMa8$*E4H_=Wyo9f zwTSpsI?{F@8+o<99}&NeBXfPl^7{G)C4T}+@h2J^xMpj8rS77fy83!q+%(Wa+VmG9`(P6Q90Kq> ziq;bOp?*J5cO~fc)yhD>{8&%h2U8`$QIEFhIhDS?LVpc5)X((&h&WpzLqv!?@@MwCho9SL-<9O)%Sl`4F+@Ky!}$+i5;#sXD>8)lPYsJ!F4* z0@&YJipgf_flM*QY=hz1uBU)~Mtp~&60J!wI z-fFRUUQuS4Wx$;s<1@8lp-*O-ZNS_RW5@PaF*cX$^S0DENx&qLS>}s~eljL$E!E~% zE9Vu+rf5?0%`9No#4#|xJx4Z*1?GIjTp!1dcGi5DT4jlO4sj<> z41D*1ic2?_Lwqvz%BkjNP(2q@X3i-N*5pj14?$-I_#)U;=o^69d$##Da92+X_!cNy zr9J#Q^L`*No*W|B>r3m)a+�7&Od#u(NzG)6}o+J|ElR{;ZxR_qy?24<(BL4UsD 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 { // TODO this argument map(cb: (t: T, i?: number) => U): Array { - const { length } = this, mapped: Array = []; + const { length } = this, mapped: U[] = []; let i: number = 0; while (i < length) { const value = this[i]; @@ -48,7 +48,7 @@ declare class Array { // // TODO any is debatable filter(cb: (t: T, i?: number) => any): Array { - const { length } = this, filtered: Array = []; + const { length } = this, filtered: T[] = []; let i: number = 0; while (i < length) { const value = this[i]; diff --git a/checker/specification/specification.md b/checker/specification/specification.md index 6e26cb6b..aa0f5b63 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -306,16 +306,6 @@ const b = x.b; - No property 'b' on { a: 2 } -#### `Object.keys`, `Object.values`, `Object.entries` - -```ts -Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"]; -Object.values({ a: 1, b: 2 }) satisfies [1, 2]; -Object.entries({ a: 1, b: 2 }) satisfies boolean; -``` - -- Expected boolean, found [["a", 1], ["b", 2]] - #### Spread condition ```ts @@ -2357,25 +2347,6 @@ try { checkedLn(-5) } catch {} - Conditional '[Error] { message: \"Cannot log\" }' was thrown in function -#### Throw through internal callback - -```ts -try { - [1, 2, 3].map((x: number) => { - if (x === 2) { - throw "error" - } - }); - console.log("unreachable") -} catch (e) { - e satisfies number; -} -``` - -- Conditional '"error"' was thrown in function -- Unreachable statement -- Expected number, found "error" - ### Collections > Some of these are built of exiting features. @@ -2412,16 +2383,6 @@ x.push("hi"); - Argument of type \"hi\" is not assignable to parameter of type number -#### Array map - -> TODO other arguments (index and `this`) - -```ts -[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11]; -``` - -- Expected [7, 8, 11], found [7, 9, 11] - #### Mutation > This is part of [assignment mismatch](https://github.com/kaleidawave/ezno/issues/18) @@ -2585,17 +2546,82 @@ With advanced_number_intrinsics - Expected null, found GreaterThan<-5> & LessThan<5> | -5 | 5 - Expected string, found GreaterThan<18> & LessThan<22> | 18 | 22 -#### Not disjoint +#### Disjoint multiple of with range + +> TODO need to redo range to use interesection of less than and greater than ```ts -function func(param: number) { - if (param !== 2) { - return param === 2 - } +function func1(a: number, b: number) { + if (a % 8 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 37) { + const x = a === b; + } + if (a % 10 === 0 && 31 < b && b < 41) { + const x = a === b; + } +} +``` + +With advanced_number_intrinsics + +- This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap + +#### Modulo range + +```ts +function func(x: number) { + return x % 5 === 6; +} +``` + +- This equality is always false as ExclusiveRange<-5, 5> and 6 have no overlap + +#### Transistivity + +```ts +function func(a: number, b: number, c: number) { + if (a < b && b < c) { + const cond = (a < c) satisfies 5; + } +} +``` + +- Expected 5, found true + +### Operators across conditions + +```ts +function func(param: boolean) { + const value = param ? 1 : 2; + return value + 1; } + +func statisfies string; ``` -- This equality is always false as Not<2> and 2 have no overlap +With advanced_number_intrinsics + +- Expected string, found (param: boolean) => 2 | 3 + +#### Disjoint not + +```ts +function func1(param: Not) { + return "hi" === param; +} + +function func2(param: Not) { + return 4 === param; +} + +function func3(p1: Not, p2: Not) { + return p1 === p2; +} +``` + +- This equality is always false as "hi" and Not\ have no overlap ### Statements, declarations and expressions @@ -3753,10 +3779,29 @@ type Introduction = `Hello ${string}`; const first: Introduction = "Hello Ben"; const second: Introduction = "Hi Ben"; const third: `Hiya ${string}` = "Hello Ben"; + +// Edge cases +const invalidNum1: `${1}` = 1; +const invalidNum2: `${1}` = "1"; +const invalidNum3: `${1}` = "2"; ``` - Type "Hi Ben" is not assignable to type Introduction - Type "Hello Ben" is not assignable to type `Hiya ${string}` +- Type 1 is not assignable to type "1" +- Type \"2\" is not assignable to type "1" + +#### Disjoint template literals + +```ts +function func(a: `a${string}`, b: `b${string}`, c: string) { + const res1 = a === b; + const res2 = (b === c) satisfies string; +} +``` + +- This equality is always false as `a${string}` and `b${string}` have no overlap +- Expected string, found boolean #### Assigning to types as keys @@ -4514,6 +4559,31 @@ function getName(name?: string) { - Expected undefined, found string +#### Implication from equality + +```ts +function func(a: boolean) { + const x = a ? 1 : 2; + if (x === 1) { + a satisfies "hi" + } +} +``` + +- Expected "hi", found true + +#### Narrowing in for loop + +> Can't do modulo because of post mutation + +```ts +for (let i = 0; i < 3; i++) { + const x = i === 50; +} +``` + +- This equality is always false as LessThan<3> and 50 have no overlap + ### Object constraint > Any references to a annotated variable **must** be within its LHS type. These test that it carries down to objects. @@ -4605,9 +4675,18 @@ const x = { a: 3 }; Object.setPrototypeOf(x, { a: 5, b: 2 }); x.a satisfies 3; x.b satisfies string; + +const obj = Object.setPrototypeOf( + {}, + Math.random() ? { a: 2 } : { get a() { return 0 } } +); + +const result = 'a' in obj; +result satisfies string; ``` - Expected string, found 2 +- Expected string, found true #### Get prototype diff --git a/checker/specification/staging.md b/checker/specification/staging.md index c1f2567f..4739e695 100644 --- a/checker/specification/staging.md +++ b/checker/specification/staging.md @@ -1,134 +1,3 @@ Currently implementing: > This file is for work-in-progress and can help separating features that are being implemented to regressions - -### Fixes - -#### Disjoint not - -```ts -function func1(param: Not) { - return "hi" === param; -} - -function func2(param: Not) { - return 4 === param; -} - -function func3(p1: Not, p2: Not) { - return p1 === p2; -} -``` - -- This equality is always false as "hi" and Not\ have no overlap - -#### Disjoint multiple of with range - -> TODO need to redo range to use interesection of less than and greater than - -```ts -function func1(a: number, b: number) { - if (a % 8 === 0 && 31 < b && b < 37) { - const x = a === b; - } - if (a % 10 === 0 && 31 < b && b < 37) { - const x = a === b; - } - if (a % 10 === 0 && 31 < b && b < 41) { - const x = a === b; - } -} -``` - -With advanced_number_intrinsics - -- This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap - -#### Narrowing: Implication from equality - -```ts -function func(a: boolean) { - const x = a ? 1 : 2; - if (x === 1) { - a satisfies "hi" - } -} -``` - -- Expected "hi", found true - -#### Modulo range - -```ts -function func(x: number) { - return x % 5 === 6; -} -``` - -- This equality is always false as ExclusiveRange<-5, 5> and 6 have no overlap - -#### Narrowing in for loop - -> Can't do modulo because of post mutation - -```ts -for (let i = 0; i < 3; i++) { - const x = i === 50; -} -``` - -- This equality is always false as LessThan<3> and 50 have no overlap - -#### Transistivity - -```ts -function func(a: number, b: number, c: number) { - if (a < b && b < c) { - const cond = (a < c) satisfies 5; - } -} -``` - -- Expected 5, found true - -### Operators across conditions - -```ts -function func(param: boolean) { - const value = param ? 1 : 2; - return value + 1; -} - -func statisfies string; -``` - -With advanced_number_intrinsics - -- Expected string, found (param: boolean) => 2 | 3 - -### Broken - -#### Template literal edge cases - -```ts -const invalidNum1: `${1}` = 1; -const invalidNum2: `${1}` = "1"; -const invalidNum3: `${1}` = "2"; -``` - -- Type 1 is not assignable to type "1" -- Type \"2\" is not assignable to type "1" - -#### Set prototype of conditional - -```ts -const obj = Object.setPrototypeOf( - {}, - Math.random() ? { a: 2 } : { get a() { return 0 } } -); - -const result = 'a' in obj; -result satisfies string; -``` - -- Expected string, found true diff --git a/checker/specification/to_implement.md b/checker/specification/to_implement.md index 9fbf3af0..5856c16b 100644 --- a/checker/specification/to_implement.md +++ b/checker/specification/to_implement.md @@ -770,6 +770,47 @@ a satisfies 0; b satisfies string; - Expected string, found 1 +#### Array map + +> TODO other arguments (index and `this`) + +```ts +[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11]; +``` + +- Expected [7, 8, 11], found [7, 9, 11] + +#### `Object.keys`, `Object.values`, `Object.entries` + +```ts +Object.keys({ a: 1, b: 2 }) satisfies ["a", "b"]; +Object.values({ a: 1, b: 2 }) satisfies [1, 2]; +Object.entries({ a: 1, b: 2 }) satisfies boolean; +``` + +- Expected boolean, found [["a", 1], ["b", 2]] + +#### Throw through internal callback + +> This test heavily relies on `.map` working + +```ts +try { + [1, 2, 3].map((x: number) => { + if (x === 2) { + throw "error" + } + }); + console.log("unreachable") +} catch (e) { + e satisfies number; +} +``` + +- Conditional '"error"' was thrown in function +- Unreachable statement +- Expected number, found "error" + ### Control flow #### Conditional break diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index b13beaa9..86f6ca05 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -152,7 +152,6 @@ pub fn narrow_based_on_expression( rhs }; - crate::utilities::notify!("Here {:?} {:?}", lhs, result); into.insert(lhs, result); // CONDITION NARROWING HERE ((x ? 1 : 2) == 1 => x) @@ -304,7 +303,6 @@ pub fn narrow_based_on_expression( } constructor => { if let Some(condition) = as_logical_not(constructor, types) { - crate::utilities::notify!("Here"); narrow_based_on_expression(condition, !negate, into, information, types); } else if let Some((lhs, rhs)) = as_logical_and(constructor, types) { // De Morgan's laws diff --git a/checker/src/features/operations/mathematical_bitwise.rs b/checker/src/features/operations/mathematical_bitwise.rs index eead3382..240e659a 100644 --- a/checker/src/features/operations/mathematical_bitwise.rs +++ b/checker/src/features/operations/mathematical_bitwise.rs @@ -185,7 +185,7 @@ pub fn evaluate_mathematical_operation( let result = intrinsics::range_to_type(range, types); let constructor = crate::types::Constructor::BinaryOperator { lhs, operator, rhs, result }; - + return Ok(types.register_type(crate::Type::Constructor(constructor))); } diff --git a/checker/src/features/operations/relation.rs b/checker/src/features/operations/relation.rs index 2ed50d73..b703e135 100644 --- a/checker/src/features/operations/relation.rs +++ b/checker/src/features/operations/relation.rs @@ -137,10 +137,10 @@ pub fn evaluate_equality_inequality_operation( } } - let is_dependent = types.get_type_by_id(lhs).is_dependent() + let either_is_dependent = types.get_type_by_id(lhs).is_dependent() || types.get_type_by_id(rhs).is_dependent(); - if is_dependent { + if either_is_dependent { { if let Type::Constructor(Constructor::BinaryOperator { lhs: op_lhs, @@ -163,15 +163,15 @@ pub fn evaluate_equality_inequality_operation( } { - let lhs = get_constraint(lhs, types).unwrap_or(lhs); - let rhs = get_constraint(rhs, types).unwrap_or(rhs); - if !helpers::simple_subtype(lhs, TypeId::NUMBER_TYPE, info, types) || !helpers::simple_subtype(rhs, TypeId::NUMBER_TYPE, info, types) { return Err(()); } + let lhs = get_constraint(lhs, types).unwrap_or(lhs); + let rhs = get_constraint(rhs, types).unwrap_or(rhs); + // Tidies some things for counting loop iterations // Checking disjoint-ness for inequalities (TODO under option) via distribution diff --git a/checker/src/synthesis/classes.rs b/checker/src/synthesis/classes.rs index 294df65e..9479c1bc 100644 --- a/checker/src/synthesis/classes.rs +++ b/checker/src/synthesis/classes.rs @@ -125,7 +125,7 @@ fn synthesise_class_declaration_extends_and_members< let _name = P::as_option_str(&class.name).map_or_else(String::new, str::to_owned); let class_prototype = class_type; - crate::utilities::notify!("At start {:?}", environment.context_type.free_variables); + // crate::utilities::notify!("At start {:?}", environment.context_type.free_variables); let extends = class.extends.as_ref().map(|extends_expression| { let extends = @@ -192,8 +192,6 @@ fn synthesise_class_declaration_extends_and_members< } }; - crate::utilities::notify!("{:?}", (getter_setter, is_async, is_generator)); - let internal_marker = if let (true, ParserPropertyKey::Identifier(name, _, _)) = (is_declare, method.name.get_ast_ref()) { @@ -209,6 +207,25 @@ fn synthesise_class_declaration_extends_and_members< let internal = internal_marker.is_some(); let has_defined_this = method.parameters.leading.0.is_some(); + let this_shape = if internal && !has_defined_this { + TypeId::ANY_TYPE + } else if let Type::Class { type_parameters: Some(type_parameters), .. } = + checking_data.types.get_type_by_id(class_prototype) + { + use source_map::Nullable; + let arguments = crate::types::GenericArguments::ExplicitRestrictions( + type_parameters + .iter() + .map(|id| (*id, (*id, crate::SpanWithSource::NULL))) + .collect(), + ); + checking_data.types.register_type(Type::PartiallyAppliedGenerics( + crate::types::PartiallyAppliedGenerics { on: class_prototype, arguments }, + )) + } else { + class_prototype + }; + let behavior = FunctionRegisterBehavior::ClassMethod { is_async, is_generator, @@ -216,11 +233,7 @@ fn synthesise_class_declaration_extends_and_members< super_type: None, // TODO expecting: TypeId::ANY_TYPE, - this_shape: if internal && !has_defined_this { - TypeId::ANY_TYPE - } else { - class_prototype - }, + this_shape, internal_marker, name: key.into_name_type(&mut checking_data.types), }; @@ -291,7 +304,13 @@ fn synthesise_class_declaration_extends_and_members< ); static_property_keys.push(key); } - ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => { + ClassMember::Indexer { + name: _name, + indexer_type, + return_type, + is_readonly, + position, + } => { // TODO this redoes work done at registration. Because the info gets overwritten let key = synthesise_type_annotation(indexer_type, environment, checking_data); let value = synthesise_type_annotation(return_type, environment, checking_data); @@ -306,7 +325,7 @@ fn synthesise_class_declaration_extends_and_members< // TODO check declare // TODO WIP - crate::utilities::notify!("Indexing (again) for '{}'", name); + // crate::utilities::notify!("Indexing (again) for '{}' {:?}", name, value); let value = PropertyValue::Value(value); environment.info.register_property_on_type( class_type, @@ -367,8 +386,6 @@ fn synthesise_class_declaration_extends_and_members< environment.info.variable_current_value.insert(variable, class_variable_type); } - crate::utilities::notify!("At end {:?}", environment.context_type.free_variables); - { // Static items and blocks static_property_keys.reverse(); @@ -699,7 +716,13 @@ fn register_extends_and_member( ); } } - ClassMember::Indexer { name, indexer_type, return_type, is_readonly, position } => { + ClassMember::Indexer { + name: _name, + indexer_type, + return_type, + is_readonly, + position, + } => { // TODO think this is okay let key = synthesise_type_annotation(indexer_type, environment, checking_data); let value = synthesise_type_annotation(return_type, environment, checking_data); @@ -714,9 +737,8 @@ fn register_extends_and_member( // TODO check declare // TODO WIP - crate::utilities::notify!("Indexing for '{}'", name); + // crate::utilities::notify!("Indexing for '{}' {:?}", name, class_type); let value = PropertyValue::Value(value); - crate::utilities::notify!("{:?}", class_type); environment.info.register_property_on_type( class_type, Publicity::Public, diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index f0dc4f6b..3c40a406 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -1,6 +1,6 @@ use super::{ - Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, Type, TypeId, - TypeStore, + helpers, Constant, Constructor, MathematicalOrBitwiseOperation, PartiallyAppliedGenerics, Type, + TypeId, TypeStore, }; use crate::context::InformationChain; @@ -181,14 +181,18 @@ pub fn types_are_disjoint( .. }) = rhs_ty { - todo!() + let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types); + let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types); + lhs.is_disjoint(&rhs) } else if let Type::Constructor(Constructor::BinaryOperator { operator: MathematicalOrBitwiseOperation::Add, result: TypeId::STRING_TYPE, .. }) = lhs_ty { - todo!() + let lhs = helpers::TemplatelLiteralExpansion::from_type(lhs, types); + let rhs = helpers::TemplatelLiteralExpansion::from_type(rhs, types); + lhs.is_disjoint(&rhs) } else if let Some(lhs) = super::get_constraint(lhs, types) { // TODO not sure whether these should be here? types_are_disjoint(lhs, rhs, already_checked, information, types) diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs index e4aa5166..feb20b72 100644 --- a/checker/src/types/helpers.rs +++ b/checker/src/types/helpers.rs @@ -382,3 +382,84 @@ pub fn get_constraint_or_alias(on: TypeId, types: &TypeStore) -> Option _ => None, } } + +#[derive(Debug)] +pub struct TemplatelLiteralExpansion { + pub parts: Vec<(String, TypeId)>, + pub rest: String, +} + +impl TemplatelLiteralExpansion { + // pub const EMPTY: TemplatelLiteralExpansion = + // TemplatelLiteralExpansion { parts: Vec::new(), rest: String::default() }; + + /// TODO more, maybe involving types + pub fn is_disjoint(&self, other: &Self) -> bool { + crate::utilities::notify!("{:?}", (self, other)); + if let (Some((lhs, _)), Some((rhs, _))) = (self.parts.first(), other.parts.first()) { + let prefix_length = std::cmp::min(lhs.len(), rhs.len()); + if &lhs[..prefix_length] != &rhs[..prefix_length] { + return true; + } + } + + let postfix_len = std::cmp::min(self.rest.len(), other.rest.len()); + if &self.rest[..postfix_len] != &other.rest[..postfix_len] { + true + } else { + false + } + } + + pub fn as_single_string(&self) -> Option<&str> { + if self.parts.is_empty() { + Some(&self.rest) + } else { + None + } + } + + pub fn is_empty(&self) -> bool { + self.parts.is_empty() && self.rest.is_empty() + } + + pub fn concatenate(self, mut other: Self) -> Self { + let mut parts: Vec<(String, TypeId)> = self.parts; + let rest = if other.parts.is_empty() { + format!("{}{}", self.rest, other.rest) + } else { + other.parts[0].0.insert_str(0, &self.rest); + parts.append(&mut other.parts); + other.rest + }; + Self { parts, rest } + } + + /// Forms a TL no matter what + pub fn from_type(id: TypeId, types: &TypeStore) -> Self { + let ty = types.get_type_by_id(id); + if let Type::Constant(constant) = ty { + TemplatelLiteralExpansion { + parts: Default::default(), + rest: constant.as_js_string().into_owned(), + } + } else if let Type::Constructor(Constructor::BinaryOperator { + lhs, + operator: MathematicalOrBitwiseOperation::Add, + rhs, + result: TypeId::STRING_TYPE, + }) = ty + { + let lhs = Self::from_type(*lhs, types); + let rhs = Self::from_type(*rhs, types); + lhs.concatenate(rhs) + } else if let Some(base) = get_constraint_or_alias(id, types) { + Self::from_type(base, types) + } else { + TemplatelLiteralExpansion { + parts: vec![(Default::default(), id)], + rest: Default::default(), + } + } + } +} diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index 52cec855..50ca71db 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -410,7 +410,7 @@ pub fn get_range_and_mod_class( } else if let Type::Constant(Constant::Number(num)) = types.get_type_by_id(ty) { (Some(FloatRange::new_single(*num)), None) } else { - crate::utilities::notify!("Not interesting or constant {:?}", ty); + // crate::utilities::notify!("Not interesting or constant {:?}", ty); (None, None) } } diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 2d012a91..18e93df2 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -58,24 +58,6 @@ pub fn print_type_with_type_arguments( buf } -pub fn print_inner_template_literal_type_into_buf( - ty: TypeId, - buf: &mut String, - cycles: &mut HashSet, - args: GenericChain, - types: &TypeStore, - info: &C, - debug: bool, -) { - if let Type::Constant(cst) = types.get_type_by_id(ty) { - buf.push_str(&cst.as_js_string()); - } else { - buf.push_str("${"); - print_type_into_buf(ty, buf, cycles, args, types, info, debug); - buf.push('}'); - } -} - /// Recursion safe + reuses buffer pub fn print_type_into_buf( ty: TypeId, @@ -125,7 +107,7 @@ pub fn print_type_into_buf( Type::RootPolyType(nature) => match nature { PolyNature::MappedGeneric { name, extends } => { if debug { - write!(buf, "[mg {} {}] ", ty.0, name).unwrap(); + write!(buf, "(mg {} {}) ", ty.0, name).unwrap(); } crate::utilities::notify!("args={:?}", args); if let Some(crate::types::CovariantContribution::String(property)) = @@ -140,13 +122,13 @@ pub fn print_type_into_buf( | PolyNature::StructureGeneric { name, extends: _ } => { if debug { if let PolyNature::FunctionGeneric { .. } = nature { - write!(buf, "[fg {} {}] ", ty.0, name).unwrap(); + write!(buf, "(fg {} {}) ", ty.0, name).unwrap(); } } if let Some(arg) = args.and_then(|args| args.get_argument_covariant(ty)) { use crate::types::CovariantContribution; if debug { - buf.push_str(" (specialised with) "); + buf.push_str("(specialised with) "); } match arg { CovariantContribution::TypeId(id) => { @@ -168,7 +150,7 @@ pub fn print_type_into_buf( if let PolyNature::FunctionGeneric { extends, .. } = nature { print_type_into_buf(*extends, buf, cycles, args, types, info, debug); } else { - write!(buf, "[sg {}] ", ty.0).unwrap(); + write!(buf, "(sg {})", ty.0).unwrap(); } } buf.push_str(name); @@ -176,7 +158,7 @@ pub fn print_type_into_buf( } PolyNature::InferGeneric { name, extends } => { if debug { - write!(buf, "[IG {}] @ ", ty.0).unwrap(); + write!(buf, "(IG {}) @ ", ty.0).unwrap(); } buf.push_str("infer "); buf.push_str(name); @@ -188,19 +170,19 @@ pub fn print_type_into_buf( PolyNature::FreeVariable { based_on: to, .. } => { if debug { // FV = free variable - write!(buf, "[FV {}] @ ", ty.0).unwrap(); + write!(buf, "(FV {}) @ ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } PolyNature::Parameter { fixed_to: to } => { if debug { - write!(buf, "[param {}] @ ", ty.0).unwrap(); + write!(buf, "(param {}) @ ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } PolyNature::Open(to) => { if debug { - write!(buf, "[open {}] ", ty.0).unwrap(); + write!(buf, "(open {}) ", ty.0).unwrap(); } print_type_into_buf(*to, buf, cycles, args, types, info, debug); } @@ -221,7 +203,7 @@ pub fn print_type_into_buf( } PolyNature::CatchVariable(constraint) => { if debug { - write!(buf, "[catch variable {ty:?}] ").unwrap(); + write!(buf, "(CV {ty:?}) ").unwrap(); } print_type_into_buf(*constraint, buf, cycles, args, types, info, debug); } @@ -476,7 +458,7 @@ pub fn print_type_into_buf( } Constructor::Image { on: _, with: _, result } => { // TODO arguments - write!(buf, "[func result {}] (*args*)", ty.0).unwrap(); + write!(buf, "(func result {}) (*args*)", ty.0).unwrap(); buf.push_str(" -> "); print_type_into_buf(*result, buf, cycles, args, types, info, debug); } @@ -508,21 +490,25 @@ pub fn print_type_into_buf( } }, constructor => { - if let Constructor::BinaryOperator { result: result_ty, lhs, rhs, .. } = constructor - { - if *result_ty != TypeId::NUMBER_TYPE - && !matches!( - types.get_type_by_id(*result_ty), - Type::PartiallyAppliedGenerics(_) | Type::RootPolyType(_) - ) { - buf.push('`'); - print_inner_template_literal_type_into_buf( - *lhs, buf, cycles, args, types, info, debug, - ); - print_inner_template_literal_type_into_buf( - *rhs, buf, cycles, args, types, info, debug, - ); - buf.push('`'); + if let Constructor::BinaryOperator { result: result_ty, .. } = constructor { + if let TypeId::STRING_TYPE = *result_ty { + let slice = + crate::types::helpers::TemplatelLiteralExpansion::from_type(ty, types); + if let Some(single) = slice.as_single_string() { + buf.push('"'); + buf.push_str(single); + buf.push('"'); + } else { + buf.push('`'); + for (s, ty) in &slice.parts { + buf.push_str(&s); + buf.push_str("${"); + print_type_into_buf(*ty, buf, cycles, args, types, info, debug); + buf.push('}'); + } + buf.push_str(&slice.rest); + buf.push('`'); + } return; } } @@ -534,12 +520,12 @@ pub fn print_type_into_buf( | Type::Interface { name, parameters: _, .. } | Type::AliasTo { to: _, name, parameters: _ }) => { if debug { - write!(buf, "{name}#{}", ty.0).unwrap(); + write!(buf, "{name}#{} ", ty.0).unwrap(); if let Type::AliasTo { to, .. } = t { - buf.push_str(" = "); + buf.push_str("= "); print_type_into_buf(*to, buf, cycles, args, types, info, debug); } else if let Type::Class { .. } = t { - buf.push_str(" (class)"); + buf.push_str("(class)"); } } else { buf.push_str(name); @@ -587,7 +573,7 @@ pub fn print_type_into_buf( if debug { let kind = if matches!(r#type, Type::FunctionReference(_)) { "ref" } else { "" }; - write!(buf, "[func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap(); + write!(buf, "(func{kind} #{}, kind {:?}, effect ", ty.0, func.behavior).unwrap(); if let FunctionEffect::SideEffects { events: _, free_variables, @@ -605,7 +591,7 @@ pub fn print_type_into_buf( buf.push_str(", this "); print_type_into_buf(*p, buf, cycles, args, types, info, debug); } - buf.push_str("] = "); + buf.push_str(") = "); } if let Some(ref parameters) = func.type_parameters { buf.push('<'); @@ -659,9 +645,9 @@ pub fn print_type_into_buf( Type::Object(kind) => { if debug { if let ObjectNature::RealDeal = kind { - write!(buf, "[obj {}] ", ty.0).unwrap(); + write!(buf, "(obj {}) ", ty.0).unwrap(); } else { - write!(buf, "[aol {}] ", ty.0).unwrap(); + write!(buf, "(anom {}) ", ty.0).unwrap(); } } let prototype = diff --git a/checker/src/types/properties/access.rs b/checker/src/types/properties/access.rs index 39df69c3..6b17a5c7 100644 --- a/checker/src/types/properties/access.rs +++ b/checker/src/types/properties/access.rs @@ -1096,7 +1096,7 @@ fn resolve_property_on_logical( ) } BasedOnKey::Right(property_on) => { - crate::utilities::notify!("{:?}", generics); + crate::utilities::notify!("generics are {:?}", generics); let result = property_on.get_on(generics, environment, types)?; // { diff --git a/checker/src/types/properties/assignment.rs b/checker/src/types/properties/assignment.rs index ee573cbd..14eb510a 100644 --- a/checker/src/types/properties/assignment.rs +++ b/checker/src/types/properties/assignment.rs @@ -130,7 +130,10 @@ pub fn set_property( environment, types, ); + if let SubTypeResult::IsNotSubType(reason) = result { + crate::utilities::notify!("Here {:?} {:?}", property_constraint, new); + let is_modifying_tuple_length = under.is_equal_to("length") && tuple_like(object_constraint, types, environment); @@ -272,6 +275,12 @@ pub fn set_property( ), } } else { + if get_constraint(on, types).is_some() { + return Err(SetPropertyError::AssigningToNonExistent { + property: PropertyKeyRepresentation::new(under, environment, types), + position, + }); + } // Sealed & no extensions check for NEW property (frozen case covered above) { if object_protection.is_some() { diff --git a/checker/src/types/properties/mod.rs b/checker/src/types/properties/mod.rs index 7f1b6060..804c740b 100644 --- a/checker/src/types/properties/mod.rs +++ b/checker/src/types/properties/mod.rs @@ -508,10 +508,10 @@ pub(crate) fn key_matches( ); let contributions = state.contributions.unwrap(); - crate::utilities::notify!( - "Here contributions {:?}", - &contributions.staging_contravariant - ); + // crate::utilities::notify!( + // "Here contributions {:?}", + // &contributions.staging_contravariant + // ); (result.is_subtype(), contributions.staging_contravariant) } diff --git a/checker/src/types/subtyping.rs b/checker/src/types/subtyping.rs index 7af1ac9a..1fe9fa0c 100644 --- a/checker/src/types/subtyping.rs +++ b/checker/src/types/subtyping.rs @@ -272,7 +272,6 @@ pub(crate) fn type_is_subtype_with_generics( return if let (Type::Narrowed { from, .. }, _, true) = (subtype, &result, super::helpers::is_not_of_constant(*right, types)) { - crate::utilities::notify!("Here"); type_is_subtype_with_generics( (base_type, base_type_arguments), (*from, ty_structure_arguments), @@ -392,20 +391,20 @@ pub(crate) fn type_is_subtype_with_generics( // If lhs is not operator unless argument is operator // if !T::INFER_GENERICS && ty_structure_arguments.is_none() { - let right_arg = get_constraint(ty, types).unwrap(); + let right_constraint = get_constraint(ty, types).unwrap(); // crate::utilities::notify!( // "RHS is parameter, edge case results to {:?}", // ( // types.get_type_by_id(ty), - // types.get_type_by_id(right_arg), - // types.get_type_by_id(right_arg).is_operator() + // types.get_type_by_id(right_constraint), + // types.get_type_by_id(right_constraint).is_operator() // ) // ); // This is important that LHS is not operator let left_is_operator_right_is_not = - supertype.is_operator() && !types.get_type_by_id(right_arg).is_operator(); + supertype.is_operator() && !types.get_type_by_id(right_constraint).is_operator(); // edge cases on edge cases // If any of these are true. Then do not perform constraint argument lookup @@ -414,12 +413,14 @@ pub(crate) fn type_is_subtype_with_generics( supertype, Type::RootPolyType(rpt) if rpt.is_substitutable() - ) || matches!(supertype, Type::Constructor(..)); - + ) || matches!(supertype, Type::Constructor(..)) + || base_type_arguments + .and_then(|args| args.get_argument_covariant(base_type)) + .is_some(); if !edge_case { let result = type_is_subtype_with_generics( (base_type, base_type_arguments), - (right_arg, ty_structure_arguments), + (right_constraint, ty_structure_arguments), state, information, types, @@ -459,7 +460,7 @@ pub(crate) fn type_is_subtype_with_generics( } } else { // TODO what about if LHS has inferred constraint - crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype); + // crate::utilities::notify!("Constant {:?} against RHS {:#?}", lhs, subtype); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } @@ -1455,9 +1456,15 @@ pub(crate) fn type_is_subtype_with_generics( if *prototype == base_type { SubTypeResult::IsSubType } else { + crate::utilities::notify!( + "Mismatched prototype {:?} != {:?}", + prototype, + base_type + ); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } else { + crate::utilities::notify!("No prototype"); SubTypeResult::IsNotSubType(NonEqualityReason::Mismatch) } } diff --git a/src/js-cli-and-library/tests/cli-test.mjs b/src/js-cli-and-library/tests/cli-test.mjs deleted file mode 100644 index 17952cdb..00000000 --- a/src/js-cli-and-library/tests/cli-test.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { equal } from "node:assert"; -import { test } from "node:test"; -import { spawn } from "node:child_process"; - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - -function read(child) { - return new Promise((res, rej) => { - child.stdout.on("data", (d) => res(decoder.decode(d))); - child.stderr.on("data", (d) => res(decoder.decode(d))); - }) -} - -function write(child, command) { - const promise = new Promise((res, rej) => { child.stdin.addListener("finish", res) }); - child.stdin.write(command + "\n"); - return promise -} - -// Use Deno as nodejs repl doesn't work at the moment -const child = spawn("deno", ["run", "--allow-read", "./dist/cli.mjs", "repl"]); - -console.dir(await read(child)); -console.dir(await write(child, "print_type(4);")); -console.dir(await read(child)); -console.dir(await write(child, "close()")); - -// test("Parse from CLI", (t) => { -// t.test("temp", async () => { - -// }) -// }) \ No newline at end of file From 8066d2271a509d0ef71973ffb422eeba3599ec1c Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 17:17:23 +0000 Subject: [PATCH 21/24] Fixes for clippy & code in unit test --- checker/src/context/environment.rs | 166 +++++++++++----------- checker/src/context/information.rs | 4 +- checker/src/features/narrowing.rs | 6 +- checker/src/synthesis/expressions.rs | 196 +++++++++++++------------- checker/src/types/disjoint.rs | 4 +- checker/src/types/helpers.rs | 18 ++- checker/src/types/intrinsics.rs | 18 +-- checker/src/types/mod.rs | 1 + checker/src/types/printing.rs | 39 +++-- checker/src/utilities/float_range.rs | 8 ++ checker/src/utilities/modulo_class.rs | 26 +++- src/cli.rs | 4 +- 12 files changed, 250 insertions(+), 240 deletions(-) diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index 6c10c4a2..0d7e5936 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -301,41 +301,38 @@ impl<'a> Environment<'a> { checking_data.options.strict_casts, checking_data.options.advanced_number_intrinsics, ); - match result { - Ok(new) => { - let assignment_position = - assignment_position.with_source(self.get_source()); - - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, - ); - - new - } - Err(()) => { - checking_data.diagnostics_container.add_error( - crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { - operator, - lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( - existing, - self, - &checking_data.types, - false, - ), - rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( - rhs, - self, - &checking_data.types, - false, - ), - position: assignment_position.with_source(self.get_source()), - }, - ); - TypeId::ERROR_TYPE - } + if let Ok(new) = result { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + + new + } else { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + rhs, + self, + &checking_data.types, + false, + ), + position: assignment_position.with_source(self.get_source()), + }, + ); + TypeId::ERROR_TYPE } } AssignmentKind::IncrementOrDecrement(direction, return_kind) => { @@ -365,44 +362,41 @@ impl<'a> Environment<'a> { checking_data.options.strict_casts, checking_data.options.advanced_number_intrinsics, ); - match result { - Ok(new) => { - let assignment_position = - assignment_position.with_source(self.get_source()); - - self.set_reference_handle_errors( - reference, - new, - assignment_position, - checking_data, - ); - - match return_kind { - AssignmentReturnStatus::Previous => existing, - AssignmentReturnStatus::New => new, - } - } - Err(()) => { - checking_data.diagnostics_container.add_error( - crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { - operator, - lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( - existing, - self, - &checking_data.types, - false, - ), - rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( - TypeId::ONE, - self, - &checking_data.types, - false, - ), - position, - }, - ); - TypeId::ERROR_TYPE + if let Ok(new) = result { + let assignment_position = + assignment_position.with_source(self.get_source()); + + self.set_reference_handle_errors( + reference, + new, + assignment_position, + checking_data, + ); + + match return_kind { + AssignmentReturnStatus::Previous => existing, + AssignmentReturnStatus::New => new, } + } else { + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + existing, + self, + &checking_data.types, + false, + ), + rhs: crate::diagnostics::TypeStringRepresentation::from_type_id( + TypeId::ONE, + self, + &checking_data.types, + false, + ), + position, + }, + ); + TypeId::ERROR_TYPE } } AssignmentKind::ConditionalUpdate(operator) => { @@ -966,22 +960,20 @@ impl<'a> Environment<'a> { // }; return Ok(VariableWithValue(og_var.clone(), precise)); + } + + crate::utilities::notify!("Free variable with no current value"); + let constraint = checking_data + .local_type_mappings + .variables_to_constraints + .0 + .get(&og_var.get_origin_variable_id()); + + if let Some(constraint) = constraint { + *constraint } else { - crate::utilities::notify!("Free variable with no current value"); - let constraint = checking_data - .local_type_mappings - .variables_to_constraints - .0 - .get(&og_var.get_origin_variable_id()); - - if let Some(constraint) = constraint { - *constraint - } else { - crate::utilities::notify!( - "TODO record that free variable is `any` here" - ); - TypeId::ANY_TYPE - } + crate::utilities::notify!("TODO record that free variable is `any` here"); + TypeId::ANY_TYPE } } VariableMutability::Mutable { reassignment_constraint } => { diff --git a/checker/src/context/information.rs b/checker/src/context/information.rs index 67ba6901..6f13dcd8 100644 --- a/checker/src/context/information.rs +++ b/checker/src/context/information.rs @@ -420,7 +420,7 @@ pub fn merge_info( // TODO temp fix for `... ? { ... } : { ... }`. // TODO add undefineds to sides etc - for (on, properties) in truthy.current_properties.into_iter() { + for (on, properties) in truthy.current_properties { // let properties = properties // .into_iter() // .map(|(publicity, key, value)| { @@ -447,7 +447,7 @@ pub fn merge_info( } if let Some(otherwise) = otherwise { - for (on, properties) in otherwise.current_properties.into_iter() { + for (on, properties) in otherwise.current_properties { if let Some(existing) = onto.current_properties.get_mut(&on) { existing.extend(properties); } else { diff --git a/checker/src/features/narrowing.rs b/checker/src/features/narrowing.rs index 86f6ca05..52d840be 100644 --- a/checker/src/features/narrowing.rs +++ b/checker/src/features/narrowing.rs @@ -100,7 +100,9 @@ pub fn narrow_based_on_expression( ); // TODO also from == x - 1 etc - let narrowed_to = if rhs != TypeId::ZERO { + let narrowed_to = if rhs == TypeId::ZERO { + narrowed_to + } else { types.register_type(Type::Constructor( crate::types::Constructor::BinaryOperator { lhs: narrowed_to, @@ -109,8 +111,6 @@ pub fn narrow_based_on_expression( result: TypeId::NUMBER_TYPE, }, )) - } else { - narrowed_to }; into.insert(operand, narrowed_to); } else { diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 4b805dea..5c8ea069 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -329,112 +329,106 @@ pub(super) fn synthesise_expression( } return result; - } else { - let lhs_pos = - ASTNode::get_position(&**lhs).with_source(environment.get_source()); - let rhs_pos = - ASTNode::get_position(&**rhs).with_source(environment.get_source()); - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); + } - checking_data.diagnostics_container.add_error( - crate::TypeCheckError::InvalidEqualityOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs_ty, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs_ty, - environment, - &checking_data.types, - false, - ), - position, - }, - ); + let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); - return TypeId::ERROR_TYPE; - } - } else { - let rhs_ty = - synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); - let operator = match operator { - BinaryOperator::Add => MathematicalOrBitwiseOperation::Add, - BinaryOperator::Subtract => MathematicalOrBitwiseOperation::Subtract, - BinaryOperator::Multiply => MathematicalOrBitwiseOperation::Multiply, - BinaryOperator::Divide => MathematicalOrBitwiseOperation::Divide, - BinaryOperator::Modulo => MathematicalOrBitwiseOperation::Modulo, - BinaryOperator::Exponent => MathematicalOrBitwiseOperation::Exponent, - BinaryOperator::BitwiseShiftLeft => { - MathematicalOrBitwiseOperation::BitwiseShiftLeft - } - BinaryOperator::BitwiseShiftRight => { - MathematicalOrBitwiseOperation::BitwiseShiftRight - } - BinaryOperator::BitwiseShiftRightUnsigned => { - MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned - } - BinaryOperator::BitwiseAnd => MathematicalOrBitwiseOperation::BitwiseAnd, - BinaryOperator::BitwiseXOr => MathematicalOrBitwiseOperation::BitwiseXOr, - BinaryOperator::BitwiseOr => MathematicalOrBitwiseOperation::BitwiseOr, - BinaryOperator::Pipe | BinaryOperator::Compose => { - checking_data.raise_unimplemented_error( - "special operations", - position.with_source(environment.get_source()), - ); - return TypeId::UNIMPLEMENTED_ERROR_TYPE; - } - _ => { - unreachable!() - } - }; - let result = evaluate_mathematical_operation( - lhs_ty, - operator, - rhs_ty, - environment, - &mut checking_data.types, - checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.diagnostics_container.add_error( + crate::TypeCheckError::InvalidEqualityOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, ); - match result { - Ok(value) => Instance::RValue(value), - Err(()) => { - let lhs_pos = - ASTNode::get_position(&**lhs).with_source(environment.get_source()); - let rhs_pos = - ASTNode::get_position(&**rhs).with_source(environment.get_source()); - let position = lhs_pos - .without_source() - .union(rhs_pos.without_source()) - .with_source(environment.get_source()); - checking_data.diagnostics_container.add_error( - TypeCheckError::InvalidMathematicalOrBitwiseOperation { - operator, - lhs: TypeStringRepresentation::from_type_id( - lhs_ty, - environment, - &checking_data.types, - false, - ), - rhs: TypeStringRepresentation::from_type_id( - rhs_ty, - environment, - &checking_data.types, - false, - ), - position, - }, - ); - return TypeId::ERROR_TYPE; - } + return TypeId::ERROR_TYPE; + } + + let rhs_ty = synthesise_expression(rhs, environment, checking_data, TypeId::ANY_TYPE); + let operator = match operator { + BinaryOperator::Add => MathematicalOrBitwiseOperation::Add, + BinaryOperator::Subtract => MathematicalOrBitwiseOperation::Subtract, + BinaryOperator::Multiply => MathematicalOrBitwiseOperation::Multiply, + BinaryOperator::Divide => MathematicalOrBitwiseOperation::Divide, + BinaryOperator::Modulo => MathematicalOrBitwiseOperation::Modulo, + BinaryOperator::Exponent => MathematicalOrBitwiseOperation::Exponent, + BinaryOperator::BitwiseShiftLeft => { + MathematicalOrBitwiseOperation::BitwiseShiftLeft + } + BinaryOperator::BitwiseShiftRight => { + MathematicalOrBitwiseOperation::BitwiseShiftRight + } + BinaryOperator::BitwiseShiftRightUnsigned => { + MathematicalOrBitwiseOperation::BitwiseShiftRightUnsigned + } + BinaryOperator::BitwiseAnd => MathematicalOrBitwiseOperation::BitwiseAnd, + BinaryOperator::BitwiseXOr => MathematicalOrBitwiseOperation::BitwiseXOr, + BinaryOperator::BitwiseOr => MathematicalOrBitwiseOperation::BitwiseOr, + BinaryOperator::Pipe | BinaryOperator::Compose => { + checking_data.raise_unimplemented_error( + "special operations", + position.with_source(environment.get_source()), + ); + return TypeId::UNIMPLEMENTED_ERROR_TYPE; } + _ => { + unreachable!() + } + }; + let result = evaluate_mathematical_operation( + lhs_ty, + operator, + rhs_ty, + environment, + &mut checking_data.types, + checking_data.options.strict_casts, + checking_data.options.advanced_number_intrinsics, + ); + if let Ok(value) = result { + Instance::RValue(value) + } else { + let lhs_pos = ASTNode::get_position(&**lhs).with_source(environment.get_source()); + let rhs_pos = ASTNode::get_position(&**rhs).with_source(environment.get_source()); + let position = lhs_pos + .without_source() + .union(rhs_pos.without_source()) + .with_source(environment.get_source()); + + checking_data.diagnostics_container.add_error( + TypeCheckError::InvalidMathematicalOrBitwiseOperation { + operator, + lhs: TypeStringRepresentation::from_type_id( + lhs_ty, + environment, + &checking_data.types, + false, + ), + rhs: TypeStringRepresentation::from_type_id( + rhs_ty, + environment, + &checking_data.types, + false, + ), + position, + }, + ); + return TypeId::ERROR_TYPE; } } Expression::UnaryOperation { operand, operator, position } => { diff --git a/checker/src/types/disjoint.rs b/checker/src/types/disjoint.rs index 3c40a406..9e088ea6 100644 --- a/checker/src/types/disjoint.rs +++ b/checker/src/types/disjoint.rs @@ -247,9 +247,7 @@ fn number_modulo_disjoint( let argument = types.get_type_by_id(arguments.get_structure_restriction(TypeId::NUMBER_GENERIC).unwrap()); - let argument = if let Type::Constant(Constant::Number(argument)) = argument { - argument - } else { + let Type::Constant(Constant::Number(argument)) = argument else { crate::utilities::notify!("Gets complex here"); return false; }; diff --git a/checker/src/types/helpers.rs b/checker/src/types/helpers.rs index feb20b72..f8b6ab18 100644 --- a/checker/src/types/helpers.rs +++ b/checker/src/types/helpers.rs @@ -282,6 +282,7 @@ pub fn simple_subtype( } // unfolds narrowing +#[must_use] pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId { if let Type::Narrowed { from, .. } = types.get_type_by_id(ty) { // Hopefully don't have a nested from @@ -292,6 +293,7 @@ pub fn get_origin(ty: TypeId, types: &TypeStore) -> TypeId { } /// Temp fix for equality of narrowing stuff +#[must_use] pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { if let Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on: TypeId::NOT_RESTRICTION, @@ -306,6 +308,7 @@ pub fn is_not_of_constant(ty: TypeId, types: &TypeStore) -> bool { } // TODO narrowed as well +#[must_use] pub fn type_equal(lhs: TypeId, rhs: TypeId, types: &TypeStore) -> bool { if lhs == rhs { true @@ -324,6 +327,7 @@ pub struct AndCondition(pub TypeId); #[derive(Debug)] pub struct OrCase(pub Vec); +#[must_use] pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec { let ty = types.get_type_by_id(id); @@ -347,6 +351,7 @@ pub fn into_conditions(id: TypeId, types: &TypeStore) -> Vec { } } +#[must_use] pub fn into_cases(id: TypeId, types: &TypeStore) -> Vec { let ty = types.get_type_by_id(id); if let Type::Or(lhs, rhs) = ty { @@ -394,23 +399,21 @@ impl TemplatelLiteralExpansion { // TemplatelLiteralExpansion { parts: Vec::new(), rest: String::default() }; /// TODO more, maybe involving types + #[must_use] pub fn is_disjoint(&self, other: &Self) -> bool { crate::utilities::notify!("{:?}", (self, other)); if let (Some((lhs, _)), Some((rhs, _))) = (self.parts.first(), other.parts.first()) { let prefix_length = std::cmp::min(lhs.len(), rhs.len()); - if &lhs[..prefix_length] != &rhs[..prefix_length] { + if lhs[..prefix_length] != rhs[..prefix_length] { return true; } } let postfix_len = std::cmp::min(self.rest.len(), other.rest.len()); - if &self.rest[..postfix_len] != &other.rest[..postfix_len] { - true - } else { - false - } + self.rest[..postfix_len] != other.rest[..postfix_len] } + #[must_use] pub fn as_single_string(&self) -> Option<&str> { if self.parts.is_empty() { Some(&self.rest) @@ -419,10 +422,12 @@ impl TemplatelLiteralExpansion { } } + #[must_use] pub fn is_empty(&self) -> bool { self.parts.is_empty() && self.rest.is_empty() } + #[must_use] pub fn concatenate(self, mut other: Self) -> Self { let mut parts: Vec<(String, TypeId)> = self.parts; let rest = if other.parts.is_empty() { @@ -436,6 +441,7 @@ impl TemplatelLiteralExpansion { } /// Forms a TL no matter what + #[must_use] pub fn from_type(id: TypeId, types: &TypeStore) -> Self { let ty = types.get_type_by_id(id); if let Type::Constant(constant) = ty { diff --git a/checker/src/types/intrinsics.rs b/checker/src/types/intrinsics.rs index 50ca71db..cde00b31 100644 --- a/checker/src/types/intrinsics.rs +++ b/checker/src/types/intrinsics.rs @@ -203,7 +203,9 @@ pub fn modulo_to_type(mod_class: ModuloClass, types: &mut TypeStore) -> TypeId { // TODO skip if infinite let modulo = types.new_constant_type(Constant::Number(mod_class.modulo)); let ty = new_intrinsic(&Intrinsic::MultipleOf, modulo, types); - if mod_class.offset != 0. { + if mod_class.offset == 0. { + ty + } else { let offset = types.new_constant_type(Constant::Number(mod_class.offset)); types.register_type(Type::Constructor(Constructor::BinaryOperator { lhs: ty, @@ -211,8 +213,6 @@ pub fn modulo_to_type(mod_class: ModuloClass, types: &mut TypeStore) -> TypeId { rhs: offset, result: TypeId::NUMBER_TYPE, })) - } else { - ty } } @@ -320,6 +320,7 @@ pub fn new_intrinsic(intrinsic: &Intrinsic, argument: TypeId, types: &mut TypeSt types.register_type(Type::PartiallyAppliedGenerics(PartiallyAppliedGenerics { on, arguments })) } +#[must_use] pub fn get_range_and_mod_class( ty: TypeId, types: &TypeStore, @@ -364,12 +365,11 @@ pub fn get_range_and_mod_class( }); } PureNumberIntrinsic::Modulo { modulo, offset } => { - modulo_class = Some(match modulo_class { - None => ModuloClass::new(modulo, offset), - Some(_) => { - crate::utilities::notify!("TODO intersection"); - return (None, None); - } + modulo_class = Some(if modulo_class.is_none() { + ModuloClass::new(modulo, offset) + } else { + crate::utilities::notify!("TODO intersection"); + return (None, None); }); } } diff --git a/checker/src/types/mod.rs b/checker/src/types/mod.rs index 7164c90c..81dcd86b 100644 --- a/checker/src/types/mod.rs +++ b/checker/src/types/mod.rs @@ -417,6 +417,7 @@ pub enum Constructor { } impl Constructor { + #[must_use] pub fn get_constraint(&self) -> TypeId { match self { Constructor::BinaryOperator { result, .. } diff --git a/checker/src/types/printing.rs b/checker/src/types/printing.rs index 18e93df2..81a2f1ca 100644 --- a/checker/src/types/printing.rs +++ b/checker/src/types/printing.rs @@ -100,7 +100,7 @@ pub fn print_type_into_buf( } Type::Narrowed { narrowed_to, from } => { if debug { - write!(buf, "(narrowed from {:?}) ", from).unwrap(); + write!(buf, "(narrowed from {from:?}) ").unwrap(); } print_type_into_buf(*narrowed_to, buf, cycles, args, types, info, debug); } @@ -490,27 +490,26 @@ pub fn print_type_into_buf( } }, constructor => { - if let Constructor::BinaryOperator { result: result_ty, .. } = constructor { - if let TypeId::STRING_TYPE = *result_ty { - let slice = - crate::types::helpers::TemplatelLiteralExpansion::from_type(ty, types); - if let Some(single) = slice.as_single_string() { - buf.push('"'); - buf.push_str(single); - buf.push('"'); - } else { - buf.push('`'); - for (s, ty) in &slice.parts { - buf.push_str(&s); - buf.push_str("${"); - print_type_into_buf(*ty, buf, cycles, args, types, info, debug); - buf.push('}'); - } - buf.push_str(&slice.rest); - buf.push('`'); + if let Constructor::BinaryOperator { result: TypeId::STRING_TYPE, .. } = constructor + { + let slice = + crate::types::helpers::TemplatelLiteralExpansion::from_type(ty, types); + if let Some(single) = slice.as_single_string() { + buf.push('"'); + buf.push_str(single); + buf.push('"'); + } else { + buf.push('`'); + for (s, ty) in &slice.parts { + buf.push_str(s); + buf.push_str("${"); + print_type_into_buf(*ty, buf, cycles, args, types, info, debug); + buf.push('}'); } - return; + buf.push_str(&slice.rest); + buf.push('`'); } + return; } let base = get_constraint(ty, types).unwrap(); print_type_into_buf(base, buf, cycles, args, types, info, debug); diff --git a/checker/src/utilities/float_range.rs b/checker/src/utilities/float_range.rs index bcc8b9f0..c0870212 100644 --- a/checker/src/utilities/float_range.rs +++ b/checker/src/utilities/float_range.rs @@ -18,6 +18,7 @@ impl InclusiveExclusive { } } + #[must_use] pub fn is_inclusive(self) -> bool { matches!(self, Inclusive) } @@ -45,6 +46,7 @@ impl FloatRange { Self { floor: (Inclusive, on), ceiling: (Inclusive, on) } } + #[must_use] pub fn as_single(self) -> Option { if let FloatRange { floor: (Inclusive, floor), ceiling: (Inclusive, ceiling) } = self { (floor == ceiling).then_some(floor) @@ -53,6 +55,7 @@ impl FloatRange { } } + #[must_use] pub fn new_greater_than(greater_than: BetterF64) -> Self { FloatRange { floor: (Exclusive, greater_than), @@ -60,10 +63,12 @@ impl FloatRange { } } + #[must_use] pub fn get_greater_than(self) -> Option { (self.floor.1 != f64::NEG_INFINITY).then_some(self.floor.1) } + #[must_use] pub fn new_less_than(less_than: BetterF64) -> Self { FloatRange { floor: (Exclusive, f64::NEG_INFINITY.try_into().unwrap()), @@ -71,10 +76,12 @@ impl FloatRange { } } + #[must_use] pub fn get_less_than(self) -> Option { (self.ceiling.1 != f64::INFINITY).then_some(self.ceiling.1) } + #[must_use] pub fn contains(self, value: BetterF64) -> bool { if self.floor.1 < value && value < self.ceiling.1 { true @@ -191,6 +198,7 @@ impl FloatRange { // This will try to get cover // A union like above might create gaps. aka if try_get_cover (0, 1) (3, 4) = (0, 4) then it implies 2 // exists is in one of the ranges. Thus in this case it returns None + #[must_use] pub fn try_get_cover(self, other: Self) -> Option { if self.contained_in(other) { Some(other) diff --git a/checker/src/utilities/modulo_class.rs b/checker/src/utilities/modulo_class.rs index 96bba2fc..e5753d50 100644 --- a/checker/src/utilities/modulo_class.rs +++ b/checker/src/utilities/modulo_class.rs @@ -9,6 +9,7 @@ pub struct ModuloClass { // TODO more operations impl ModuloClass { + #[must_use] pub fn new(modulo: BetterF64, offset: BetterF64) -> Self { debug_assert!(modulo != 0.); if modulo > 0f64.try_into().unwrap() { @@ -20,12 +21,14 @@ impl ModuloClass { } } + #[must_use] pub fn contains(self, value: BetterF64) -> bool { // Note -0. = 0. (value - self.offset) % self.modulo == 0. } /// WIP + #[must_use] pub fn disjoint(self, other: Self) -> bool { if let Ok(gcd) = gcd_of_float(self.modulo, other.modulo) { crate::utilities::notify!("{:?}", gcd); @@ -36,14 +39,17 @@ impl ModuloClass { } } + #[must_use] pub fn intersection(self, _other: Self) -> Option { todo!() } + #[must_use] pub fn get_cover(self, _other: Self) -> Option { todo!() } + #[must_use] pub fn offset(self, offset: BetterF64) -> Self { // TODO temp fix if self.is_default() { @@ -53,6 +59,7 @@ impl ModuloClass { } } + #[must_use] pub fn multiply(self, multiple: BetterF64) -> Self { // TODO temp fix if self.is_default() { @@ -62,6 +69,7 @@ impl ModuloClass { } } + #[must_use] pub fn negate(self) -> Self { // TODO temp fix if self.is_default() { @@ -71,6 +79,7 @@ impl ModuloClass { } } + #[must_use] pub fn is_default(self) -> bool { self.modulo == f64::EPSILON } @@ -92,17 +101,17 @@ fn try_get_numerator_denominator(input: BetterF64) -> Result<(i32, i32), ()> { let (mut a, mut b, mut c, mut d) = (0, 1, 1, 1); for _ in 0..STEPS { - let mediant_float = (a as f64 + b as f64) / (c as f64 + d as f64); + let mediant_float = (f64::from(a) + f64::from(b)) / (f64::from(c) + f64::from(d)); if (fractional_part - mediant_float).abs() < MARGIN { let numerator = a + b + integer_part * (c + d); let denominator = c + d; return Ok((numerator, denominator)); } else if fractional_part > mediant_float { - a = a + b; - c = c + d; + a += b; + c += d; } else { - b = a + b; - d = c + d; + b += a; + d += c; } } @@ -128,7 +137,7 @@ fn gcd_of_float(n1: BetterF64, n2: BetterF64) -> Result { let (c, d) = try_get_numerator_denominator(n2)?; // gcd(a / b, c / d) = gcd(a, c) / lcm(b, d) - Ok(BetterF64::new(gcd(a, c) as f64 / lcm(b, d) as f64).unwrap()) + Ok(BetterF64::new(f64::from(gcd(a, c)) / f64::from(lcm(b, d))).unwrap()) } // hmmm @@ -145,6 +154,9 @@ mod tests { // TODO test negatives etc #[test] fn gcd() { - assert_eq!(gcd_of_float(1. / 3., 3. / 2.), Ok(3.)); + assert_eq!( + gcd_of_float(BetterF64::new(1. / 3.).unwrap(), BetterF64::new(3. / 2.).unwrap()), + Ok(BetterF64::new(0.16666666666666666).unwrap()) + ); } } diff --git a/src/cli.rs b/src/cli.rs index a62dab08..53448b6c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -293,7 +293,7 @@ pub fn run_cli( new_debouncer(Duration::from_millis(200), None, tx).unwrap(); for e in &entry_points { - _ = debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap(); + debouncer.watcher().watch(e, notify::RecursiveMode::Recursive).unwrap(); } let _ = run_checker( @@ -359,7 +359,7 @@ pub fn run_cli( source_maps: build_config.source_maps, type_definition_module: build_config.definition_file, // TODO not sure - output_path: PathBuf::from(output_path), + output_path, other_transformers: None, lsp_mode: false, }; From 956940a5b01699b93f85efcc717287fa5edafd1d Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 17:48:26 +0000 Subject: [PATCH 22/24] Fixes - add number_intrinsics options (+ rename) - fix typo in specification --- checker/examples/run_checker.rs | 4 ++-- checker/specification/specification.md | 8 ++++---- checker/src/context/environment.rs | 4 ++-- checker/src/features/template_literal.rs | 6 +++--- checker/src/options.rs | 4 ++-- checker/src/synthesis/expressions.rs | 2 +- checker/src/types/generics/substitution.rs | 4 ++-- parser/examples/code_blocks_to_script.rs | 7 ++++++- src/cli.rs | 10 +++++++++- 9 files changed, 31 insertions(+), 18 deletions(-) diff --git a/checker/examples/run_checker.rs b/checker/examples/run_checker.rs index 74581100..8c54fb7e 100644 --- a/checker/examples/run_checker.rs +++ b/checker/examples/run_checker.rs @@ -20,7 +20,7 @@ fn main() { let no_lib = args.iter().any(|item| item == "--no-lib"); let debug_dts = args.iter().any(|item| item == "--debug-dts"); let extras = args.iter().any(|item| item == "--extras"); - let advanced_number_intrinsics = args.iter().any(|item| item == "--advanced-numbers"); + let advanced_numbers = args.iter().any(|item| item == "--advanced-numbers"); let now = Instant::now(); @@ -47,7 +47,7 @@ fn main() { max_inline_count: 600, debug_dts, extra_syntax: extras, - advanced_number_intrinsics, + advanced_numbers, ..Default::default() }; diff --git a/checker/specification/specification.md b/checker/specification/specification.md index aa0f5b63..7e2841af 100644 --- a/checker/specification/specification.md +++ b/checker/specification/specification.md @@ -2541,7 +2541,7 @@ function func(a: number) { } ``` -With advanced_number_intrinsics +With advanced_numbers - Expected null, found GreaterThan<-5> & LessThan<5> | -5 | 5 - Expected string, found GreaterThan<18> & LessThan<22> | 18 | 22 @@ -2564,7 +2564,7 @@ function func1(a: number, b: number) { } ``` -With advanced_number_intrinsics +With advanced_numbers - This equality is always false as MultipleOf<10> and GreaterThan<31> & LessThan<37> have no overlap @@ -2598,10 +2598,10 @@ function func(param: boolean) { return value + 1; } -func statisfies string; +func satisfies string; ``` -With advanced_number_intrinsics +With advanced_numbers - Expected string, found (param: boolean) => 2 | 3 diff --git a/checker/src/context/environment.rs b/checker/src/context/environment.rs index 0d7e5936..2c5decc7 100644 --- a/checker/src/context/environment.rs +++ b/checker/src/context/environment.rs @@ -299,7 +299,7 @@ impl<'a> Environment<'a> { self, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(new) = result { let assignment_position = @@ -360,7 +360,7 @@ impl<'a> Environment<'a> { self, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(new) = result { let assignment_position = diff --git a/checker/src/features/template_literal.rs b/checker/src/features/template_literal.rs index 69a99485..ab68d775 100644 --- a/checker/src/features/template_literal.rs +++ b/checker/src/features/template_literal.rs @@ -149,7 +149,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { acc = result; @@ -170,7 +170,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { acc = result; @@ -191,7 +191,7 @@ where environment, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(result) = result { result diff --git a/checker/src/options.rs b/checker/src/options.rs index 5592f4a4..19fc0ef5 100644 --- a/checker/src/options.rs +++ b/checker/src/options.rs @@ -57,7 +57,7 @@ pub struct TypeCheckOptions { /// Enables two things: /// - range and modulo class inference /// - modifications to ranges and classes based on operations - pub advanced_number_intrinsics: bool, + pub advanced_numbers: bool, /// Printing internal diagnostics in dts pub debug_dts: bool, @@ -84,7 +84,7 @@ impl Default for TypeCheckOptions { measure_time: false, debug_dts: false, extra_syntax: true, - advanced_number_intrinsics: false, + advanced_numbers: false, } } } diff --git a/checker/src/synthesis/expressions.rs b/checker/src/synthesis/expressions.rs index 5c8ea069..b7ab5cfa 100644 --- a/checker/src/synthesis/expressions.rs +++ b/checker/src/synthesis/expressions.rs @@ -398,7 +398,7 @@ pub(super) fn synthesise_expression( environment, &mut checking_data.types, checking_data.options.strict_casts, - checking_data.options.advanced_number_intrinsics, + checking_data.options.advanced_numbers, ); if let Ok(value) = result { Instance::RValue(value) diff --git a/checker/src/types/generics/substitution.rs b/checker/src/types/generics/substitution.rs index 62a2cd61..1a6c6900 100644 --- a/checker/src/types/generics/substitution.rs +++ b/checker/src/types/generics/substitution.rs @@ -238,7 +238,7 @@ pub(crate) fn substitute( let rhs = substitute(rhs, arguments, environment, types); // TODO - let advanced_number_intrinsics = false; + let advanced_numbers = false; match evaluate_mathematical_operation( lhs, @@ -247,7 +247,7 @@ pub(crate) fn substitute( environment, types, false, - advanced_number_intrinsics, + advanced_numbers, ) { Ok(result) => result, Err(()) => { diff --git a/parser/examples/code_blocks_to_script.rs b/parser/examples/code_blocks_to_script.rs index 58e9a179..4abf44f4 100644 --- a/parser/examples/code_blocks_to_script.rs +++ b/parser/examples/code_blocks_to_script.rs @@ -104,7 +104,12 @@ fn main() -> Result<(), Box> { let mut final_blocks: Vec<(HashSet, String)> = Vec::new(); for (header, mut code) in blocks { // TODO clone - let module = Module::from_string(code.clone(), Default::default()).map_err(Box::new)?; + let module = match Module::from_string(code.clone(), Default::default()) { + Ok(module) => module, + Err(err) => { + return Err(From::from(format!("Parse error on {code}: {err:?}"))); + } + }; let mut names = HashSet::new(); diff --git a/src/cli.rs b/src/cli.rs index 53448b6c..fbdd1de4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -121,6 +121,9 @@ pub(crate) struct CheckArguments { /// compact diagnostics #[argh(switch)] pub compact_diagnostics: bool, + /// more behavior for numbers + #[argh(switch)] + pub advanced_numbers: bool, /// maximum diagnostics to print (defaults to 30, pass `all` for all and `0` to count) #[argh(option, default = "MaxDiagnostics::default()")] pub max_diagnostics: MaxDiagnostics, @@ -262,12 +265,17 @@ pub fn run_cli( timings, compact_diagnostics, max_diagnostics, + advanced_numbers, } = check_arguments; let type_check_options: TypeCheckOptions = if cfg!(target_family = "wasm") { Default::default() } else { - TypeCheckOptions { measure_time: timings, ..TypeCheckOptions::default() } + TypeCheckOptions { + measure_time: timings, + advanced_numbers, + ..TypeCheckOptions::default() + } }; let entry_points = match get_entry_points(input) { From 70433c3c0840a9d9bd8b512c4e594d9f5c0ced60 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 19:19:30 +0000 Subject: [PATCH 23/24] Fixes to workflows --- .github/workflows/performance-and-size.yml | 32 ++++++++++++---------- .github/workflows/rust.yml | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/performance-and-size.yml b/.github/workflows/performance-and-size.yml index 6d5dcdb6..e248bd27 100644 --- a/.github/workflows/performance-and-size.yml +++ b/.github/workflows/performance-and-size.yml @@ -37,13 +37,15 @@ jobs: env: CARGO_PROFILE_RELEASE_DEBUG: true - - name: Get base ezno - if: github.ref_name != 'main' - uses: actions/download-artifact@v4 - continue-on-error: true - with: - name: latest-checker - path: previous-ezno + # TODO need to lookup existing workflow on main + # even if this worked, it might have issues with newer features added in this run + # - name: Get base ezno + # if: github.ref_name != 'main' + # uses: actions/download-artifact@v4 + # continue-on-error: true + # with: + # name: latest-checker + # path: previous-ezno - name: Set compilers id: compilers @@ -197,16 +199,16 @@ jobs: curl -sS $url > input.js echo "::group::Comparison" - hyperfine \ + hyperfine -i \ -L compiler ${{ steps.compilers.outputs.BINARIES }} \ '{compiler} ast-explorer full input.js --timings' echo "::endgroup::" done - - name: Upload checker - if: github.ref == 'main' - uses: actions/upload-artifact@v4 - with: - name: latest-checker - path: target/release/ezno - retention-days: 90 + # - name: Upload checker + # if: github.ref == 'main' + # uses: actions/upload-artifact@v4 + # with: + # name: latest-checker + # path: target/release/ezno + # retention-days: 90 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e45c83c5..c6e1b695 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -95,7 +95,7 @@ jobs: - name: Run checker specification if: (steps.changes.outputs.checker == 'true' && github.event_name != 'pull_request') || github.ref_name == 'main' - run: cargo test + run: cargo test -p ezno-checker-specification - name: Run checker specification (w/ staging) if: steps.changes.outputs.checker == 'true' && github.event_name == 'pull_request' From 98872dd743884571e9e6899b7661e4e6aa9fd2b3 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Nov 2024 19:39:38 +0000 Subject: [PATCH 24/24] Last minute fix for playground --- src/playground/main.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/playground/main.js b/src/playground/main.js index 9a7065b5..0aa7f379 100644 --- a/src/playground/main.js +++ b/src/playground/main.js @@ -5,7 +5,7 @@ import { defaultKeymap, indentWithTab, toggleLineComment } from "@codemirror/com import { parser as jsParser } from "@lezer/javascript"; import { tags } from "@lezer/highlight"; import { HighlightStyle, syntaxHighlighting, LanguageSupport, LRLanguage } from "@codemirror/language"; -import { init as init_ezno, check_with_options, get_version } from "ezno"; +import { init as init_ezno, check, get_version } from "ezno"; const diagnosticsEntry = document.querySelector(".diagnostics"); const editorParent = document.querySelector("#editor"); @@ -65,6 +65,14 @@ if (id) { let currentState = null; const ROOT_PATH = "index.tsx"; +const options = { + // Allow partial syntax + lsp_mode: true, + // For hover + store_type_mappings: true, + // For showing off + number_intrinsics: true +}; async function setup() { await init_ezno(); @@ -74,7 +82,7 @@ async function setup() { text = args.state.doc.text.join("\n"); try { const start = performance.now(); - currentState = check_with_options(ROOT_PATH, (_) => text, { lsp_mode: true, store_type_mappings: true }); + currentState = check(ROOT_PATH, (_) => text, options); const elapsed = performance.now() - start; timeOutput.innerText = `Parsed & checked in ${Math.trunc(elapsed)}ms`;