From fff227f3aed38dacf671532d9a1daf8650d72904 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sat, 11 May 2024 21:12:49 +0800 Subject: [PATCH] dev: interning types (#271) * refactor: a bit * fix: named completion * dev: replace complete_literal by complete_type * dev: remove unused code * dev: basic interner * dev: basic types * dev: type operations * dev: migrate all type definitions * dev: check syntax and builtin types * dev: make TypeSimplifier simply work * dev: make TypeDescriber simply work * dev: make TypeChecker simply work * dev: recover type check * fix: context check * fix: use after free in seen fields * fix: typed with * fix: record type on field * dev: check type of constructors and element containing * dev: show sig by type * fix: mixed context checking * QAQ * >_< * dev: fix documents --- Cargo.lock | 9 + Cargo.toml | 6 + crates/tinymist-query/Cargo.toml | 5 + crates/tinymist-query/src/adt/interner.rs | 283 +++ crates/tinymist-query/src/adt/mod.rs | 1 + crates/tinymist-query/src/analysis.rs | 8 +- crates/tinymist-query/src/analysis/call.rs | 5 +- crates/tinymist-query/src/analysis/global.rs | 44 +- .../tinymist-query/src/analysis/signature.rs | 75 +- crates/tinymist-query/src/analysis/ty.rs | 1883 ++--------------- .../tinymist-query/src/analysis/ty/apply.rs | 112 + crates/tinymist-query/src/analysis/ty/def.rs | 511 ----- .../src/analysis/ty/post_check.rs | 400 ++-- .../tinymist-query/src/analysis/ty/syntax.rs | 586 +++++ crates/tinymist-query/src/completion.rs | 27 +- .../call_info/snaps/test@builtin.typ.snap | 2 +- .../snaps/test@builtin_poly.typ.snap | 6 +- .../snaps/test@builtin_poly2.typ.snap | 2 +- .../call_info/snaps/test@user.typ.snap | 4 +- .../call_info/snaps/test@user_named.typ.snap | 4 +- .../snaps/test@user_named_with.typ.snap | 2 +- .../snaps/test@user_named_with2.typ.snap | 2 +- .../call_info/snaps/test@user_with.typ.snap | 2 +- .../completion/bug_mix_context_type.typ | 6 + .../src/fixtures/completion/sig_dict.typ | 3 + .../src/fixtures/completion/sig_dict_rest.typ | 3 + .../snaps/test@bug_mix_context_type.typ.snap | 12 + .../snaps/test@func_args_after.typ.snap | 2 +- .../completion/snaps/test@sig_dict.typ.snap | 49 + .../snaps/test@sig_dict_rest.typ.snap | 31 + .../src/fixtures/playground/snaps/test.snap | 7 - .../snaps/test@text_font2.typ.snap | 2 +- .../snaps/test@text_font3.typ.snap | 2 +- .../snaps/test@text_font4.typ.snap | 2 +- .../snaps/test@text_font5.typ.snap | 2 +- .../snaps/test@text_font_element.typ.snap | 2 +- .../snaps/test@text_font_element2.typ.snap | 2 +- .../snaps/test@text_font_element3.typ.snap | 2 +- .../snaps/test@text_font_element4.typ.snap | 2 +- .../snaps/test@text_stroke.typ.snap | 4 +- .../snaps/test@text_stroke1.typ.snap | 2 +- .../snaps/test@text_stroke2.typ.snap | 2 +- .../snaps/test@text_stroke3.typ.snap | 2 +- .../snaps/test@text_stroke4.typ.snap | 2 +- .../snaps/test@user_external.typ.snap | 2 +- .../snaps/test@user_external_alias.typ.snap | 2 +- .../snaps/test@user_external_ever.typ.snap | 2 +- .../snaps/test@user_func.typ.snap | 2 +- .../snaps/test@user_func_pos.typ.snap | 7 + .../snaps/test@user_func_pos2.typ.snap | 7 + .../snaps/test@user_named.typ.snap | 2 +- .../snaps/test@with_builtin.typ.snap | 7 + .../snaps/test@with_element.typ.snap | 7 + .../snaps/test@with_user_func.typ.snap | 7 + .../post_type_check/user_func_pos.typ | 4 + .../post_type_check/user_func_pos2.typ | 4 + .../fixtures/post_type_check/with_builtin.typ | 1 + .../fixtures/post_type_check/with_element.typ | 1 + .../post_type_check/with_user_func.typ | 2 + .../src/fixtures/type_check/op_contains.typ | 3 + .../fixtures/type_check/op_contains_str.typ | 3 + .../src/fixtures/type_check/op_type_of.typ | 3 + .../type_check/snaps/test@base.typ.snap | 2 +- .../type_check/snaps/test@constants.typ.snap | 2 +- .../snaps/test@control_flow.typ.snap | 7 +- .../type_check/snaps/test@external.typ.snap | 6 +- .../type_check/snaps/test@infer.typ.snap | 6 - .../type_check/snaps/test@infer2.typ.snap | 61 +- .../snaps/test@infer_stroke_dict.typ.snap | 6 - .../snaps/test@op_contains.typ.snap | 13 + .../snaps/test@op_contains_str.typ.snap | 13 + .../type_check/snaps/test@op_type_of.typ.snap | 15 + .../type_check/snaps/test@recursive.typ.snap | 4 +- .../snaps/test@recursive_use.typ.snap | 4 +- .../type_check/snaps/test@set_font.typ.snap | 3 +- .../snaps/test@sig_template.typ.snap | 2 +- .../snaps/test@sig_template_set.typ.snap | 13 +- .../type_check/snaps/test@text_font.typ.snap | 17 +- .../type_check/snaps/test@with.typ.snap | 10 +- crates/tinymist-query/src/lib.rs | 1 + crates/tinymist-query/src/signature_help.rs | 39 +- crates/tinymist-query/src/syntax/matcher.rs | 49 +- crates/tinymist-query/src/ty/apply.rs | 65 + crates/tinymist-query/src/ty/bound.rs | 68 + .../src/{analysis => }/ty/builtin.rs | 260 ++- crates/tinymist-query/src/ty/def.rs | 931 ++++++++ crates/tinymist-query/src/ty/describe.rs | 190 ++ crates/tinymist-query/src/ty/mod.rs | 14 + crates/tinymist-query/src/ty/sig.rs | 307 +++ crates/tinymist-query/src/ty/simplify.rs | 304 +++ crates/tinymist-query/src/ty/subst.rs | 55 + .../tinymist-query/src/upstream/complete.rs | 9 +- .../src/upstream/complete/ext.rs | 387 ++-- tests/e2e/main.rs | 4 +- 94 files changed, 4011 insertions(+), 3042 deletions(-) create mode 100644 crates/tinymist-query/src/adt/interner.rs create mode 100644 crates/tinymist-query/src/analysis/ty/apply.rs delete mode 100644 crates/tinymist-query/src/analysis/ty/def.rs create mode 100644 crates/tinymist-query/src/analysis/ty/syntax.rs create mode 100644 crates/tinymist-query/src/fixtures/completion/bug_mix_context_type.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/sig_dict.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/sig_dict_rest.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@bug_mix_context_type.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict_rest.typ.snap delete mode 100644 crates/tinymist-query/src/fixtures/playground/snaps/test.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos2.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_builtin.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_element.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_user_func.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/user_func_pos.typ create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/user_func_pos2.typ create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/with_builtin.typ create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/with_element.typ create mode 100644 crates/tinymist-query/src/fixtures/post_type_check/with_user_func.typ create mode 100644 crates/tinymist-query/src/fixtures/type_check/op_contains.typ create mode 100644 crates/tinymist-query/src/fixtures/type_check/op_contains_str.typ create mode 100644 crates/tinymist-query/src/fixtures/type_check/op_type_of.typ create mode 100644 crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains_str.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/type_check/snaps/test@op_type_of.typ.snap create mode 100644 crates/tinymist-query/src/ty/apply.rs create mode 100644 crates/tinymist-query/src/ty/bound.rs rename crates/tinymist-query/src/{analysis => }/ty/builtin.rs (60%) create mode 100644 crates/tinymist-query/src/ty/def.rs create mode 100644 crates/tinymist-query/src/ty/describe.rs create mode 100644 crates/tinymist-query/src/ty/mod.rs create mode 100644 crates/tinymist-query/src/ty/sig.rs create mode 100644 crates/tinymist-query/src/ty/simplify.rs create mode 100644 crates/tinymist-query/src/ty/subst.rs diff --git a/Cargo.lock b/Cargo.lock index d790ac7e..1002441c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3871,9 +3871,11 @@ dependencies = [ "anyhow", "biblatex", "comemo 0.4.0", + "dashmap", "ecow 0.2.2", "ena", "fxhash", + "hashbrown 0.14.3", "hex", "if_chain", "indexmap 2.2.6", @@ -3896,6 +3898,7 @@ dependencies = [ "siphasher 1.0.1", "strum 0.26.2", "toml 0.8.12", + "triomphe", "ttf-parser", "typst", "typst-ts-compiler", @@ -4114,6 +4117,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "triomphe" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index be0ba0e2..0ed15f80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,12 @@ serde_json = "1" serde_yaml = "0.9" yaml-rust2 = "0.8" biblatex = "0.9" +# We need to freeze the version of the crate, as the raw-api feature is considered unstable +dashmap = { version = "=5.5.3", features = ["raw-api"] } +triomphe = { version = "0.1.10", default-features = false, features = ["std"] } +hashbrown = { version = "0.14", features = [ + "inline-more", +], default-features = false } divan = "0.1.14" insta = { version = "1.36", features = ["glob"] } diff --git a/crates/tinymist-query/Cargo.toml b/crates/tinymist-query/Cargo.toml index 706e7c00..34d1c2f1 100644 --- a/crates/tinymist-query/Cargo.toml +++ b/crates/tinymist-query/Cargo.toml @@ -46,6 +46,11 @@ ttf-parser = "0.20.0" rust_iso639 = "0.0.1" rust_iso3166 = "0.1.4" +# We need to freeze the version of the crate, as the raw-api feature is considered unstable +dashmap.workspace = true +hashbrown.workspace = true +triomphe.workspace = true + [dev-dependencies] once_cell.workspace = true insta.workspace = true diff --git a/crates/tinymist-query/src/adt/interner.rs b/crates/tinymist-query/src/adt/interner.rs new file mode 100644 index 00000000..fa28df00 --- /dev/null +++ b/crates/tinymist-query/src/adt/interner.rs @@ -0,0 +1,283 @@ +//! Global `Arc`-based object interning infrastructure. +//! +//! Eventually this should probably be replaced with salsa-based interning. +//! +//! todo: This is less efficient as the arc object will change its reference +//! count every time it is cloned. todo: we may be able to optimize use by +//! following approach: +//! ```plain +//! fn run_analyze(f) { +//! let local = thread_local_intern(); +//! let res = f(local); +//! std::thread::spawn(move || gc(local)); +//! return res +//! } +//! ``` +//! However, this is out of scope for now. + +use std::{ + fmt::{self, Debug, Display}, + hash::{BuildHasherDefault, Hash, Hasher}, + ops::Deref, + sync::OnceLock, +}; + +use dashmap::{DashMap, SharedValue}; +use ecow::EcoString; +use fxhash::FxHasher; +use hashbrown::{hash_map::RawEntryMut, HashMap}; +use triomphe::Arc; + +type InternMap = DashMap, (), BuildHasherDefault>; +type Guard = dashmap::RwLockWriteGuard< + 'static, + HashMap, SharedValue<()>, BuildHasherDefault>, +>; + +pub struct Interned { + arc: Arc, +} + +impl Interned { + pub fn new(obj: T) -> Self { + let (mut shard, hash) = Self::select(&obj); + // Atomically, + // - check if `obj` is already in the map + // - if so, clone its `Arc` and return it + // - if not, box it up, insert it, and return a clone + // This needs to be atomic (locking the shard) to avoid races with other thread, + // which could insert the same object between us looking it up and + // inserting it. + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, &obj) { + RawEntryMut::Occupied(occ) => Self { + arc: occ.key().clone(), + }, + RawEntryMut::Vacant(vac) => Self { + arc: vac + .insert_hashed_nocheck(hash, Arc::new(obj), SharedValue::new(())) + .0 + .clone(), + }, + } + } +} + +// Note: It is dangerous to keep interned object temporarily (u128) +// Case: +// ``` +// insert(hash(Interned::new_str("a"))) == true +// insert(hash(Interned::new_str("a"))) == true +// ``` +impl Interned { + pub fn new_str(s: &str) -> Self { + let (mut shard, hash) = Self::select(s); + // Atomically, + // - check if `obj` is already in the map + // - if so, clone its `Arc` and return it + // - if not, box it up, insert it, and return a clone + // This needs to be atomic (locking the shard) to avoid races with other thread, + // which could insert the same object between us looking it up and + // inserting it. + match shard.raw_entry_mut().from_key_hashed_nocheck(hash, s) { + RawEntryMut::Occupied(occ) => Self { + arc: occ.key().clone(), + }, + RawEntryMut::Vacant(vac) => Self { + arc: vac + .insert_hashed_nocheck(hash, Arc::from(s), SharedValue::new(())) + .0 + .clone(), + }, + } + } +} + +impl From<&str> for Interned { + fn from(s: &str) -> Self { + Interned::new_str(s) + } +} + +impl From<&EcoString> for Interned { + fn from(s: &EcoString) -> Self { + Interned::new_str(s) + } +} +impl From<&Interned> for EcoString { + fn from(s: &Interned) -> Self { + s.as_ref().into() + } +} + +impl Interned { + #[inline] + fn select(obj: &T) -> (Guard, u64) { + let storage = T::storage().get(); + let hash = { + let mut hasher = std::hash::BuildHasher::build_hasher(storage.hasher()); + obj.hash(&mut hasher); + hasher.finish() + }; + let shard_idx = storage.determine_shard(hash as usize); + let shard = &storage.shards()[shard_idx]; + (shard.write(), hash) + } +} + +impl Drop for Interned { + #[inline] + fn drop(&mut self) { + // When the last `Ref` is dropped, remove the object from the global map. + if Arc::count(&self.arc) == 2 { + // Only `self` and the global map point to the object. + + self.drop_slow(); + } + } +} + +impl Interned { + #[cold] + fn drop_slow(&mut self) { + let (mut shard, hash) = Self::select(&self.arc); + + if Arc::count(&self.arc) != 2 { + // Another thread has interned another copy + return; + } + + match shard + .raw_entry_mut() + .from_key_hashed_nocheck(hash, &self.arc) + { + RawEntryMut::Occupied(occ) => occ.remove(), + RawEntryMut::Vacant(_) => unreachable!(), + }; + + // Shrink the backing storage if the shard is less than 50% occupied. + if shard.len() * 2 < shard.capacity() { + shard.shrink_to_fit(); + } + } +} + +/// Compares interned `Ref`s using pointer equality. +impl PartialEq for Interned { + // NOTE: No `?Sized` because `ptr_eq` doesn't work right with trait objects. + + #[inline] + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) + } +} + +impl Eq for Interned {} + +impl PartialOrd for Interned { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Interned { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self == other { + std::cmp::Ordering::Equal + } else { + self.as_ref().cmp(other.as_ref()) + } + } +} + +impl PartialEq for Interned { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) + } +} + +impl Eq for Interned {} + +impl Hash for Interned { + fn hash(&self, state: &mut H) { + // NOTE: Cast disposes vtable pointer / slice/str length. + state.write_usize(Arc::as_ptr(&self.arc) as *const () as usize) + } +} + +impl AsRef for Interned { + #[inline] + fn as_ref(&self) -> &T { + &self.arc + } +} + +impl Deref for Interned { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.arc + } +} + +impl Clone for Interned { + fn clone(&self) -> Self { + Self { + arc: self.arc.clone(), + } + } +} + +impl Debug for Interned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self.arc).fmt(f) + } +} + +impl Display for Interned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (*self.arc).fmt(f) + } +} + +pub struct InternStorage { + map: OnceLock>, +} + +#[allow(clippy::new_without_default)] // this a const fn, so it can't be default +impl InternStorage { + pub const fn new() -> Self { + Self { + map: OnceLock::new(), + } + } +} + +impl InternStorage { + fn get(&self) -> &InternMap { + self.map.get_or_init(DashMap::default) + } +} + +pub trait Internable: Hash + Eq + 'static { + fn storage() -> &'static InternStorage; +} + +/// Implements `Internable` for a given list of types, making them usable with +/// `Interned`. +#[macro_export] +#[doc(hidden)] +macro_rules! _impl_internable { + ( $($t:ty),+ $(,)? ) => { $( + impl $crate::adt::interner::Internable for $t { + fn storage() -> &'static $crate::adt::interner::InternStorage { + static STORAGE: $crate::adt::interner::InternStorage<$t> = $crate::adt::interner::InternStorage::new(); + &STORAGE + } + } + )+ }; +} + +pub use crate::_impl_internable as impl_internable; + +impl_internable!(str,); diff --git a/crates/tinymist-query/src/adt/mod.rs b/crates/tinymist-query/src/adt/mod.rs index a3f6267e..56b3275a 100644 --- a/crates/tinymist-query/src/adt/mod.rs +++ b/crates/tinymist-query/src/adt/mod.rs @@ -1 +1,2 @@ +pub mod interner; pub mod snapshot_map; diff --git a/crates/tinymist-query/src/analysis.rs b/crates/tinymist-query/src/analysis.rs index de18fa23..81977f86 100644 --- a/crates/tinymist-query/src/analysis.rs +++ b/crates/tinymist-query/src/analysis.rs @@ -15,6 +15,7 @@ pub use linked_def::*; pub mod signature; pub use signature::*; mod ty; +pub(crate) use crate::ty::*; pub(crate) use ty::*; pub mod track_values; pub use track_values::*; @@ -33,7 +34,7 @@ mod type_check_tests { use crate::analysis::ty; use crate::tests::*; - use super::TypeCheckInfo; + use super::{Ty, TypeCheckInfo}; #[test] fn test() { @@ -65,7 +66,7 @@ mod type_check_tests { vars.sort_by(|x, y| x.0.cmp(&y.0)); for (name, var) in vars { - writeln!(f, "{:?} = {:?}", name, info.simplify(var.get_ref(), true))?; + writeln!(f, "{:?} = {:?}", name, info.simplify(var.as_type(), true))?; } writeln!(f, "---")?; @@ -82,7 +83,8 @@ mod type_check_tests { }); for (range, value) in mapping { - writeln!(f, "{range:?} -> {value:?}")?; + let ty = Ty::from_types(value.clone().into_iter()); + writeln!(f, "{range:?} -> {ty:?}")?; } Ok(()) diff --git a/crates/tinymist-query/src/analysis/call.rs b/crates/tinymist-query/src/analysis/call.rs index b79a2fb6..5d9fd11f 100644 --- a/crates/tinymist-query/src/analysis/call.rs +++ b/crates/tinymist-query/src/analysis/call.rs @@ -4,6 +4,7 @@ use typst::syntax::SyntaxNode; use super::{ParamSpec, Signature}; use crate::{ + adt::interner::Interned, analysis::{analyze_signature, PrimarySignature, SignatureTarget}, prelude::*, }; @@ -210,9 +211,9 @@ pub fn analyze_call_no_cache( match arg { ast::Arg::Named(named) => { - let n = named.name().as_str(); + let n = Interned::new_str(named.name().as_str()); - if let Some(param) = signature.primary().named.get(n) { + if let Some(param) = signature.primary().named.get(&n) { info.arg_mapping.insert( arg_tag, CallParamInfo { diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index f6eb3ed8..20107e7a 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -13,7 +13,7 @@ use parking_lot::RwLock; use reflexo::hash::hash128; use reflexo::{cow_mut::CowMut, debug_loc::DataSource, ImmutPath}; use typst::eval::Eval; -use typst::foundations; +use typst::foundations::{self, Func}; use typst::syntax::{LinkedNode, SyntaxNode}; use typst::{ diag::{eco_format, FileError, FileResult, PackageError}, @@ -25,9 +25,11 @@ use typst::{foundations::Value, syntax::ast, text::Font}; use typst::{layout::Position, syntax::FileId as TypstFileId}; use super::{ - analyze_bib, post_type_check, BibInfo, DefUseInfo, FlowType, ImportInfo, PathPreference, - Signature, SignatureTarget, TypeCheckInfo, + analyze_bib, post_type_check, BibInfo, DefUseInfo, DefinitionLink, IdentRef, ImportInfo, + PathPreference, SigTy, Signature, SignatureTarget, Ty, TypeCheckInfo, }; +use crate::adt::interner::Interned; +use crate::analysis::analyze_dyn_signature; use crate::path_to_url; use crate::syntax::{get_deref_target, resolve_id_by_path, DerefTarget}; use crate::{ @@ -859,31 +861,41 @@ impl<'w> AnalysisContext<'w> { .or_else(|| self.with_vm(|vm| rr.eval(vm).ok())) } - pub(crate) fn type_of(&mut self, rr: &SyntaxNode) -> Option { + pub(crate) fn type_of(&mut self, rr: &SyntaxNode) -> Option { self.type_of_span(rr.span()) } - pub(crate) fn type_of_span(&mut self, s: Span) -> Option { + pub(crate) fn type_of_func(&mut self, func: &Func) -> Option> { + log::debug!("check runtime func {func:?}"); + Some(analyze_dyn_signature(self, func.clone()).type_sig()) + } + + pub(crate) fn user_type_of_def(&mut self, source: &Source, def: &DefinitionLink) -> Option { + let def_at = def.def_at.clone()?; + let ty_chk = self.type_check(source.clone())?; + let def_use = self.def_use(source.clone())?; + + let def_ident = IdentRef { + name: def.name.clone(), + range: def_at.1, + }; + let (def_id, _) = def_use.get_def(def_at.0, &def_ident)?; + ty_chk.type_of_def(def_id) + } + + pub(crate) fn type_of_span(&mut self, s: Span) -> Option { let id = s.id()?; let source = self.source_by_id(id).ok()?; let ty_chk = self.type_check(source)?; - ty_chk.mapping.get(&s).cloned() + ty_chk.type_of_span(s) } - pub(crate) fn literal_type_of_span(&mut self, s: Span) -> Option { - let id = s.id()?; - let source = self.source_by_id(id).ok()?; - let k = LinkedNode::new(source.root()).find(s)?; - - self.literal_type_of_node(k) - } - - pub(crate) fn literal_type_of_node(&mut self, k: LinkedNode) -> Option { + pub(crate) fn literal_type_of_node(&mut self, k: LinkedNode) -> Option { let id = k.span().id()?; let source = self.source_by_id(id).ok()?; let ty_chk = self.type_check(source.clone())?; - post_type_check(self, &ty_chk, k.clone()).or_else(|| ty_chk.mapping.get(&k.span()).cloned()) + post_type_check(self, &ty_chk, k.clone()).or_else(|| ty_chk.type_of_span(k.span())) } } diff --git a/crates/tinymist-query/src/analysis/signature.rs b/crates/tinymist-query/src/analysis/signature.rs index 0f92ac94..3999ff94 100644 --- a/crates/tinymist-query/src/analysis/signature.rs +++ b/crates/tinymist-query/src/analysis/signature.rs @@ -15,11 +15,13 @@ use typst::{ util::LazyHash, }; -use crate::analysis::{resolve_callee, FlowSignature}; +use crate::adt::interner::Interned; +use crate::analysis::resolve_callee; use crate::syntax::{get_def_target, get_deref_target, DefTarget}; +use crate::ty::SigTy; use crate::AnalysisContext; -use super::{find_definition, DefinitionLink, FlowType, FlowVar, LexicalKind, LexicalVarKind}; +use super::{find_definition, DefinitionLink, LexicalKind, LexicalVarKind, Ty}; // pub fn analyze_signature @@ -27,13 +29,13 @@ use super::{find_definition, DefinitionLink, FlowType, FlowVar, LexicalKind, Lex #[derive(Debug, Clone)] pub struct ParamSpec { /// The parameter's name. - pub name: Cow<'static, str>, + pub name: Interned, /// Documentation for the parameter. pub docs: Cow<'static, str>, /// Describe what values this parameter accepts. pub input: CastInfo, /// Inferred type of the parameter. - pub(crate) infer_type: Option, + pub(crate) base_type: Option, /// The parameter's default name as type. pub type_repr: Option, /// The parameter's default name as value. @@ -56,10 +58,10 @@ pub struct ParamSpec { impl ParamSpec { fn from_static(f: &Func, p: &ParamInfo) -> Arc { Arc::new(Self { - name: Cow::Borrowed(p.name), + name: Interned::new_str(p.name), docs: Cow::Borrowed(p.docs), input: p.input.clone(), - infer_type: FlowType::from_param_site(f, p, &p.input), + base_type: Ty::from_param_site(f, p, &p.input), type_repr: Some(eco_format!("{}", TypeExpr(&p.input))), expr: None, default: p.default, @@ -96,6 +98,12 @@ impl Signature { Signature::Partial(sig) => &sig.with_stack, } } + + pub(crate) fn type_sig(&self) -> Interned { + let primary = self.primary().sig_ty.clone(); + // todo: with stack + primary + } } /// Describes a primary function signature. @@ -104,18 +112,25 @@ pub struct PrimarySignature { /// The positional parameters. pub pos: Vec>, /// The named parameters. - pub named: HashMap, Arc>, + pub named: HashMap, Arc>, /// Whether the function has fill, stroke, or size parameters. pub has_fill_or_size_or_stroke: bool, /// The rest parameter. pub rest: Option>, /// The return type. - pub(crate) ret_ty: Option, + pub(crate) ret_ty: Option, /// The signature type. - pub(crate) sig_ty: Option, + pub(crate) sig_ty: Interned, _broken: bool, } +impl PrimarySignature { + /// Returns the type representation of the function. + pub(crate) fn ty(&self) -> Ty { + Ty::Func(self.sig_ty.clone()) + } +} + /// Describes a function argument instance #[derive(Debug, Clone)] pub struct ArgInfo { @@ -298,8 +313,6 @@ fn resolve_callee_v2( }; let _t = ctx.type_check(source)?; - let _ = FlowVar::name; - let _ = FlowVar::id; let root = LinkedNode::new(def_source.root()); let def_node = root.leaf_at(def_at.1.start + 1)?; @@ -319,9 +332,7 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc { Repr::With(..) => unreachable!(), Repr::Closure(c) => (analyze_closure_signature(c.clone()), None), Repr::Element(..) | Repr::Native(..) => { - let ret_ty = func - .returns() - .and_then(|r| FlowType::from_return_site(&func, r)); + let ret_ty = func.returns().and_then(|r| Ty::from_return_site(&func, r)); let params = func.params().unwrap(); ( params @@ -355,7 +366,7 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc { } _ => {} } - named.insert(param.name.clone(), param.clone()); + named.insert(Interned::new_str(param.name.as_ref()), param.clone()); } if param.variadic { @@ -369,24 +380,18 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc { } } - let mut named_vec: Vec<(EcoString, FlowType)> = named + let mut named_vec: Vec<(Interned, Ty)> = named .iter() - .map(|e| { - ( - e.0.as_ref().into(), - e.1.infer_type.clone().unwrap_or(FlowType::Any), - ) - }) + .map(|e| (e.0.clone(), e.1.base_type.clone().unwrap_or(Ty::Any))) .collect::>(); named_vec.sort_by(|a, b| a.0.cmp(&b.0)); - let sig_ty = FlowSignature::new( - pos.iter() - .map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)), + let sig_ty = SigTy::new( + pos.iter().map(|e| e.base_type.clone().unwrap_or(Ty::Any)), named_vec.into_iter(), rest.as_ref() - .map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)), + .map(|e| e.base_type.clone().unwrap_or(Ty::Any)), ret_ty.clone(), ); Arc::new(PrimarySignature { @@ -395,7 +400,7 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc { rest, ret_ty, has_fill_or_size_or_stroke: has_fill || has_stroke || has_size, - sig_ty: Some(FlowType::Func(Box::new(sig_ty))), + sig_ty: Interned::new(sig_ty), _broken: broken, }) } @@ -415,9 +420,9 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { match param { ast::Param::Pos(ast::Pattern::Placeholder(..)) => { params.push(Arc::new(ParamSpec { - name: Cow::Borrowed("_"), + name: Interned::new_str("_"), input: CastInfo::Any, - infer_type: None, + base_type: None, type_repr: None, expr: None, default: None, @@ -437,9 +442,9 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { let name = name[0].as_str(); params.push(Arc::new(ParamSpec { - name: Cow::Owned(name.to_owned()), + name: Interned::new_str(name), input: CastInfo::Any, - infer_type: None, + base_type: None, type_repr: None, expr: None, default: None, @@ -454,9 +459,9 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { ast::Param::Named(n) => { let expr = unwrap_expr(n.expr()).to_untyped().clone().into_text(); params.push(Arc::new(ParamSpec { - name: Cow::Owned(n.name().as_str().to_owned()), + name: Interned::new_str(n.name().as_str()), input: CastInfo::Any, - infer_type: None, + base_type: None, type_repr: Some(expr.clone()), expr: Some(expr.clone()), default: None, @@ -470,9 +475,9 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { ast::Param::Spread(n) => { let ident = n.sink_ident().map(|e| e.as_str()); params.push(Arc::new(ParamSpec { - name: Cow::Owned(ident.unwrap_or_default().to_owned()), + name: Interned::new_str(ident.unwrap_or_default()), input: CastInfo::Any, - infer_type: None, + base_type: None, type_repr: None, expr: None, default: None, diff --git a/crates/tinymist-query/src/analysis/ty.rs b/crates/tinymist-query/src/analysis/ty.rs index f3653f24..583b2c78 100644 --- a/crates/tinymist-query/src/analysis/ty.rs +++ b/crates/tinymist-query/src/analysis/ty.rs @@ -1,31 +1,30 @@ -//! Top-level evaluation of a source file. +//! Type checking on source file -use std::{ - collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; -use ecow::{EcoString, EcoVec}; use once_cell::sync::Lazy; -use parking_lot::{Mutex, RwLock}; -use reflexo::{hash::hash128, vector::ir::DefId}; +use reflexo::vector::ir::DefId; use typst::{ - foundations::{Func, Repr, Value}, + foundations::Value, syntax::{ ast::{self, AstNode}, LinkedNode, Source, Span, SyntaxKind, }, }; -use crate::{analysis::analyze_dyn_signature, AnalysisContext}; +use crate::analysis::{Ty, *}; +use crate::{adt::interner::Interned, analysis::TypeCheckInfo, ty::TypeInterace, AnalysisContext}; -use super::{resolve_global_value, DefUseInfo, IdentRef}; +use super::{ + resolve_global_value, BuiltinTy, DefUseInfo, FlowVarKind, IdentRef, TypeBounds, TypeVar, + TypeVarBounds, +}; -mod def; -pub(crate) use def::*; -mod builtin; -pub(crate) use builtin::*; +mod apply; mod post_check; +mod syntax; + +pub(crate) use apply::*; pub(crate) use post_check::*; /// Type checking at the source unit level. @@ -50,79 +49,9 @@ pub(crate) fn type_check(ctx: &mut AnalysisContext, source: Source) -> Option, - pub mapping: HashMap, - - cano_cache: Mutex, -} - -impl TypeCheckInfo { - pub fn simplify(&self, ty: FlowType, principal: bool) -> FlowType { - let mut c = self.cano_cache.lock(); - let c = &mut *c; - - c.cano_local_cache.clear(); - c.positives.clear(); - c.negatives.clear(); - - let mut worker = TypeSimplifier { - principal, - vars: &self.vars, - cano_cache: &mut c.cano_cache, - cano_local_cache: &mut c.cano_local_cache, - - positives: &mut c.positives, - negatives: &mut c.negatives, - }; - - worker.simplify(ty, principal) - } - - pub fn describe(&self, ty: &FlowType) -> Option { - let mut worker = TypeDescriber::default(); - worker.describe_root(ty) - } - - // todo: distinguish at least, at most - pub fn witness_at_least(&mut self, site: Span, ty: FlowType) { - Self::witness_(site, ty, &mut self.mapping); - } - - pub fn witness_at_most(&mut self, site: Span, ty: FlowType) { - Self::witness_(site, ty, &mut self.mapping); - } - - fn witness_(site: Span, ty: FlowType, mapping: &mut HashMap) { - if site.is_detached() { - return; - } - - // todo: intersect/union - let site_store = mapping.entry(site); - match site_store { - Entry::Occupied(e) => match e.into_mut() { - FlowType::Union(v) => { - v.push(ty); - } - e => { - *e = FlowType::from_types([e.clone(), ty].into_iter()); - } - }, - Entry::Vacant(e) => { - e.insert(ty); - } - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum InterpretMode { Markup, @@ -136,14 +65,14 @@ struct TypeChecker<'a, 'w> { def_use_info: Arc, info: &'a mut TypeCheckInfo, - externals: HashMap>, + externals: HashMap>, mode: InterpretMode, } impl<'a, 'w> TypeChecker<'a, 'w> { - fn check(&mut self, root: LinkedNode) -> FlowType { + fn check(&mut self, root: LinkedNode) -> Ty { let should_record = matches!(root.kind(), SyntaxKind::FuncCall).then(|| root.span()); - let w = self.check_inner(root).unwrap_or(FlowType::Undef); + let w = self.check_syntax(root).unwrap_or(Ty::Undef); if let Some(s) = should_record { self.info.witness_at_least(s, w.clone()); @@ -152,568 +81,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> { w } - fn check_inner(&mut self, root: LinkedNode) -> Option { - Some(match root.kind() { - SyntaxKind::Markup => return self.check_in_mode(root, InterpretMode::Markup), - SyntaxKind::Math => return self.check_in_mode(root, InterpretMode::Math), - SyntaxKind::Code => return self.check_in_mode(root, InterpretMode::Code), - SyntaxKind::CodeBlock => return self.check_in_mode(root, InterpretMode::Code), - SyntaxKind::ContentBlock => return self.check_in_mode(root, InterpretMode::Markup), - - // todo: space effect - SyntaxKind::Space => FlowType::Space, - SyntaxKind::Parbreak => FlowType::Space, - - SyntaxKind::Text => FlowType::Content, - SyntaxKind::Linebreak => FlowType::Content, - SyntaxKind::Escape => FlowType::Content, - SyntaxKind::Shorthand => FlowType::Content, - SyntaxKind::SmartQuote => FlowType::Content, - SyntaxKind::Raw => FlowType::Content, - SyntaxKind::RawLang => FlowType::Content, - SyntaxKind::RawDelim => FlowType::Content, - SyntaxKind::RawTrimmed => FlowType::Content, - SyntaxKind::Link => FlowType::Content, - SyntaxKind::Label => FlowType::Content, - SyntaxKind::Ref => FlowType::Content, - SyntaxKind::RefMarker => FlowType::Content, - SyntaxKind::HeadingMarker => FlowType::Content, - SyntaxKind::EnumMarker => FlowType::Content, - SyntaxKind::ListMarker => FlowType::Content, - SyntaxKind::TermMarker => FlowType::Content, - SyntaxKind::MathAlignPoint => FlowType::Content, - SyntaxKind::MathPrimes => FlowType::Content, - - SyntaxKind::Strong => return self.check_children(root), - SyntaxKind::Emph => return self.check_children(root), - SyntaxKind::Heading => return self.check_children(root), - SyntaxKind::ListItem => return self.check_children(root), - SyntaxKind::EnumItem => return self.check_children(root), - SyntaxKind::TermItem => return self.check_children(root), - SyntaxKind::Equation => return self.check_children(root), - SyntaxKind::MathDelimited => return self.check_children(root), - SyntaxKind::MathAttach => return self.check_children(root), - SyntaxKind::MathFrac => return self.check_children(root), - SyntaxKind::MathRoot => return self.check_children(root), - - SyntaxKind::LoopBreak => FlowType::None, - SyntaxKind::LoopContinue => FlowType::None, - SyntaxKind::FuncReturn => FlowType::None, - SyntaxKind::Error => FlowType::None, - SyntaxKind::Eof => FlowType::None, - - SyntaxKind::None => FlowType::None, - SyntaxKind::Auto => FlowType::Auto, - SyntaxKind::Break => FlowType::FlowNone, - SyntaxKind::Continue => FlowType::FlowNone, - SyntaxKind::Return => FlowType::FlowNone, - SyntaxKind::Ident => return self.check_ident(root, InterpretMode::Code), - SyntaxKind::MathIdent => return self.check_ident(root, InterpretMode::Math), - SyntaxKind::Bool - | SyntaxKind::Int - | SyntaxKind::Float - | SyntaxKind::Numeric - | SyntaxKind::Str => { - return self - .ctx - .mini_eval(root.cast()?) - .map(|v| (FlowType::Value(Box::new((v, Span::detached()))))) - } - SyntaxKind::Parenthesized => return self.check_children(root), - SyntaxKind::Array => return self.check_array(root), - SyntaxKind::Dict => return self.check_dict(root), - SyntaxKind::Unary => return self.check_unary(root), - SyntaxKind::Binary => return self.check_binary(root), - SyntaxKind::FieldAccess => return self.check_field_access(root), - SyntaxKind::FuncCall => return self.check_func_call(root), - SyntaxKind::Args => return self.check_args(root), - SyntaxKind::Closure => return self.check_closure(root), - SyntaxKind::LetBinding => return self.check_let(root), - SyntaxKind::SetRule => return self.check_set(root), - SyntaxKind::ShowRule => return self.check_show(root), - SyntaxKind::Contextual => return self.check_contextual(root), - SyntaxKind::Conditional => return self.check_conditional(root), - SyntaxKind::WhileLoop => return self.check_while_loop(root), - SyntaxKind::ForLoop => return self.check_for_loop(root), - SyntaxKind::ModuleImport => return self.check_module_import(root), - SyntaxKind::ModuleInclude => return self.check_module_include(root), - SyntaxKind::Destructuring => return self.check_destructuring(root), - SyntaxKind::DestructAssignment => return self.check_destruct_assign(root), - - // Rest all are clauses - SyntaxKind::LineComment => FlowType::Clause, - SyntaxKind::BlockComment => FlowType::Clause, - SyntaxKind::Named => FlowType::Clause, - SyntaxKind::Keyed => FlowType::Clause, - SyntaxKind::Spread => FlowType::Clause, - SyntaxKind::Params => FlowType::Clause, - SyntaxKind::ImportItems => FlowType::Clause, - SyntaxKind::RenamedImportItem => FlowType::Clause, - SyntaxKind::Hash => FlowType::Clause, - SyntaxKind::LeftBrace => FlowType::Clause, - SyntaxKind::RightBrace => FlowType::Clause, - SyntaxKind::LeftBracket => FlowType::Clause, - SyntaxKind::RightBracket => FlowType::Clause, - SyntaxKind::LeftParen => FlowType::Clause, - SyntaxKind::RightParen => FlowType::Clause, - SyntaxKind::Comma => FlowType::Clause, - SyntaxKind::Semicolon => FlowType::Clause, - SyntaxKind::Colon => FlowType::Clause, - SyntaxKind::Star => FlowType::Clause, - SyntaxKind::Underscore => FlowType::Clause, - SyntaxKind::Dollar => FlowType::Clause, - SyntaxKind::Plus => FlowType::Clause, - SyntaxKind::Minus => FlowType::Clause, - SyntaxKind::Slash => FlowType::Clause, - SyntaxKind::Hat => FlowType::Clause, - SyntaxKind::Prime => FlowType::Clause, - SyntaxKind::Dot => FlowType::Clause, - SyntaxKind::Eq => FlowType::Clause, - SyntaxKind::EqEq => FlowType::Clause, - SyntaxKind::ExclEq => FlowType::Clause, - SyntaxKind::Lt => FlowType::Clause, - SyntaxKind::LtEq => FlowType::Clause, - SyntaxKind::Gt => FlowType::Clause, - SyntaxKind::GtEq => FlowType::Clause, - SyntaxKind::PlusEq => FlowType::Clause, - SyntaxKind::HyphEq => FlowType::Clause, - SyntaxKind::StarEq => FlowType::Clause, - SyntaxKind::SlashEq => FlowType::Clause, - SyntaxKind::Dots => FlowType::Clause, - SyntaxKind::Arrow => FlowType::Clause, - SyntaxKind::Root => FlowType::Clause, - SyntaxKind::Not => FlowType::Clause, - SyntaxKind::And => FlowType::Clause, - SyntaxKind::Or => FlowType::Clause, - SyntaxKind::Let => FlowType::Clause, - SyntaxKind::Set => FlowType::Clause, - SyntaxKind::Show => FlowType::Clause, - SyntaxKind::Context => FlowType::Clause, - SyntaxKind::If => FlowType::Clause, - SyntaxKind::Else => FlowType::Clause, - SyntaxKind::For => FlowType::Clause, - SyntaxKind::In => FlowType::Clause, - SyntaxKind::While => FlowType::Clause, - SyntaxKind::Import => FlowType::Clause, - SyntaxKind::Include => FlowType::Clause, - SyntaxKind::As => FlowType::Clause, - }) - } - - fn check_in_mode(&mut self, root: LinkedNode, into_mode: InterpretMode) -> Option { - let mode = self.mode; - self.mode = into_mode; - let res = self.check_children(root); - self.mode = mode; - res - } - - fn check_children(&mut self, root: LinkedNode<'_>) -> Option { - let mut joiner = Joiner::default(); - - for child in root.children() { - joiner.join(self.check(child)); - } - Some(joiner.finalize()) - } - - fn check_ident(&mut self, root: LinkedNode<'_>, mode: InterpretMode) -> Option { - let ident: ast::Ident = root.cast()?; - let ident_ref = IdentRef { - name: ident.get().to_string(), - range: root.range(), - }; - - let Some(var) = self.get_var(root.span(), ident_ref) else { - let s = root.span(); - let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?; - return Some(FlowType::Value(Box::new((v, s)))); - }; - - Some(var.get_ref()) - } - - fn check_array(&mut self, root: LinkedNode<'_>) -> Option { - let _arr: ast::Array = root.cast()?; - - let mut elements = EcoVec::new(); - - for elem in root.children() { - let ty = self.check(elem); - if matches!(ty, FlowType::Clause | FlowType::Space) { - continue; - } - elements.push(ty); - } - - Some(FlowType::Tuple(elements)) - } - - fn check_dict(&mut self, root: LinkedNode<'_>) -> Option { - let dict: ast::Dict = root.cast()?; - - let mut fields = EcoVec::new(); - - for field in dict.items() { - match field { - ast::DictItem::Named(n) => { - let name = n.name().get().clone(); - let value = self.check_expr_in(n.expr().span(), root.clone()); - fields.push((name, value, n.span())); - } - ast::DictItem::Keyed(k) => { - let key = self.ctx.const_eval(k.key()); - if let Some(Value::Str(key)) = key { - let value = self.check_expr_in(k.expr().span(), root.clone()); - fields.push((key.into(), value, k.span())); - } - } - // todo: var dict union - ast::DictItem::Spread(_s) => {} - } - } - - Some(FlowType::Dict(FlowRecord { fields })) - } - - fn check_unary(&mut self, root: LinkedNode<'_>) -> Option { - let unary: ast::Unary = root.cast()?; - - if let Some(constant) = self.ctx.mini_eval(ast::Expr::Unary(unary)) { - return Some(FlowType::Value(Box::new((constant, Span::detached())))); - } - - let op = unary.op(); - - let lhs = Box::new(self.check_expr_in(unary.expr().span(), root)); - let op = match op { - ast::UnOp::Pos => UnaryOp::Pos, - ast::UnOp::Neg => UnaryOp::Neg, - ast::UnOp::Not => UnaryOp::Not, - }; - - Some(FlowType::Unary(FlowUnaryType { op, lhs })) - } - - fn check_binary(&mut self, root: LinkedNode<'_>) -> Option { - let binary: ast::Binary = root.cast()?; - - if let Some(constant) = self.ctx.mini_eval(ast::Expr::Binary(binary)) { - return Some(FlowType::Value(Box::new((constant, Span::detached())))); - } - - let op = binary.op(); - let lhs_span = binary.lhs().span(); - let lhs = self.check_expr_in(lhs_span, root.clone()); - let rhs_span = binary.rhs().span(); - let rhs = self.check_expr_in(rhs_span, root); - - match op { - ast::BinOp::Add | ast::BinOp::Sub | ast::BinOp::Mul | ast::BinOp::Div => {} - ast::BinOp::Eq | ast::BinOp::Neq | ast::BinOp::Leq | ast::BinOp::Geq => { - self.check_comparable(&lhs, &rhs); - self.possible_ever_be(&lhs, &rhs); - self.possible_ever_be(&rhs, &lhs); - } - ast::BinOp::Lt | ast::BinOp::Gt => { - self.check_comparable(&lhs, &rhs); - } - ast::BinOp::And | ast::BinOp::Or => { - self.constrain(&lhs, &FlowType::Boolean(None)); - self.constrain(&rhs, &FlowType::Boolean(None)); - } - ast::BinOp::NotIn | ast::BinOp::In => { - self.check_containing(&rhs, &lhs, op == ast::BinOp::In); - } - ast::BinOp::Assign => { - self.check_assignable(&lhs, &rhs); - self.possible_ever_be(&lhs, &rhs); - } - ast::BinOp::AddAssign - | ast::BinOp::SubAssign - | ast::BinOp::MulAssign - | ast::BinOp::DivAssign => { - self.check_assignable(&lhs, &rhs); - } - } - - let res = FlowType::Binary(FlowBinaryType { - op, - operands: Box::new((lhs, rhs)), - }); - - Some(res) - } - - fn check_field_access(&mut self, root: LinkedNode<'_>) -> Option { - let field_access: ast::FieldAccess = root.cast()?; - - let obj = self.check_expr_in(field_access.target().span(), root.clone()); - let field = field_access.field().get().clone(); - - Some(FlowType::At(FlowAt(Box::new((obj, field))))) - } - - fn check_func_call(&mut self, root: LinkedNode<'_>) -> Option { - let func_call: ast::FuncCall = root.cast()?; - - let args = self.check_expr_in(func_call.args().span(), root.clone()); - let callee = self.check_expr_in(func_call.callee().span(), root.clone()); - let mut candidates = Vec::with_capacity(1); - - log::debug!("func_call: {callee:?} with {args:?}"); - - if let FlowType::Args(args) = args { - self.check_apply( - callee, - func_call.callee().span(), - &args, - &func_call.args(), - &mut candidates, - )?; - } - - if candidates.len() == 1 { - return Some(candidates[0].clone()); - } - - if candidates.is_empty() { - return Some(FlowType::Any); - } - - Some(FlowType::Union(Box::new(candidates))) - } - - fn check_args(&mut self, root: LinkedNode<'_>) -> Option { - let args: ast::Args = root.cast()?; - - let mut args_res = Vec::new(); - let mut named = vec![]; - - for arg in args.items() { - match arg { - ast::Arg::Pos(e) => { - args_res.push(self.check_expr_in(e.span(), root.clone())); - } - ast::Arg::Named(n) => { - let name = n.name().get().clone(); - let value = self.check_expr_in(n.expr().span(), root.clone()); - named.push((name, value)); - } - // todo - ast::Arg::Spread(_w) => {} - } - } - - Some(FlowType::Args(Box::new(FlowArgs { - args: args_res, - named, - }))) - } - - fn check_closure(&mut self, root: LinkedNode<'_>) -> Option { - let closure: ast::Closure = root.cast()?; - - // let _params = self.check_expr_in(closure.params().span(), root.clone()); - - let mut pos = vec![]; - let mut named = BTreeMap::new(); - let mut rest = None; - - for param in closure.params().children() { - match param { - ast::Param::Pos(pattern) => { - pos.push(self.check_pattern(pattern, FlowType::Any, root.clone())); - } - ast::Param::Named(e) => { - let exp = self.check_expr_in(e.expr().span(), root.clone()); - let v = self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?; - v.ever_be(exp); - named.insert(e.name().get().clone(), v.get_ref()); - } - // todo: spread left/right - ast::Param::Spread(a) => { - if let Some(e) = a.sink_ident() { - let exp = FlowType::Builtin(FlowBuiltinType::Args); - let v = self.get_var(e.span(), to_ident_ref(&root, e)?)?; - v.ever_be(exp); - rest = Some(v.get_ref()); - } - // todo: ..(args) - } - } - } - - let body = self.check_expr_in(closure.body().span(), root); - - let named: Vec<(EcoString, FlowType)> = named.into_iter().collect(); - - // freeze the signature - for pos in pos.iter() { - self.weaken(pos); - } - for (_, named) in named.iter() { - self.weaken(named); - } - if let Some(rest) = &rest { - self.weaken(rest); - } - - Some(FlowType::Func(Box::new(FlowSignature { - pos, - named, - rest, - ret: body, - }))) - } - - fn check_let(&mut self, root: LinkedNode<'_>) -> Option { - let let_binding: ast::LetBinding = root.cast()?; - - match let_binding.kind() { - ast::LetBindingKind::Closure(c) => { - // let _name = let_binding.name().get().to_string(); - let value = let_binding - .init() - .map(|init| self.check_expr_in(init.span(), root.clone())) - .unwrap_or_else(|| FlowType::Infer); - - let v = self.get_var(c.span(), to_ident_ref(&root, c)?)?; - v.ever_be(value); - // todo lbs is the lexical signature. - } - ast::LetBindingKind::Normal(pattern) => { - // let _name = let_binding.name().get().to_string(); - let value = let_binding - .init() - .map(|init| self.check_expr_in(init.span(), root.clone())) - .unwrap_or_else(|| FlowType::Infer); - - self.check_pattern(pattern, value, root.clone()); - } - } - - Some(FlowType::Any) - } - - // todo: merge with func call, and regard difference (may be here) - fn check_set(&mut self, root: LinkedNode<'_>) -> Option { - let set_rule: ast::SetRule = root.cast()?; - - let callee = self.check_expr_in(set_rule.target().span(), root.clone()); - let args = self.check_expr_in(set_rule.args().span(), root.clone()); - let _cond = set_rule - .condition() - .map(|cond| self.check_expr_in(cond.span(), root.clone())); - let mut candidates = Vec::with_capacity(1); - - log::debug!("set rule: {callee:?} with {args:?}"); - - if let FlowType::Args(args) = args { - self.check_apply( - callee, - set_rule.target().span(), - &args, - &set_rule.args(), - &mut candidates, - )?; - } - - if candidates.len() == 1 { - return Some(candidates[0].clone()); - } - - if candidates.is_empty() { - return Some(FlowType::Any); - } - - Some(FlowType::Union(Box::new(candidates))) - } - - fn check_show(&mut self, root: LinkedNode<'_>) -> Option { - let show_rule: ast::ShowRule = root.cast()?; - - let _selector = show_rule - .selector() - .map(|sel| self.check_expr_in(sel.span(), root.clone())); - let t = show_rule.transform(); - // todo: infer it type by selector - let _transform = self.check_expr_in(t.span(), root.clone()); - - Some(FlowType::Any) - } - - // currently we do nothing on contextual - fn check_contextual(&mut self, root: LinkedNode<'_>) -> Option { - let contextual: ast::Contextual = root.cast()?; - - let body = self.check_expr_in(contextual.body().span(), root); - - Some(FlowType::Unary(FlowUnaryType { - op: UnaryOp::Context, - lhs: Box::new(body), - })) - } - - fn check_conditional(&mut self, root: LinkedNode<'_>) -> Option { - let conditional: ast::Conditional = root.cast()?; - - let cond = self.check_expr_in(conditional.condition().span(), root.clone()); - let then = self.check_expr_in(conditional.if_body().span(), root.clone()); - let else_ = conditional - .else_body() - .map(|else_body| self.check_expr_in(else_body.span(), root.clone())) - .unwrap_or(FlowType::None); - - Some(FlowType::If(Box::new(FlowIfType { cond, then, else_ }))) - } - - fn check_while_loop(&mut self, root: LinkedNode<'_>) -> Option { - let while_loop: ast::WhileLoop = root.cast()?; - - let _cond = self.check_expr_in(while_loop.condition().span(), root.clone()); - let _body = self.check_expr_in(while_loop.body().span(), root); - - Some(FlowType::Any) - } - - fn check_for_loop(&mut self, root: LinkedNode<'_>) -> Option { - let for_loop: ast::ForLoop = root.cast()?; - - let _iter = self.check_expr_in(for_loop.iterable().span(), root.clone()); - let _pattern = self.check_expr_in(for_loop.pattern().span(), root.clone()); - let _body = self.check_expr_in(for_loop.body().span(), root); - - Some(FlowType::Any) - } - - fn check_module_import(&mut self, root: LinkedNode<'_>) -> Option { - let _module_import: ast::ModuleImport = root.cast()?; - - // check all import items - - Some(FlowType::None) - } - - fn check_module_include(&mut self, _root: LinkedNode<'_>) -> Option { - Some(FlowType::Content) - } - - fn check_destructuring(&mut self, _root: LinkedNode<'_>) -> Option { - Some(FlowType::Any) - } - - fn check_destruct_assign(&mut self, _root: LinkedNode<'_>) -> Option { - Some(FlowType::None) - } - - fn check_expr_in(&mut self, span: Span, root: LinkedNode<'_>) -> FlowType { - root.find(span) - .map(|node| self.check(node)) - .unwrap_or(FlowType::Undef) - } - - fn get_var(&mut self, s: Span, r: IdentRef) -> Option<&mut FlowVar> { + fn get_var(&mut self, s: Span, r: IdentRef) -> Option<&mut TypeVarBounds> { let def_id = self .def_use_info .get_ref(&r) @@ -722,24 +90,27 @@ impl<'a, 'w> TypeChecker<'a, 'w> { // todo: false positive of clippy #[allow(clippy::map_entry)] if !self.info.vars.contains_key(&def_id) { - let def = self.check_external(def_id); - let kind = FlowVarKind::Strong(Arc::new(RwLock::new(self.init_var(def)))); + let def = self.import_ty(def_id); + let init_expr = self.init_var(def); self.info.vars.insert( def_id, - FlowVar { - name: r.name.into(), - id: def_id, - kind, - }, + TypeVarBounds::new( + TypeVar { + name: Interned::new_str(&r.name), + def: def_id, + syntax: None, + }, + init_expr, + ), ); } let var = self.info.vars.get_mut(&def_id).unwrap(); - TypeCheckInfo::witness_(s, var.get_ref(), &mut self.info.mapping); + TypeCheckInfo::witness_(s, var.as_type(), &mut self.info.mapping); Some(var) } - fn check_external(&mut self, def_id: DefId) -> Option { + fn import_ty(&mut self, def_id: DefId) -> Option { if let Some(ty) = self.externals.get(&def_id) { return ty.clone(); } @@ -759,283 +130,162 @@ impl<'a, 'w> TypeChecker<'a, 'w> { range: def_pos.range.clone(), }, )?; - let ext_ty = ext_type_info.vars.get(&ext_def_id)?.get_ref(); + let ext_ty = ext_type_info.vars.get(&ext_def_id)?.as_type(); Some(ext_type_info.simplify(ext_ty, false)) } - fn check_pattern( - &mut self, - pattern: ast::Pattern<'_>, - value: FlowType, - root: LinkedNode<'_>, - ) -> FlowType { - self.check_pattern_(pattern, value, root) - .unwrap_or(FlowType::Undef) - } + fn constrain(&mut self, lhs: &Ty, rhs: &Ty) { + static FLOW_STROKE_DICT_TYPE: Lazy = Lazy::new(|| Ty::Dict(FLOW_STROKE_DICT.clone())); + static FLOW_MARGIN_DICT_TYPE: Lazy = Lazy::new(|| Ty::Dict(FLOW_MARGIN_DICT.clone())); + static FLOW_INSET_DICT_TYPE: Lazy = Lazy::new(|| Ty::Dict(FLOW_INSET_DICT.clone())); + static FLOW_OUTSET_DICT_TYPE: Lazy = Lazy::new(|| Ty::Dict(FLOW_OUTSET_DICT.clone())); + static FLOW_RADIUS_DICT_TYPE: Lazy = Lazy::new(|| Ty::Dict(FLOW_RADIUS_DICT.clone())); - fn check_pattern_( - &mut self, - pattern: ast::Pattern<'_>, - value: FlowType, - root: LinkedNode<'_>, - ) -> Option { - Some(match pattern { - ast::Pattern::Normal(ast::Expr::Ident(ident)) => { - let v = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?; - v.ever_be(value); - v.get_ref() + fn is_ty(ty: &Ty) -> bool { + match ty { + Ty::Builtin(BuiltinTy::Type(..)) => true, + Ty::Value(val) => matches!(val.val, Value::Type(..)), + _ => false, } - ast::Pattern::Normal(_) => FlowType::Any, - ast::Pattern::Placeholder(_) => FlowType::Any, - ast::Pattern::Parenthesized(exp) => self.check_pattern(exp.pattern(), value, root), - // todo: pattern - ast::Pattern::Destructuring(_destruct) => FlowType::Any, - }) - } - - fn check_apply( - &mut self, - callee: FlowType, - callee_span: Span, - args: &FlowArgs, - syntax_args: &ast::Args, - candidates: &mut Vec, - ) -> Option<()> { - log::debug!("check func callee {callee:?}"); - - match &callee { - FlowType::Var(v) => { - let w = self.info.vars.get(&v.0).cloned()?; - match &w.kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - // It is instantiated here by clone. - let w = w.read().clone(); - for lb in w.lbs.iter() { - self.check_apply( - lb.clone(), - callee_span, - args, - syntax_args, - candidates, - )?; - } - for ub in w.ubs.iter() { - self.check_apply( - ub.clone(), - callee_span, - args, - syntax_args, - candidates, - )?; - } - } - } - } - FlowType::Func(v) => { - self.info.witness_at_least(callee_span, callee.clone()); - - let f = v.as_ref(); - let mut pos = f.pos.iter(); - // let mut named = f.named.clone(); - // let mut rest = f.rest.clone(); - - for pos_in in args.start_match() { - let pos_ty = pos.next().unwrap_or(&FlowType::Any); - self.constrain(pos_in, pos_ty); - } - - for (name, named_in) in &args.named { - let named_ty = f.named.iter().find(|(n, _)| n == name).map(|(_, ty)| ty); - if let Some(named_ty) = named_ty { - self.constrain(named_in, named_ty); - } - } - - // log::debug!("check applied {v:?}"); - - candidates.push(f.ret.clone()); - } - FlowType::Dict(_v) => {} - FlowType::Tuple(_v) => {} - FlowType::Array(_v) => {} - // todo: with - FlowType::With(_e) => {} - FlowType::Args(_e) => {} - FlowType::Union(_e) => {} - FlowType::Field(_e) => {} - FlowType::Let(_) => {} - FlowType::Value(f) => { - if let Value::Func(f) = &f.0 { - self.check_apply_runtime(f, callee_span, args, syntax_args, candidates); - } - } - FlowType::ValueDoc(f) => { - if let Value::Func(f) = &f.0 { - self.check_apply_runtime(f, callee_span, args, syntax_args, candidates); - } - } - - FlowType::Clause => {} - FlowType::Undef => {} - FlowType::Content => {} - FlowType::Any => {} - FlowType::None => {} - FlowType::Infer => {} - FlowType::FlowNone => {} - FlowType::Space => {} - FlowType::Auto => {} - FlowType::Builtin(_) => {} - FlowType::Boolean(_) => {} - FlowType::At(e) => { - let primary_type = self.check_primary_type(e.0 .0.clone()); - self.check_apply_method( - primary_type, - callee_span, - e.0 .1.clone(), - args, - candidates, - ); - } - FlowType::Unary(_) => {} - FlowType::Binary(_) => {} - FlowType::If(_) => {} - FlowType::Element(_elem) => {} } - Some(()) - } - - fn constrain(&mut self, lhs: &FlowType, rhs: &FlowType) { - static FLOW_STROKE_DICT_TYPE: Lazy = - Lazy::new(|| FlowType::Dict(FLOW_STROKE_DICT.clone())); - static FLOW_MARGIN_DICT_TYPE: Lazy = - Lazy::new(|| FlowType::Dict(FLOW_MARGIN_DICT.clone())); - static FLOW_INSET_DICT_TYPE: Lazy = - Lazy::new(|| FlowType::Dict(FLOW_INSET_DICT.clone())); - static FLOW_OUTSET_DICT_TYPE: Lazy = - Lazy::new(|| FlowType::Dict(FLOW_OUTSET_DICT.clone())); - static FLOW_RADIUS_DICT_TYPE: Lazy = - Lazy::new(|| FlowType::Dict(FLOW_RADIUS_DICT.clone())); - match (lhs, rhs) { - (FlowType::Var(v), FlowType::Var(w)) => { - if v.0 .0 == w.0 .0 { + (Ty::Var(v), Ty::Var(w)) => { + if v.def == w.def { return; } // todo: merge - let _ = v.0 .0; - let _ = w.0 .0; + let _ = v.def; + let _ = w.def; } - (FlowType::Var(v), rhs) => { + (Ty::Var(v), rhs) => { log::debug!("constrain var {v:?} ⪯ {rhs:?}"); - let w = self.info.vars.get_mut(&v.0).unwrap(); + let w = self.info.vars.get_mut(&v.def).unwrap(); // strict constraint on upper bound let bound = rhs.clone(); - match &w.kind { + match &w.bounds { FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { let mut w = w.write(); w.ubs.push(bound); } } } - (lhs, FlowType::Var(v)) => { - let w = self.info.vars.get(&v.0).unwrap(); - let bound = self.weaken_constraint(lhs, &w.kind); + (lhs, Ty::Var(v)) => { + let w = self.info.vars.get(&v.def).unwrap(); + let bound = self.weaken_constraint(lhs, &w.bounds); log::debug!("constrain var {v:?} ⪰ {bound:?}"); - match &w.kind { + match &w.bounds { FlowVarKind::Strong(v) | FlowVarKind::Weak(v) => { let mut v = v.write(); v.lbs.push(bound); } } } - (FlowType::Union(v), rhs) => { + (Ty::Union(v), rhs) => { for e in v.iter() { self.constrain(e, rhs); } } - (lhs, FlowType::Union(v)) => { + (lhs, Ty::Union(v)) => { for e in v.iter() { self.constrain(lhs, e); } } - (lhs, FlowType::Builtin(FlowBuiltinType::Stroke)) => { + (lhs, Ty::Builtin(BuiltinTy::Stroke)) => { // empty array is also a constructing dict but we can safely ignore it during // type checking, since no fields are added yet. if lhs.is_dict() { self.constrain(lhs, &FLOW_STROKE_DICT_TYPE); } } - (FlowType::Builtin(FlowBuiltinType::Stroke), rhs) => { + (Ty::Builtin(BuiltinTy::Stroke), rhs) => { if rhs.is_dict() { self.constrain(&FLOW_STROKE_DICT_TYPE, rhs); } } - (lhs, FlowType::Builtin(FlowBuiltinType::Margin)) => { + (lhs, Ty::Builtin(BuiltinTy::Margin)) => { if lhs.is_dict() { self.constrain(lhs, &FLOW_MARGIN_DICT_TYPE); } } - (FlowType::Builtin(FlowBuiltinType::Margin), rhs) => { + (Ty::Builtin(BuiltinTy::Margin), rhs) => { if rhs.is_dict() { self.constrain(&FLOW_MARGIN_DICT_TYPE, rhs); } } - (lhs, FlowType::Builtin(FlowBuiltinType::Inset)) => { + (lhs, Ty::Builtin(BuiltinTy::Inset)) => { if lhs.is_dict() { self.constrain(lhs, &FLOW_INSET_DICT_TYPE); } } - (FlowType::Builtin(FlowBuiltinType::Inset), rhs) => { + (Ty::Builtin(BuiltinTy::Inset), rhs) => { if rhs.is_dict() { self.constrain(&FLOW_INSET_DICT_TYPE, rhs); } } - (lhs, FlowType::Builtin(FlowBuiltinType::Outset)) => { + (lhs, Ty::Builtin(BuiltinTy::Outset)) => { if lhs.is_dict() { self.constrain(lhs, &FLOW_OUTSET_DICT_TYPE); } } - (FlowType::Builtin(FlowBuiltinType::Outset), rhs) => { + (Ty::Builtin(BuiltinTy::Outset), rhs) => { if rhs.is_dict() { self.constrain(&FLOW_OUTSET_DICT_TYPE, rhs); } } - (lhs, FlowType::Builtin(FlowBuiltinType::Radius)) => { + (lhs, Ty::Builtin(BuiltinTy::Radius)) => { if lhs.is_dict() { self.constrain(lhs, &FLOW_RADIUS_DICT_TYPE); } } - (FlowType::Builtin(FlowBuiltinType::Radius), rhs) => { + (Ty::Builtin(BuiltinTy::Radius), rhs) => { if rhs.is_dict() { self.constrain(&FLOW_RADIUS_DICT_TYPE, rhs); } } - (FlowType::Dict(lhs), FlowType::Dict(rhs)) => { - for ((key, lhs, sl), (_, rhs, sr)) in lhs.intersect_keys(rhs) { + (Ty::Dict(lhs), Ty::Dict(rhs)) => { + for (key, lhs, rhs) in lhs.intersect_keys(rhs) { log::debug!("constrain record item {key} {lhs:?} ⪯ {rhs:?}"); self.constrain(lhs, rhs); - if !sl.is_detached() { - self.info.witness_at_most(*sl, rhs.clone()); - } - if !sr.is_detached() { - self.info.witness_at_least(*sr, lhs.clone()); - } + // if !sl.is_detached() { + // self.info.witness_at_most(*sl, rhs.clone()); + // } + // if !sr.is_detached() { + // self.info.witness_at_least(*sr, lhs.clone()); + // } } } - (FlowType::Value(lhs), rhs) => { - log::debug!("constrain value {lhs:?} ⪯ {rhs:?}"); - if !lhs.1.is_detached() { - self.info.witness_at_most(lhs.1, rhs.clone()); - } + (Ty::Unary(lhs), Ty::Unary(rhs)) if lhs.op == rhs.op => { + // todo: more information could be extracted from unary constraint structure + // e.g. type(l) == type(r) + self.constrain(&lhs.lhs, &rhs.lhs); } - (lhs, FlowType::Value(rhs)) => { + (Ty::Unary(lhs), rhs) if lhs.op == UnaryOp::TypeOf && is_ty(rhs) => { + log::debug!("constrain type of {lhs:?} ⪯ {rhs:?}"); + + self.constrain(&lhs.lhs, rhs); + } + (lhs, Ty::Unary(rhs)) if rhs.op == UnaryOp::TypeOf && is_ty(lhs) => { + log::debug!( + "constrain type of {lhs:?} ⪯ {rhs:?} {:?}", + matches!(lhs, Ty::Builtin(..)) + ); + self.constrain(lhs, &rhs.lhs); + } + (Ty::Value(lhs), rhs) => { log::debug!("constrain value {lhs:?} ⪯ {rhs:?}"); - if !rhs.1.is_detached() { - self.info.witness_at_least(rhs.1, lhs.clone()); - } + let _ = TypeCheckInfo::witness_at_most; + // if !lhs.1.is_detached() { + // self.info.witness_at_most(lhs.1, rhs.clone()); + // } + } + (lhs, Ty::Value(rhs)) => { + log::debug!("constrain value {lhs:?} ⪯ {rhs:?}"); + // if !rhs.1.is_detached() { + // self.info.witness_at_least(rhs.1, lhs.clone()); + // } } _ => { log::debug!("constrain {lhs:?} ⪯ {rhs:?}"); @@ -1043,218 +293,64 @@ impl<'a, 'w> TypeChecker<'a, 'w> { } } - fn check_primary_type(&self, e: FlowType) -> FlowType { - match &e { - FlowType::Var(v) => { - let w = self.info.vars.get(&v.0).unwrap(); - match &w.kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - let w = w.read(); - if !w.ubs.is_empty() { - return w.ubs[0].clone(); - } - if !w.lbs.is_empty() { - return w.lbs[0].clone(); - } - FlowType::Any - } - } - } - FlowType::Func(..) => e, - FlowType::Dict(..) => e, - FlowType::With(..) => e, - FlowType::Args(..) => e, - FlowType::Union(..) => e, - FlowType::Let(_) => e, - FlowType::Value(..) => e, - FlowType::ValueDoc(..) => e, - - FlowType::Tuple(..) => e, - FlowType::Array(..) => e, - FlowType::Field(..) => e, - FlowType::Clause => e, - FlowType::Undef => e, - FlowType::Content => e, - FlowType::Any => e, - FlowType::None => e, - FlowType::Infer => e, - FlowType::FlowNone => e, - FlowType::Space => e, - FlowType::Auto => e, - FlowType::Builtin(_) => e, - FlowType::At(e) => self.check_primary_type(e.0 .0.clone()), - FlowType::Unary(_) => e, - FlowType::Binary(_) => e, - FlowType::Boolean(_) => e, - FlowType::If(_) => e, - FlowType::Element(_) => e, - } - } - - fn check_apply_method( - &mut self, - primary_type: FlowType, - callee_span: Span, - method_name: EcoString, - args: &FlowArgs, - _candidates: &mut Vec, - ) -> Option<()> { - log::debug!("check method at {method_name:?} on {primary_type:?}"); - self.info - .witness_at_least(callee_span, primary_type.clone()); - match primary_type { - FlowType::Func(v) => match method_name.as_str() { - // todo: process where specially - "with" | "where" => { - // log::debug!("check method at args: {v:?}.with({args:?})"); - - let f = v.as_ref(); - let mut pos = f.pos.iter(); - // let mut named = f.named.clone(); - // let mut rest = f.rest.clone(); - - for pos_in in args.start_match() { - let pos_ty = pos.next().unwrap_or(&FlowType::Any); - self.constrain(pos_in, pos_ty); - } - - for (name, named_in) in &args.named { - let named_ty = f.named.iter().find(|(n, _)| n == name).map(|(_, ty)| ty); - if let Some(named_ty) = named_ty { - self.constrain(named_in, named_ty); - } - } - - _candidates.push(self.partial_apply(f, args)); - } - _ => {} - }, - FlowType::Array(..) => {} - FlowType::Dict(..) => {} - _ => {} - } - - Some(()) - } - - fn check_apply_runtime( - &mut self, - f: &Func, - callee_span: Span, - args: &FlowArgs, - syntax_args: &ast::Args, - candidates: &mut Vec, - ) -> Option<()> { - // todo: hold signature - self.info.witness_at_least( - callee_span, - FlowType::Value(Box::new((Value::Func(f.clone()), Span::detached()))), - ); - let sig = analyze_dyn_signature(self.ctx, f.clone()); - - log::debug!("check runtime func {f:?} at args: {args:?}"); - - let mut pos = sig - .primary() - .pos - .iter() - .map(|e| e.infer_type.as_ref().unwrap_or(&FlowType::Any)); - let mut syntax_pos = syntax_args.items().filter_map(|arg| match arg { - ast::Arg::Pos(e) => Some(e), - _ => None, - }); - - for pos_in in args.start_match() { - let pos_ty = pos.next().unwrap_or(&FlowType::Any); - self.constrain(pos_in, pos_ty); - if let Some(syntax_pos) = syntax_pos.next() { - self.info - .witness_at_least(syntax_pos.span(), pos_ty.clone()); - } - } - - for (name, named_in) in &args.named { - let named_ty = sig - .primary() - .named - .get(name.as_ref()) - .and_then(|e| e.infer_type.as_ref()); - let syntax_named = syntax_args - .items() - .filter_map(|arg| match arg { - ast::Arg::Named(n) => Some(n), - _ => None, - }) - .find(|n| n.name().get() == name.as_ref()); - if let Some(named_ty) = named_ty { - self.constrain(named_in, named_ty); - if let Some(syntax_named) = syntax_named { - self.info - .witness_at_least(syntax_named.span(), named_ty.clone()); - self.info - .witness_at_least(syntax_named.expr().span(), named_ty.clone()); - } - } - } - - candidates.push(sig.primary().ret_ty.clone().unwrap_or(FlowType::Any)); - - Some(()) - } - - fn partial_apply(&self, f: &FlowSignature, args: &FlowArgs) -> FlowType { - FlowType::With(Box::new(( - FlowType::Func(Box::new(f.clone())), - vec![args.clone()], - ))) - } - - fn check_comparable(&self, lhs: &FlowType, rhs: &FlowType) { + fn check_comparable(&self, lhs: &Ty, rhs: &Ty) { let _ = lhs; let _ = rhs; } - fn check_assignable(&self, lhs: &FlowType, rhs: &FlowType) { + fn check_assignable(&self, lhs: &Ty, rhs: &Ty) { let _ = lhs; let _ = rhs; } - fn check_containing(&self, container: &FlowType, elem: &FlowType, expected_in: bool) { - let _ = container; - let _ = elem; - let _ = expected_in; + fn check_containing(&mut self, container: &Ty, elem: &Ty, expected_in: bool) { + let rhs = if expected_in { + match container { + Ty::Tuple(elements) => Ty::Union(elements.clone()), + _ => Ty::Unary(Interned::new(TypeUnary { + op: UnaryOp::ElementOf, + lhs: Interned::new(container.clone()), + })), + } + } else { + Ty::Unary(Interned::new(TypeUnary { + // todo: remove not element of + op: UnaryOp::NotElementOf, + lhs: Interned::new(container.clone()), + })) + }; + + self.constrain(elem, &rhs); } - fn possible_ever_be(&mut self, lhs: &FlowType, rhs: &FlowType) { + fn possible_ever_be(&mut self, lhs: &Ty, rhs: &Ty) { // todo: instantiataion match rhs { - FlowType::Undef - | FlowType::Content - | FlowType::None - | FlowType::FlowNone - | FlowType::Auto - | FlowType::Element(..) - | FlowType::Builtin(..) - | FlowType::Value(..) - | FlowType::Boolean(..) - | FlowType::ValueDoc(..) => { + Ty::Undef + | Ty::Content + | Ty::None + | Ty::FlowNone + | Ty::Auto + | Ty::Builtin(..) + | Ty::Value(..) + | Ty::Boolean(..) => { self.constrain(rhs, lhs); } _ => {} } } - fn init_var(&mut self, def: Option) -> FlowVarStore { - let mut store = FlowVarStore::default(); + fn init_var(&mut self, def: Option) -> TypeBounds { + let mut store = TypeBounds::default(); let Some(def) = def else { return store; }; match def { - FlowType::Var(v) => { - let w = self.info.vars.get(&v.0).unwrap(); - match &w.kind { + Ty::Var(v) => { + let w = self.info.vars.get(&v.def).unwrap(); + match &w.bounds { FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { let w = w.read(); store.lbs.extend(w.lbs.iter().cloned()); @@ -1262,7 +358,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> { } } } - FlowType::Let(v) => { + Ty::Let(v) => { store.lbs.extend(v.lbs.iter().cloned()); store.ubs.extend(v.ubs.iter().cloned()); } @@ -1274,95 +370,73 @@ impl<'a, 'w> TypeChecker<'a, 'w> { store } - fn weaken(&mut self, v: &FlowType) { + fn weaken(&mut self, v: &Ty) { match v { - FlowType::Var(v) => { - let w = self.info.vars.get_mut(&v.0).unwrap(); + Ty::Var(v) => { + let w = self.info.vars.get_mut(&v.def).unwrap(); w.weaken(); } - FlowType::Clause - | FlowType::Undef - | FlowType::Content - | FlowType::Any - | FlowType::Space - | FlowType::None - | FlowType::Infer - | FlowType::FlowNone - | FlowType::Auto - | FlowType::Boolean(_) - | FlowType::Builtin(_) - | FlowType::Value(_) => {} - FlowType::Element(_) => {} - FlowType::ValueDoc(_) => {} - FlowType::Field(v) => { - self.weaken(&v.1); + Ty::Clause + | Ty::Undef + | Ty::Content + | Ty::Any + | Ty::Space + | Ty::None + | Ty::Infer + | Ty::FlowNone + | Ty::Auto + | Ty::Boolean(_) + | Ty::Builtin(_) + | Ty::Value(_) => {} + Ty::Field(v) => { + self.weaken(&v.field); } - FlowType::Func(v) => { - for ty in v.pos.iter() { - self.weaken(ty); - } - for (_, ty) in v.named.iter() { - self.weaken(ty); - } - if let Some(ty) = &v.rest { - self.weaken(ty); - } - self.weaken(&v.ret); - } - FlowType::Dict(v) => { - for (_, ty, _) in v.fields.iter() { + Ty::Func(v) | Ty::Args(v) => { + for ty in v.inputs() { self.weaken(ty); } } - FlowType::Array(v) => { + Ty::With(v) => { + self.weaken(&v.sig); + for ty in v.with.inputs() { + self.weaken(ty); + } + } + Ty::Dict(v) => { + for (_, ty) in v.interface() { + self.weaken(ty); + } + } + Ty::Array(v) => { self.weaken(v); } - FlowType::Tuple(v) => { + Ty::Tuple(v) => { for ty in v.iter() { self.weaken(ty); } } - FlowType::With(v) => { - self.weaken(&v.0); - for args in v.1.iter() { - for ty in args.args.iter() { - self.weaken(ty); - } - for (_, ty) in args.named.iter() { - self.weaken(ty); - } - } + Ty::Select(v) => { + self.weaken(&v.ty); } - FlowType::Args(v) => { - for ty in v.args.iter() { - self.weaken(ty); - } - for (_, ty) in v.named.iter() { - self.weaken(ty); - } - } - FlowType::At(v) => { - self.weaken(&v.0 .0); - } - FlowType::Unary(v) => { + Ty::Unary(v) => { self.weaken(&v.lhs); } - FlowType::Binary(v) => { + Ty::Binary(v) => { let (lhs, rhs) = v.repr(); self.weaken(lhs); self.weaken(rhs); } - FlowType::If(v) => { + Ty::If(v) => { self.weaken(&v.cond); self.weaken(&v.then); self.weaken(&v.else_); } - FlowType::Union(v) => { + Ty::Union(v) => { for ty in v.iter() { self.weaken(ty); } } - FlowType::Let(v) => { + Ty::Let(v) => { for ty in v.lbs.iter() { self.weaken(ty); } @@ -1373,516 +447,19 @@ impl<'a, 'w> TypeChecker<'a, 'w> { } } - fn weaken_constraint(&self, c: &FlowType, kind: &FlowVarKind) -> FlowType { + fn weaken_constraint(&self, c: &Ty, kind: &FlowVarKind) -> Ty { if matches!(kind, FlowVarKind::Strong(_)) { return c.clone(); } - if let FlowType::Value(v) = c { - return FlowBuiltinType::from_value(&v.0); + if let Ty::Value(v) = c { + return BuiltinTy::from_value(&v.val); } c.clone() } } -#[derive(Default)] -struct TypeCanoStore { - cano_cache: HashMap<(u128, bool), FlowType>, - cano_local_cache: HashMap<(DefId, bool), FlowType>, - negatives: HashSet, - positives: HashSet, -} - -#[derive(Default)] -struct TypeDescriber { - described: HashMap, - results: HashSet, - functions: Vec, -} - -impl TypeDescriber { - fn describe_root(&mut self, ty: &FlowType) -> Option { - // recursive structure - if let Some(t) = self.described.get(&hash128(ty)) { - return Some(t.clone()); - } - - let res = self.describe(ty); - if !res.is_empty() { - return Some(res); - } - self.described.insert(hash128(ty), "$self".to_string()); - - let mut results = std::mem::take(&mut self.results) - .into_iter() - .collect::>(); - let functions = std::mem::take(&mut self.functions); - if !functions.is_empty() { - // todo: union signature - // only first function is described - let f = functions[0].clone(); - - let mut res = String::new(); - res.push('('); - let mut not_first = false; - for ty in f.pos.iter() { - if not_first { - res.push_str(", "); - } else { - not_first = true; - } - res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); - } - for (k, ty) in f.named.iter() { - if not_first { - res.push_str(", "); - } else { - not_first = true; - } - res.push_str(k); - res.push_str(": "); - res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); - } - if let Some(r) = &f.rest { - if not_first { - res.push_str(", "); - } - res.push_str("..: "); - res.push_str(self.describe_root(r).as_deref().unwrap_or("")); - res.push_str("[]"); - } - res.push_str(") => "); - res.push_str(self.describe_root(&f.ret).as_deref().unwrap_or("any")); - results.push(res); - } - - if results.is_empty() { - self.described.insert(hash128(ty), "any".to_string()); - return None; - } - - results.sort(); - results.dedup(); - let res = results.join(" | "); - self.described.insert(hash128(ty), res.clone()); - Some(res) - } - - fn describe_iter(&mut self, ty: &[FlowType]) { - for ty in ty.iter() { - let desc = self.describe(ty); - if !desc.is_empty() { - self.results.insert(desc); - } - } - } - - fn describe(&mut self, ty: &FlowType) -> String { - match ty { - FlowType::Var(..) => {} - FlowType::Union(tys) => { - self.describe_iter(tys); - } - FlowType::Let(lb) => { - self.describe_iter(&lb.lbs); - self.describe_iter(&lb.ubs); - } - FlowType::Func(f) => { - self.functions.push(*f.clone()); - } - FlowType::Dict(..) => { - return "dict".to_string(); - } - FlowType::Tuple(..) => { - return "array".to_string(); - } - FlowType::Array(..) => { - return "array".to_string(); - } - FlowType::With(w) => { - return self.describe(&w.0); - } - FlowType::Clause => {} - FlowType::Undef => {} - FlowType::Content => { - return "content".to_string(); - } - // Doesn't provide any information, hence we doesn't describe it intermediately here. - FlowType::Any => {} - FlowType::Space => {} - FlowType::None => { - return "none".to_string(); - } - FlowType::Infer => {} - FlowType::FlowNone => { - return "none".to_string(); - } - FlowType::Auto => { - return "auto".to_string(); - } - FlowType::Boolean(None) => { - return "boolean".to_string(); - } - FlowType::Boolean(Some(b)) => { - return b.to_string(); - } - FlowType::Builtin(b) => { - return b.describe().to_string(); - } - FlowType::Value(v) => return v.0.repr().to_string(), - FlowType::ValueDoc(v) => return v.0.repr().to_string(), - FlowType::Field(..) => { - return "field".to_string(); - } - FlowType::Element(..) => { - return "element".to_string(); - } - FlowType::Args(..) => { - return "args".to_string(); - } - FlowType::At(..) => { - return "any".to_string(); - } - FlowType::Unary(..) => { - return "any".to_string(); - } - FlowType::Binary(..) => { - return "any".to_string(); - } - FlowType::If(..) => { - return "any".to_string(); - } - } - - String::new() - } -} - -struct TypeSimplifier<'a, 'b> { - principal: bool, - - vars: &'a HashMap, - - cano_cache: &'b mut HashMap<(u128, bool), FlowType>, - cano_local_cache: &'b mut HashMap<(DefId, bool), FlowType>, - negatives: &'b mut HashSet, - positives: &'b mut HashSet, -} - -impl<'a, 'b> TypeSimplifier<'a, 'b> { - fn simplify(&mut self, ty: FlowType, principal: bool) -> FlowType { - // todo: hash safety - let ty_key = hash128(&ty); - if let Some(cano) = self.cano_cache.get(&(ty_key, principal)) { - return cano.clone(); - } - - self.analyze(&ty, true); - - self.transform(&ty, true) - } - - fn analyze(&mut self, ty: &FlowType, pol: bool) { - match ty { - FlowType::Var(v) => { - let w = self.vars.get(&v.0).unwrap(); - match &w.kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - let w = w.read(); - let inserted = if pol { - self.positives.insert(v.0) - } else { - self.negatives.insert(v.0) - }; - if !inserted { - return; - } - - if pol { - for lb in w.lbs.iter() { - self.analyze(lb, pol); - } - } else { - for ub in w.ubs.iter() { - self.analyze(ub, pol); - } - } - } - } - } - FlowType::Func(f) => { - for p in &f.pos { - self.analyze(p, !pol); - } - for (_, p) in &f.named { - self.analyze(p, !pol); - } - if let Some(r) = &f.rest { - self.analyze(r, !pol); - } - self.analyze(&f.ret, pol); - } - FlowType::Dict(r) => { - for (_, p, _) in &r.fields { - self.analyze(p, pol); - } - } - FlowType::Tuple(e) => { - for ty in e.iter() { - self.analyze(ty, pol); - } - } - FlowType::Array(e) => { - self.analyze(e, pol); - } - FlowType::With(w) => { - self.analyze(&w.0, pol); - for m in &w.1 { - for arg in m.args.iter() { - self.analyze(arg, pol); - } - for (_, arg) in m.named.iter() { - self.analyze(arg, pol); - } - } - } - FlowType::Args(args) => { - for arg in &args.args { - self.analyze(arg, pol); - } - } - FlowType::Unary(u) => self.analyze(u.lhs(), pol), - FlowType::Binary(b) => { - let (lhs, rhs) = b.repr(); - self.analyze(lhs, pol); - self.analyze(rhs, pol); - } - FlowType::If(i) => { - self.analyze(&i.cond, pol); - self.analyze(&i.then, pol); - self.analyze(&i.else_, pol); - } - FlowType::Union(v) => { - for ty in v.iter() { - self.analyze(ty, pol); - } - } - FlowType::At(a) => { - self.analyze(&a.0 .0, pol); - } - FlowType::Let(v) => { - for lb in v.lbs.iter() { - self.analyze(lb, !pol); - } - for ub in v.ubs.iter() { - self.analyze(ub, pol); - } - } - FlowType::Field(v) => { - self.analyze(&v.1, pol); - } - FlowType::Value(_v) => {} - FlowType::ValueDoc(_v) => {} - FlowType::Clause => {} - FlowType::Undef => {} - FlowType::Content => {} - FlowType::Any => {} - FlowType::None => {} - FlowType::Infer => {} - FlowType::FlowNone => {} - FlowType::Space => {} - FlowType::Auto => {} - FlowType::Boolean(_) => {} - FlowType::Builtin(_) => {} - FlowType::Element(_) => {} - } - } - - fn transform(&mut self, ty: &FlowType, pol: bool) -> FlowType { - match ty { - // todo - FlowType::Let(w) => self.transform_let(w, None, pol), - FlowType::Var(v) => { - if let Some(cano) = self.cano_local_cache.get(&(v.0, self.principal)) { - return cano.clone(); - } - // todo: avoid cycle - self.cano_local_cache - .insert((v.0, self.principal), FlowType::Any); - - let res = match &self.vars.get(&v.0).unwrap().kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - let w = w.read(); - - self.transform_let(&w, Some(&v.0), pol) - } - }; - - self.cano_local_cache - .insert((v.0, self.principal), res.clone()); - - res - } - FlowType::Func(f) => { - let pos = f.pos.iter().map(|p| self.transform(p, !pol)).collect(); - let named = f - .named - .iter() - .map(|(n, p)| (n.clone(), self.transform(p, !pol))) - .collect(); - let rest = f.rest.as_ref().map(|r| self.transform(r, !pol)); - let ret = self.transform(&f.ret, pol); - - FlowType::Func(Box::new(FlowSignature { - pos, - named, - rest, - ret, - })) - } - FlowType::Dict(f) => { - let fields = f - .fields - .iter() - .map(|p| (p.0.clone(), self.transform(&p.1, !pol), p.2)) - .collect(); - - FlowType::Dict(FlowRecord { fields }) - } - FlowType::Tuple(e) => { - let e2 = e.iter().map(|ty| self.transform(ty, pol)).collect(); - - FlowType::Tuple(e2) - } - FlowType::Array(e) => { - let e2 = self.transform(e, pol); - - FlowType::Array(Box::new(e2)) - } - FlowType::With(w) => { - let primary = self.transform(&w.0, pol); - let args = - w.1.iter() - .map(|a| { - let args_res = a.args.iter().map(|a| self.transform(a, pol)).collect(); - let named = a - .named - .iter() - .map(|(n, a)| (n.clone(), self.transform(a, pol))) - .collect(); - - FlowArgs { - args: args_res, - named, - } - }) - .collect(); - FlowType::With(Box::new((primary, args))) - } - FlowType::Args(args) => { - let args_res = args.args.iter().map(|a| self.transform(a, pol)).collect(); - let named = args - .named - .iter() - .map(|(n, a)| (n.clone(), self.transform(a, pol))) - .collect(); - - FlowType::Args(Box::new(FlowArgs { - args: args_res, - named, - })) - } - FlowType::Unary(u) => { - let lhs = self.transform(u.lhs(), pol); - FlowType::Unary(FlowUnaryType { - op: u.op, - lhs: Box::new(lhs), - }) - } - FlowType::Binary(b) => { - let (lhs, rhs) = b.repr(); - let lhs = self.transform(lhs, pol); - let rhs = self.transform(rhs, pol); - - FlowType::Binary(FlowBinaryType { - op: b.op, - operands: Box::new((lhs, rhs)), - }) - } - FlowType::If(i) => { - let i2 = *i.clone(); - - FlowType::If(Box::new(FlowIfType { - cond: self.transform(&i2.cond, pol), - then: self.transform(&i2.then, pol), - else_: self.transform(&i2.else_, pol), - })) - } - FlowType::Union(v) => { - let v2 = v.iter().map(|ty| self.transform(ty, pol)).collect(); - - FlowType::Union(Box::new(v2)) - } - FlowType::Field(f) => { - let (x, y, z) = *f.clone(); - - FlowType::Field(Box::new((x, self.transform(&y, pol), z))) - } - FlowType::At(a) => { - let FlowAt(at) = a.clone(); - let atee = self.transform(&at.0, pol); - - FlowType::At(FlowAt(Box::new((atee, at.1)))) - } - - FlowType::Value(v) => FlowType::Value(v.clone()), - FlowType::ValueDoc(v) => FlowType::ValueDoc(v.clone()), - FlowType::Element(v) => FlowType::Element(*v), - FlowType::Clause => FlowType::Clause, - FlowType::Undef => FlowType::Undef, - FlowType::Content => FlowType::Content, - FlowType::Any => FlowType::Any, - FlowType::None => FlowType::None, - FlowType::Infer => FlowType::Infer, - FlowType::FlowNone => FlowType::FlowNone, - FlowType::Space => FlowType::Space, - FlowType::Auto => FlowType::Auto, - FlowType::Boolean(b) => FlowType::Boolean(*b), - FlowType::Builtin(b) => FlowType::Builtin(b.clone()), - } - } - - fn transform_let(&mut self, w: &FlowVarStore, def_id: Option<&DefId>, pol: bool) -> FlowType { - let mut lbs = EcoVec::with_capacity(w.lbs.len()); - let mut ubs = EcoVec::with_capacity(w.ubs.len()); - - log::debug!("transform let [principal={}] with {w:?}", self.principal); - - if !self.principal || ((pol) && !def_id.is_some_and(|i| self.negatives.contains(i))) { - for lb in w.lbs.iter() { - lbs.push(self.transform(lb, pol)); - } - } - if !self.principal || ((!pol) && !def_id.is_some_and(|i| self.positives.contains(i))) { - for ub in w.ubs.iter() { - ubs.push(self.transform(ub, !pol)); - } - } - - if ubs.is_empty() { - if lbs.len() == 1 { - return lbs.pop().unwrap(); - } - if lbs.is_empty() { - return FlowType::Any; - } - } - - FlowType::Let(Arc::new(FlowVarStore { lbs, ubs })) - } -} - fn to_ident_ref(root: &LinkedNode, c: ast::Ident) -> Option { Some(IdentRef { name: c.get().to_string(), @@ -1892,11 +469,11 @@ fn to_ident_ref(root: &LinkedNode, c: ast::Ident) -> Option { struct Joiner { break_or_continue_or_return: bool, - definite: FlowType, - possibles: Vec, + definite: Ty, + possibles: Vec, } impl Joiner { - fn finalize(self) -> FlowType { + fn finalize(self) -> Ty { log::debug!("join: {:?} {:?}", self.possibles, self.definite); if self.possibles.is_empty() { return self.definite; @@ -1912,68 +489,64 @@ impl Joiner { // log::debug!("possibles: {:?} {:?}", self.definite, self.possibles); - FlowType::Any + Ty::Any } - fn join(&mut self, child: FlowType) { + fn join(&mut self, child: Ty) { if self.break_or_continue_or_return { return; } match (child, &self.definite) { - (FlowType::Clause, _) => {} - (FlowType::Undef, _) => {} - (FlowType::Space, _) => {} - (FlowType::Any, _) | (_, FlowType::Any) => {} - (FlowType::Infer, _) => {} - (FlowType::None, _) => {} + (Ty::Clause, _) => {} + (Ty::Undef, _) => {} + (Ty::Space, _) => {} + (Ty::Any, _) | (_, Ty::Any) => {} + (Ty::Infer, _) => {} + (Ty::None, _) => {} // todo: mystery flow none - (FlowType::FlowNone, _) => {} - (FlowType::Content, FlowType::Content) => {} - (FlowType::Content, FlowType::None) => self.definite = FlowType::Content, - (FlowType::Content, _) => self.definite = FlowType::Undef, - (FlowType::Var(v), _) => self.possibles.push(FlowType::Var(v)), + (Ty::FlowNone, _) => {} + (Ty::Content, Ty::Content) => {} + (Ty::Content, Ty::None) => self.definite = Ty::Content, + (Ty::Content, _) => self.definite = Ty::Undef, + (Ty::Var(v), _) => self.possibles.push(Ty::Var(v)), // todo: check possibles - (FlowType::Array(e), FlowType::None) => self.definite = FlowType::Array(e), - (FlowType::Array(..), _) => self.definite = FlowType::Undef, - (FlowType::Tuple(e), FlowType::None) => self.definite = FlowType::Tuple(e), - (FlowType::Tuple(..), _) => self.definite = FlowType::Undef, + (Ty::Array(e), Ty::None) => self.definite = Ty::Array(e), + (Ty::Array(..), _) => self.definite = Ty::Undef, + (Ty::Tuple(e), Ty::None) => self.definite = Ty::Tuple(e), + (Ty::Tuple(..), _) => self.definite = Ty::Undef, // todo: possible some style - (FlowType::Auto, FlowType::None) => self.definite = FlowType::Auto, - (FlowType::Auto, _) => self.definite = FlowType::Undef, - (FlowType::Builtin(b), FlowType::None) => self.definite = FlowType::Builtin(b), - (FlowType::Builtin(..), _) => self.definite = FlowType::Undef, + (Ty::Auto, Ty::None) => self.definite = Ty::Auto, + (Ty::Auto, _) => self.definite = Ty::Undef, + (Ty::Builtin(b), Ty::None) => self.definite = Ty::Builtin(b), + (Ty::Builtin(..), _) => self.definite = Ty::Undef, // todo: value join - (FlowType::Value(v), FlowType::None) => self.definite = FlowType::Value(v), - (FlowType::Value(..), _) => self.definite = FlowType::Undef, - (FlowType::ValueDoc(v), FlowType::None) => self.definite = FlowType::ValueDoc(v), - (FlowType::ValueDoc(..), _) => self.definite = FlowType::Undef, - (FlowType::Element(e), FlowType::None) => self.definite = FlowType::Element(e), - (FlowType::Element(..), _) => self.definite = FlowType::Undef, - (FlowType::Func(f), FlowType::None) => self.definite = FlowType::Func(f), - (FlowType::Func(..), _) => self.definite = FlowType::Undef, - (FlowType::Dict(w), FlowType::None) => self.definite = FlowType::Dict(w), - (FlowType::Dict(..), _) => self.definite = FlowType::Undef, - (FlowType::With(w), FlowType::None) => self.definite = FlowType::With(w), - (FlowType::With(..), _) => self.definite = FlowType::Undef, - (FlowType::Args(w), FlowType::None) => self.definite = FlowType::Args(w), - (FlowType::Args(..), _) => self.definite = FlowType::Undef, - (FlowType::At(w), FlowType::None) => self.definite = FlowType::At(w), - (FlowType::At(..), _) => self.definite = FlowType::Undef, - (FlowType::Unary(w), FlowType::None) => self.definite = FlowType::Unary(w), - (FlowType::Unary(..), _) => self.definite = FlowType::Undef, - (FlowType::Binary(w), FlowType::None) => self.definite = FlowType::Binary(w), - (FlowType::Binary(..), _) => self.definite = FlowType::Undef, - (FlowType::If(w), FlowType::None) => self.definite = FlowType::If(w), - (FlowType::If(..), _) => self.definite = FlowType::Undef, - (FlowType::Union(w), FlowType::None) => self.definite = FlowType::Union(w), - (FlowType::Union(..), _) => self.definite = FlowType::Undef, - (FlowType::Let(w), FlowType::None) => self.definite = FlowType::Let(w), - (FlowType::Let(..), _) => self.definite = FlowType::Undef, - (FlowType::Field(w), FlowType::None) => self.definite = FlowType::Field(w), - (FlowType::Field(..), _) => self.definite = FlowType::Undef, - (FlowType::Boolean(b), FlowType::None) => self.definite = FlowType::Boolean(b), - (FlowType::Boolean(..), _) => self.definite = FlowType::Undef, + (Ty::Value(v), Ty::None) => self.definite = Ty::Value(v), + (Ty::Value(..), _) => self.definite = Ty::Undef, + (Ty::Func(f), Ty::None) => self.definite = Ty::Func(f), + (Ty::Func(..), _) => self.definite = Ty::Undef, + (Ty::Dict(w), Ty::None) => self.definite = Ty::Dict(w), + (Ty::Dict(..), _) => self.definite = Ty::Undef, + (Ty::With(w), Ty::None) => self.definite = Ty::With(w), + (Ty::With(..), _) => self.definite = Ty::Undef, + (Ty::Args(w), Ty::None) => self.definite = Ty::Args(w), + (Ty::Args(..), _) => self.definite = Ty::Undef, + (Ty::Select(w), Ty::None) => self.definite = Ty::Select(w), + (Ty::Select(..), _) => self.definite = Ty::Undef, + (Ty::Unary(w), Ty::None) => self.definite = Ty::Unary(w), + (Ty::Unary(..), _) => self.definite = Ty::Undef, + (Ty::Binary(w), Ty::None) => self.definite = Ty::Binary(w), + (Ty::Binary(..), _) => self.definite = Ty::Undef, + (Ty::If(w), Ty::None) => self.definite = Ty::If(w), + (Ty::If(..), _) => self.definite = Ty::Undef, + (Ty::Union(w), Ty::None) => self.definite = Ty::Union(w), + (Ty::Union(..), _) => self.definite = Ty::Undef, + (Ty::Let(w), Ty::None) => self.definite = Ty::Let(w), + (Ty::Let(..), _) => self.definite = Ty::Undef, + (Ty::Field(w), Ty::None) => self.definite = Ty::Field(w), + (Ty::Field(..), _) => self.definite = Ty::Undef, + (Ty::Boolean(b), Ty::None) => self.definite = Ty::Boolean(b), + (Ty::Boolean(..), _) => self.definite = Ty::Undef, } } } @@ -1981,7 +554,7 @@ impl Default for Joiner { fn default() -> Self { Self { break_or_continue_or_return: false, - definite: FlowType::None, + definite: Ty::None, possibles: Vec::new(), } } diff --git a/crates/tinymist-query/src/analysis/ty/apply.rs b/crates/tinymist-query/src/analysis/ty/apply.rs new file mode 100644 index 00000000..186e9da1 --- /dev/null +++ b/crates/tinymist-query/src/analysis/ty/apply.rs @@ -0,0 +1,112 @@ +//! Type checking on source file + +use typst::syntax::{ast, Span}; + +use crate::analysis::Ty; +use crate::ty::Sig; +use crate::{analysis::ApplyChecker, ty::ArgsTy}; + +use super::*; +use crate::adt::interner::Interned; + +pub struct ApplyTypeChecker<'a, 'b, 'w> { + pub(super) base: &'a mut TypeChecker<'b, 'w>, + pub call_site: Span, + pub args: ast::Args<'a>, + pub resultant: Vec, +} + +impl<'a, 'b, 'w> ApplyChecker for ApplyTypeChecker<'a, 'b, 'w> { + fn bound_of_var( + &mut self, + var: &Interned, + _pol: bool, + ) -> Option { + self.base + .info + .vars + .get(&var.def) + .map(|v| v.bounds.bounds().read().clone()) + } + + fn call(&mut self, sig: Sig, args: &Interned, pol: bool) { + let _ = self.args; + + let (sig, is_partialize) = match sig { + Sig::Partialize(sig) => (*sig, true), + sig => (sig, false), + }; + + if let Some(ty) = sig.call(args, pol, Some(self.base.ctx)) { + self.resultant.push(ty); + } + + // todo: remove this after we implemented dependent types + if let Sig::TypeCons { val, .. } = sig { + if *val == typst::foundations::Type::of::() { + if let Some(p0) = args.pos(0) { + self.resultant.push(Ty::Unary(Interned::new(TypeUnary { + op: UnaryOp::TypeOf, + lhs: Interned::new(p0.clone()), + }))); + } + } + } + // let v = val.inner(); + // use typst::foundations::func::Repr; + // if let Repr::Native(v) = v { + // match v.name { + // "assert" => {} + // "panic" => {} + // _ => {} + // } + // } + + let callee = sig.ty(); + + let Some(SigShape { sig, withs }) = sig.shape(Some(self.base.ctx)) else { + return; + }; + for (arg_recv, arg_ins) in sig.matches(args, withs) { + self.base.constrain(arg_ins, arg_recv); + } + + if let Some(callee) = callee.clone() { + self.base.info.witness_at_least(self.call_site, callee); + } + + if is_partialize { + let Some(sig) = callee else { + log::warn!("Partialize is not implemented yet {sig:?}"); + return; + }; + self.resultant.push(Ty::With(Interned::new(SigWithTy { + sig: Interned::new(sig), + with: args.clone(), + }))); + } + + // let f = v.as_ref(); + // let mut pos = f.pos.iter(); + // // let mut named = f.named.clone(); + // // let mut rest = f.rest.clone(); + + // for pos_in in args.start_match() { + // let pos_ty = pos.next().unwrap_or(&FlowType::Any); + // self.constrain(pos_in, pos_ty); + // } + + // for (name, named_in) in &args.named { + // let named_ty = f.named.iter().find(|(n, _)| n == + // name).map(|(_, ty)| ty); if let Some(named_ty) = + // named_ty { self.constrain(named_in, + // named_ty); } + // }' + + // todo: hold signature + // self.info.witness_at_least( + // callee_span, + // FlowType::Value(TypeIns::new(Value::Func(f.clone()))), + // ); + } +} diff --git a/crates/tinymist-query/src/analysis/ty/def.rs b/crates/tinymist-query/src/analysis/ty/def.rs deleted file mode 100644 index b2f3b5d8..00000000 --- a/crates/tinymist-query/src/analysis/ty/def.rs +++ /dev/null @@ -1,511 +0,0 @@ -use core::fmt; -use std::sync::Arc; - -use ecow::{EcoString, EcoVec}; -use parking_lot::RwLock; -use reflexo::vector::ir::DefId; -use typst::{ - foundations::{CastInfo, Element, Func, ParamInfo, Value}, - syntax::{ast, Span}, -}; - -use crate::analysis::ty::param_mapping; - -use super::{FlowBuiltinType, TypeDescriber}; - -struct RefDebug<'a>(&'a FlowType); - -impl<'a> fmt::Debug for RefDebug<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - FlowType::Var(v) => write!(f, "@{}", v.1), - _ => write!(f, "{:?}", self.0), - } - } -} - -#[derive(Hash, Clone)] -#[allow(clippy::box_collection)] -pub(crate) enum FlowType { - Clause, - Undef, - Content, - Any, - Space, - None, - Infer, - FlowNone, - Auto, - Boolean(Option), - Builtin(FlowBuiltinType), - Value(Box<(Value, Span)>), - ValueDoc(Box<(Value, &'static str)>), - Field(Box<(EcoString, FlowType, Span)>), - Element(Element), - - Var(Box<(DefId, EcoString)>), - Func(Box), - Dict(FlowRecord), - Array(Box), - // Note: may contains spread types - Tuple(EcoVec), - With(Box<(FlowType, Vec)>), - Args(Box), - At(FlowAt), - Unary(FlowUnaryType), - Binary(FlowBinaryType), - If(Box), - Union(Box>), - Let(Arc), -} - -impl fmt::Debug for FlowType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FlowType::Clause => f.write_str("Clause"), - FlowType::Undef => f.write_str("Undef"), - FlowType::Content => f.write_str("Content"), - FlowType::Any => f.write_str("Any"), - FlowType::Space => f.write_str("Space"), - FlowType::None => f.write_str("None"), - FlowType::Infer => f.write_str("Infer"), - FlowType::FlowNone => f.write_str("FlowNone"), - FlowType::Auto => f.write_str("Auto"), - FlowType::Builtin(t) => write!(f, "{t:?}"), - FlowType::Args(a) => write!(f, "&({a:?})"), - FlowType::Func(s) => write!(f, "{s:?}"), - FlowType::Dict(r) => write!(f, "{r:?}"), - FlowType::Array(a) => write!(f, "Array<{a:?}>"), - FlowType::Tuple(t) => { - f.write_str("(")?; - for t in t { - write!(f, "{t:?}, ")?; - } - f.write_str(")") - } - FlowType::With(w) => write!(f, "({:?}).with(..{:?})", w.0, w.1), - FlowType::At(a) => write!(f, "{a:?}"), - FlowType::Union(u) => { - f.write_str("(")?; - if let Some((first, u)) = u.split_first() { - write!(f, "{first:?}")?; - for u in u { - write!(f, " | {u:?}")?; - } - } - f.write_str(")") - } - FlowType::Let(v) => write!(f, "({v:?})"), - FlowType::Field(ff) => write!(f, "{:?}: {:?}", ff.0, ff.1), - FlowType::Var(v) => write!(f, "@{}", v.1), - FlowType::Unary(u) => write!(f, "{u:?}"), - FlowType::Binary(b) => write!(f, "{b:?}"), - FlowType::If(i) => write!(f, "{i:?}"), - FlowType::Value(v) => write!(f, "{v:?}", v = v.0), - FlowType::ValueDoc(v) => write!(f, "{v:?}"), - FlowType::Element(e) => write!(f, "{e:?}"), - FlowType::Boolean(b) => { - if let Some(b) = b { - write!(f, "{b}") - } else { - f.write_str("Boolean") - } - } - } - } -} - -impl FlowType { - pub fn from_return_site(f: &Func, c: &'_ CastInfo) -> Option { - use typst::foundations::func::Repr; - match f.inner() { - Repr::Element(e) => return Some(FlowType::Element(*e)), - Repr::Closure(_) => {} - Repr::With(w) => return FlowType::from_return_site(&w.0, c), - Repr::Native(_) => {} - }; - - let ty = match c { - CastInfo::Any => FlowType::Any, - CastInfo::Value(v, doc) => FlowType::ValueDoc(Box::new((v.clone(), *doc))), - CastInfo::Type(ty) => FlowType::Value(Box::new((Value::Type(*ty), Span::detached()))), - CastInfo::Union(e) => { - // flat union - let e = UnionIter(vec![e.as_slice().iter()]); - - FlowType::Union(Box::new( - e.flat_map(|e| Self::from_return_site(f, e)).collect(), - )) - } - }; - - Some(ty) - } - - pub(crate) fn from_param_site(f: &Func, p: &ParamInfo, s: &CastInfo) -> Option { - use typst::foundations::func::Repr; - match f.inner() { - Repr::Element(..) | Repr::Native(..) => { - if let Some(ty) = param_mapping(f, p) { - return Some(ty); - } - } - Repr::Closure(_) => {} - Repr::With(w) => return FlowType::from_param_site(&w.0, p, s), - }; - - let ty = match &s { - CastInfo::Any => FlowType::Any, - CastInfo::Value(v, doc) => FlowType::ValueDoc(Box::new((v.clone(), *doc))), - CastInfo::Type(ty) => FlowType::Value(Box::new((Value::Type(*ty), Span::detached()))), - CastInfo::Union(e) => { - // flat union - let e = UnionIter(vec![e.as_slice().iter()]); - - FlowType::Union(Box::new( - e.flat_map(|e| Self::from_param_site(f, p, e)).collect(), - )) - } - }; - - Some(ty) - } - - pub fn describe(&self) -> Option { - let mut worker = TypeDescriber::default(); - worker.describe_root(self) - } - - pub(crate) fn is_dict(&self) -> bool { - matches!(self, FlowType::Dict(..)) - } - - pub(crate) fn from_types(e: impl ExactSizeIterator) -> Self { - if e.len() == 0 { - FlowType::Any - } else if e.len() == 1 { - let mut e = e; - e.next().unwrap() - } else { - FlowType::Union(Box::new(e.collect())) - } - } -} - -struct UnionIter<'a>(Vec>); - -impl<'a> Iterator for UnionIter<'a> { - type Item = &'a CastInfo; - - fn next(&mut self) -> Option { - loop { - let iter = self.0.last_mut()?; - if let Some(e) = iter.next() { - match e { - CastInfo::Union(e) => { - self.0.push(e.as_slice().iter()); - } - _ => return Some(e), - } - } else { - self.0.pop(); - } - } - } -} - -#[derive(Debug, Clone, Copy, Hash)] -pub(crate) enum UnaryOp { - Pos, - Neg, - Not, - Context, -} - -#[derive(Debug, Clone, Hash)] -pub(crate) struct FlowUnaryType { - pub op: UnaryOp, - pub lhs: Box, -} - -impl FlowUnaryType { - pub fn lhs(&self) -> &FlowType { - &self.lhs - } -} - -#[derive(Debug, Clone, Hash)] -pub(crate) struct FlowBinaryType { - pub op: ast::BinOp, - pub operands: Box<(FlowType, FlowType)>, -} - -impl FlowBinaryType { - pub fn repr(&self) -> (&FlowType, &FlowType) { - (&self.operands.0, &self.operands.1) - } -} - -#[derive(Debug, Clone, Hash)] -pub(crate) struct FlowIfType { - pub cond: FlowType, - pub then: FlowType, - pub else_: FlowType, -} - -impl FlowIfType {} - -#[derive(Clone, Hash, Default)] -pub(crate) struct FlowVarStore { - pub lbs: EcoVec, - pub ubs: EcoVec, -} - -impl fmt::Debug for FlowVarStore { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // write!(f, "{}", self.name) - // also where - if !self.lbs.is_empty() { - write!(f, " ⪰ {:?}", self.lbs[0])?; - for lb in &self.lbs[1..] { - write!(f, " | {lb:?}")?; - } - } - if !self.ubs.is_empty() { - write!(f, " ⪯ {:?}", self.ubs[0])?; - for ub in &self.ubs[1..] { - write!(f, " & {ub:?}")?; - } - } - Ok(()) - } -} - -#[derive(Clone)] -pub(crate) enum FlowVarKind { - Strong(Arc>), - Weak(Arc>), -} - -#[derive(Clone)] -pub(crate) struct FlowVar { - pub name: EcoString, - pub id: DefId, - pub kind: FlowVarKind, -} - -impl std::hash::Hash for FlowVar { - fn hash(&self, state: &mut H) { - 0.hash(state); - self.id.hash(state); - } -} - -impl fmt::Debug for FlowVar { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "@{}", self.name)?; - match &self.kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => write!(f, "{w:?}"), - } - } -} - -impl FlowVar { - pub fn name(&self) -> EcoString { - self.name.clone() - } - - pub fn id(&self) -> DefId { - self.id - } - - pub fn get_ref(&self) -> FlowType { - FlowType::Var(Box::new((self.id, self.name.clone()))) - } - - pub fn ever_be(&self, exp: FlowType) { - match &self.kind { - // FlowVarKind::Strong(_t) => {} - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - let mut w = w.write(); - w.lbs.push(exp.clone()); - } - } - } - - pub(crate) fn weaken(&mut self) { - match &self.kind { - FlowVarKind::Strong(w) => { - self.kind = FlowVarKind::Weak(w.clone()); - } - FlowVarKind::Weak(_) => {} - } - } -} - -#[derive(Hash, Clone)] -pub(crate) struct FlowAt(pub Box<(FlowType, EcoString)>); - -impl fmt::Debug for FlowAt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}.{}", RefDebug(&self.0 .0), self.0 .1) - } -} - -#[derive(Clone, Hash)] -pub(crate) struct FlowArgs { - pub args: Vec, - pub named: Vec<(EcoString, FlowType)>, -} -impl FlowArgs { - pub fn start_match(&self) -> &[FlowType] { - &self.args - } -} - -impl fmt::Debug for FlowArgs { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::fmt::Write; - - f.write_str("&(")?; - if let Some((first, args)) = self.args.split_first() { - write!(f, "{first:?}")?; - for arg in args { - write!(f, "{arg:?}, ")?; - } - } - f.write_char(')') - } -} - -#[derive(Clone, Hash)] -pub(crate) struct FlowSignature { - pub pos: Vec, - pub named: Vec<(EcoString, FlowType)>, - pub rest: Option, - pub ret: FlowType, -} -impl FlowSignature { - /// Array constructor - pub(crate) fn array_cons(elem: FlowType, anyify: bool) -> FlowSignature { - let ret = if anyify { FlowType::Any } else { elem.clone() }; - FlowSignature { - pos: Vec::new(), - named: Vec::new(), - rest: Some(elem), - ret, - } - } - - /// Dictionary constructor - pub(crate) fn dict_cons(named: &FlowRecord, anyify: bool) -> FlowSignature { - let ret = if anyify { - FlowType::Any - } else { - FlowType::Dict(named.clone()) - }; - FlowSignature { - pos: Vec::new(), - named: named - .fields - .clone() - .into_iter() - .map(|(name, ty, _)| (name, ty)) - .collect(), - rest: None, - ret, - } - } - - pub(crate) fn new( - pos: impl Iterator, - named: impl Iterator, - rest: Option, - ret_ty: Option, - ) -> Self { - FlowSignature { - pos: pos.collect(), - named: named.collect(), - rest, - ret: ret_ty.unwrap_or(FlowType::Any), - } - } -} - -impl fmt::Debug for FlowSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("(")?; - if let Some((first, pos)) = self.pos.split_first() { - write!(f, "{first:?}")?; - for p in pos { - write!(f, ", {p:?}")?; - } - } - for (name, ty) in &self.named { - write!(f, ", {name}: {ty:?}")?; - } - if let Some(rest) = &self.rest { - write!(f, ", ...: {rest:?}")?; - } - f.write_str(") -> ")?; - write!(f, "{:?}", self.ret) - } -} - -#[derive(Clone, Hash)] -pub(crate) struct FlowRecord { - pub fields: EcoVec<(EcoString, FlowType, Span)>, -} -impl FlowRecord { - pub(crate) fn intersect_keys_enumerate<'a>( - &'a self, - rhs: &'a FlowRecord, - ) -> impl Iterator + 'a { - let mut lhs = self; - let mut rhs = rhs; - - // size optimization - let mut swapped = false; - if lhs.fields.len() < rhs.fields.len() { - swapped = true; - std::mem::swap(&mut lhs, &mut rhs); - } - - lhs.fields - .iter() - .enumerate() - .filter_map(move |(i, (name, _, _))| { - rhs.fields - .iter() - .position(|(name2, _, _)| name == name2) - .map(|j| (i, j)) - }) - .map(move |(i, j)| if swapped { (j, i) } else { (i, j) }) - } - - pub(crate) fn intersect_keys<'a>( - &'a self, - rhs: &'a FlowRecord, - ) -> impl Iterator + 'a - { - self.intersect_keys_enumerate(rhs) - .filter_map(move |(i, j)| { - self.fields - .get(i) - .and_then(|lhs| rhs.fields.get(j).map(|rhs| (lhs, rhs))) - }) - } -} - -impl fmt::Debug for FlowRecord { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("{")?; - if let Some((first, fields)) = self.fields.split_first() { - write!(f, "{name:?}: {ty:?}", name = first.0, ty = first.1)?; - for (name, ty, _) in fields { - write!(f, ", {name:?}: {ty:?}")?; - } - } - f.write_str("}") - } -} diff --git a/crates/tinymist-query/src/analysis/ty/post_check.rs b/crates/tinymist-query/src/analysis/ty/post_check.rs index 81e9b282..ed7c7f31 100644 --- a/crates/tinymist-query/src/analysis/ty/post_check.rs +++ b/crates/tinymist-query/src/analysis/ty/post_check.rs @@ -1,25 +1,21 @@ //! Infer more than the principal type of some expression. -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; -use typst::{ - foundations::Value, - syntax::{ - ast::{self, AstNode}, - LinkedNode, Span, SyntaxKind, - }, +use hashbrown::HashSet; +use typst::syntax::{ + ast::{self, AstNode}, + LinkedNode, Span, SyntaxKind, }; use crate::{ - analysis::{analyze_dyn_signature, FlowVarStore, Signature}, - syntax::{get_check_target, CheckTarget, ParamTarget}, + adt::interner::Interned, + analysis::{ArgsTy, Sig, SigChecker, SigSurfaceKind, TypeBounds}, + syntax::{get_check_target, get_check_target_by_context, CheckTarget, ParamTarget}, AnalysisContext, }; -use super::{ - FlowArgs, FlowBuiltinType, FlowRecord, FlowSignature, FlowType, FlowVarKind, TypeCheckInfo, - FLOW_INSET_DICT, FLOW_MARGIN_DICT, FLOW_OUTSET_DICT, FLOW_RADIUS_DICT, FLOW_STROKE_DICT, -}; +use super::{FieldTy, SigShape, Ty, TypeCheckInfo}; /// With given type information, check the type of a literal expression again by /// touching the possible related nodes. @@ -27,7 +23,7 @@ pub(crate) fn post_type_check( _ctx: &mut AnalysisContext, info: &TypeCheckInfo, node: LinkedNode, -) -> Option { +) -> Option { let mut worker = PostTypeCheckWorker { ctx: _ctx, checked: HashMap::new(), @@ -37,90 +33,41 @@ pub(crate) fn post_type_check( worker.check(&node) } -enum Abstracted { - Type(T), - Value(V), -} - -type AbstractedSignature<'a> = Abstracted<&'a FlowSignature, &'a Signature>; - -struct SignatureWrapper<'a>(AbstractedSignature<'a>); - -impl<'a> SignatureWrapper<'a> { - fn named(&self, name: &str) -> Option<&FlowType> { - match &self.0 { - Abstracted::Type(sig) => sig.named.iter().find(|(k, _)| k == name).map(|(_, v)| v), - Abstracted::Value(sig) => sig - .primary() - .named - .get(name) - .and_then(|p| p.infer_type.as_ref()), - } - } - - fn names(&self, mut f: impl FnMut(&str)) { - match &self.0 { - Abstracted::Type(sig) => { - for (k, _) in &sig.named { - f(k); - } - } - Abstracted::Value(sig) => { - for (k, p) in &sig.primary().named { - if p.infer_type.is_some() { - f(k); - } - } - } - } - } - - fn pos(&self, pos: usize) -> Option<&FlowType> { - match &self.0 { - Abstracted::Type(sig) => sig.pos.get(pos), - // todo: bindings - Abstracted::Value(sig) => sig - .primary() - .pos - .get(pos) - .and_then(|p| p.infer_type.as_ref()), - } - } - - fn rest(&self) -> Option<&FlowType> { - match &self.0 { - Abstracted::Type(sig) => sig.rest.as_ref(), - Abstracted::Value(sig) => sig - .primary() - .rest - .as_ref() - .and_then(|p| p.infer_type.as_ref()), - } - } -} - #[derive(Default)] -struct SignatureReceiver(FlowVarStore); +struct SignatureReceiver { + lbs_dedup: HashSet, + ubs_dedup: HashSet, + bounds: TypeBounds, +} impl SignatureReceiver { - fn insert(&mut self, ty: &FlowType, pol: bool) { - if pol { - self.0.lbs.push(ty.clone()); - } else { - self.0.ubs.push(ty.clone()); + fn insert(&mut self, ty: &Ty, pol: bool) { + log::debug!("post check receive: {ty:?}"); + if !pol { + if self.lbs_dedup.insert(ty.clone()) { + self.bounds.lbs.push(ty.clone()); + } + } else if self.ubs_dedup.insert(ty.clone()) { + self.bounds.ubs.push(ty.clone()); } } + + fn finalize(self) -> Ty { + Ty::Let(Interned::new(self.bounds)) + } } fn check_signature<'a>( receiver: &'a mut SignatureReceiver, target: &'a ParamTarget, -) -> impl FnMut(&mut PostTypeCheckWorker, SignatureWrapper, &[FlowArgs], bool) -> Option<()> + 'a { - move |_worker, sig, args, pol| { +) -> impl FnMut(&mut PostTypeCheckWorker, Sig, &[Interned], bool) -> Option<()> + 'a { + move |worker, sig, args, pol| { + let SigShape { sig: sig_ins, .. } = sig.shape(Some(worker.ctx))?; + match &target { ParamTarget::Named(n) => { let ident = n.cast::()?; - let ty = sig.named(ident.get())?; + let ty = sig_ins.named(&Interned::new_str(ident.get()))?; receiver.insert(ty, !pol); Some(()) @@ -136,18 +83,21 @@ fn check_signature<'a>( } // truncate args - let c = args.iter().map(|args| args.args.len()).sum::(); - let nth = sig.pos(c + positional).or_else(|| sig.rest())?; - receiver.insert(nth, !pol); + let c = args + .iter() + .map(|args| args.positional_params().len()) + .sum::(); + let nth = sig_ins.pos(c + positional).or_else(|| sig_ins.rest_param()); + if let Some(nth) = nth { + receiver.insert(nth, !pol); + } // names - sig.names(|name| { - // todo: reduce fields - receiver.insert( - &FlowType::Field(Box::new((name.into(), FlowType::Any, Span::detached()))), - !pol, - ); - }); + for (name, _) in sig_ins.named_params() { + // todo: reduce fields, fields ty + let field = FieldTy::new_untyped(name.clone()); + receiver.insert(&Ty::Field(field), !pol); + } Some(()) } @@ -157,12 +107,12 @@ fn check_signature<'a>( struct PostTypeCheckWorker<'a, 'w> { ctx: &'a mut AnalysisContext<'w>, - checked: HashMap>, + checked: HashMap>, info: &'a TypeCheckInfo, } impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { - fn check(&mut self, node: &LinkedNode) -> Option { + fn check(&mut self, node: &LinkedNode) -> Option { let span = node.span(); if let Some(ty) = self.checked.get(&span) { return ty.clone(); @@ -175,7 +125,7 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { ty } - fn check_(&mut self, node: &LinkedNode) -> Option { + fn check_(&mut self, node: &LinkedNode) -> Option { let context = node.parent()?; log::debug!("post check: {:?}::{:?}", context.kind(), node.kind()); let checked_context = self.check_context(context, node); @@ -188,27 +138,19 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { res } - fn check_context_or( - &mut self, - context: &LinkedNode, - context_ty: Option, - ) -> Option { + fn check_context_or(&mut self, context: &LinkedNode, context_ty: Option) -> Option { let checked_context = self.check(context); if checked_context.is_some() && context_ty.is_some() { let c = checked_context?; let s = context_ty?; - Some(FlowType::from_types([c, s].into_iter())) + Some(Ty::from_types([c, s].into_iter())) } else { checked_context.or(context_ty) } } - fn check_target( - &mut self, - node: Option, - context_ty: Option, - ) -> Option { + fn check_target(&mut self, node: Option, context_ty: Option) -> Option { let Some(node) = node else { return context_ty; }; @@ -217,6 +159,7 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { match node { CheckTarget::Param { callee, + args: _, target, is_set, } => { @@ -227,12 +170,12 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { self.check_signatures(&callee, false, &mut check_signature(&mut resp, &target)); - log::debug!("post check target iterated: {:?}", resp.0); - Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false)) + log::debug!("post check target iterated: {:?}", resp.bounds); + Some(self.info.simplify(resp.finalize(), false)) } CheckTarget::Element { container, target } => { let container_ty = self.check_context_or(&container, context_ty)?; - log::debug!("post check element target: {container_ty:?}::{target:?}"); + log::debug!("post check element target: ({container_ty:?})::{target:?}"); let mut resp = SignatureReceiver::default(); @@ -243,18 +186,18 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { &mut check_signature(&mut resp, &target), ); - log::debug!("post check target iterated: {:?}", resp.0); - Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false)) + log::debug!("post check target iterated: {:?}", resp.bounds); + Some(self.info.simplify(resp.finalize(), false)) } CheckTarget::Paren { container, is_before, } => { let container_ty = self.check_context_or(&container, context_ty)?; - log::info!("post check param target: {container_ty:?}::{is_before:?}"); + log::debug!("post check paren target: {container_ty:?}::{is_before:?}"); let mut resp = SignatureReceiver::default(); - resp.0.lbs.push(container_ty.clone()); + resp.bounds.lbs.push(container_ty.clone()); let target = ParamTarget::positional_from_before(true); self.check_element_of( @@ -264,18 +207,18 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { &mut check_signature(&mut resp, &target), ); - log::debug!("post check target iterated: {:?}", resp.0); - Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false)) + log::debug!("post check target iterated: {:?}", resp.bounds); + Some(self.info.simplify(resp.finalize(), false)) } CheckTarget::Normal(target) => { let ty = self.check_context_or(&target, context_ty)?; - log::debug!("post check target: {ty:?}"); + log::debug!("post check target normal: {ty:?}"); Some(ty) } } } - fn check_context(&mut self, context: &LinkedNode, node: &LinkedNode) -> Option { + fn check_context(&mut self, context: &LinkedNode, node: &LinkedNode) -> Option { match context.kind() { SyntaxKind::LetBinding => { let p = context.cast::()?; @@ -291,10 +234,13 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { } } } + SyntaxKind::Args => self.check_target( + // todo: not well behaved + get_check_target_by_context(context.clone(), node.clone()), + None, + ), // todo: constraint node - SyntaxKind::Args | SyntaxKind::Named => { - self.check_target(get_check_target(context.clone()), None) - } + SyntaxKind::Named => self.check_target(get_check_target(context.clone()), None), _ => None, } } @@ -303,33 +249,32 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { &mut self, context: &LinkedNode, node: &LinkedNode, - context_ty: Option, - ) -> Option { + context_ty: Option, + ) -> Option { match node.kind() { SyntaxKind::Ident => { - let ty = self.info.mapping.get(&node.span()); + let ty = self.info.type_of_span(node.span()); log::debug!("post check ident: {node:?} -> {ty:?}"); - self.simplify(ty?) + self.simplify(&ty?) } // todo: destructuring SyntaxKind::FieldAccess => { - let ty = self.info.mapping.get(&node.span()); - self.simplify(ty?) + let ty = self.info.type_of_span(node.span()); + self.simplify(&ty?) .or_else(|| self.check_context_or(context, context_ty)) } _ => self.check_target(get_check_target(node.clone()), context_ty), } } - fn destruct_let(&mut self, pattern: ast::Pattern, node: LinkedNode) -> Option { + fn destruct_let(&mut self, pattern: ast::Pattern, node: LinkedNode) -> Option { match pattern { ast::Pattern::Placeholder(_) => None, ast::Pattern::Normal(n) => { let ast::Expr::Ident(ident) = n else { return None; }; - let ty = self.info.mapping.get(&ident.span())?; - self.simplify(ty) + self.simplify(&self.info.type_of_span(ident.span())?) } ast::Pattern::Parenthesized(p) => { self.destruct_let(p.expr().to_untyped().cast()?, node) @@ -344,162 +289,63 @@ impl<'a, 'w> PostTypeCheckWorker<'a, 'w> { fn check_signatures( &mut self, - ty: &FlowType, + ty: &Ty, pol: bool, - checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>, + checker: &mut impl FnMut(&mut Self, Sig, &[Interned], bool) -> Option<()>, ) { - self.check_signatures_(ty, pol, SigParamKind::Call, &mut Vec::new(), checker); + ty.sig_surface(pol, SigSurfaceKind::Call, &mut (self, checker)); } - fn check_element_of( - &mut self, - ty: &FlowType, - pol: bool, - context: &LinkedNode, - checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>, - ) { - self.check_signatures_(ty, pol, sig_context_of(context), &mut Vec::new(), checker); + fn check_element_of(&mut self, ty: &Ty, pol: bool, context: &LinkedNode, checker: &mut T) + where + T: FnMut(&mut Self, Sig, &[Interned], bool) -> Option<()>, + { + ty.sig_surface(pol, sig_context_of(context), &mut (self, checker)) } - fn check_signatures_( - &mut self, - ty: &FlowType, - pol: bool, - sig_kind: SigParamKind, - args: &mut Vec, - checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>, - ) { - match ty { - FlowType::Builtin(FlowBuiltinType::Stroke) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(&FLOW_STROKE_DICT, pol, checker); - } - FlowType::Builtin(FlowBuiltinType::Margin) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(&FLOW_MARGIN_DICT, pol, checker); - } - FlowType::Builtin(FlowBuiltinType::Inset) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(&FLOW_INSET_DICT, pol, checker); - } - FlowType::Builtin(FlowBuiltinType::Outset) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(&FLOW_OUTSET_DICT, pol, checker); - } - FlowType::Builtin(FlowBuiltinType::Radius) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(&FLOW_RADIUS_DICT, pol, checker); - } - FlowType::Func(sig) if sig_kind == SigParamKind::Call => { - checker(self, SignatureWrapper(Abstracted::Type(sig)), args, pol); - } - FlowType::Array(sig) - if matches!(sig_kind, SigParamKind::Array | SigParamKind::ArrayOrDict) => - { - let sig = FlowSignature::array_cons(*sig.clone(), true); - checker(self, SignatureWrapper(Abstracted::Type(&sig)), args, pol); - } - FlowType::Dict(sig) - if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) => - { - self.check_dict_signature(sig, pol, checker); - } - FlowType::With(w) if sig_kind == SigParamKind::Call => { - let c = args.len(); - args.extend(w.1.iter().cloned()); - self.check_signatures_(&w.0, pol, sig_kind, args, checker); - args.truncate(c); - } - FlowType::Union(u) => { - for ty in u.iter() { - self.check_signatures_(ty, pol, sig_kind, args, checker); - } - } - FlowType::Let(u) => { - for lb in &u.ubs { - self.check_signatures_(lb, pol, sig_kind, args, checker); - } - for ub in &u.lbs { - self.check_signatures_(ub, !pol, sig_kind, args, checker); - } - } - FlowType::Var(u) => { - let Some(v) = self.info.vars.get(&u.0) else { - return; - }; - match &v.kind { - FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { - let r = w.read(); - for lb in &r.ubs { - self.check_signatures_(lb, pol, sig_kind, args, checker); - } - for ub in &r.lbs { - self.check_signatures_(ub, !pol, sig_kind, args, checker); - } - } - } - } - // todo: deduplicate checking early - FlowType::Value(v) => { - if sig_kind == SigParamKind::Call { - if let Value::Func(f) = &v.0 { - let sig = analyze_dyn_signature(self.ctx, f.clone()); - checker(self, SignatureWrapper(Abstracted::Value(&sig)), args, pol); - } - } - } - FlowType::ValueDoc(v) => { - if sig_kind == SigParamKind::Call { - if let Value::Func(f) = &v.0 { - let sig = analyze_dyn_signature(self.ctx, f.clone()); - checker(self, SignatureWrapper(Abstracted::Value(&sig)), args, pol); - } - } - } - _ => {} - } - } - - fn check_dict_signature( - &mut self, - sig: &FlowRecord, - pol: bool, - checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>, - ) { - let sig = FlowSignature::dict_cons(sig, true); - checker(self, SignatureWrapper(Abstracted::Type(&sig)), &[], pol); - } - - fn simplify(&mut self, ty: &FlowType) -> Option { + fn simplify(&mut self, ty: &Ty) -> Option { Some(self.info.simplify(ty.clone(), false)) } } -fn sig_context_of(context: &LinkedNode) -> SigParamKind { - match context.kind() { - SyntaxKind::Parenthesized => SigParamKind::ArrayOrDict, - SyntaxKind::Array => { - let c = context.cast::(); - if c.is_some_and(|e| e.items().next().is_some()) { - SigParamKind::ArrayOrDict - } else { - SigParamKind::Array - } - } - SyntaxKind::Dict => SigParamKind::Dict, - _ => SigParamKind::Array, +impl<'a, 'w, T> SigChecker for (&mut PostTypeCheckWorker<'a, 'w>, &mut T) +where + T: FnMut(&mut PostTypeCheckWorker<'a, 'w>, Sig, &[Interned], bool) -> Option<()>, +{ + fn check( + &mut self, + sig: Sig, + args: &mut crate::analysis::SigCheckContext, + pol: bool, + ) -> Option<()> { + self.1(self.0, sig, &args.args, pol) + } + + fn check_var( + &mut self, + var: &Interned, + _pol: bool, + ) -> Option { + self.0 + .info + .vars + .get(&var.def) + .map(|v| v.bounds.bounds().read().clone()) } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum SigParamKind { - Call, - Array, - Dict, - ArrayOrDict, +fn sig_context_of(context: &LinkedNode) -> SigSurfaceKind { + match context.kind() { + SyntaxKind::Parenthesized => SigSurfaceKind::ArrayOrDict, + SyntaxKind::Array => { + let c = context.cast::(); + if c.is_some_and(|e| e.items().next().is_some()) { + SigSurfaceKind::Array + } else { + SigSurfaceKind::ArrayOrDict + } + } + SyntaxKind::Dict => SigSurfaceKind::Dict, + _ => SigSurfaceKind::Array, + } } diff --git a/crates/tinymist-query/src/analysis/ty/syntax.rs b/crates/tinymist-query/src/analysis/ty/syntax.rs new file mode 100644 index 00000000..d3d1f7a4 --- /dev/null +++ b/crates/tinymist-query/src/analysis/ty/syntax.rs @@ -0,0 +1,586 @@ +//! Type checking on source file + +use std::collections::BTreeMap; + +use typst::{ + foundations::Value, + syntax::{ + ast::{self, AstNode}, + LinkedNode, SyntaxKind, + }, +}; + +use super::*; +use crate::{adt::interner::Interned, ty::*}; + +impl<'a, 'w> TypeChecker<'a, 'w> { + pub(crate) fn check_syntax(&mut self, root: LinkedNode) -> Option { + Some(match root.kind() { + SyntaxKind::Markup => return self.check_in_mode(root, InterpretMode::Markup), + SyntaxKind::Math => return self.check_in_mode(root, InterpretMode::Math), + SyntaxKind::Code => return self.check_in_mode(root, InterpretMode::Code), + SyntaxKind::CodeBlock => return self.check_in_mode(root, InterpretMode::Code), + SyntaxKind::ContentBlock => return self.check_in_mode(root, InterpretMode::Markup), + + // todo: space effect + SyntaxKind::Space => Ty::Space, + SyntaxKind::Parbreak => Ty::Space, + + SyntaxKind::Text => Ty::Content, + SyntaxKind::Linebreak => Ty::Content, + SyntaxKind::Escape => Ty::Content, + SyntaxKind::Shorthand => Ty::Content, + SyntaxKind::SmartQuote => Ty::Content, + SyntaxKind::Raw => Ty::Content, + SyntaxKind::RawLang => Ty::Content, + SyntaxKind::RawDelim => Ty::Content, + SyntaxKind::RawTrimmed => Ty::Content, + SyntaxKind::Link => Ty::Content, + SyntaxKind::Label => Ty::Content, + SyntaxKind::Ref => Ty::Content, + SyntaxKind::RefMarker => Ty::Content, + SyntaxKind::HeadingMarker => Ty::Content, + SyntaxKind::EnumMarker => Ty::Content, + SyntaxKind::ListMarker => Ty::Content, + SyntaxKind::TermMarker => Ty::Content, + SyntaxKind::MathAlignPoint => Ty::Content, + SyntaxKind::MathPrimes => Ty::Content, + + SyntaxKind::Strong => return self.check_children(root), + SyntaxKind::Emph => return self.check_children(root), + SyntaxKind::Heading => return self.check_children(root), + SyntaxKind::ListItem => return self.check_children(root), + SyntaxKind::EnumItem => return self.check_children(root), + SyntaxKind::TermItem => return self.check_children(root), + SyntaxKind::Equation => return self.check_children(root), + SyntaxKind::MathDelimited => return self.check_children(root), + SyntaxKind::MathAttach => return self.check_children(root), + SyntaxKind::MathFrac => return self.check_children(root), + SyntaxKind::MathRoot => return self.check_children(root), + + SyntaxKind::LoopBreak => Ty::None, + SyntaxKind::LoopContinue => Ty::None, + SyntaxKind::FuncReturn => Ty::None, + SyntaxKind::Error => Ty::None, + SyntaxKind::Eof => Ty::None, + + SyntaxKind::None => Ty::None, + SyntaxKind::Auto => Ty::Auto, + SyntaxKind::Break => Ty::FlowNone, + SyntaxKind::Continue => Ty::FlowNone, + SyntaxKind::Return => Ty::FlowNone, + SyntaxKind::Ident => return self.check_ident(root, InterpretMode::Code), + SyntaxKind::MathIdent => return self.check_ident(root, InterpretMode::Math), + SyntaxKind::Bool + | SyntaxKind::Int + | SyntaxKind::Float + | SyntaxKind::Numeric + | SyntaxKind::Str => { + return self + .ctx + .mini_eval(root.cast()?) + .map(|v| (Ty::Value(InsTy::new(v)))) + } + SyntaxKind::Parenthesized => return self.check_children(root), + SyntaxKind::Array => return self.check_array(root), + SyntaxKind::Dict => return self.check_dict(root), + SyntaxKind::Unary => return self.check_unary(root), + SyntaxKind::Binary => return self.check_binary(root), + SyntaxKind::FieldAccess => return self.check_field_access(root), + SyntaxKind::FuncCall => return self.check_func_call(root), + SyntaxKind::Args => return self.check_args(root), + SyntaxKind::Closure => return self.check_closure(root), + SyntaxKind::LetBinding => return self.check_let(root), + SyntaxKind::SetRule => return self.check_set(root), + SyntaxKind::ShowRule => return self.check_show(root), + SyntaxKind::Contextual => return self.check_contextual(root), + SyntaxKind::Conditional => return self.check_conditional(root), + SyntaxKind::WhileLoop => return self.check_while_loop(root), + SyntaxKind::ForLoop => return self.check_for_loop(root), + SyntaxKind::ModuleImport => return self.check_module_import(root), + SyntaxKind::ModuleInclude => return self.check_module_include(root), + SyntaxKind::Destructuring => return self.check_destructuring(root), + SyntaxKind::DestructAssignment => return self.check_destruct_assign(root), + + // Rest all are clauses + SyntaxKind::LineComment => Ty::Clause, + SyntaxKind::BlockComment => Ty::Clause, + SyntaxKind::Named => Ty::Clause, + SyntaxKind::Keyed => Ty::Clause, + SyntaxKind::Spread => Ty::Clause, + SyntaxKind::Params => Ty::Clause, + SyntaxKind::ImportItems => Ty::Clause, + SyntaxKind::RenamedImportItem => Ty::Clause, + SyntaxKind::Hash => Ty::Clause, + SyntaxKind::LeftBrace => Ty::Clause, + SyntaxKind::RightBrace => Ty::Clause, + SyntaxKind::LeftBracket => Ty::Clause, + SyntaxKind::RightBracket => Ty::Clause, + SyntaxKind::LeftParen => Ty::Clause, + SyntaxKind::RightParen => Ty::Clause, + SyntaxKind::Comma => Ty::Clause, + SyntaxKind::Semicolon => Ty::Clause, + SyntaxKind::Colon => Ty::Clause, + SyntaxKind::Star => Ty::Clause, + SyntaxKind::Underscore => Ty::Clause, + SyntaxKind::Dollar => Ty::Clause, + SyntaxKind::Plus => Ty::Clause, + SyntaxKind::Minus => Ty::Clause, + SyntaxKind::Slash => Ty::Clause, + SyntaxKind::Hat => Ty::Clause, + SyntaxKind::Prime => Ty::Clause, + SyntaxKind::Dot => Ty::Clause, + SyntaxKind::Eq => Ty::Clause, + SyntaxKind::EqEq => Ty::Clause, + SyntaxKind::ExclEq => Ty::Clause, + SyntaxKind::Lt => Ty::Clause, + SyntaxKind::LtEq => Ty::Clause, + SyntaxKind::Gt => Ty::Clause, + SyntaxKind::GtEq => Ty::Clause, + SyntaxKind::PlusEq => Ty::Clause, + SyntaxKind::HyphEq => Ty::Clause, + SyntaxKind::StarEq => Ty::Clause, + SyntaxKind::SlashEq => Ty::Clause, + SyntaxKind::Dots => Ty::Clause, + SyntaxKind::Arrow => Ty::Clause, + SyntaxKind::Root => Ty::Clause, + SyntaxKind::Not => Ty::Clause, + SyntaxKind::And => Ty::Clause, + SyntaxKind::Or => Ty::Clause, + SyntaxKind::Let => Ty::Clause, + SyntaxKind::Set => Ty::Clause, + SyntaxKind::Show => Ty::Clause, + SyntaxKind::Context => Ty::Clause, + SyntaxKind::If => Ty::Clause, + SyntaxKind::Else => Ty::Clause, + SyntaxKind::For => Ty::Clause, + SyntaxKind::In => Ty::Clause, + SyntaxKind::While => Ty::Clause, + SyntaxKind::Import => Ty::Clause, + SyntaxKind::Include => Ty::Clause, + SyntaxKind::As => Ty::Clause, + }) + } + + fn check_in_mode(&mut self, root: LinkedNode, into_mode: InterpretMode) -> Option { + let mode = self.mode; + self.mode = into_mode; + let res = self.check_children(root); + self.mode = mode; + res + } + + fn check_children(&mut self, root: LinkedNode<'_>) -> Option { + let mut joiner = Joiner::default(); + + for child in root.children() { + joiner.join(self.check(child)); + } + Some(joiner.finalize()) + } + + fn check_ident(&mut self, root: LinkedNode<'_>, mode: InterpretMode) -> Option { + let ident: ast::Ident = root.cast()?; + let ident_ref = IdentRef { + name: ident.get().to_string(), + range: root.range(), + }; + + let Some(var) = self.get_var(root.span(), ident_ref) else { + let s = root.span(); + let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?; + return Some(Ty::Value(InsTy::new_at(v, s))); + }; + + Some(var.as_type()) + } + + fn check_array(&mut self, root: LinkedNode<'_>) -> Option { + let _arr: ast::Array = root.cast()?; + + let mut elements = Vec::new(); + + for elem in root.children() { + let ty = self.check(elem); + if matches!(ty, Ty::Clause | Ty::Space) { + continue; + } + elements.push(ty); + } + + Some(Ty::Tuple(Interned::new(elements))) + } + + fn check_dict(&mut self, root: LinkedNode<'_>) -> Option { + let dict: ast::Dict = root.cast()?; + + let mut fields = Vec::new(); + + for field in dict.items() { + match field { + ast::DictItem::Named(n) => { + let name = Interned::new_str(n.name().get()); + let value = self.check_expr_in(n.expr().span(), root.clone()); + fields.push((name, value, n.span())); + } + ast::DictItem::Keyed(k) => { + let key = self.ctx.const_eval(k.key()); + if let Some(Value::Str(key)) = key { + let value = self.check_expr_in(k.expr().span(), root.clone()); + fields.push((Interned::new_str(&key), value, k.span())); + } + } + // todo: var dict union + ast::DictItem::Spread(_s) => {} + } + } + + Some(Ty::Dict(RecordTy::new(fields))) + } + + fn check_unary(&mut self, root: LinkedNode<'_>) -> Option { + let unary: ast::Unary = root.cast()?; + + if let Some(constant) = self.ctx.mini_eval(ast::Expr::Unary(unary)) { + return Some(Ty::Value(InsTy::new(constant))); + } + + let op = unary.op(); + + let lhs = Interned::new(self.check_expr_in(unary.expr().span(), root)); + let op = match op { + ast::UnOp::Pos => UnaryOp::Pos, + ast::UnOp::Neg => UnaryOp::Neg, + ast::UnOp::Not => UnaryOp::Not, + }; + + Some(Ty::Unary(Interned::new(TypeUnary { op, lhs }))) + } + + fn check_binary(&mut self, root: LinkedNode<'_>) -> Option { + let binary: ast::Binary = root.cast()?; + + if let Some(constant) = self.ctx.mini_eval(ast::Expr::Binary(binary)) { + return Some(Ty::Value(InsTy::new(constant))); + } + + let op = binary.op(); + let lhs_span = binary.lhs().span(); + let lhs = self.check_expr_in(lhs_span, root.clone()); + let rhs_span = binary.rhs().span(); + let rhs = self.check_expr_in(rhs_span, root); + + match op { + ast::BinOp::Add | ast::BinOp::Sub | ast::BinOp::Mul | ast::BinOp::Div => {} + ast::BinOp::Eq | ast::BinOp::Neq | ast::BinOp::Leq | ast::BinOp::Geq => { + self.check_comparable(&lhs, &rhs); + self.possible_ever_be(&lhs, &rhs); + self.possible_ever_be(&rhs, &lhs); + } + ast::BinOp::Lt | ast::BinOp::Gt => { + self.check_comparable(&lhs, &rhs); + } + ast::BinOp::And | ast::BinOp::Or => { + self.constrain(&lhs, &Ty::Boolean(None)); + self.constrain(&rhs, &Ty::Boolean(None)); + } + ast::BinOp::NotIn | ast::BinOp::In => { + self.check_containing(&rhs, &lhs, op == ast::BinOp::In); + } + ast::BinOp::Assign => { + self.check_assignable(&lhs, &rhs); + self.possible_ever_be(&lhs, &rhs); + } + ast::BinOp::AddAssign + | ast::BinOp::SubAssign + | ast::BinOp::MulAssign + | ast::BinOp::DivAssign => { + self.check_assignable(&lhs, &rhs); + } + } + + let res = Ty::Binary(Interned::new(TypeBinary { + op, + operands: Interned::new((lhs, rhs)), + })); + + Some(res) + } + + fn check_field_access(&mut self, root: LinkedNode<'_>) -> Option { + let field_access: ast::FieldAccess = root.cast()?; + + let ty = self.check_expr_in(field_access.target().span(), root.clone()); + let field = field_access.field().get().clone(); + + Some(Ty::Select(Interned::new(SelectTy { + ty: Interned::new(ty), + select: Interned::new_str(&field), + }))) + } + + fn check_func_call(&mut self, root: LinkedNode<'_>) -> Option { + let func_call: ast::FuncCall = root.cast()?; + + let args = self.check_expr_in(func_call.args().span(), root.clone()); + let callee = self.check_expr_in(func_call.callee().span(), root.clone()); + + log::debug!("func_call: {callee:?} with {args:?}"); + + if let Ty::Args(args) = args { + let mut worker = ApplyTypeChecker { + base: self, + call_site: func_call.callee().span(), + args: func_call.args(), + resultant: vec![], + }; + callee.call(&args, true, &mut worker); + return Some(Ty::from_types(worker.resultant.into_iter())); + } + + None + } + + fn check_args(&mut self, root: LinkedNode<'_>) -> Option { + let args: ast::Args = root.cast()?; + + let mut args_res = Vec::new(); + let mut named = vec![]; + + for arg in args.items() { + match arg { + ast::Arg::Pos(e) => { + args_res.push(self.check_expr_in(e.span(), root.clone())); + } + ast::Arg::Named(n) => { + let name = Interned::new_str(n.name().get()); + let value = self.check_expr_in(n.expr().span(), root.clone()); + named.push((name, value)); + } + // todo + ast::Arg::Spread(_w) => {} + } + } + + let args = ArgsTy::new(args_res.into_iter(), named.into_iter(), None, None); + + Some(Ty::Args(Interned::new(args))) + } + + fn check_closure(&mut self, root: LinkedNode<'_>) -> Option { + let closure: ast::Closure = root.cast()?; + + // let _params = self.check_expr_in(closure.params().span(), root.clone()); + + let mut pos = vec![]; + let mut named = BTreeMap::new(); + let mut rest = None; + + for param in closure.params().children() { + match param { + ast::Param::Pos(pattern) => { + pos.push(self.check_pattern(pattern, Ty::Any, root.clone())); + } + ast::Param::Named(e) => { + let exp = self.check_expr_in(e.expr().span(), root.clone()); + let v = self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?; + v.ever_be(exp); + named.insert(Interned::new_str(e.name().get()), v.as_type()); + } + // todo: spread left/right + ast::Param::Spread(a) => { + if let Some(e) = a.sink_ident() { + let exp = Ty::Builtin(BuiltinTy::Args); + let v = self.get_var(e.span(), to_ident_ref(&root, e)?)?; + v.ever_be(exp); + rest = Some(v.as_type()); + } + // todo: ..(args) + } + } + } + + let body = self.check_expr_in(closure.body().span(), root); + + let named: Vec<(Interned, Ty)> = named.into_iter().collect(); + + // freeze the signature + for pos in pos.iter() { + self.weaken(pos); + } + for (_, named) in named.iter() { + self.weaken(named); + } + if let Some(rest) = &rest { + self.weaken(rest); + } + + let sig = SigTy::new(pos.into_iter(), named.into_iter(), rest, Some(body)); + Some(Ty::Func(Interned::new(sig))) + } + + fn check_let(&mut self, root: LinkedNode<'_>) -> Option { + let let_binding: ast::LetBinding = root.cast()?; + + match let_binding.kind() { + ast::LetBindingKind::Closure(c) => { + // let _name = let_binding.name().get().to_string(); + let value = let_binding + .init() + .map(|init| self.check_expr_in(init.span(), root.clone())) + .unwrap_or_else(|| Ty::Infer); + + let v = self.get_var(c.span(), to_ident_ref(&root, c)?)?; + v.ever_be(value); + // todo lbs is the lexical signature. + } + ast::LetBindingKind::Normal(pattern) => { + // let _name = let_binding.name().get().to_string(); + let value = let_binding + .init() + .map(|init| self.check_expr_in(init.span(), root.clone())) + .unwrap_or_else(|| Ty::Infer); + + self.check_pattern(pattern, value, root.clone()); + } + } + + Some(Ty::Any) + } + + // todo: merge with func call, and regard difference (may be here) + fn check_set(&mut self, root: LinkedNode<'_>) -> Option { + let set_rule: ast::SetRule = root.cast()?; + + let callee = self.check_expr_in(set_rule.target().span(), root.clone()); + let args = self.check_expr_in(set_rule.args().span(), root.clone()); + let _cond = set_rule + .condition() + .map(|cond| self.check_expr_in(cond.span(), root.clone())); + + log::debug!("set rule: {callee:?} with {args:?}"); + + if let Ty::Args(args) = args { + let mut worker = ApplyTypeChecker { + base: self, + call_site: set_rule.target().span(), + args: set_rule.args(), + resultant: vec![], + }; + callee.call(&args, true, &mut worker); + return Some(Ty::from_types(worker.resultant.into_iter())); + } + + None + } + + fn check_show(&mut self, root: LinkedNode<'_>) -> Option { + let show_rule: ast::ShowRule = root.cast()?; + + let _selector = show_rule + .selector() + .map(|sel| self.check_expr_in(sel.span(), root.clone())); + let t = show_rule.transform(); + // todo: infer it type by selector + let _transform = self.check_expr_in(t.span(), root.clone()); + + Some(Ty::Any) + } + + // currently we do nothing on contextual + fn check_contextual(&mut self, root: LinkedNode<'_>) -> Option { + let contextual: ast::Contextual = root.cast()?; + + let body = self.check_expr_in(contextual.body().span(), root); + + Some(Ty::Unary(Interned::new(TypeUnary { + op: UnaryOp::Context, + lhs: Interned::new(body), + }))) + } + + fn check_conditional(&mut self, root: LinkedNode<'_>) -> Option { + let conditional: ast::Conditional = root.cast()?; + + let cond = self.check_expr_in(conditional.condition().span(), root.clone()); + let then = self.check_expr_in(conditional.if_body().span(), root.clone()); + let else_ = conditional + .else_body() + .map(|else_body| self.check_expr_in(else_body.span(), root.clone())) + .unwrap_or(Ty::None); + + let cond = Interned::new(cond); + let then = Interned::new(then); + let else_ = Interned::new(else_); + Some(Ty::If(Interned::new(IfTy { cond, then, else_ }))) + } + + fn check_while_loop(&mut self, root: LinkedNode<'_>) -> Option { + let while_loop: ast::WhileLoop = root.cast()?; + + let _cond = self.check_expr_in(while_loop.condition().span(), root.clone()); + let _body = self.check_expr_in(while_loop.body().span(), root); + + Some(Ty::Any) + } + + fn check_for_loop(&mut self, root: LinkedNode<'_>) -> Option { + let for_loop: ast::ForLoop = root.cast()?; + + let _iter = self.check_expr_in(for_loop.iterable().span(), root.clone()); + let _pattern = self.check_expr_in(for_loop.pattern().span(), root.clone()); + let _body = self.check_expr_in(for_loop.body().span(), root); + + Some(Ty::Any) + } + + fn check_module_import(&mut self, root: LinkedNode<'_>) -> Option { + let _module_import: ast::ModuleImport = root.cast()?; + + // check all import items + + Some(Ty::None) + } + + fn check_module_include(&mut self, _root: LinkedNode<'_>) -> Option { + Some(Ty::Content) + } + + fn check_destructuring(&mut self, _root: LinkedNode<'_>) -> Option { + Some(Ty::Any) + } + + fn check_destruct_assign(&mut self, _root: LinkedNode<'_>) -> Option { + Some(Ty::None) + } + fn check_expr_in(&mut self, span: Span, root: LinkedNode<'_>) -> Ty { + root.find(span) + .map(|node| self.check(node)) + .unwrap_or(Ty::Undef) + } + + fn check_pattern(&mut self, pattern: ast::Pattern<'_>, value: Ty, root: LinkedNode<'_>) -> Ty { + self.check_pattern_(pattern, value, root) + .unwrap_or(Ty::Undef) + } + + fn check_pattern_( + &mut self, + pattern: ast::Pattern<'_>, + value: Ty, + root: LinkedNode<'_>, + ) -> Option { + Some(match pattern { + ast::Pattern::Normal(ast::Expr::Ident(ident)) => { + let v = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?; + v.ever_be(value); + v.as_type() + } + ast::Pattern::Normal(_) => Ty::Any, + ast::Pattern::Placeholder(_) => Ty::Any, + ast::Pattern::Parenthesized(exp) => self.check_pattern(exp.pattern(), value, root), + // todo: pattern + ast::Pattern::Destructuring(_destruct) => Ty::Any, + }) + } +} diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 92034cf0..4f85ffdb 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -6,7 +6,7 @@ use once_cell::sync::Lazy; use regex::{Captures, Regex}; use crate::{ - analysis::{FlowBuiltinType, FlowType}, + analysis::{BuiltinTy, Ty}, prelude::*, syntax::DerefTarget, upstream::{autocomplete, complete_path, CompletionContext}, @@ -115,16 +115,15 @@ impl StatefulRequest for CompletionRequest { if matches!(parent.kind(), SyntaxKind::Named | SyntaxKind::Args) { let ty_chk = ctx.type_check(source.clone()); if let Some(ty_chk) = ty_chk { - let ty = ty_chk.mapping.get(&cano_expr.span()); + let ty = ty_chk.type_of_span(cano_expr.span()); log::debug!("check string ty: {:?}", ty); - if let Some(FlowType::Builtin(FlowBuiltinType::Path(path_filter))) = ty { + if let Some(Ty::Builtin(BuiltinTy::Path(path_filter))) = ty { completion_result = - complete_path(ctx, Some(cano_expr), &source, cursor, path_filter); + complete_path(ctx, Some(cano_expr), &source, cursor, &path_filter); } } } } - // todo: label, reference Some(DerefTarget::Label(..) | DerefTarget::Ref(..) | DerefTarget::Normal(..)) => {} None => {} } @@ -322,6 +321,15 @@ mod tests { let get_items = |items: Vec| { let mut res: Vec<_> = items .into_iter() + .filter(|item| { + if !excludes.is_empty() && excludes.contains(item.label.as_str()) { + panic!("{item:?} was excluded in {excludes:?}"); + } + if includes.is_empty() { + return true; + } + includes.contains(item.label.as_str()) + }) .map(|item| CompletionItem { label: item.label, label_details: item.label_details, @@ -330,15 +338,6 @@ mod tests { text_edit: item.text_edit, ..Default::default() }) - .filter(|item| { - if includes.is_empty() { - return true; - } - if !excludes.is_empty() && excludes.contains(item.label.as_str()) { - panic!("{item:?} was excluded in {excludes:?}"); - } - includes.contains(item.label.as_str()) - }) .collect(); res.sort_by(|a, b| { diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap index 1f9a1555..d5695de3 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "angle", docs: "The angle whose sine to calculate.", input: Union([Type(Type(integer)), Type(Type(float)), Type(Type(angle))]), infer_type: Some((Type(integer) | Type(float) | Type(angle))), type_repr: Some("int | float | angle"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "angle", docs: "The angle whose sine to calculate.", input: Union([Type(Type(integer)), Type(Type(float)), Type(Type(angle))]), base_type: Some((Type(integer) | Type(float) | Type(angle))), type_repr: Some("int | float | angle"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap index 21e8b753..c5e31fc0 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap @@ -3,6 +3,6 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly.typ --- -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), infer_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "green", docs: "The green component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), infer_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "blue", docs: "The blue component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), infer_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), base_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "green", docs: "The green component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), base_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "blue", docs: "The blue component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), base_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap index e7119f11..5cf645a6 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly2.typ --- -"#fff" -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), infer_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +"#fff" -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", input: Union([Type(Type(integer)), Type(Type(ratio))]), base_type: Some((Type(integer) | Type(ratio))), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap index 35dba9df..8ad470a2 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap @@ -3,5 +3,5 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, infer_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", input: Any, infer_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, base_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", input: Any, base_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap index 290319e8..95566cc8 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap @@ -3,5 +3,5 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named.typ --- -y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", input: Any, infer_type: None, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, infer_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", input: Any, base_type: None, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, base_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap index c01cb526..6f0f10b2 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, infer_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", input: Any, base_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap index b744ad2c..8968f10d 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with2.typ --- -y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", input: Any, infer_type: None, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } +y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", input: Any, base_type: None, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap index ce4af8b1..4db097dd 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_with.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", input: Any, infer_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", input: Any, base_type: None, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/completion/bug_mix_context_type.typ b/crates/tinymist-query/src/fixtures/completion/bug_mix_context_type.typ new file mode 100644 index 00000000..9cb1f29e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/bug_mix_context_type.typ @@ -0,0 +1,6 @@ +// contains: base +#let tmpl2(x, y) = { + assert(type(x) in (int, str) and type(y) == int) + x + y +} +#tmpl2( /* range -1..0 */) \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/completion/sig_dict.typ b/crates/tinymist-query/src/fixtures/completion/sig_dict.typ new file mode 100644 index 00000000..3afa6b60 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/sig_dict.typ @@ -0,0 +1,3 @@ +// contains: paint,cap + +#text(stroke: (/* range after 1..2 */ ))[] diff --git a/crates/tinymist-query/src/fixtures/completion/sig_dict_rest.typ b/crates/tinymist-query/src/fixtures/completion/sig_dict_rest.typ new file mode 100644 index 00000000..f3baf321 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/sig_dict_rest.typ @@ -0,0 +1,3 @@ +// contains: paint,cap + +#text(stroke: (paint: red, /* range after 1..2 */ ))[] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@bug_mix_context_type.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@bug_mix_context_type.typ.snap new file mode 100644 index 00000000..64643fae --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@bug_mix_context_type.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/completion.rs +description: Completion on (107..108) +expression: "JsonRepr::new_pure(results)" +input_file: crates/tinymist-query/src/fixtures/completion/bug_mix_context_type.typ +--- +[ + { + "isIncomplete": false, + "items": [] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args_after.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args_after.typ.snap index 8a507564..9e444949 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args_after.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args_after.typ.snap @@ -9,7 +9,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args_after.typ "isIncomplete": false, "items": [ { - "kind": 6, + "kind": 5, "label": "class", "sortText": "000", "textEdit": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict.typ.snap new file mode 100644 index 00000000..017e9e65 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict.typ.snap @@ -0,0 +1,49 @@ +--- +source: crates/tinymist-query/src/completion.rs +description: Completion on ) (62..63) +expression: "JsonRepr::new_pure(results)" +input_file: crates/tinymist-query/src/fixtures/completion/sig_dict.typ +--- +[ + { + "isIncomplete": false, + "items": [ + { + "kind": 5, + "label": "cap", + "sortText": "000", + "textEdit": { + "newText": "cap: ${1:}", + "range": { + "end": { + "character": 38, + "line": 2 + }, + "start": { + "character": 38, + "line": 2 + } + } + } + }, + { + "kind": 5, + "label": "paint", + "sortText": "004", + "textEdit": { + "newText": "paint: ${1:}", + "range": { + "end": { + "character": 38, + "line": 2 + }, + "start": { + "character": 38, + "line": 2 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict_rest.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict_rest.typ.snap new file mode 100644 index 00000000..3024268b --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@sig_dict_rest.typ.snap @@ -0,0 +1,31 @@ +--- +source: crates/tinymist-query/src/completion.rs +description: Completion on ) (74..75) +expression: "JsonRepr::new_pure(results)" +input_file: crates/tinymist-query/src/fixtures/completion/sig_dict_rest.typ +--- +[ + { + "isIncomplete": false, + "items": [ + { + "kind": 5, + "label": "cap", + "sortText": "000", + "textEdit": { + "newText": "cap: ${1:}", + "range": { + "end": { + "character": 50, + "line": 2 + }, + "start": { + "character": 50, + "line": 2 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/playground/snaps/test.snap b/crates/tinymist-query/src/fixtures/playground/snaps/test.snap deleted file mode 100644 index 21492005..00000000 --- a/crates/tinymist-query/src/fixtures/playground/snaps/test.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/tinymist-query/src/analysis.rs -description: "Check on \"(\" (48)" -expression: literal_type -input_file: crates/tinymist-query/src/fixtures/playground/base.typ ---- -( ⪰ Any ⪯ Stroke) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font2.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font2.typ.snap index 267b1c04..16efd7bf 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font2.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font2.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (30)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font2.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font3.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font3.typ.snap index f29830cd..65d38c3a 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font3.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font3.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (33)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font3.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font4.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font4.typ.snap index 058cc0de..6c0a8d91 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font4.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font4.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (31)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font4.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font5.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font5.typ.snap index bb12c36b..08f9b65a 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font5.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font5.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (47)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font5.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element.typ.snap index 74b6d296..594569be 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element.typ --- -( ⪰ (TextFont | Array) | TextFont) +( ⪰ ( ⪯ (TextFont | Array)) ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element2.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element2.typ.snap index 54f75797..38990cb6 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element2.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element2.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element2.typ --- -( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array)) | TextFont) +( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array)) ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element3.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element3.typ.snap index 3e603fdf..9e7e267e 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element3.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element3.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (34)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element3.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element4.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element4.typ.snap index 2b678830..57d207cb 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element4.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_font_element4.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"\\\"Test\\\"\" (31)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element4.typ --- -TextFont +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke.typ.snap index d54f2bbd..ebcf47a0 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke.typ.snap @@ -1,7 +1,7 @@ --- source: crates/tinymist-query/src/analysis.rs -description: "Check on \")\" (78)" +description: "Check on \")\" (82)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke.typ --- -Any +( ⪯ "cap": Any & "dash": Any & "join": Any & "miter-limit": Any & "paint": Any & "thickness": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke1.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke1.typ.snap index 7d8f9449..cc8160dc 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke1.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke1.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (61)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke1.typ --- -Any +( ⪯ "cap": Any & "dash": Any & "join": Any & "miter-limit": Any & "paint": Any & "thickness": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke2.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke2.typ.snap index c6d426c6..428f259a 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke2.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke2.typ.snap @@ -4,4 +4,4 @@ description: "Check on \")\" (69)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke2.typ --- -( ⪰ ( ⪰ Any ⪯ Stroke) | ( ⪰ Any ⪯ Stroke)) +( ⪯ ( ⪰ Any ⪯ Stroke)) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke3.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke3.typ.snap index 9f1cdb64..d7880ebe 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke3.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke3.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (49)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke3.typ --- -Any +( ⪯ "cap": Any & "dash": Any & "join": Any & "miter-limit": Any & "paint": Any & "thickness": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke4.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke4.typ.snap index a01f4e49..2313397f 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke4.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@text_stroke4.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (48)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke4.typ --- -( ⪰ ( ⪰ Any ⪯ Stroke) | ( ⪰ Any ⪯ Stroke)) +( ⪯ ( ⪰ Any ⪯ Stroke)) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external.typ.snap index f4dd95d0..18ee2654 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (56)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external.typ --- -( ⪯ TextFont & TextFont) +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_alias.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_alias.typ.snap index 5cb6d8eb..f490a980 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_alias.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_alias.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (59)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_alias.typ --- -( ⪯ TextFont & TextFont) +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_ever.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_ever.typ.snap index 983d4dbc..9e94ef65 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_ever.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_external_ever.typ.snap @@ -4,4 +4,4 @@ description: "Check on \":\" (34)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_ever.typ --- -( ⪰ ( ⪰ "article" | "article" | "letter" | "article" | "letter") | ( ⪰ "article" | "article" | "letter" | "article" | "letter")) +( ⪯ ( ⪰ "article" | "article" | "letter" | "article" | "letter")) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func.typ.snap index fa272a5c..f57589a1 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func.typ.snap @@ -4,4 +4,4 @@ description: "Check on \"(\" (105)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func.typ --- -( ⪯ TextFont & TextFont) +( ⪯ TextFont) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos.typ.snap new file mode 100644 index 00000000..b27cb8b8 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/analysis.rs +description: "Check on \",\" (83)" +expression: literal_type +input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func_pos.typ +--- +( ⪯ ( ⪰ Any ⪯ Stroke)) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos2.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos2.typ.snap new file mode 100644 index 00000000..ab86aeaf --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_func_pos2.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/analysis.rs +description: "Check on \"(\" (85)" +expression: literal_type +input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func_pos2.typ +--- +( ⪯ "cap": Any & "dash": Any & "join": Any & "miter-limit": Any & "paint": Any & "thickness": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_named.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_named.typ.snap index 75d0571b..bdf9fe7a 100644 --- a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_named.typ.snap +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@user_named.typ.snap @@ -4,4 +4,4 @@ description: "Check on \")\" (98)" expression: literal_type input_file: crates/tinymist-query/src/fixtures/post_type_check/user_named.typ --- -( ⪰ ( ⪰ Any | None) | "font": Any | ( ⪰ Any | None) | "font": Any) +( ⪯ ( ⪰ Any | None) & "font": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_builtin.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_builtin.typ.snap new file mode 100644 index 00000000..0d0f357c --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_builtin.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/analysis.rs +description: "Check on \"(\" (17)" +expression: literal_type +input_file: crates/tinymist-query/src/fixtures/post_type_check/with_builtin.typ +--- +( ⪯ (Type(integer) | Type(ratio))) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_element.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_element.typ.snap new file mode 100644 index 00000000..25406cf0 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_element.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/analysis.rs +description: "Check on \"(\" (18)" +expression: literal_type +input_file: crates/tinymist-query/src/fixtures/post_type_check/with_element.typ +--- +( ⪯ Type(content) & "alternates": Any & "baseline": Any & "bottom-edge": Any & "cjk-latin-spacing": Any & "dir": Any & "discretionary-ligatures": Any & "fallback": Any & "features": Any & "fill": Any & "font": Any & "fractions": Any & "historical-ligatures": Any & "hyphenate": Any & "kerning": Any & "lang": Any & "ligatures": Any & "number-type": Any & "number-width": Any & "overhang": Any & "region": Any & "script": Any & "size": Any & "slashed-zero": Any & "spacing": Any & "stretch": Any & "stroke": Any & "style": Any & "stylistic-set": Any & "top-edge": Any & "tracking": Any & "weight": Any) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_user_func.typ.snap b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_user_func.typ.snap new file mode 100644 index 00000000..54cf338d --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/snaps/test@with_user_func.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/analysis.rs +description: "Check on \"(\" (72)" +expression: literal_type +input_file: crates/tinymist-query/src/fixtures/post_type_check/with_user_func.typ +--- +( ⪯ ( ⪰ Any ⪯ (TextFont | Array))) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos.typ b/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos.typ new file mode 100644 index 00000000..a9b6d745 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos.typ @@ -0,0 +1,4 @@ + +#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[] + +#tmpl3(("Agency FB"),/* position */) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos2.typ b/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos2.typ new file mode 100644 index 00000000..3cc0fbf1 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/user_func_pos2.typ @@ -0,0 +1,4 @@ + +#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[] + +#tmpl3(("Agency FB"), (/* position */)) diff --git a/crates/tinymist-query/src/fixtures/post_type_check/with_builtin.typ b/crates/tinymist-query/src/fixtures/post_type_check/with_builtin.typ new file mode 100644 index 00000000..685b4499 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/with_builtin.typ @@ -0,0 +1 @@ +#let g = rgb.with(/* position */); \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/post_type_check/with_element.typ b/crates/tinymist-query/src/fixtures/post_type_check/with_element.typ new file mode 100644 index 00000000..12695262 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/with_element.typ @@ -0,0 +1 @@ +#let g = text.with(/* position */); \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/post_type_check/with_user_func.typ b/crates/tinymist-query/src/fixtures/post_type_check/with_user_func.typ new file mode 100644 index 00000000..1c062b97 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/post_type_check/with_user_func.typ @@ -0,0 +1,2 @@ +#let f(font, stroke) = text(font: font, stroke: stroke); +#let g = f.with(/* position */); \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/type_check/op_contains.typ b/crates/tinymist-query/src/fixtures/type_check/op_contains.typ new file mode 100644 index 00000000..e7b1d376 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/op_contains.typ @@ -0,0 +1,3 @@ +#let f(x) = { + assert(x in ("line", "number")) +}; \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/type_check/op_contains_str.typ b/crates/tinymist-query/src/fixtures/type_check/op_contains_str.typ new file mode 100644 index 00000000..0e4fbd18 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/op_contains_str.typ @@ -0,0 +1,3 @@ +#let f(x) = { + assert(x in "abc") +}; \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/type_check/op_type_of.typ b/crates/tinymist-query/src/fixtures/type_check/op_type_of.typ new file mode 100644 index 00000000..171d15c9 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/op_type_of.typ @@ -0,0 +1,3 @@ +#let f(x) = { + assert(type(x) == int) +}; diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@base.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@base.typ.snap index 45160b8f..f0dec368 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@base.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@base.typ.snap @@ -3,6 +3,6 @@ source: crates/tinymist-query/src/analysis.rs expression: result input_file: crates/tinymist-query/src/fixtures/type_check/base.typ --- -"f" = () -> 1 +"f" = () => 1 --- 5..6 -> @f diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@constants.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@constants.typ.snap index b2ebaa00..67f4a1ae 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@constants.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@constants.typ.snap @@ -3,7 +3,7 @@ source: crates/tinymist-query/src/analysis.rs expression: result input_file: crates/tinymist-query/src/fixtures/type_check/constants.typ --- -"f" = (Any) -> 2 +"f" = (Any) => 2 "x" = Any --- 5..6 -> @f diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@control_flow.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@control_flow.typ.snap index d7730235..d1d256fa 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@control_flow.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@control_flow.typ.snap @@ -3,14 +3,13 @@ source: crates/tinymist-query/src/analysis.rs expression: result input_file: crates/tinymist-query/src/fixtures/type_check/control_flow.typ --- -"x0" = FlowIfType { cond: true, then: 1, else_: None } -"x1" = FlowIfType { cond: false, then: 2, else_: None } -"x2" = FlowUnaryType { op: Context, lhs: FlowIfType { cond: FlowBinaryType { op: Gt, operands: (Any, 0) }, then: 1, else_: 2 } } +"x0" = IfTy { cond: true, then: 1, else_: None } +"x1" = IfTy { cond: false, then: 2, else_: None } +"x2" = TypeUnary { lhs: IfTy { cond: TypeBinary { operands: (Any, 0), op: Gt }, then: 1, else_: 2 }, op: Context } --- 5..7 -> @x0 31..33 -> @x1 58..60 -> @x2 74..78 -> Func(here) 74..80 -> Type(location) -74..85 -> Type(location) 74..87 -> Any diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@external.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@external.typ.snap index 490bdb55..0fa63865 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@external.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@external.typ.snap @@ -4,11 +4,11 @@ expression: result input_file: crates/tinymist-query/src/fixtures/type_check/external.typ --- "bad-instantiate" = Any -"prefix" = (, title: ( ⪯ Any)) -> FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (Any, Infer) }, Infer) }, Infer) } +"prefix" = ("title": ( ⪯ Any)) => TypeBinary { operands: (TypeBinary { operands: (TypeBinary { operands: (Any, Infer), op: Add }, Infer), op: Add }, Infer), op: Add } "title" = None --- 27..33 -> @prefix 34..39 -> @title -53..68 -> (@bad-instantiate | (Any) -> FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (Any, Infer) }, Infer) }, Infer) }) -53..75 -> FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (FlowBinaryType { op: Add, operands: (Any, Infer) }, Infer) }, Infer) } +53..68 -> (@bad-instantiate | (Any) => TypeBinary { operands: (TypeBinary { operands: (TypeBinary { operands: (Any, Infer), op: Add }, Infer), op: Add }, Infer), op: Add }) +53..75 -> TypeBinary { operands: (TypeBinary { operands: (TypeBinary { operands: (Any, Infer), op: Add }, Infer), op: Add }, Infer), op: Add } 69..74 -> @title diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer.typ.snap index e9cfca37..afd197c3 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer.typ.snap @@ -6,19 +6,13 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer.typ --- 1..6 -> Func(image) 1..18 -> Element(image) -7..17 -> Path(Image) 21..25 -> Func(read) 21..37 -> (Type(string) | Type(bytes)) -26..36 -> Path(None) 40..44 -> Func(json) 40..57 -> Any -45..56 -> Path(Json) 60..64 -> Func(yaml) 60..77 -> Any -65..76 -> Path(Yaml) 80..83 -> Func(xml) 80..95 -> Any -84..94 -> Path(Xml) 98..102 -> Func(toml) 98..115 -> Any -103..114 -> Path(Toml) diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer2.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer2.typ.snap index f192bfb0..52182e87 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer2.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer2.typ.snap @@ -6,80 +6,23 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ --- 1..5 -> Func(text) 1..52 -> Element(text) -6..15 -> TextSize -12..15 -> TextSize -17..25 -> (TextFont | Array) -23..25 -> (TextFont | Array) -27..38 -> Stroke -35..38 -> Stroke -40..49 -> Color -46..49 -> (Color | Color) -50..52 -> Type(content) 54..58 -> Func(path) 54..82 -> Element(path) -59..68 -> Color -65..68 -> (Color | Color) -70..81 -> Stroke -78..81 -> Stroke 84..88 -> Func(line) 84..127 -> Element(line) -89..100 -> Type(angle) -96..100 -> Type(angle) -102..113 -> Type(relative length) -110..113 -> Type(relative length) -115..126 -> Stroke -123..126 -> Stroke 129..133 -> Func(rect) 129..220 -> Element(rect) -134..144 -> (Type(relative length) | Type(auto)) -141..144 -> (Type(relative length) | Type(auto)) -146..157 -> (Type(relative length) | Type(auto)) -154..157 -> (Type(relative length) | Type(auto)) -159..168 -> Color -165..168 -> (Color | Color) -170..181 -> Stroke -178..181 -> Stroke -183..194 -> Radius -191..194 -> Radius -196..206 -> Inset -203..206 -> Inset -208..219 -> Outset -216..219 -> Outset 222..229 -> Func(ellipse) 222..253 -> Element(ellipse) -230..239 -> Color -236..239 -> (Color | Color) -241..252 -> Stroke -249..252 -> Stroke 255..261 -> Func(circle) 255..285 -> Element(circle) -262..271 -> Color -268..271 -> (Color | Color) -273..284 -> Stroke -281..284 -> Stroke 287..290 -> Func(box) 287..314 -> Element(box) -291..300 -> Color -297..300 -> (Color | Color) -302..313 -> Stroke -310..313 -> Stroke 316..321 -> Func(block) 316..345 -> Element(block) -322..331 -> Color -328..331 -> (Color | Color) -333..344 -> Stroke -341..344 -> Stroke 347..352 -> Func(table) 347..439 -> Element(table) -356..365 -> Color -362..365 -> (Color | Color) -369..380 -> Stroke -377..380 -> Stroke -384..395 -> Func(table) -384..408 -> (Any | Any) -412..423 -> Func(table) -412..436 -> (Any | Any) +384..408 -> Any +412..436 -> Any 441..445 -> Func(text) 441..457 -> Element(text) -446..456 -> Stroke -454..456 -> Stroke diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer_stroke_dict.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer_stroke_dict.typ.snap index d0ed4bd9..fe2e2ef4 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer_stroke_dict.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@infer_stroke_dict.typ.snap @@ -6,9 +6,3 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer_stroke_dict.typ --- 1..5 -> Func(text) 1..54 -> Element(text) -6..51 -> Stroke -14..51 -> Stroke -18..30 -> Color -25..30 -> Color -34..48 -> Length -52..54 -> Type(content) diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains.typ.snap new file mode 100644 index 00000000..5535a6cf --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains.typ.snap @@ -0,0 +1,13 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: result +input_file: crates/tinymist-query/src/fixtures/type_check/op_contains.typ +--- +"f" = (( ⪯ ("line" | "number"))) => Type(none) +"x" = Any +--- +5..6 -> @f +7..8 -> @x +16..22 -> Func(assert) +16..47 -> Type(none) +23..24 -> @x diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains_str.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains_str.typ.snap new file mode 100644 index 00000000..82020f4a --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_contains_str.typ.snap @@ -0,0 +1,13 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: result +input_file: crates/tinymist-query/src/fixtures/type_check/op_contains_str.typ +--- +"f" = (( ⪯ TypeUnary { lhs: "abc", op: ElementOf })) => Type(none) +"x" = Any +--- +5..6 -> @f +7..8 -> @x +16..22 -> Func(assert) +16..34 -> Type(none) +23..24 -> @x diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_type_of.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_type_of.typ.snap new file mode 100644 index 00000000..7b099502 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@op_type_of.typ.snap @@ -0,0 +1,15 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: result +input_file: crates/tinymist-query/src/fixtures/type_check/op_type_of.typ +--- +"f" = (( ⪯ Any)) => Type(none) +"x" = ( ⪰ Any | Type(integer)) +--- +5..6 -> @f +7..8 -> @x +16..22 -> Func(assert) +16..38 -> Type(none) +23..27 -> Type(type) +23..30 -> (Type(type) | TypeUnary { lhs: @x, op: TypeOf }) +28..29 -> @x diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive.typ.snap index d37a6343..d5b6e69c 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive.typ.snap @@ -4,8 +4,8 @@ expression: result input_file: crates/tinymist-query/src/fixtures/type_check/recursive.typ --- "a" = Any -"f" = () -> Any +"f" = () => Any --- 27..28 -> @f -33..34 -> (@a | (Any) -> Any) +33..34 -> (@a | (Any) => Any) 33..36 -> Any diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive_use.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive_use.typ.snap index 2a6dce1b..f830a468 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive_use.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@recursive_use.typ.snap @@ -4,9 +4,9 @@ expression: result input_file: crates/tinymist-query/src/fixtures/type_check/recursive_use.typ --- "a" = Any -"f" = () -> Any +"f" = () => Any --- 27..28 -> @f -33..34 -> (@a | (Any) -> Any) +33..34 -> (@a | (Any) => Any) 33..37 -> Any 35..36 -> @a diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@set_font.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@set_font.typ.snap index 40b4c60d..ac1f22f2 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@set_font.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@set_font.typ.snap @@ -7,5 +7,4 @@ input_file: crates/tinymist-query/src/fixtures/type_check/set_font.typ --- 5..9 -> @font 36..40 -> Func(text) -41..51 -> (TextFont | Array) -47..51 -> (@font | (TextFont | Array)) +47..51 -> @font diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template.typ.snap index fd74784a..19a0c36a 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template.typ.snap @@ -4,7 +4,7 @@ expression: result input_file: crates/tinymist-query/src/fixtures/type_check/sig_template.typ --- "content" = Any -"tmpl" = (Any) -> Any +"tmpl" = (Any) => Any --- 5..9 -> @tmpl 10..17 -> @content diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template_set.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template_set.typ.snap index 09988aea..68496b10 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template_set.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@sig_template_set.typ.snap @@ -7,7 +7,7 @@ input_file: crates/tinymist-query/src/fixtures/type_check/sig_template_set.typ "class" = ( ⪰ "article" | "article" | "letter" | "article" | "letter") "content" = Any "font" = None -"tmpl" = (Any, authors: ( ⪯ (Type(string) | Type(array))), class: Any, font: ( ⪯ (TextFont | Array))) -> Any +"tmpl" = (Any, "authors": ( ⪯ (Type(string) | Type(array))), "class": Any, "font": ( ⪯ (TextFont | Array))) => Any --- 5..9 -> @tmpl 10..17 -> @content @@ -18,19 +18,12 @@ input_file: crates/tinymist-query/src/fixtures/type_check/sig_template_set.typ 94..99 -> @class 118..123 -> Func(panic) 118..127 -> () -124..126 -> Any 139..147 -> Func(document) -148..163 -> (Type(string) | Type(array)) -156..163 -> (@authors | (Type(string) | Type(array))) +156..163 -> @authors 171..175 -> Func(text) -176..186 -> (TextFont | Array) -182..186 -> (@font | (TextFont | Array)) +182..186 -> @font 195..199 -> Func(page) -200..211 -> (("a0", "Produces a paper of the respective size.") | ("a1", "Produces a paper of the respective size.") | ("a2", "Produces a paper of the respective size.") | ("a3", "Produces a paper of the respective size.") | ("a4", "Produces a paper of the respective size.") | ("a5", "Produces a paper of the respective size.") | ("a6", "Produces a paper of the respective size.") | ("a7", "Produces a paper of the respective size.") | ("a8", "Produces a paper of the respective size.") | ("a9", "Produces a paper of the respective size.") | ("a10", "Produces a paper of the respective size.") | ("a11", "Produces a paper of the respective size.") | ("iso-b1", "Produces a paper of the respective size.") | ("iso-b2", "Produces a paper of the respective size.") | ("iso-b3", "Produces a paper of the respective size.") | ("iso-b4", "Produces a paper of the respective size.") | ("iso-b5", "Produces a paper of the respective size.") | ("iso-b6", "Produces a paper of the respective size.") | ("iso-b7", "Produces a paper of the respective size.") | ("iso-b8", "Produces a paper of the respective size.") | ("iso-c3", "Produces a paper of the respective size.") | ("iso-c4", "Produces a paper of the respective size.") | ("iso-c5", "Produces a paper of the respective size.") | ("iso-c6", "Produces a paper of the respective size.") | ("iso-c7", "Produces a paper of the respective size.") | ("iso-c8", "Produces a paper of the respective size.") | ("din-d3", "Produces a paper of the respective size.") | ("din-d4", "Produces a paper of the respective size.") | ("din-d5", "Produces a paper of the respective size.") | ("din-d6", "Produces a paper of the respective size.") | ("din-d7", "Produces a paper of the respective size.") | ("din-d8", "Produces a paper of the respective size.") | ("sis-g5", "Produces a paper of the respective size.") | ("sis-e5", "Produces a paper of the respective size.") | ("ansi-a", "Produces a paper of the respective size.") | ("ansi-b", "Produces a paper of the respective size.") | ("ansi-c", "Produces a paper of the respective size.") | ("ansi-d", "Produces a paper of the respective size.") | ("ansi-e", "Produces a paper of the respective size.") | ("arch-a", "Produces a paper of the respective size.") | ("arch-b", "Produces a paper of the respective size.") | ("arch-c", "Produces a paper of the respective size.") | ("arch-d", "Produces a paper of the respective size.") | ("arch-e1", "Produces a paper of the respective size.") | ("arch-e", "Produces a paper of the respective size.") | ("jis-b0", "Produces a paper of the respective size.") | ("jis-b1", "Produces a paper of the respective size.") | ("jis-b2", "Produces a paper of the respective size.") | ("jis-b3", "Produces a paper of the respective size.") | ("jis-b4", "Produces a paper of the respective size.") | ("jis-b5", "Produces a paper of the respective size.") | ("jis-b6", "Produces a paper of the respective size.") | ("jis-b7", "Produces a paper of the respective size.") | ("jis-b8", "Produces a paper of the respective size.") | ("jis-b9", "Produces a paper of the respective size.") | ("jis-b10", "Produces a paper of the respective size.") | ("jis-b11", "Produces a paper of the respective size.") | ("sac-d0", "Produces a paper of the respective size.") | ("sac-d1", "Produces a paper of the respective size.") | ("sac-d2", "Produces a paper of the respective size.") | ("sac-d3", "Produces a paper of the respective size.") | ("sac-d4", "Produces a paper of the respective size.") | ("sac-d5", "Produces a paper of the respective size.") | ("sac-d6", "Produces a paper of the respective size.") | ("iso-id-1", "Produces a paper of the respective size.") | ("iso-id-2", "Produces a paper of the respective size.") | ("iso-id-3", "Produces a paper of the respective size.") | ("asia-f4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-5", "Produces a paper of the respective size.") | ("jp-shiroku-ban-6", "Produces a paper of the respective size.") | ("jp-kiku-4", "Produces a paper of the respective size.") | ("jp-kiku-5", "Produces a paper of the respective size.") | ("jp-business-card", "Produces a paper of the respective size.") | ("cn-business-card", "Produces a paper of the respective size.") | ("eu-business-card", "Produces a paper of the respective size.") | ("fr-tellière", "Produces a paper of the respective size.") | ("fr-couronne-écriture", "Produces a paper of the respective size.") | ("fr-couronne-édition", "Produces a paper of the respective size.") | ("fr-raisin", "Produces a paper of the respective size.") | ("fr-carré", "Produces a paper of the respective size.") | ("fr-jésus", "Produces a paper of the respective size.") | ("uk-brief", "Produces a paper of the respective size.") | ("uk-draft", "Produces a paper of the respective size.") | ("uk-foolscap", "Produces a paper of the respective size.") | ("uk-quarto", "Produces a paper of the respective size.") | ("uk-crown", "Produces a paper of the respective size.") | ("uk-book-a", "Produces a paper of the respective size.") | ("uk-book-b", "Produces a paper of the respective size.") | ("us-letter", "Produces a paper of the respective size.") | ("us-legal", "Produces a paper of the respective size.") | ("us-tabloid", "Produces a paper of the respective size.") | ("us-executive", "Produces a paper of the respective size.") | ("us-foolscap-folio", "Produces a paper of the respective size.") | ("us-statement", "Produces a paper of the respective size.") | ("us-ledger", "Produces a paper of the respective size.") | ("us-oficio", "Produces a paper of the respective size.") | ("us-gov-letter", "Produces a paper of the respective size.") | ("us-gov-legal", "Produces a paper of the respective size.") | ("us-business-card", "Produces a paper of the respective size.") | ("us-digest", "Produces a paper of the respective size.") | ("us-trade", "Produces a paper of the respective size.") | ("newspaper-compact", "Produces a paper of the respective size.") | ("newspaper-berliner", "Produces a paper of the respective size.") | ("newspaper-broadsheet", "Produces a paper of the respective size.") | ("presentation-16-9", "Produces a paper of the respective size.") | ("presentation-4-3", "Produces a paper of the respective size.")) -207..211 -> (("a0", "Produces a paper of the respective size.") | ("a1", "Produces a paper of the respective size.") | ("a2", "Produces a paper of the respective size.") | ("a3", "Produces a paper of the respective size.") | ("a4", "Produces a paper of the respective size.") | ("a5", "Produces a paper of the respective size.") | ("a6", "Produces a paper of the respective size.") | ("a7", "Produces a paper of the respective size.") | ("a8", "Produces a paper of the respective size.") | ("a9", "Produces a paper of the respective size.") | ("a10", "Produces a paper of the respective size.") | ("a11", "Produces a paper of the respective size.") | ("iso-b1", "Produces a paper of the respective size.") | ("iso-b2", "Produces a paper of the respective size.") | ("iso-b3", "Produces a paper of the respective size.") | ("iso-b4", "Produces a paper of the respective size.") | ("iso-b5", "Produces a paper of the respective size.") | ("iso-b6", "Produces a paper of the respective size.") | ("iso-b7", "Produces a paper of the respective size.") | ("iso-b8", "Produces a paper of the respective size.") | ("iso-c3", "Produces a paper of the respective size.") | ("iso-c4", "Produces a paper of the respective size.") | ("iso-c5", "Produces a paper of the respective size.") | ("iso-c6", "Produces a paper of the respective size.") | ("iso-c7", "Produces a paper of the respective size.") | ("iso-c8", "Produces a paper of the respective size.") | ("din-d3", "Produces a paper of the respective size.") | ("din-d4", "Produces a paper of the respective size.") | ("din-d5", "Produces a paper of the respective size.") | ("din-d6", "Produces a paper of the respective size.") | ("din-d7", "Produces a paper of the respective size.") | ("din-d8", "Produces a paper of the respective size.") | ("sis-g5", "Produces a paper of the respective size.") | ("sis-e5", "Produces a paper of the respective size.") | ("ansi-a", "Produces a paper of the respective size.") | ("ansi-b", "Produces a paper of the respective size.") | ("ansi-c", "Produces a paper of the respective size.") | ("ansi-d", "Produces a paper of the respective size.") | ("ansi-e", "Produces a paper of the respective size.") | ("arch-a", "Produces a paper of the respective size.") | ("arch-b", "Produces a paper of the respective size.") | ("arch-c", "Produces a paper of the respective size.") | ("arch-d", "Produces a paper of the respective size.") | ("arch-e1", "Produces a paper of the respective size.") | ("arch-e", "Produces a paper of the respective size.") | ("jis-b0", "Produces a paper of the respective size.") | ("jis-b1", "Produces a paper of the respective size.") | ("jis-b2", "Produces a paper of the respective size.") | ("jis-b3", "Produces a paper of the respective size.") | ("jis-b4", "Produces a paper of the respective size.") | ("jis-b5", "Produces a paper of the respective size.") | ("jis-b6", "Produces a paper of the respective size.") | ("jis-b7", "Produces a paper of the respective size.") | ("jis-b8", "Produces a paper of the respective size.") | ("jis-b9", "Produces a paper of the respective size.") | ("jis-b10", "Produces a paper of the respective size.") | ("jis-b11", "Produces a paper of the respective size.") | ("sac-d0", "Produces a paper of the respective size.") | ("sac-d1", "Produces a paper of the respective size.") | ("sac-d2", "Produces a paper of the respective size.") | ("sac-d3", "Produces a paper of the respective size.") | ("sac-d4", "Produces a paper of the respective size.") | ("sac-d5", "Produces a paper of the respective size.") | ("sac-d6", "Produces a paper of the respective size.") | ("iso-id-1", "Produces a paper of the respective size.") | ("iso-id-2", "Produces a paper of the respective size.") | ("iso-id-3", "Produces a paper of the respective size.") | ("asia-f4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-5", "Produces a paper of the respective size.") | ("jp-shiroku-ban-6", "Produces a paper of the respective size.") | ("jp-kiku-4", "Produces a paper of the respective size.") | ("jp-kiku-5", "Produces a paper of the respective size.") | ("jp-business-card", "Produces a paper of the respective size.") | ("cn-business-card", "Produces a paper of the respective size.") | ("eu-business-card", "Produces a paper of the respective size.") | ("fr-tellière", "Produces a paper of the respective size.") | ("fr-couronne-écriture", "Produces a paper of the respective size.") | ("fr-couronne-édition", "Produces a paper of the respective size.") | ("fr-raisin", "Produces a paper of the respective size.") | ("fr-carré", "Produces a paper of the respective size.") | ("fr-jésus", "Produces a paper of the respective size.") | ("uk-brief", "Produces a paper of the respective size.") | ("uk-draft", "Produces a paper of the respective size.") | ("uk-foolscap", "Produces a paper of the respective size.") | ("uk-quarto", "Produces a paper of the respective size.") | ("uk-crown", "Produces a paper of the respective size.") | ("uk-book-a", "Produces a paper of the respective size.") | ("uk-book-b", "Produces a paper of the respective size.") | ("us-letter", "Produces a paper of the respective size.") | ("us-legal", "Produces a paper of the respective size.") | ("us-tabloid", "Produces a paper of the respective size.") | ("us-executive", "Produces a paper of the respective size.") | ("us-foolscap-folio", "Produces a paper of the respective size.") | ("us-statement", "Produces a paper of the respective size.") | ("us-ledger", "Produces a paper of the respective size.") | ("us-oficio", "Produces a paper of the respective size.") | ("us-gov-letter", "Produces a paper of the respective size.") | ("us-gov-legal", "Produces a paper of the respective size.") | ("us-business-card", "Produces a paper of the respective size.") | ("us-digest", "Produces a paper of the respective size.") | ("us-trade", "Produces a paper of the respective size.") | ("newspaper-compact", "Produces a paper of the respective size.") | ("newspaper-berliner", "Produces a paper of the respective size.") | ("newspaper-broadsheet", "Produces a paper of the respective size.") | ("presentation-16-9", "Produces a paper of the respective size.") | ("presentation-4-3", "Produces a paper of the respective size.")) 216..221 -> @class 241..245 -> Func(page) -246..264 -> (("a0", "Produces a paper of the respective size.") | ("a1", "Produces a paper of the respective size.") | ("a2", "Produces a paper of the respective size.") | ("a3", "Produces a paper of the respective size.") | ("a4", "Produces a paper of the respective size.") | ("a5", "Produces a paper of the respective size.") | ("a6", "Produces a paper of the respective size.") | ("a7", "Produces a paper of the respective size.") | ("a8", "Produces a paper of the respective size.") | ("a9", "Produces a paper of the respective size.") | ("a10", "Produces a paper of the respective size.") | ("a11", "Produces a paper of the respective size.") | ("iso-b1", "Produces a paper of the respective size.") | ("iso-b2", "Produces a paper of the respective size.") | ("iso-b3", "Produces a paper of the respective size.") | ("iso-b4", "Produces a paper of the respective size.") | ("iso-b5", "Produces a paper of the respective size.") | ("iso-b6", "Produces a paper of the respective size.") | ("iso-b7", "Produces a paper of the respective size.") | ("iso-b8", "Produces a paper of the respective size.") | ("iso-c3", "Produces a paper of the respective size.") | ("iso-c4", "Produces a paper of the respective size.") | ("iso-c5", "Produces a paper of the respective size.") | ("iso-c6", "Produces a paper of the respective size.") | ("iso-c7", "Produces a paper of the respective size.") | ("iso-c8", "Produces a paper of the respective size.") | ("din-d3", "Produces a paper of the respective size.") | ("din-d4", "Produces a paper of the respective size.") | ("din-d5", "Produces a paper of the respective size.") | ("din-d6", "Produces a paper of the respective size.") | ("din-d7", "Produces a paper of the respective size.") | ("din-d8", "Produces a paper of the respective size.") | ("sis-g5", "Produces a paper of the respective size.") | ("sis-e5", "Produces a paper of the respective size.") | ("ansi-a", "Produces a paper of the respective size.") | ("ansi-b", "Produces a paper of the respective size.") | ("ansi-c", "Produces a paper of the respective size.") | ("ansi-d", "Produces a paper of the respective size.") | ("ansi-e", "Produces a paper of the respective size.") | ("arch-a", "Produces a paper of the respective size.") | ("arch-b", "Produces a paper of the respective size.") | ("arch-c", "Produces a paper of the respective size.") | ("arch-d", "Produces a paper of the respective size.") | ("arch-e1", "Produces a paper of the respective size.") | ("arch-e", "Produces a paper of the respective size.") | ("jis-b0", "Produces a paper of the respective size.") | ("jis-b1", "Produces a paper of the respective size.") | ("jis-b2", "Produces a paper of the respective size.") | ("jis-b3", "Produces a paper of the respective size.") | ("jis-b4", "Produces a paper of the respective size.") | ("jis-b5", "Produces a paper of the respective size.") | ("jis-b6", "Produces a paper of the respective size.") | ("jis-b7", "Produces a paper of the respective size.") | ("jis-b8", "Produces a paper of the respective size.") | ("jis-b9", "Produces a paper of the respective size.") | ("jis-b10", "Produces a paper of the respective size.") | ("jis-b11", "Produces a paper of the respective size.") | ("sac-d0", "Produces a paper of the respective size.") | ("sac-d1", "Produces a paper of the respective size.") | ("sac-d2", "Produces a paper of the respective size.") | ("sac-d3", "Produces a paper of the respective size.") | ("sac-d4", "Produces a paper of the respective size.") | ("sac-d5", "Produces a paper of the respective size.") | ("sac-d6", "Produces a paper of the respective size.") | ("iso-id-1", "Produces a paper of the respective size.") | ("iso-id-2", "Produces a paper of the respective size.") | ("iso-id-3", "Produces a paper of the respective size.") | ("asia-f4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-5", "Produces a paper of the respective size.") | ("jp-shiroku-ban-6", "Produces a paper of the respective size.") | ("jp-kiku-4", "Produces a paper of the respective size.") | ("jp-kiku-5", "Produces a paper of the respective size.") | ("jp-business-card", "Produces a paper of the respective size.") | ("cn-business-card", "Produces a paper of the respective size.") | ("eu-business-card", "Produces a paper of the respective size.") | ("fr-tellière", "Produces a paper of the respective size.") | ("fr-couronne-écriture", "Produces a paper of the respective size.") | ("fr-couronne-édition", "Produces a paper of the respective size.") | ("fr-raisin", "Produces a paper of the respective size.") | ("fr-carré", "Produces a paper of the respective size.") | ("fr-jésus", "Produces a paper of the respective size.") | ("uk-brief", "Produces a paper of the respective size.") | ("uk-draft", "Produces a paper of the respective size.") | ("uk-foolscap", "Produces a paper of the respective size.") | ("uk-quarto", "Produces a paper of the respective size.") | ("uk-crown", "Produces a paper of the respective size.") | ("uk-book-a", "Produces a paper of the respective size.") | ("uk-book-b", "Produces a paper of the respective size.") | ("us-letter", "Produces a paper of the respective size.") | ("us-legal", "Produces a paper of the respective size.") | ("us-tabloid", "Produces a paper of the respective size.") | ("us-executive", "Produces a paper of the respective size.") | ("us-foolscap-folio", "Produces a paper of the respective size.") | ("us-statement", "Produces a paper of the respective size.") | ("us-ledger", "Produces a paper of the respective size.") | ("us-oficio", "Produces a paper of the respective size.") | ("us-gov-letter", "Produces a paper of the respective size.") | ("us-gov-legal", "Produces a paper of the respective size.") | ("us-business-card", "Produces a paper of the respective size.") | ("us-digest", "Produces a paper of the respective size.") | ("us-trade", "Produces a paper of the respective size.") | ("newspaper-compact", "Produces a paper of the respective size.") | ("newspaper-berliner", "Produces a paper of the respective size.") | ("newspaper-broadsheet", "Produces a paper of the respective size.") | ("presentation-16-9", "Produces a paper of the respective size.") | ("presentation-4-3", "Produces a paper of the respective size.")) -253..264 -> (("a0", "Produces a paper of the respective size.") | ("a1", "Produces a paper of the respective size.") | ("a2", "Produces a paper of the respective size.") | ("a3", "Produces a paper of the respective size.") | ("a4", "Produces a paper of the respective size.") | ("a5", "Produces a paper of the respective size.") | ("a6", "Produces a paper of the respective size.") | ("a7", "Produces a paper of the respective size.") | ("a8", "Produces a paper of the respective size.") | ("a9", "Produces a paper of the respective size.") | ("a10", "Produces a paper of the respective size.") | ("a11", "Produces a paper of the respective size.") | ("iso-b1", "Produces a paper of the respective size.") | ("iso-b2", "Produces a paper of the respective size.") | ("iso-b3", "Produces a paper of the respective size.") | ("iso-b4", "Produces a paper of the respective size.") | ("iso-b5", "Produces a paper of the respective size.") | ("iso-b6", "Produces a paper of the respective size.") | ("iso-b7", "Produces a paper of the respective size.") | ("iso-b8", "Produces a paper of the respective size.") | ("iso-c3", "Produces a paper of the respective size.") | ("iso-c4", "Produces a paper of the respective size.") | ("iso-c5", "Produces a paper of the respective size.") | ("iso-c6", "Produces a paper of the respective size.") | ("iso-c7", "Produces a paper of the respective size.") | ("iso-c8", "Produces a paper of the respective size.") | ("din-d3", "Produces a paper of the respective size.") | ("din-d4", "Produces a paper of the respective size.") | ("din-d5", "Produces a paper of the respective size.") | ("din-d6", "Produces a paper of the respective size.") | ("din-d7", "Produces a paper of the respective size.") | ("din-d8", "Produces a paper of the respective size.") | ("sis-g5", "Produces a paper of the respective size.") | ("sis-e5", "Produces a paper of the respective size.") | ("ansi-a", "Produces a paper of the respective size.") | ("ansi-b", "Produces a paper of the respective size.") | ("ansi-c", "Produces a paper of the respective size.") | ("ansi-d", "Produces a paper of the respective size.") | ("ansi-e", "Produces a paper of the respective size.") | ("arch-a", "Produces a paper of the respective size.") | ("arch-b", "Produces a paper of the respective size.") | ("arch-c", "Produces a paper of the respective size.") | ("arch-d", "Produces a paper of the respective size.") | ("arch-e1", "Produces a paper of the respective size.") | ("arch-e", "Produces a paper of the respective size.") | ("jis-b0", "Produces a paper of the respective size.") | ("jis-b1", "Produces a paper of the respective size.") | ("jis-b2", "Produces a paper of the respective size.") | ("jis-b3", "Produces a paper of the respective size.") | ("jis-b4", "Produces a paper of the respective size.") | ("jis-b5", "Produces a paper of the respective size.") | ("jis-b6", "Produces a paper of the respective size.") | ("jis-b7", "Produces a paper of the respective size.") | ("jis-b8", "Produces a paper of the respective size.") | ("jis-b9", "Produces a paper of the respective size.") | ("jis-b10", "Produces a paper of the respective size.") | ("jis-b11", "Produces a paper of the respective size.") | ("sac-d0", "Produces a paper of the respective size.") | ("sac-d1", "Produces a paper of the respective size.") | ("sac-d2", "Produces a paper of the respective size.") | ("sac-d3", "Produces a paper of the respective size.") | ("sac-d4", "Produces a paper of the respective size.") | ("sac-d5", "Produces a paper of the respective size.") | ("sac-d6", "Produces a paper of the respective size.") | ("iso-id-1", "Produces a paper of the respective size.") | ("iso-id-2", "Produces a paper of the respective size.") | ("iso-id-3", "Produces a paper of the respective size.") | ("asia-f4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-4", "Produces a paper of the respective size.") | ("jp-shiroku-ban-5", "Produces a paper of the respective size.") | ("jp-shiroku-ban-6", "Produces a paper of the respective size.") | ("jp-kiku-4", "Produces a paper of the respective size.") | ("jp-kiku-5", "Produces a paper of the respective size.") | ("jp-business-card", "Produces a paper of the respective size.") | ("cn-business-card", "Produces a paper of the respective size.") | ("eu-business-card", "Produces a paper of the respective size.") | ("fr-tellière", "Produces a paper of the respective size.") | ("fr-couronne-écriture", "Produces a paper of the respective size.") | ("fr-couronne-édition", "Produces a paper of the respective size.") | ("fr-raisin", "Produces a paper of the respective size.") | ("fr-carré", "Produces a paper of the respective size.") | ("fr-jésus", "Produces a paper of the respective size.") | ("uk-brief", "Produces a paper of the respective size.") | ("uk-draft", "Produces a paper of the respective size.") | ("uk-foolscap", "Produces a paper of the respective size.") | ("uk-quarto", "Produces a paper of the respective size.") | ("uk-crown", "Produces a paper of the respective size.") | ("uk-book-a", "Produces a paper of the respective size.") | ("uk-book-b", "Produces a paper of the respective size.") | ("us-letter", "Produces a paper of the respective size.") | ("us-legal", "Produces a paper of the respective size.") | ("us-tabloid", "Produces a paper of the respective size.") | ("us-executive", "Produces a paper of the respective size.") | ("us-foolscap-folio", "Produces a paper of the respective size.") | ("us-statement", "Produces a paper of the respective size.") | ("us-ledger", "Produces a paper of the respective size.") | ("us-oficio", "Produces a paper of the respective size.") | ("us-gov-letter", "Produces a paper of the respective size.") | ("us-gov-legal", "Produces a paper of the respective size.") | ("us-business-card", "Produces a paper of the respective size.") | ("us-digest", "Produces a paper of the respective size.") | ("us-trade", "Produces a paper of the respective size.") | ("newspaper-compact", "Produces a paper of the respective size.") | ("newspaper-berliner", "Produces a paper of the respective size.") | ("newspaper-broadsheet", "Produces a paper of the respective size.") | ("presentation-16-9", "Produces a paper of the respective size.") | ("presentation-4-3", "Produces a paper of the respective size.")) 269..274 -> @class 290..297 -> @content diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@text_font.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@text_font.typ.snap index 2400d74d..a86cd3a7 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@text_font.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@text_font.typ.snap @@ -8,28 +8,15 @@ input_file: crates/tinymist-query/src/fixtures/type_check/text_font.typ --- 1..5 -> Func(text) 1..21 -> Element(text) -6..18 -> (TextFont | Array) -12..18 -> (TextFont | Array) -19..21 -> Type(content) 23..27 -> Func(text) 23..39 -> Element(text) -28..36 -> (TextFont | Array) -34..36 -> (TextFont | Array) -37..39 -> Type(content) 41..45 -> Func(text) 41..64 -> Element(text) -46..61 -> (TextFont | Array) -52..61 -> (TextFont | Array) -62..64 -> Type(content) 70..71 -> @x 82..86 -> Func(text) 82..97 -> Element(text) -87..94 -> (TextFont | Array) -93..94 -> (@x | (TextFont | Array)) -95..97 -> Type(content) +93..94 -> @x 103..104 -> @y 118..122 -> Func(text) 118..133 -> Element(text) -123..130 -> (TextFont | Array) -129..130 -> (@y | (TextFont | Array)) -131..133 -> Type(content) +129..130 -> @y diff --git a/crates/tinymist-query/src/fixtures/type_check/snaps/test@with.typ.snap b/crates/tinymist-query/src/fixtures/type_check/snaps/test@with.typ.snap index 93a8c081..0213a9ae 100644 --- a/crates/tinymist-query/src/fixtures/type_check/snaps/test@with.typ.snap +++ b/crates/tinymist-query/src/fixtures/type_check/snaps/test@with.typ.snap @@ -3,14 +3,14 @@ source: crates/tinymist-query/src/analysis.rs expression: result input_file: crates/tinymist-query/src/fixtures/type_check/with.typ --- -"f" = (Any) -> Any -"g" = ((Any) -> Any).with(..[&(1)]) -"x" = ( ⪰ Any | Type(Type(integer))) +"f" = (Any) => Any +"g" = (Any | ((Any) => Any).with(..(1) => any)) +"x" = ( ⪰ Any | Type(integer)) --- 5..6 -> @f 7..8 -> @x 12..13 -> @x 20..21 -> @g 24..25 -> @f -24..30 -> (@x) -> @x -24..33 -> ((@x) -> @x).with(..[&(1)]) +24..30 -> (@x) => @x +24..33 -> (@x | ((@x) => @x).with(..(1) => any)) diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs index f16e6ae3..633e2ab8 100644 --- a/crates/tinymist-query/src/lib.rs +++ b/crates/tinymist-query/src/lib.rs @@ -10,6 +10,7 @@ mod adt; pub mod analysis; pub mod syntax; +mod ty; mod upstream; pub(crate) mod diagnostics; diff --git a/crates/tinymist-query/src/signature_help.rs b/crates/tinymist-query/src/signature_help.rs index 1b2cafd6..9b4bac21 100644 --- a/crates/tinymist-query/src/signature_help.rs +++ b/crates/tinymist-query/src/signature_help.rs @@ -1,5 +1,5 @@ use crate::{ - analysis::{analyze_dyn_signature, find_definition, FlowType}, + analysis::{analyze_dyn_signature, find_definition, Ty}, prelude::*, syntax::{get_check_target, get_deref_target, CheckTarget, ParamTarget}, DocTooltip, LspParamInfo, SemanticRequest, @@ -33,6 +33,8 @@ impl SemanticRequest for SignatureHelpRequest { let def_link = find_definition(ctx, source.clone(), None, deref_target)?; + let type_sig = ctx.user_type_of_def(&source, &def_link); + let documentation = DocTooltip::get(ctx, &def_link) .as_deref() .map(markdown_docs); @@ -55,6 +57,10 @@ impl SemanticRequest for SignatureHelpRequest { let mut named = sig.primary().named.values().collect::>(); let rest = &sig.primary().rest; + let type_sig = type_sig.and_then(|type_sig| type_sig.sig_repr(true)); + + log::info!("got type signature {type_sig:?}"); + named.sort_by_key(|x| &x.name); let active_parameter = match &target { @@ -72,27 +78,36 @@ impl SemanticRequest for SignatureHelpRequest { let mut params = Vec::new(); label.push('('); - for ty in pos.iter().chain(named.into_iter()).chain(rest.iter()) { + let pos = pos + .iter() + .enumerate() + .map(|(i, pos)| (pos, type_sig.as_ref().and_then(|sig| sig.pos(i)))); + let named = named + .into_iter() + .map(|x| (x, type_sig.as_ref().and_then(|sig| sig.named(&x.name)))); + let rest = rest + .iter() + .map(|x| (x, type_sig.as_ref().and_then(|sig| sig.rest_param()))); + for (param, ty) in pos.chain(named).chain(rest) { if !params.is_empty() { label.push_str(", "); } label.push_str(&format!( "{}: {}", - ty.name, - ty.infer_type - .as_ref() - .unwrap_or(&FlowType::Any) + param.name, + ty.or_else(|| param.base_type.as_ref()) + .unwrap_or(&Ty::Any) .describe() .as_deref() .unwrap_or("any") )); params.push(LspParamInfo { - label: lsp_types::ParameterLabel::Simple(ty.name.clone().into()), - documentation: if !ty.docs.is_empty() { + label: lsp_types::ParameterLabel::Simple(param.name.as_ref().into()), + documentation: if !param.docs.is_empty() { Some(Documentation::MarkupContent(MarkupContent { - value: ty.docs.clone().into(), + value: param.docs.clone().into(), kind: MarkupKind::Markdown, })) } else { @@ -101,7 +116,11 @@ impl SemanticRequest for SignatureHelpRequest { }); } label.push(')'); - if let Some(ret_ty) = sig.primary().ret_ty.as_ref() { + let ret = type_sig + .as_ref() + .and_then(|sig| sig.ret.as_ref()) + .or_else(|| sig.primary().ret_ty.as_ref()); + if let Some(ret_ty) = ret { label.push_str(" -> "); label.push_str(ret_ty.describe().as_deref().unwrap_or("any")); } diff --git a/crates/tinymist-query/src/syntax/matcher.rs b/crates/tinymist-query/src/syntax/matcher.rs index 8190e4be..cc999c8f 100644 --- a/crates/tinymist-query/src/syntax/matcher.rs +++ b/crates/tinymist-query/src/syntax/matcher.rs @@ -101,14 +101,7 @@ pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option ParamTarget<'a> { pub enum CheckTarget<'a> { Param { callee: LinkedNode<'a>, + args: LinkedNode<'a>, target: ParamTarget<'a>, is_set: bool, }, @@ -284,12 +278,46 @@ impl<'a> CheckTarget<'a> { } } +#[derive(Debug)] enum ParamKind { Call, Array, Dict, } +pub fn get_check_target_by_context<'a>( + context: LinkedNode<'a>, + node: LinkedNode<'a>, +) -> Option> { + let context_deref_target = get_deref_target(context.clone(), node.offset())?; + let node_deref_target = get_deref_target(node.clone(), node.offset())?; + + match context_deref_target { + DerefTarget::Callee(callee) + if matches!(node_deref_target, DerefTarget::Normal(..)) + && !matches!(node_deref_target, DerefTarget::Callee(..)) => + { + let parent = callee.parent()?; + let args = match parent.cast::() { + Some(ast::Expr::FuncCall(call)) => call.args(), + Some(ast::Expr::Set(set)) => set.args(), + _ => return None, + }; + let args = parent.find(args.span())?; + + let is_set = parent.kind() == SyntaxKind::Set; + let target = get_param_target(args.clone(), node, ParamKind::Call)?; + Some(CheckTarget::Param { + callee, + args, + target, + is_set, + }) + } + _ => None, + } +} + pub fn get_check_target(node: LinkedNode) -> Option> { let mut node = node; while node.kind().is_trivia() { @@ -306,12 +334,13 @@ pub fn get_check_target(node: LinkedNode) -> Option> { Some(ast::Expr::Set(set)) => set.args(), _ => return None, }; - let args_node = parent.find(args.span())?; + let args = parent.find(args.span())?; let is_set = parent.kind() == SyntaxKind::Set; - let target = get_param_target(args_node, node, ParamKind::Call)?; + let target = get_param_target(args.clone(), node, ParamKind::Call)?; return Some(CheckTarget::Param { callee, + args, target, is_set, }); diff --git a/crates/tinymist-query/src/ty/apply.rs b/crates/tinymist-query/src/ty/apply.rs new file mode 100644 index 00000000..58873251 --- /dev/null +++ b/crates/tinymist-query/src/ty/apply.rs @@ -0,0 +1,65 @@ +use crate::{adt::interner::Interned, ty::def::*}; + +use super::{Sig, SigChecker, SigSurfaceKind}; + +pub trait ApplyChecker { + fn call(&mut self, sig: Sig, arguments: &Interned, pol: bool); + + fn bound_of_var(&mut self, _var: &Interned, _pol: bool) -> Option { + None + } +} + +impl Ty { + pub fn call(&self, args: &Interned, pol: bool, checker: &mut impl ApplyChecker) { + self.apply(SigSurfaceKind::Call, args, pol, checker) + } + + // pub fn element_of(&self, pol: bool, checker: &mut impl ApplyChecker) { + // static EMPTY_ARGS: Lazy> = + // Lazy::new(|| Interned::new(ArgsTy::default())); + + // self.apply(SigSurfaceKind::ArrayOrDict, &EMPTY_ARGS, pol, checker) + // } + + fn apply( + &self, + surface: SigSurfaceKind, + args: &Interned, + pol: bool, + checker: &mut impl ApplyChecker, + ) { + let mut worker = ApplySigChecker(checker, args); + worker.ty(self, surface, pol); + } +} + +pub struct ApplySigChecker<'a, T>(&'a mut T, &'a Interned); + +impl<'a, T: ApplyChecker> ApplySigChecker<'a, T> { + fn ty(&mut self, ty: &Ty, surface: SigSurfaceKind, pol: bool) { + ty.sig_surface(pol, surface, self) + } +} + +impl<'a, T: ApplyChecker> SigChecker for ApplySigChecker<'a, T> { + fn check(&mut self, cano_sig: Sig, ctx: &mut super::SigCheckContext, pol: bool) -> Option<()> { + let args = &ctx.args; + let partial_sig = if args.is_empty() { + cano_sig + } else { + Sig::With { + sig: &cano_sig, + withs: args, + at: &ctx.at, + } + }; + + self.0.call(partial_sig, self.1, pol); + Some(()) + } + + fn check_var(&mut self, _var: &Interned, _pol: bool) -> Option { + self.0.bound_of_var(_var, _pol) + } +} diff --git a/crates/tinymist-query/src/ty/bound.rs b/crates/tinymist-query/src/ty/bound.rs new file mode 100644 index 00000000..2f3a1df0 --- /dev/null +++ b/crates/tinymist-query/src/ty/bound.rs @@ -0,0 +1,68 @@ +use crate::{adt::interner::Interned, ty::def::*}; + +pub trait BoundChecker { + fn collect(&mut self, ty: &Ty, pol: bool); + fn bound_of_var(&mut self, _var: &Interned, _pol: bool) -> Option { + None + } +} + +impl BoundChecker for T +where + T: FnMut(&Ty, bool) -> Option, +{ + fn collect(&mut self, ty: &Ty, pol: bool) { + self(ty, pol); + } +} + +impl Ty { + pub fn has_bounds(&self) -> bool { + matches!(self, Ty::Union(_) | Ty::Let(_) | Ty::Var(_)) + } + + pub fn bounds(&self, pol: bool, checker: &mut impl BoundChecker) { + let mut worker = BoundCheckContext; + worker.ty(self, pol, checker); + } +} + +pub struct BoundCheckContext; + +impl BoundCheckContext { + fn tys<'a>( + &mut self, + tys: impl Iterator, + pol: bool, + checker: &mut impl BoundChecker, + ) { + for ty in tys { + self.ty(ty, pol, checker); + } + } + + fn ty(&mut self, ty: &Ty, pol: bool, checker: &mut impl BoundChecker) { + match ty { + Ty::Union(u) => { + self.tys(u.iter(), pol, checker); + } + Ty::Let(u) => { + self.tys(u.ubs.iter(), pol, checker); + self.tys(u.lbs.iter(), !pol, checker); + } + Ty::Var(u) => { + let Some(w) = checker.bound_of_var(u, pol) else { + return; + }; + self.tys(w.ubs.iter(), pol, checker); + self.tys(w.lbs.iter(), !pol, checker); + } + // todo: calculate these operators + // Ty::Select(_) => {} + // Ty::Unary(_) => {} + // Ty::Binary(_) => {} + // Ty::If(_) => {} + ty => checker.collect(ty, pol), + } + } +} diff --git a/crates/tinymist-query/src/analysis/ty/builtin.rs b/crates/tinymist-query/src/ty/builtin.rs similarity index 60% rename from crates/tinymist-query/src/analysis/ty/builtin.rs rename to crates/tinymist-query/src/ty/builtin.rs index f63a1d3f..85f5dc9c 100644 --- a/crates/tinymist-query/src/analysis/ty/builtin.rs +++ b/crates/tinymist-query/src/ty/builtin.rs @@ -1,15 +1,16 @@ -use ecow::EcoVec; +use core::fmt; + use once_cell::sync::Lazy; use regex::RegexSet; +use typst::{foundations::CastInfo, syntax::Span}; use typst::{ foundations::{AutoValue, Content, Func, NoneValue, ParamInfo, Type, Value}, layout::Length, - syntax::Span, }; -use super::{FlowRecord, FlowType}; +use crate::{adt::interner::Interned, ty::*}; -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) enum PathPreference { None, Special, @@ -82,8 +83,87 @@ impl PathPreference { } } -#[derive(Debug, Clone, Hash)] -pub(crate) enum FlowBuiltinType { +impl Ty { + pub fn from_return_site(f: &Func, c: &'_ CastInfo) -> Option { + use typst::foundations::func::Repr; + match f.inner() { + Repr::Element(e) => return Some(Ty::Builtin(BuiltinTy::Element(*e))), + Repr::Closure(_) => {} + Repr::With(w) => return Ty::from_return_site(&w.0, c), + Repr::Native(_) => {} + }; + + let ty = match c { + CastInfo::Any => Ty::Any, + CastInfo::Value(v, doc) => Ty::Value(InsTy::new_doc(v.clone(), doc)), + CastInfo::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)), + CastInfo::Union(e) => { + // flat union + let e = UnionIter(vec![e.as_slice().iter()]); + + Ty::Union(Interned::new( + e.flat_map(|e| Self::from_return_site(f, e)).collect(), + )) + } + }; + + Some(ty) + } + + pub(crate) fn from_param_site(f: &Func, p: &ParamInfo, s: &CastInfo) -> Option { + use typst::foundations::func::Repr; + match f.inner() { + Repr::Element(..) | Repr::Native(..) => { + if let Some(ty) = param_mapping(f, p) { + return Some(ty); + } + } + Repr::Closure(_) => {} + Repr::With(w) => return Ty::from_param_site(&w.0, p, s), + }; + + let ty = match &s { + CastInfo::Any => Ty::Any, + CastInfo::Value(v, doc) => Ty::Value(InsTy::new_doc(v.clone(), doc)), + CastInfo::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)), + CastInfo::Union(e) => { + // flat union + let e = UnionIter(vec![e.as_slice().iter()]); + + Ty::Union(Interned::new( + e.flat_map(|e| Self::from_param_site(f, p, e)).collect(), + )) + } + }; + + Some(ty) + } +} + +struct UnionIter<'a>(Vec>); + +impl<'a> Iterator for UnionIter<'a> { + type Item = &'a CastInfo; + + fn next(&mut self) -> Option { + loop { + let iter = self.0.last_mut()?; + if let Some(e) = iter.next() { + match e { + CastInfo::Union(e) => { + self.0.push(e.as_slice().iter()); + } + _ => return Some(e), + } + } else { + self.0.pop(); + } + } + } +} + +#[derive(Clone, Hash, PartialEq, Eq)] +pub(crate) enum BuiltinTy { Args, Color, TextSize, @@ -102,30 +182,55 @@ pub(crate) enum FlowBuiltinType { Radius, Type(typst::foundations::Type), + Element(typst::foundations::Element), Path(PathPreference), } -impl FlowBuiltinType { - pub fn from_value(builtin: &Value) -> FlowType { +impl fmt::Debug for BuiltinTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BuiltinTy::Args => write!(f, "Args"), + BuiltinTy::Color => write!(f, "Color"), + BuiltinTy::TextSize => write!(f, "TextSize"), + BuiltinTy::TextFont => write!(f, "TextFont"), + BuiltinTy::TextLang => write!(f, "TextLang"), + BuiltinTy::TextRegion => write!(f, "TextRegion"), + BuiltinTy::Dir => write!(f, "Dir"), + BuiltinTy::Length => write!(f, "Length"), + BuiltinTy::Float => write!(f, "Float"), + BuiltinTy::Stroke => write!(f, "Stroke"), + BuiltinTy::Margin => write!(f, "Margin"), + BuiltinTy::Inset => write!(f, "Inset"), + BuiltinTy::Outset => write!(f, "Outset"), + BuiltinTy::Radius => write!(f, "Radius"), + BuiltinTy::Type(ty) => write!(f, "Type({})", ty.long_name()), + BuiltinTy::Element(e) => e.fmt(f), + BuiltinTy::Path(p) => write!(f, "Path({p:?})"), + } + } +} + +impl BuiltinTy { + pub fn from_value(builtin: &Value) -> Ty { if let Value::Bool(v) = builtin { - return FlowType::Boolean(Some(*v)); + return Ty::Boolean(Some(*v)); } Self::from_builtin(builtin.ty()) } - pub fn from_builtin(builtin: Type) -> FlowType { + pub fn from_builtin(builtin: Type) -> Ty { if builtin == Type::of::() { - return FlowType::Auto; + return Ty::Auto; } if builtin == Type::of::() { - return FlowType::None; + return Ty::None; } if builtin == Type::of::() { return Color.literally(); } if builtin == Type::of::() { - return FlowType::None; + return Ty::None; } if builtin == Type::of::() { return Float.literally(); @@ -134,30 +239,31 @@ impl FlowBuiltinType { return Length.literally(); } if builtin == Type::of::() { - return FlowType::Content; + return Ty::Content; } - FlowBuiltinType::Type(builtin).literally() + BuiltinTy::Type(builtin).literally() } pub(crate) fn describe(&self) -> &'static str { match self { - FlowBuiltinType::Args => "args", - FlowBuiltinType::Color => "color", - FlowBuiltinType::TextSize => "text.size", - FlowBuiltinType::TextFont => "text.font", - FlowBuiltinType::TextLang => "text.lang", - FlowBuiltinType::TextRegion => "text.region", - FlowBuiltinType::Dir => "dir", - FlowBuiltinType::Length => "length", - FlowBuiltinType::Float => "float", - FlowBuiltinType::Stroke => "stroke", - FlowBuiltinType::Margin => "margin", - FlowBuiltinType::Inset => "inset", - FlowBuiltinType::Outset => "outset", - FlowBuiltinType::Radius => "radius", - FlowBuiltinType::Type(ty) => ty.short_name(), - FlowBuiltinType::Path(s) => match s { + BuiltinTy::Args => "args", + BuiltinTy::Color => "color", + BuiltinTy::TextSize => "text.size", + BuiltinTy::TextFont => "text.font", + BuiltinTy::TextLang => "text.lang", + BuiltinTy::TextRegion => "text.region", + BuiltinTy::Dir => "dir", + BuiltinTy::Length => "length", + BuiltinTy::Float => "float", + BuiltinTy::Stroke => "stroke", + BuiltinTy::Margin => "margin", + BuiltinTy::Inset => "inset", + BuiltinTy::Outset => "outset", + BuiltinTy::Radius => "radius", + BuiltinTy::Type(ty) => ty.short_name(), + BuiltinTy::Element(ty) => ty.name(), + BuiltinTy::Path(s) => match s { PathPreference::None => "[any]", PathPreference::Special => "[any]", PathPreference::Source => "[source]", @@ -175,30 +281,30 @@ impl FlowBuiltinType { } } -use FlowBuiltinType::*; +use BuiltinTy::*; -fn literally(s: impl FlowBuiltinLiterally) -> FlowType { +fn literally(s: impl FlowBuiltinLiterally) -> Ty { s.literally() } trait FlowBuiltinLiterally { - fn literally(self) -> FlowType; + fn literally(self) -> Ty; } impl FlowBuiltinLiterally for &str { - fn literally(self) -> FlowType { - FlowType::Value(Box::new((Value::Str((*self).into()), Span::detached()))) + fn literally(self) -> Ty { + Ty::Value(InsTy::new(Value::Str(self.into()))) } } -impl FlowBuiltinLiterally for FlowBuiltinType { - fn literally(self) -> FlowType { - FlowType::Builtin(self.clone()) +impl FlowBuiltinLiterally for BuiltinTy { + fn literally(self) -> Ty { + Ty::Builtin(self.clone()) } } -impl FlowBuiltinLiterally for FlowType { - fn literally(self) -> FlowType { +impl FlowBuiltinLiterally for Ty { + fn literally(self) -> Ty { self } } @@ -218,29 +324,27 @@ macro_rules! flow_builtin_union_inner { macro_rules! flow_union { // the first one is string ($($b:tt)*) => { - FlowType::Union(Box::new(flow_builtin_union_inner!( $($b)* ))) + Ty::Union(Interned::new(flow_builtin_union_inner!( $($b)* ))) }; } macro_rules! flow_record { ($($name:expr => $ty:expr),* $(,)?) => { - FlowRecord { - fields: EcoVec::from_iter([ - $( - ( - $name.into(), - $ty, - Span::detached(), - ), - )* - ]) - } + RecordTy::new(vec![ + $( + ( + $name.into(), + $ty, + Span::detached(), + ), + )* + ]) }; } -pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option { - match (f.name().unwrap(), p.name) { +pub(super) fn param_mapping(f: &Func, p: &ParamInfo) -> Option { + match (f.name()?, p.name) { ("cbor", "path") => Some(literally(Path(PathPreference::None))), ("csv", "path") => Some(literally(Path(PathPreference::Csv))), ("image", "path") => Some(literally(Path(PathPreference::Image))), @@ -254,10 +358,10 @@ pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option< ("bibliography", "path") => Some(literally(Path(PathPreference::Bibliography))), ("text", "size") => Some(literally(TextSize)), ("text", "font") => { - static FONT_TYPE: Lazy = Lazy::new(|| { - FlowType::Union(Box::new(vec![ + static FONT_TYPE: Lazy = Lazy::new(|| { + Ty::Union(Interned::new(vec![ literally(TextFont), - FlowType::Array(Box::new(literally(TextFont))), + Ty::Array(Interned::new(literally(TextFont))), ])) }); Some(FONT_TYPE.clone()) @@ -281,21 +385,21 @@ pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option< } ("block" | "box" | "rect" | "square", "radius") => Some(literally(Radius)), ("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => { - static COLUMN_TYPE: Lazy = Lazy::new(|| { + static COLUMN_TYPE: Lazy = Lazy::new(|| { flow_union!( - FlowType::Value(Box::new((Value::Auto, Span::detached()))), - FlowType::Value(Box::new((Value::Type(Type::of::()), Span::detached()))), + Ty::Value(InsTy::new(Value::Auto)), + Ty::Value(InsTy::new(Value::Type(Type::of::()))), literally(Length), - FlowType::Array(Box::new(literally(Length))), + Ty::Array(Interned::new(literally(Length))), ) }); Some(COLUMN_TYPE.clone()) } ("pattern", "size") => { - static PATTERN_SIZE_TYPE: Lazy = Lazy::new(|| { + static PATTERN_SIZE_TYPE: Lazy = Lazy::new(|| { flow_union!( - FlowType::Value(Box::new((Value::Auto, Span::detached()))), - FlowType::Array(Box::new(FlowType::Builtin(Length))), + Ty::Value(InsTy::new(Value::Auto)), + Ty::Array(Interned::new(Ty::Builtin(Length))), ) }); Some(PATTERN_SIZE_TYPE.clone()) @@ -307,13 +411,13 @@ pub(in crate::analysis::ty) fn param_mapping(f: &Func, p: &ParamInfo) -> Option< | "ellipse" | "circle" | "polygon" | "box" | "block" | "table" | "line" | "cell" | "hline" | "vline" | "regular", "stroke", - ) => Some(FlowType::Builtin(Stroke)), - ("page", "margin") => Some(FlowType::Builtin(Margin)), + ) => Some(Ty::Builtin(Stroke)), + ("page", "margin") => Some(Ty::Builtin(Margin)), _ => None, } } -static FLOW_STROKE_DASH_TYPE: Lazy = Lazy::new(|| { +static FLOW_STROKE_DASH_TYPE: Lazy = Lazy::new(|| { flow_union!( "solid", "dotted", @@ -325,15 +429,15 @@ static FLOW_STROKE_DASH_TYPE: Lazy = Lazy::new(|| { "dash-dotted", "densely-dash-dotted", "loosely-dash-dotted", - FlowType::Array(Box::new(flow_union!("dot", literally(Float)))), - FlowType::Dict(flow_record!( - "array" => FlowType::Array(Box::new(flow_union!("dot", literally(Float)))), + Ty::Array(Interned::new(flow_union!("dot", literally(Float)))), + Ty::Dict(flow_record!( + "array" => Ty::Array(Interned::new(flow_union!("dot", literally(Float)))), "phase" => literally(Length), )) ) }); -pub static FLOW_STROKE_DICT: Lazy = Lazy::new(|| { +pub static FLOW_STROKE_DICT: Lazy> = Lazy::new(|| { flow_record!( "paint" => literally(Color), "thickness" => literally(Length), @@ -344,7 +448,7 @@ pub static FLOW_STROKE_DICT: Lazy = Lazy::new(|| { ) }); -pub static FLOW_MARGIN_DICT: Lazy = Lazy::new(|| { +pub static FLOW_MARGIN_DICT: Lazy> = Lazy::new(|| { flow_record!( "top" => literally(Length), "right" => literally(Length), @@ -358,7 +462,7 @@ pub static FLOW_MARGIN_DICT: Lazy = Lazy::new(|| { ) }); -pub static FLOW_INSET_DICT: Lazy = Lazy::new(|| { +pub static FLOW_INSET_DICT: Lazy> = Lazy::new(|| { flow_record!( "top" => literally(Length), "right" => literally(Length), @@ -370,7 +474,7 @@ pub static FLOW_INSET_DICT: Lazy = Lazy::new(|| { ) }); -pub static FLOW_OUTSET_DICT: Lazy = Lazy::new(|| { +pub static FLOW_OUTSET_DICT: Lazy> = Lazy::new(|| { flow_record!( "top" => literally(Length), "right" => literally(Length), @@ -382,7 +486,7 @@ pub static FLOW_OUTSET_DICT: Lazy = Lazy::new(|| { ) }); -pub static FLOW_RADIUS_DICT: Lazy = Lazy::new(|| { +pub static FLOW_RADIUS_DICT: Lazy> = Lazy::new(|| { flow_record!( "top" => literally(Length), "right" => literally(Length), @@ -396,16 +500,12 @@ pub static FLOW_RADIUS_DICT: Lazy = Lazy::new(|| { ) }); -// todo bad case: function.with -// todo bad case: function.where // todo bad case: array.fold // todo bad case: datetime // todo bad case: selector // todo: function signatures, for example: `locate(loc => ...)` // todo: numbering/supplement -// todo: grid/table.columns/rows/gutter/column-gutter/row-gutter array of length -// todo: pattern.size array of length // todo: grid/table.fill/align/stroke/inset can be a function // todo: math.cancel.angle can be a function // todo: text.features array/dictionary diff --git a/crates/tinymist-query/src/ty/def.rs b/crates/tinymist-query/src/ty/def.rs new file mode 100644 index 00000000..e92ce3f1 --- /dev/null +++ b/crates/tinymist-query/src/ty/def.rs @@ -0,0 +1,931 @@ +#![allow(unused)] + +use core::fmt; +use ecow::{EcoString, EcoVec}; +use once_cell::sync::OnceCell; +use parking_lot::{Mutex, RwLock}; +use reflexo::vector::ir::DefId; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + hash::{Hash, Hasher}, + sync::Arc, +}; +use typst::{ + foundations::Value, + syntax::{ast, Span, SyntaxNode}, +}; + +use crate::{ + adt::interner::{impl_internable, Interned}, + analysis::BuiltinTy, +}; + +pub type TyRef = Interned; + +#[derive(Default)] +pub(crate) struct TypeCheckInfo { + pub vars: HashMap, + pub mapping: HashMap>, + + pub(super) cano_cache: Mutex, +} + +impl TypeCheckInfo { + // todo: distinguish at least, at most + pub fn witness_at_least(&mut self, site: Span, ty: Ty) { + Self::witness_(site, ty, &mut self.mapping); + } + + pub fn witness_at_most(&mut self, site: Span, ty: Ty) { + Self::witness_(site, ty, &mut self.mapping); + } + + pub(crate) fn witness_(site: Span, ty: Ty, mapping: &mut HashMap>) { + if site.is_detached() { + return; + } + + // todo: intersect/union + let site_store = mapping.entry(site); + match site_store { + Entry::Occupied(e) => { + e.into_mut().push(ty); + } + Entry::Vacant(e) => { + e.insert(vec![ty]); + } + } + } + + pub fn type_of_span(&self, site: Span) -> Option { + self.mapping + .get(&site) + .cloned() + .map(|e| Ty::from_types(e.into_iter())) + } + + pub fn type_of_def(&self, def: DefId) -> Option { + Some(self.simplify(self.vars.get(&def).map(|e| e.as_type())?, false)) + } +} + +#[derive(Default)] +pub(super) struct TypeCanoStore { + pub cano_cache: HashMap<(Ty, bool), Ty>, + pub cano_local_cache: HashMap<(DefId, bool), Ty>, + pub negatives: HashSet, + pub positives: HashSet, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TypeSource { + pub name_node: SyntaxNode, + pub name_repr: OnceCell>, + pub span: Span, + pub doc: Interned, +} + +impl Hash for TypeSource { + fn hash(&self, state: &mut H) { + self.name_node.hash(state); + self.span.hash(state); + self.doc.hash(state); + } +} + +impl TypeSource { + pub fn name(&self) -> Interned { + self.name_repr + .get_or_init(|| { + let name = self.name_node.text(); + if !name.is_empty() { + return Interned::new_str(name.as_str()); + } + let name = self.name_node.clone().into_text(); + Interned::new_str(name.as_str()) + }) + .clone() + } +} + +pub trait TypeInterace { + fn bone(&self) -> &Interned; + fn interface(&self) -> impl Iterator, &Ty)>; +} + +struct RefDebug<'a>(&'a Ty); + +impl<'a> fmt::Debug for RefDebug<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Ty::Var(v) => write!(f, "@v{:?}", v.name()), + _ => write!(f, "{:?}", self.0), + } + } +} + +#[derive(Debug, Hash, Clone, PartialEq)] +pub struct InsTy { + pub val: Value, + + pub syntax: Option>, +} + +// There are some case that val is not Eq, but we make it Eq for simplicity +impl Eq for InsTy {} + +impl InsTy { + pub fn new(val: Value) -> Interned { + Interned::new(Self { val, syntax: None }) + } + pub fn new_at(val: Value, s: Span) -> Interned { + Interned::new(Self { + val, + syntax: Some(Interned::new(TypeSource { + name_node: SyntaxNode::default(), + name_repr: OnceCell::new(), + span: s, + doc: Interned::new_str(""), + })), + }) + } + pub fn new_doc(val: Value, doc: &str) -> Interned { + Interned::new(Self { + val, + syntax: Some(Interned::new(TypeSource { + name_node: SyntaxNode::default(), + name_repr: OnceCell::new(), + span: Span::detached(), + doc: Interned::new_str(doc), + })), + }) + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct NameBone { + pub names: Vec>, +} + +impl NameBone { + pub fn empty() -> Interned { + Interned::new(Self { names: Vec::new() }) + } +} + +impl NameBone { + fn find(&self, name: &Interned) -> Option { + // binary search + self.names.binary_search_by(|probe| probe.cmp(name)).ok() + } +} + +impl NameBone { + pub(crate) fn intersect_keys_enumerate<'a>( + &'a self, + rhs: &'a NameBone, + ) -> impl Iterator + 'a { + let mut lhs_iter = self.names.iter().enumerate(); + let mut rhs_iter = rhs.names.iter().enumerate(); + + let mut lhs = lhs_iter.next(); + let mut rhs = rhs_iter.next(); + + std::iter::from_fn(move || 'key_scanning: loop { + if let (Some((i, lhs_key)), Some((j, rhs_key))) = (lhs, rhs) { + match lhs_key.cmp(rhs_key) { + std::cmp::Ordering::Less => { + lhs = lhs_iter.next(); + continue 'key_scanning; + } + std::cmp::Ordering::Greater => { + rhs = rhs_iter.next(); + continue 'key_scanning; + } + std::cmp::Ordering::Equal => { + lhs = lhs_iter.next(); + rhs = rhs_iter.next(); + return Some((i, j)); + } + } + } + return None; + }) + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct FieldTy { + pub name: Interned, + pub field: Ty, + + pub syntax: Option>, +} +impl FieldTy { + pub(crate) fn new_untyped(name: Interned) -> Interned { + Interned::new(Self { + name, + field: Ty::Any, + syntax: None, + }) + } +} + +#[derive(Hash, Clone, PartialEq, Eq, Default)] +pub struct TypeBounds { + pub lbs: EcoVec, + pub ubs: EcoVec, +} + +impl fmt::Debug for TypeBounds { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // write!(f, "{}", self.name) + // also where + if !self.lbs.is_empty() { + write!(f, " ⪰ {:?}", self.lbs[0])?; + for lb in &self.lbs[1..] { + write!(f, " | {lb:?}")?; + } + } + if !self.ubs.is_empty() { + write!(f, " ⪯ {:?}", self.ubs[0])?; + for ub in &self.ubs[1..] { + write!(f, " & {ub:?}")?; + } + } + Ok(()) + } +} +#[derive(Hash, Clone, PartialEq, Eq)] +pub struct TypeVar { + pub name: Interned, + pub def: DefId, + + pub syntax: Option>, +} + +impl fmt::Debug for TypeVar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "@{}", self.name) + } +} + +#[derive(Clone)] +pub(crate) enum FlowVarKind { + Strong(Arc>), + Weak(Arc>), +} + +impl FlowVarKind { + pub fn bounds(&self) -> &RwLock { + match self { + FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => w, + } + } +} + +#[derive(Clone)] +pub struct TypeVarBounds { + pub var: Interned, + pub bounds: FlowVarKind, +} + +impl fmt::Debug for TypeVarBounds { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.var)?; + match &self.bounds { + FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => write!(f, "{w:?}"), + } + } +} + +impl TypeVarBounds { + pub fn name(&self) -> Interned { + self.var.name.clone() + } + + pub fn id(&self) -> DefId { + self.var.def + } + + pub fn as_type(&self) -> Ty { + Ty::Var(self.var.clone()) + } + + pub(crate) fn new(var: TypeVar, init: TypeBounds) -> Self { + Self { + var: Interned::new(var), + bounds: FlowVarKind::Strong(Arc::new(RwLock::new(init))), + } + } + + pub fn ever_be(&self, exp: Ty) { + match &self.bounds { + // FlowVarKind::Strong(_t) => {} + FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { + let mut w = w.write(); + w.lbs.push(exp.clone()); + } + } + } + + pub(crate) fn weaken(&mut self) { + match &self.bounds { + FlowVarKind::Strong(w) => { + self.bounds = FlowVarKind::Weak(w.clone()); + } + FlowVarKind::Weak(_) => {} + } + } +} + +impl TypeVar { + pub fn new(name: Interned, def: DefId) -> Interned { + Interned::new(Self { + name, + def, + syntax: None, + }) + } + + pub fn name(&self) -> Interned { + self.name.clone() + } + + pub fn id(&self) -> DefId { + self.def + } +} + +#[derive(Hash, Clone, PartialEq, Eq)] +pub struct RecordTy { + pub types: Interned>, + pub names: Interned, + pub syntax: Option>, +} + +impl RecordTy { + pub(crate) fn shape_fields(mut fields: Vec<(Interned, Ty, Span)>) -> (NameBone, Vec) { + fields.sort_by(|a, b| a.0.cmp(&b.0)); + let names = NameBone { + names: fields.iter().map(|e| e.0.clone()).collect(), + }; + let types = fields.into_iter().map(|(_, ty, _)| ty).collect::>(); + + (names, types) + } + + pub(crate) fn new(fields: Vec<(Interned, Ty, Span)>) -> Interned { + let (names, types) = Self::shape_fields(fields); + Interned::new(Self { + types: Interned::new(types), + names: Interned::new(names), + syntax: None, + }) + } + + pub(crate) fn intersect_keys<'a>( + &'a self, + rhs: &'a RecordTy, + ) -> impl Iterator, &Ty, &Ty)> + 'a { + self.names + .intersect_keys_enumerate(&rhs.names) + .filter_map(move |(i, j)| { + self.types + .get(i) + .and_then(|lhs| rhs.types.get(j).map(|rhs| (&self.names.names[i], lhs, rhs))) + }) + } +} + +impl TypeInterace for RecordTy { + fn bone(&self) -> &Interned { + &self.names + } + + fn interface(&self) -> impl Iterator, &Ty)> { + self.names.names.iter().zip(self.types.iter()) + } +} + +impl fmt::Debug for RecordTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("{")?; + interpersed( + f, + self.interface().map(|(name, ty)| ParamTy::Named(name, ty)), + )?; + f.write_str("}") + } +} + +enum ParamTy<'a> { + Pos(&'a Ty), + Named(&'a Interned, &'a Ty), + Rest(&'a Ty), +} + +impl fmt::Debug for ParamTy<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParamTy::Pos(ty) => write!(f, "{ty:?}"), + ParamTy::Named(name, ty) => write!(f, "{name:?}: {ty:?}"), + ParamTy::Rest(ty) => write!(f, "...: {ty:?}[]"), + } + } +} + +#[derive(Hash, Clone, PartialEq, Eq)] +pub struct SigTy { + pub types: Interned>, + pub ret: Option, + pub names: Interned, + pub name_started: u32, + pub spread_left: bool, + pub spread_right: bool, + pub has_free_variables: bool, + + pub syntax: Option>, +} + +impl SigTy { + /// Array constructor + #[comemo::memoize] + pub(crate) fn array_cons(elem: Ty, anyify: bool) -> Interned { + let ret = if anyify { + Ty::Any + } else { + Ty::Array(Interned::new(elem.clone())) + }; + Interned::new(Self { + types: Interned::new(vec![elem]), + ret: Some(ret), + names: NameBone::empty(), + name_started: 0, + spread_left: false, + spread_right: true, + has_free_variables: false, + syntax: None, + }) + } + + pub(crate) fn inputs(&self) -> impl Iterator { + self.types.iter() + } + + /// Dictionary constructor + #[comemo::memoize] + pub(crate) fn dict_cons(named: &Interned, anyify: bool) -> Interned { + let ret = if anyify { + Ty::Any + } else { + Ty::Dict(named.clone()) + }; + + Interned::new(Self { + types: named.types.clone(), + ret: Some(ret), + names: named.names.clone(), + name_started: 0, + spread_left: false, + spread_right: false, + has_free_variables: false, + syntax: None, + }) + } + + pub(crate) fn new( + pos: impl Iterator, + named: impl Iterator, Ty)>, + rest: Option, + ret_ty: Option, + ) -> Self { + let named = named + .map(|(name, ty)| (name, ty, Span::detached())) + .collect::>(); + let (names, types) = RecordTy::shape_fields(named); + let spread_right = rest.is_some(); + + let name_started = if spread_right { 1 } else { 0 } + types.len(); + let types = pos.chain(types).chain(rest).collect::>(); + + let name_started = (types.len() - name_started) as u32; + + Self { + types: Interned::new(types), + ret: ret_ty, + names: Interned::new(names), + name_started, + spread_left: false, + spread_right, + // todo: substitute with actual value + has_free_variables: false, + syntax: None, + } + } +} + +impl Default for SigTy { + fn default() -> Self { + Self { + types: Interned::new(Vec::new()), + ret: None, + names: NameBone::empty(), + name_started: 0, + spread_left: false, + spread_right: false, + has_free_variables: false, + syntax: None, + } + } +} + +impl SigTy { + pub fn positional_params(&self) -> impl ExactSizeIterator { + self.types.iter().take(self.name_started as usize) + } + + pub fn named_params(&self) -> impl ExactSizeIterator, &Ty)> { + let named_names = self.names.names.iter(); + let named_types = self.types.iter().skip(self.name_started as usize); + + named_names.zip(named_types) + } + + pub fn rest_param(&self) -> Option<&Ty> { + if self.spread_right { + self.types.last() + } else { + None + } + } + + pub fn pos(&self, idx: usize) -> Option<&Ty> { + (idx < self.name_started as usize) + .then_some(()) + .and_then(|_| self.types.get(idx)) + } + + pub fn named(&self, name: &Interned) -> Option<&Ty> { + let idx = self.names.find(name)?; + self.types.get(idx + self.name_started as usize) + } + + pub(crate) fn matches<'a>( + &'a self, + args: &'a SigTy, + withs: Option<&Vec>>, + ) -> impl Iterator + 'a { + let with_len = withs + .map(|w| w.iter().map(|w| w.positional_params().len()).sum::()) + .unwrap_or(0); + + let sig_pos = self.positional_params().skip(with_len); + let arg_pos = args.positional_params(); + + let sig_rest = self.rest_param(); + let arg_rest = args.rest_param(); + + let max_len = sig_pos.len().max(arg_pos.len()) + + if sig_rest.is_some() && arg_rest.is_some() { + 1 + } else { + 0 + }; + + let sig_stream = sig_pos.chain(sig_rest.into_iter().cycle()).take(max_len); + let arg_stream = arg_pos.chain(arg_rest.into_iter().cycle()).take(max_len); + + let mut pos = sig_stream.zip(arg_stream); + let mut named = + self.names + .intersect_keys_enumerate(&args.names) + .filter_map(move |(i, j)| { + let lhs = self.types.get(i + self.name_started as usize)?; + let rhs = args.types.get(j + args.name_started as usize)?; + Some((lhs, rhs)) + }); + + pos.chain(named) + } +} + +impl fmt::Debug for SigTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("(")?; + let pos = self.positional_params().map(ParamTy::Pos); + let named = self + .named_params() + .map(|(name, ty)| ParamTy::Named(name, ty)); + let rest = self.rest_param().map(ParamTy::Rest); + interpersed(f, pos.chain(named).chain(rest))?; + f.write_str(") => ")?; + if let Some(ret) = &self.ret { + ret.fmt(f)?; + } else { + f.write_str("any")?; + } + Ok(()) + } +} + +pub type ArgsTy = SigTy; + +#[derive(Hash, Clone, PartialEq, Eq)] +pub struct SigWithTy { + pub sig: TyRef, + pub with: Interned, +} + +impl fmt::Debug for SigWithTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}.with({:?})", self.sig, self.with) + } +} + +#[derive(Hash, Clone, PartialEq, Eq)] +pub struct SelectTy { + pub ty: Interned, + pub select: Interned, +} + +impl fmt::Debug for SelectTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}.{}", RefDebug(&self.ty), self.select) + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) enum UnaryOp { + Pos, + Neg, + Not, + Context, + NotElementOf, + ElementOf, + TypeOf, +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct TypeUnary { + pub lhs: Interned, + pub op: UnaryOp, +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub struct TypeBinary { + pub operands: Interned<(Ty, Ty)>, + pub op: ast::BinOp, +} + +impl TypeBinary { + pub fn repr(&self) -> &(Ty, Ty) { + &self.operands + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +pub(crate) struct IfTy { + pub cond: Interned, + pub then: Interned, + pub else_: Interned, +} + +#[derive(Hash, Clone, PartialEq, Eq)] +pub(crate) enum Ty { + Clause, + Undef, + Content, + Any, + Space, + None, + Infer, + FlowNone, + Auto, + Boolean(Option), + Builtin(BuiltinTy), + Value(Interned), + Field(Interned), + + Var(Interned), + Union(Interned>), + Let(Interned), + + Func(Interned), + With(Interned), + Args(Interned), + Dict(Interned), + Array(Interned), + // Note: may contains spread types + Tuple(Interned>), + Select(Interned), + Unary(Interned), + Binary(Interned), + If(Interned), +} + +impl fmt::Debug for Ty { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Ty::Clause => f.write_str("Clause"), + Ty::Undef => f.write_str("Undef"), + Ty::Content => f.write_str("Content"), + Ty::Any => f.write_str("Any"), + Ty::Space => f.write_str("Space"), + Ty::None => f.write_str("None"), + Ty::Infer => f.write_str("Infer"), + Ty::FlowNone => f.write_str("FlowNone"), + Ty::Auto => f.write_str("Auto"), + Ty::Builtin(t) => write!(f, "{t:?}"), + Ty::Args(a) => write!(f, "&({a:?})"), + Ty::Func(s) => write!(f, "{s:?}"), + Ty::Dict(r) => write!(f, "{r:?}"), + Ty::Array(a) => write!(f, "Array<{a:?}>"), + Ty::Tuple(t) => { + f.write_str("(")?; + for t in t.iter() { + write!(f, "{t:?}, ")?; + } + f.write_str(")") + } + Ty::With(w) => write!(f, "({:?}).with(..{:?})", w.sig, w.with), + Ty::Select(a) => write!(f, "{a:?}"), + Ty::Union(u) => { + f.write_str("(")?; + if let Some((first, u)) = u.split_first() { + write!(f, "{first:?}")?; + for u in u { + write!(f, " | {u:?}")?; + } + } + f.write_str(")") + } + Ty::Let(v) => write!(f, "({v:?})"), + Ty::Field(ff) => write!(f, "{:?}: {:?}", ff.name, ff.field), + Ty::Var(v) => write!(f, "@{}", v.name()), + Ty::Unary(u) => write!(f, "{u:?}"), + Ty::Binary(b) => write!(f, "{b:?}"), + Ty::If(i) => write!(f, "{i:?}"), + Ty::Value(v) => write!(f, "{v:?}", v = v.val), + Ty::Boolean(b) => { + if let Some(b) = b { + write!(f, "{b}") + } else { + f.write_str("Boolean") + } + } + } + } +} + +impl Ty { + pub(crate) fn is_dict(&self) -> bool { + matches!(self, Ty::Dict(..)) + } + + pub(crate) fn from_types(e: impl ExactSizeIterator) -> Self { + if e.len() == 0 { + Ty::Any + } else if e.len() == 1 { + let mut e = e; + e.next().unwrap() + } else { + Ty::Union(Interned::new(e.collect())) + } + } +} + +impl_internable!(Ty,); +impl_internable!(InsTy,); +impl_internable!(FieldTy,); +impl_internable!(TypeSource,); +impl_internable!(TypeVar,); +impl_internable!(SigWithTy,); +impl_internable!(SigTy,); +impl_internable!(RecordTy,); +impl_internable!(SelectTy,); +impl_internable!(TypeUnary,); +impl_internable!(TypeBinary,); +impl_internable!(IfTy,); +impl_internable!(Vec,); +impl_internable!(TypeBounds,); +impl_internable!(NameBone,); +impl_internable!((Ty, Ty),); + +fn interpersed( + f: &mut fmt::Formatter<'_>, + iter: impl Iterator, +) -> fmt::Result { + let mut first = true; + for arg in iter { + if first { + first = false; + } else { + f.write_str(", ")?; + } + arg.fmt(f)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use insta::{assert_debug_snapshot, assert_snapshot}; + #[test] + fn test_ty() { + use super::*; + let ty = Ty::Clause; + let ty_ref = TyRef::new(ty.clone()); + assert_debug_snapshot!(ty_ref, @"Clause"); + } + + #[test] + fn test_sig_matches() { + use super::*; + + fn str_ins(s: &str) -> Ty { + Ty::Value(InsTy::new(Value::Str(s.into()))) + } + + fn str_sig( + pos: &[&str], + named: &[(&str, &str)], + rest: Option<&str>, + ret: Option<&str>, + ) -> Interned { + let pos = pos.iter().map(|s| str_ins(s)); + let named = named + .iter() + .map(|(n, t)| (Interned::new_str(n), str_ins(t))); + let rest = rest.map(str_ins); + let ret = ret.map(str_ins); + Interned::new(SigTy::new(pos, named, rest, ret)) + } + + fn matches( + sig: Interned, + args: Interned, + withs: Option>>, + ) -> String { + let res = sig.matches(&args, withs.as_ref()).collect::>(); + format!("{res:?}") + } + + // args*, (keys: values)*, ...rest -> ret + macro_rules! literal_sig { + ($($pos:ident),* $(!$named:ident: $named_ty:ident),* $(,)? ...$rest:ident -> $ret:ident) => { + str_sig(&[$(stringify!($pos)),*], &[$((stringify!($named), stringify!($named_ty))),*], Some(stringify!($rest)), Some(stringify!($ret))) + }; + ($($pos:ident),* $(!$named:ident: $named_ty:ident),* $(,)? -> $ret:ident) => { + str_sig(&[$(stringify!($pos)),*], &[$((stringify!($named), stringify!($named_ty))),*], None, Some(stringify!($ret))) + }; + ($($pos:ident),* $(!$named:ident: $named_ty:ident),* $(,)? ...$rest:ident) => { + str_sig(&[$(stringify!($pos)),*], &[$((stringify!($named), stringify!($named_ty))),*], Some(stringify!($rest)), None) + }; + ($($pos:ident),* $(!$named:ident: $named_ty:ident),* $(,)?) => { + str_sig(&[$(stringify!($pos)),*], &[$((stringify!($named), stringify!($named_ty))),*], None, None) + }; + } + + assert_snapshot!(matches(literal_sig!(p1), literal_sig!(q1), None), @r###"[("p1", "q1")]"###); + assert_snapshot!(matches(literal_sig!(p1, p2), literal_sig!(q1), None), @r###"[("p1", "q1")]"###); + assert_snapshot!(matches(literal_sig!(p1, p2), literal_sig!(q1, q2), None), @r###"[("p1", "q1"), ("p2", "q2")]"###); + assert_snapshot!(matches(literal_sig!(p1), literal_sig!(q1, q2), None), @r###"[("p1", "q1")]"###); + + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(q1), None), @r###"[("p1", "q1")]"###); + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(q1, q2), None), @r###"[("p1", "q1"), ("r1", "q2")]"###); + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(q1, q2, q3), None), @r###"[("p1", "q1"), ("r1", "q2"), ("r1", "q3")]"###); + assert_snapshot!(matches(literal_sig!(...r1), literal_sig!(q1, q2), None), @r###"[("r1", "q1"), ("r1", "q2")]"###); + + assert_snapshot!(matches(literal_sig!(p1), literal_sig!(q1, ...s2), None), @r###"[("p1", "q1")]"###); + assert_snapshot!(matches(literal_sig!(p1, p2), literal_sig!(q1, ...s2), None), @r###"[("p1", "q1"), ("p2", "s2")]"###); + assert_snapshot!(matches(literal_sig!(p1, p2, p3), literal_sig!(q1, ...s2), None), @r###"[("p1", "q1"), ("p2", "s2"), ("p3", "s2")]"###); + assert_snapshot!(matches(literal_sig!(p1, p2), literal_sig!(...s2), None), @r###"[("p1", "s2"), ("p2", "s2")]"###); + + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(q1, ...s2), None), @r###"[("p1", "q1"), ("r1", "s2")]"###); + assert_snapshot!(matches(literal_sig!(...r1), literal_sig!(q1, ...s2), None), @r###"[("r1", "q1"), ("r1", "s2")]"###); + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(...s2), None), @r###"[("p1", "s2"), ("r1", "s2")]"###); + assert_snapshot!(matches(literal_sig!(...r1), literal_sig!(...s2), None), @r###"[("r1", "s2")]"###); + + assert_snapshot!(matches(literal_sig!(p0, p1, ...r1), literal_sig!(q1, ...s2), None), @r###"[("p0", "q1"), ("p1", "s2"), ("r1", "s2")]"###); + assert_snapshot!(matches(literal_sig!(p0, p1, ...r1), literal_sig!(...s2), None), @r###"[("p0", "s2"), ("p1", "s2"), ("r1", "s2")]"###); + + assert_snapshot!(matches(literal_sig!(p1, ...r1), literal_sig!(q0, q1, ...s2), None), @r###"[("p1", "q0"), ("r1", "q1"), ("r1", "s2")]"###); + assert_snapshot!(matches(literal_sig!(...r1), literal_sig!(q0, q1, ...s2), None), @r###"[("r1", "q0"), ("r1", "q1"), ("r1", "s2")]"###); + + assert_snapshot!(matches(literal_sig!(p1 !u1: w1), literal_sig!(q1 !u1: w2), None), @r###"[("p1", "q1"), ("w1", "w2")]"###); + assert_snapshot!(matches(literal_sig!(p1 !u1: w1, ...r1), literal_sig!(q1 !u1: w2), None), @r###"[("p1", "q1"), ("w1", "w2")]"###); + assert_snapshot!(matches(literal_sig!(p1 !u1: w1), literal_sig!(q1 !u1: w2, ...s2), None), @r###"[("p1", "q1"), ("w1", "w2")]"###); + assert_snapshot!(matches(literal_sig!(p1 !u1: w1, ...r1), literal_sig!(q1 !u1: w2, ...s2), None), @r###"[("p1", "q1"), ("r1", "s2"), ("w1", "w2")]"###); + + assert_snapshot!(matches(literal_sig!(), literal_sig!(!u1: w2), None), @"[]"); + assert_snapshot!(matches(literal_sig!(!u1: w1), literal_sig!(), None), @"[]"); + assert_snapshot!(matches(literal_sig!(!u1: w1), literal_sig!(!u1: w2), None), @r###"[("w1", "w2")]"###); + assert_snapshot!(matches(literal_sig!(!u1: w1), literal_sig!(!u2: w2), None), @"[]"); + assert_snapshot!(matches(literal_sig!(!u2: w1), literal_sig!(!u1: w2), None), @"[]"); + assert_snapshot!(matches(literal_sig!(!u1: w1, !u2: w3), literal_sig!(!u1: w2, !u2: w4), None), @r###"[("w1", "w2"), ("w3", "w4")]"###); + assert_snapshot!(matches(literal_sig!(!u1: w1, !u2: w3), literal_sig!(!u2: w2, !u1: w4), None), @r###"[("w1", "w4"), ("w3", "w2")]"###); + assert_snapshot!(matches(literal_sig!(!u2: w1), literal_sig!(!u1: w2, !u2: w4), None), @r###"[("w1", "w4")]"###); + assert_snapshot!(matches(literal_sig!(!u1: w1, !u2: w2), literal_sig!(!u2: w4), None), @r###"[("w2", "w4")]"###); + } +} diff --git a/crates/tinymist-query/src/ty/describe.rs b/crates/tinymist-query/src/ty/describe.rs new file mode 100644 index 00000000..10b902e4 --- /dev/null +++ b/crates/tinymist-query/src/ty/describe.rs @@ -0,0 +1,190 @@ +use std::collections::HashSet; + +use reflexo::hash::hash128; +use typst::foundations::Repr; + +use crate::{adt::interner::Interned, analysis::*, ty::def::*}; + +impl TypeCheckInfo { + pub fn describe(&self, ty: &Ty) -> Option { + let mut worker = TypeDescriber::default(); + worker.describe_root(ty) + } +} + +impl Ty { + pub fn describe(&self) -> Option { + let mut worker = TypeDescriber::default(); + worker.describe_root(self) + } +} + +#[derive(Default)] +struct TypeDescriber { + described: HashMap, + results: HashSet, + functions: Vec>, +} + +impl TypeDescriber { + fn describe_root(&mut self, ty: &Ty) -> Option { + let _ = TypeDescriber::describe_iter; + // recursive structure + if let Some(t) = self.described.get(&hash128(ty)) { + return Some(t.clone()); + } + + let res = self.describe(ty); + if !res.is_empty() { + return Some(res); + } + self.described.insert(hash128(ty), "$self".to_string()); + + let mut results = std::mem::take(&mut self.results) + .into_iter() + .collect::>(); + let functions = std::mem::take(&mut self.functions); + if !functions.is_empty() { + // todo: union signature + // only first function is described + let f = functions[0].clone(); + + let mut res = String::new(); + res.push('('); + let mut not_first = false; + for ty in f.positional_params() { + if not_first { + res.push_str(", "); + } else { + not_first = true; + } + res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); + } + for (k, ty) in f.named_params() { + if not_first { + res.push_str(", "); + } else { + not_first = true; + } + res.push_str(k); + res.push_str(": "); + res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); + } + if let Some(r) = f.rest_param() { + if not_first { + res.push_str(", "); + } + res.push_str("..: "); + res.push_str(self.describe_root(r).as_deref().unwrap_or("")); + res.push_str("[]"); + } + res.push_str(") => "); + res.push_str( + f.ret + .as_ref() + .and_then(|ret| self.describe_root(ret)) + .as_deref() + .unwrap_or("any"), + ); + + results.push(res); + } + + if results.is_empty() { + self.described.insert(hash128(ty), "any".to_string()); + return None; + } + + results.sort(); + results.dedup(); + let res = results.join(" | "); + self.described.insert(hash128(ty), res.clone()); + Some(res) + } + + fn describe_iter(&mut self, ty: &[Ty]) { + for ty in ty.iter() { + let desc = self.describe(ty); + if !desc.is_empty() { + self.results.insert(desc); + } + } + } + + fn describe(&mut self, ty: &Ty) -> String { + match ty { + Ty::Var(..) => {} + Ty::Union(tys) => { + self.describe_iter(tys); + } + Ty::Let(lb) => { + self.describe_iter(&lb.lbs); + self.describe_iter(&lb.ubs); + } + Ty::Func(f) => { + self.functions.push(f.clone()); + } + Ty::Dict(..) => { + return "dict".to_string(); + } + Ty::Tuple(..) => { + return "array".to_string(); + } + Ty::Array(..) => { + return "array".to_string(); + } + // todo: sig with + Ty::With(w) => { + return self.describe(&w.sig); + } + Ty::Clause => {} + Ty::Undef => {} + Ty::Content => { + return "content".to_string(); + } + // Doesn't provide any information, hence we doesn't describe it intermediately here. + Ty::Any => {} + Ty::Space => {} + Ty::None => { + return "none".to_string(); + } + Ty::Infer => {} + Ty::FlowNone => { + return "none".to_string(); + } + Ty::Auto => { + return "auto".to_string(); + } + Ty::Boolean(None) => { + return "boolean".to_string(); + } + Ty::Boolean(Some(b)) => { + return b.to_string(); + } + Ty::Builtin(b) => { + return b.describe().to_string(); + } + Ty::Value(v) => return v.val.repr().to_string(), + Ty::Field(..) => { + return "field".to_string(); + } + Ty::Args(..) => { + return "args".to_string(); + } + Ty::Select(..) => { + return "any".to_string(); + } + Ty::Unary(..) => { + return "any".to_string(); + } + Ty::Binary(..) => { + return "any".to_string(); + } + Ty::If(..) => { + return "any".to_string(); + } + } + + String::new() + } +} diff --git a/crates/tinymist-query/src/ty/mod.rs b/crates/tinymist-query/src/ty/mod.rs new file mode 100644 index 00000000..e6cb2967 --- /dev/null +++ b/crates/tinymist-query/src/ty/mod.rs @@ -0,0 +1,14 @@ +mod apply; +mod bound; +mod builtin; +mod def; +mod describe; +mod sig; +mod simplify; +mod subst; + +pub(crate) use apply::*; +pub(crate) use bound::*; +pub(crate) use builtin::*; +pub(crate) use def::*; +pub(crate) use sig::*; diff --git a/crates/tinymist-query/src/ty/sig.rs b/crates/tinymist-query/src/ty/sig.rs new file mode 100644 index 00000000..195b2405 --- /dev/null +++ b/crates/tinymist-query/src/ty/sig.rs @@ -0,0 +1,307 @@ +use typst::foundations::{Func, Value}; + +use crate::{adt::interner::Interned, analysis::*, ty::def::*}; + +#[derive(Debug, Clone, Copy)] +pub enum Sig<'a> { + Type(&'a Interned), + TypeCons { + val: &'a typst::foundations::Type, + at: &'a Ty, + }, + ArrayCons(&'a TyRef), + DictCons(&'a Interned), + Value { + val: &'a Func, + at: &'a Ty, + }, + Partialize(&'a Sig<'a>), + With { + sig: &'a Sig<'a>, + withs: &'a Vec>, + at: &'a Ty, + }, +} + +pub struct SigShape<'a> { + pub sig: Interned, + pub withs: Option<&'a Vec>>, +} + +impl<'a> Sig<'a> { + pub fn ty(self) -> Option { + Some(match self { + Sig::Type(t) => Ty::Func(t.clone()), + Sig::ArrayCons(t) => Ty::Array(t.clone()), + Sig::DictCons(t) => Ty::Dict(t.clone()), + Sig::TypeCons { val, .. } => Ty::Builtin(BuiltinTy::Type(*val)), + Sig::Value { at, .. } => at.clone(), + Sig::With { at, .. } => at.clone(), + Sig::Partialize(..) => return None, + }) + } + + pub fn shape(self, ctx: Option<&mut AnalysisContext>) -> Option> { + let (cano_sig, withs) = match self { + Sig::With { sig, withs, .. } => (*sig, Some(withs)), + _ => (self, None), + }; + + let sig_ins = match cano_sig { + Sig::ArrayCons(a) => SigTy::array_cons(a.as_ref().clone(), false), + Sig::DictCons(d) => SigTy::dict_cons(d, false), + Sig::TypeCons { val, .. } => ctx?.type_of_func(&val.constructor().ok()?)?, + Sig::Value { val, .. } => ctx?.type_of_func(val)?, + // todo + Sig::Partialize(..) => return None, + Sig::With { .. } => return None, + Sig::Type(t) => t.clone(), + }; + + Some(SigShape { + sig: sig_ins, + withs, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SigSurfaceKind { + Call, + Array, + Dict, + ArrayOrDict, +} + +pub trait SigChecker { + fn check(&mut self, sig: Sig, args: &mut SigCheckContext, pol: bool) -> Option<()>; + fn check_var(&mut self, _var: &Interned, _pol: bool) -> Option { + None + } +} + +impl SigChecker for T +where + T: FnMut(Sig, &mut SigCheckContext, bool) -> Option<()>, +{ + fn check(&mut self, sig: Sig, args: &mut SigCheckContext, pol: bool) -> Option<()> { + self(sig, args, pol) + } +} + +impl Ty { + pub fn sig_surface(&self, pol: bool, sig_kind: SigSurfaceKind, checker: &mut impl SigChecker) { + let context = SigCheckContext { + sig_kind, + args: Vec::new(), + at: TyRef::new(Ty::Any), + }; + let mut worker = SigCheckDriver { + ctx: context, + checker, + }; + + worker.ty(self, pol); + } + + pub fn sig_repr(&self, pol: bool) -> Option> { + // todo: union sig + // let mut pos = vec![]; + // let mut named = HashMap::new(); + // let mut rest = None; + // let mut ret = None; + + let mut primary = None; + + self.sig_surface( + pol, + SigSurfaceKind::Call, + &mut |sig: Sig, _ctx: &mut SigCheckContext, _pol: bool| { + let sig = sig.shape(None)?; + primary = Some(sig.sig.clone()); + Some(()) + }, + ); + + primary + } +} + +pub struct SigCheckContext { + pub sig_kind: SigSurfaceKind, + pub args: Vec>, + pub at: TyRef, +} + +pub struct SigCheckDriver<'a> { + ctx: SigCheckContext, + checker: &'a mut dyn SigChecker, +} + +impl<'a> SigCheckDriver<'a> { + fn func_as_sig(&self) -> bool { + matches!(self.ctx.sig_kind, SigSurfaceKind::Call) + } + + fn array_as_sig(&self) -> bool { + matches!( + self.ctx.sig_kind, + SigSurfaceKind::Array | SigSurfaceKind::ArrayOrDict + ) + } + + fn dict_as_sig(&self) -> bool { + matches!( + self.ctx.sig_kind, + SigSurfaceKind::Dict | SigSurfaceKind::ArrayOrDict + ) + } + + fn ty(&mut self, ty: &Ty, pol: bool) { + match ty { + Ty::Builtin(BuiltinTy::Stroke) if self.dict_as_sig() => { + self.checker + .check(Sig::DictCons(&FLOW_STROKE_DICT), &mut self.ctx, pol); + } + Ty::Builtin(BuiltinTy::Margin) if self.dict_as_sig() => { + self.checker + .check(Sig::DictCons(&FLOW_MARGIN_DICT), &mut self.ctx, pol); + } + Ty::Builtin(BuiltinTy::Inset) if self.dict_as_sig() => { + self.checker + .check(Sig::DictCons(&FLOW_INSET_DICT), &mut self.ctx, pol); + } + Ty::Builtin(BuiltinTy::Outset) if self.dict_as_sig() => { + self.checker + .check(Sig::DictCons(&FLOW_OUTSET_DICT), &mut self.ctx, pol); + } + Ty::Builtin(BuiltinTy::Radius) if self.dict_as_sig() => { + self.checker + .check(Sig::DictCons(&FLOW_RADIUS_DICT), &mut self.ctx, pol); + } + // todo: deduplicate checking early + Ty::Value(v) => { + if self.func_as_sig() { + match &v.val { + Value::Func(f) => { + self.checker + .check(Sig::Value { val: f, at: ty }, &mut self.ctx, pol); + } + Value::Type(t) => { + self.checker.check( + Sig::TypeCons { val: t, at: ty }, + &mut self.ctx, + pol, + ); + } + _ => {} + } + } + } + Ty::Builtin(BuiltinTy::Type(e)) if self.func_as_sig() => { + // todo: distinguish between element and function + self.checker + .check(Sig::TypeCons { val: e, at: ty }, &mut self.ctx, pol); + } + Ty::Builtin(BuiltinTy::Element(e)) if self.func_as_sig() => { + // todo: distinguish between element and function + let f = (*e).into(); + self.checker + .check(Sig::Value { val: &f, at: ty }, &mut self.ctx, pol); + } + Ty::Func(sig) if self.func_as_sig() => { + self.checker.check(Sig::Type(sig), &mut self.ctx, pol); + } + Ty::Array(sig) if self.array_as_sig() => { + // let sig = FlowSignature::array_cons(*sig.clone(), true); + self.checker.check(Sig::ArrayCons(sig), &mut self.ctx, pol); + } + // todo: tuple + Ty::Tuple(_) => {} + Ty::Dict(sig) if self.dict_as_sig() => { + // self.check_dict_signature(sig, pol, self.checker); + self.checker.check(Sig::DictCons(sig), &mut self.ctx, pol); + } + Ty::With(w) if self.func_as_sig() => { + self.ctx.args.push(w.with.clone()); + self.ty(&w.sig, pol); + self.ctx.args.pop(); + } + Ty::Select(sel) => sel.ty.bounds(pol, &mut MethodDriver(self, &sel.select)), + // todo: calculate these operators + Ty::Unary(_) => {} + Ty::Binary(_) => {} + Ty::If(_) => {} + _ if ty.has_bounds() => ty.bounds(pol, self), + _ => {} + } + } +} + +impl BoundChecker for SigCheckDriver<'_> { + fn collect(&mut self, ty: &Ty, pol: bool) { + self.ty(ty, pol); + } + + fn bound_of_var(&mut self, var: &Interned, pol: bool) -> Option { + self.checker.check_var(var, pol) + } +} + +struct MethodDriver<'a, 'b>(&'a mut SigCheckDriver<'b>, &'a Interned); + +impl<'a, 'b> MethodDriver<'a, 'b> { + fn is_binder(&self) -> bool { + matches!(self.1.as_ref(), "with" | "where") + } +} + +impl<'a, 'b> BoundChecker for MethodDriver<'a, 'b> { + fn collect(&mut self, ty: &Ty, pol: bool) { + log::debug!("check method: {ty:?}.{}", self.1.as_ref()); + match ty { + // todo: deduplicate checking early + Ty::Value(v) => { + if let Value::Func(f) = &v.val { + if self.is_binder() { + self.0.checker.check( + Sig::Partialize(&Sig::Value { val: f, at: ty }), + &mut self.0.ctx, + pol, + ); + } else { + // todo: general select operator + } + } + } + Ty::Builtin(BuiltinTy::Element(e)) => { + // todo: distinguish between element and function + if self.is_binder() { + let f = (*e).into(); + self.0.checker.check( + Sig::Partialize(&Sig::Value { val: &f, at: ty }), + &mut self.0.ctx, + pol, + ); + } else { + // todo: general select operator + } + } + Ty::Func(sig) => { + if self.is_binder() { + self.0 + .checker + .check(Sig::Partialize(&Sig::Type(sig)), &mut self.0.ctx, pol); + } else { + // todo: general select operator + } + } + // todo: general select operator + _ => {} + } + } + + fn bound_of_var(&mut self, var: &Interned, pol: bool) -> Option { + self.0.checker.check_var(var, pol) + } +} diff --git a/crates/tinymist-query/src/ty/simplify.rs b/crates/tinymist-query/src/ty/simplify.rs new file mode 100644 index 00000000..e50919b4 --- /dev/null +++ b/crates/tinymist-query/src/ty/simplify.rs @@ -0,0 +1,304 @@ +#![allow(unused)] + +use std::collections::HashSet; + +use ecow::EcoVec; +use reflexo::hash::hash128; + +use crate::{adt::interner::Interned, analysis::*, ty::def::*}; + +#[derive(Default)] +struct CompactTy { + equiv_vars: HashSet, + primitives: HashSet, + recursives: HashMap, + signatures: Vec>, + + is_final: bool, +} + +impl TypeCheckInfo { + pub fn simplify(&self, ty: Ty, principal: bool) -> Ty { + let mut c = self.cano_cache.lock(); + let c = &mut *c; + + c.cano_local_cache.clear(); + c.positives.clear(); + c.negatives.clear(); + + let mut worker = TypeSimplifier { + principal, + vars: &self.vars, + cano_cache: &mut c.cano_cache, + cano_local_cache: &mut c.cano_local_cache, + + positives: &mut c.positives, + negatives: &mut c.negatives, + }; + + worker.simplify(ty, principal) + } +} + +struct TypeSimplifier<'a, 'b> { + principal: bool, + + vars: &'a HashMap, + + cano_cache: &'b mut HashMap<(Ty, bool), Ty>, + cano_local_cache: &'b mut HashMap<(DefId, bool), Ty>, + negatives: &'b mut HashSet, + positives: &'b mut HashSet, +} + +impl<'a, 'b> TypeSimplifier<'a, 'b> { + fn simplify(&mut self, ty: Ty, principal: bool) -> Ty { + if let Some(cano) = self.cano_cache.get(&(ty.clone(), principal)) { + return cano.clone(); + } + + self.analyze(&ty, true); + + self.transform(&ty, true) + } + + fn analyze(&mut self, ty: &Ty, pol: bool) { + match ty { + Ty::Var(v) => { + let w = self.vars.get(&v.def).unwrap(); + match &w.bounds { + FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { + let w = w.read(); + let inserted = if pol { + self.positives.insert(v.def) + } else { + self.negatives.insert(v.def) + }; + if !inserted { + return; + } + + if pol { + for lb in w.lbs.iter() { + self.analyze(lb, pol); + } + } else { + for ub in w.ubs.iter() { + self.analyze(ub, pol); + } + } + } + } + } + Ty::Func(f) => { + for p in f.inputs() { + self.analyze(p, !pol); + } + if let Some(ret) = &f.ret { + self.analyze(ret, pol); + } + } + Ty::Dict(r) => { + for p in r.types.iter() { + self.analyze(p, pol); + } + } + Ty::Tuple(e) => { + for ty in e.iter() { + self.analyze(ty, pol); + } + } + Ty::Array(e) => { + self.analyze(e, pol); + } + Ty::With(w) => { + self.analyze(&w.sig, pol); + for p in w.with.inputs() { + self.analyze(p, pol); + } + } + Ty::Args(args) => { + for p in args.inputs() { + self.analyze(p, pol); + } + } + Ty::Unary(u) => self.analyze(&u.lhs, pol), + Ty::Binary(b) => { + let (lhs, rhs) = b.repr(); + self.analyze(lhs, pol); + self.analyze(rhs, pol); + } + Ty::If(i) => { + self.analyze(&i.cond, pol); + self.analyze(&i.then, pol); + self.analyze(&i.else_, pol); + } + Ty::Union(v) => { + for ty in v.iter() { + self.analyze(ty, pol); + } + } + Ty::Select(a) => { + self.analyze(&a.ty, pol); + } + Ty::Let(v) => { + for lb in v.lbs.iter() { + self.analyze(lb, !pol); + } + for ub in v.ubs.iter() { + self.analyze(ub, pol); + } + } + Ty::Field(v) => { + self.analyze(&v.field, pol); + } + Ty::Value(_v) => {} + Ty::Clause => {} + Ty::Undef => {} + Ty::Content => {} + Ty::Any => {} + Ty::None => {} + Ty::Infer => {} + Ty::FlowNone => {} + Ty::Space => {} + Ty::Auto => {} + Ty::Boolean(_) => {} + Ty::Builtin(_) => {} + } + } + + fn transform(&mut self, ty: &Ty, pol: bool) -> Ty { + match ty { + Ty::Let(w) => self.transform_let(w, None, pol), + Ty::Var(v) => { + if let Some(cano) = self.cano_local_cache.get(&(v.def, self.principal)) { + return cano.clone(); + } + // todo: avoid cycle + self.cano_local_cache + .insert((v.def, self.principal), Ty::Any); + + let res = match &self.vars.get(&v.def).unwrap().bounds { + FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => { + let w = w.read(); + + self.transform_let(&w, Some(&v.def), pol) + } + }; + + self.cano_local_cache + .insert((v.def, self.principal), res.clone()); + + res + } + Ty::Func(f) => Ty::Func(self.transform_sig(f, pol)), + Ty::Dict(f) => { + let mut f = f.as_ref().clone(); + f.types = self.transform_seq(&f.types, pol); + + Ty::Dict(Interned::new(f)) + } + Ty::Tuple(e) => Ty::Tuple(self.transform_seq(e, pol)), + Ty::Array(e) => Ty::Array(Interned::new(self.transform(e, pol))), + Ty::With(w) => { + let sig = Interned::new(self.transform(&w.sig, pol)); + // Negate the pol to make correct covariance + let with = self.transform_sig(&w.with, !pol); + + Ty::With(Interned::new(SigWithTy { sig, with })) + } + // Negate the pol to make correct covariance + Ty::Args(args) => Ty::Args(self.transform_sig(args, !pol)), + Ty::Unary(u) => Ty::Unary(Interned::new(TypeUnary { + op: u.op, + lhs: Interned::new(self.transform(&u.lhs, pol)), + })), + Ty::Binary(b) => { + let (lhs, rhs) = b.repr(); + let lhs = self.transform(lhs, pol); + let rhs = self.transform(rhs, pol); + + Ty::Binary(Interned::new(TypeBinary { + op: b.op, + operands: Interned::new((lhs, rhs)), + })) + } + Ty::If(i) => Ty::If(Interned::new(IfTy { + cond: Interned::new(self.transform(&i.cond, pol)), + then: Interned::new(self.transform(&i.then, pol)), + else_: Interned::new(self.transform(&i.else_, pol)), + })), + Ty::Union(v) => Ty::Union(self.transform_seq(v, pol)), + Ty::Field(ty) => { + let mut ty = ty.as_ref().clone(); + ty.field = self.transform(&ty.field, pol); + + Ty::Field(Interned::new(ty)) + } + Ty::Select(sel) => { + let mut sel = sel.as_ref().clone(); + sel.ty = Interned::new(self.transform(&sel.ty, pol)); + + Ty::Select(Interned::new(sel)) + } + + Ty::Value(v) => Ty::Value(v.clone()), + Ty::Clause => Ty::Clause, + Ty::Undef => Ty::Undef, + Ty::Content => Ty::Content, + Ty::Any => Ty::Any, + Ty::None => Ty::None, + Ty::Infer => Ty::Infer, + Ty::FlowNone => Ty::FlowNone, + Ty::Space => Ty::Space, + Ty::Auto => Ty::Auto, + Ty::Boolean(b) => Ty::Boolean(*b), + Ty::Builtin(b) => Ty::Builtin(b.clone()), + } + } + + fn transform_seq(&mut self, seq: &[Ty], pol: bool) -> Interned> { + Interned::new(seq.iter().map(|ty| self.transform(ty, pol)).collect()) + } + + // todo: reduce duplication + fn transform_let(&mut self, w: &TypeBounds, def_id: Option<&DefId>, pol: bool) -> Ty { + let mut lbs = EcoVec::with_capacity(w.lbs.len()); + let mut ubs = EcoVec::with_capacity(w.ubs.len()); + + log::debug!("transform let [principal={}] with {w:?}", self.principal); + + if !self.principal || ((pol) && !def_id.is_some_and(|i| self.negatives.contains(i))) { + for lb in w.lbs.iter() { + lbs.push(self.transform(lb, pol)); + } + } + if !self.principal || ((!pol) && !def_id.is_some_and(|i| self.positives.contains(i))) { + for ub in w.ubs.iter() { + ubs.push(self.transform(ub, !pol)); + } + } + + if ubs.is_empty() { + if lbs.len() == 1 { + return lbs.pop().unwrap(); + } + if lbs.is_empty() { + return Ty::Any; + } + } + + Ty::Let(Interned::new(TypeBounds { lbs, ubs })) + } + + fn transform_sig(&mut self, sig: &SigTy, pol: bool) -> Interned { + let mut sig = sig.clone(); + sig.types = self.transform_seq(&sig.types, !pol); + if let Some(ret) = &sig.ret { + sig.ret = Some(self.transform(ret, pol)); + } + + // todo: we can reduce one clone by early compare on sig.types + Interned::new(sig) + } +} diff --git a/crates/tinymist-query/src/ty/subst.rs b/crates/tinymist-query/src/ty/subst.rs new file mode 100644 index 00000000..d25801b6 --- /dev/null +++ b/crates/tinymist-query/src/ty/subst.rs @@ -0,0 +1,55 @@ +use hashbrown::HashMap; + +use crate::{adt::interner::Interned, analysis::*, ty::def::*}; + +impl<'a> Sig<'a> { + pub fn call( + &self, + args: &Interned, + pol: bool, + ctx: Option<&mut AnalysisContext>, + ) -> Option { + let (bound_variables, body) = self.check_bind(args, ctx)?; + + if bound_variables.is_empty() { + return body; + } + + let mut checker = SubstituteChecker { bound_variables }; + checker.ty(&body?, pol) + } + + pub fn check_bind( + &self, + args: &Interned, + ctx: Option<&mut AnalysisContext>, + ) -> Option<(HashMap, Option)> { + let SigShape { sig, withs } = self.shape(ctx)?; + + let has_free_vars = sig.has_free_variables; + + let mut arguments = HashMap::new(); + for (arg_recv, arg_ins) in sig.matches(args, withs) { + if has_free_vars { + if let Ty::Var(arg_recv) = arg_recv { + arguments.insert(arg_recv.def, arg_ins.clone()); + } + } + } + + Some((arguments, sig.ret.clone())) + } +} + +// todo +struct SubstituteChecker { + bound_variables: HashMap, +} +impl SubstituteChecker { + fn ty(&mut self, body: &Ty, pol: bool) -> Option { + let _ = self.bound_variables; + let _ = pol; + + Some(body.clone()) + } +} diff --git a/crates/tinymist-query/src/upstream/complete.rs b/crates/tinymist-query/src/upstream/complete.rs index 9eb8418d..a7aae09b 100644 --- a/crates/tinymist-query/src/upstream/complete.rs +++ b/crates/tinymist-query/src/upstream/complete.rs @@ -17,6 +17,7 @@ use typst::visualize::Color; use unscanny::Scanner; use super::{plain_docs_sentence, summarize_font_family}; +use crate::adt::interner::Interned; use crate::analysis::{analyze_expr, analyze_import, analyze_labels, DynLabel}; use crate::AnalysisContext; @@ -39,8 +40,8 @@ pub fn autocomplete( mut ctx: CompletionContext, ) -> Option<(usize, bool, Vec, Vec)> { let _ = complete_comments(&mut ctx) - || complete_literal(&mut ctx).is_none() && { - log::info!("continue after completing literal"); + || complete_type(&mut ctx).is_none() && { + log::info!("continue after completing type"); complete_field_accesses(&mut ctx) || complete_open_labels(&mut ctx) || complete_imports(&mut ctx) @@ -721,7 +722,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool { let ty = ctx.ctx.type_of(param.to_untyped()); log::debug!("named param type: {:?}", ty); - named_param_value_completions(ctx, callee, ¶m, ty.as_ref()); + named_param_value_completions(ctx, callee, &Interned::new_str(param.get()), ty.as_ref()); return true; } } @@ -963,6 +964,7 @@ pub struct CompletionContext<'a, 'w> { pub completions2: Vec, pub incomplete: bool, pub seen_casts: HashSet, + pub seen_fields: HashSet>, } impl<'a, 'w> CompletionContext<'a, 'w> { @@ -992,6 +994,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> { completions: vec![], completions2: vec![], seen_casts: HashSet::new(), + seen_fields: HashSet::new(), }) } diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index 88fa9d32..fd5f95b2 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -1,9 +1,7 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use ecow::{eco_format, EcoString}; use lsp_types::{CompletionItem, CompletionTextEdit, InsertTextFormat, TextEdit}; -use once_cell::sync::OnceCell; -use parking_lot::Mutex; use reflexo::path::{unix_slash, PathClean}; use typst::foundations::{AutoValue, Func, Label, NoneValue, Repr, Type, Value}; use typst::layout::{Dir, Length}; @@ -12,12 +10,11 @@ use typst::syntax::{ast, Span, SyntaxKind, SyntaxNode}; use typst::visualize::Color; use super::{Completion, CompletionContext, CompletionKind}; +use crate::adt::interner::Interned; use crate::analysis::{ - analyze_dyn_signature, analyze_import, resolve_call_target, FlowBuiltinType, FlowRecord, - FlowType, PathPreference, FLOW_INSET_DICT, FLOW_MARGIN_DICT, FLOW_OUTSET_DICT, - FLOW_RADIUS_DICT, FLOW_STROKE_DICT, + analyze_dyn_signature, analyze_import, resolve_call_target, BuiltinTy, PathPreference, Ty, }; -use crate::syntax::param_index_at_leaf; +use crate::syntax::{param_index_at_leaf, CheckTarget}; use crate::upstream::complete::complete_code; use crate::upstream::plain_docs_sentence; @@ -36,10 +33,8 @@ impl<'a, 'w> CompletionContext<'a, 'w> { self.scope_completions_(parens, |v| v.map_or(false, &filter)); } - fn seen_field(&mut self, field: EcoString) -> bool { - !self - .seen_casts - .insert(typst::util::hash128(&FieldName(field))) + fn seen_field(&mut self, field: Interned) -> bool { + !self.seen_fields.insert(field) } /// Add completions for definitions that are available at the cursor. @@ -290,8 +285,8 @@ impl<'a, 'w> CompletionContext<'a, 'w> { } else { types .and_then(|types| { - let ty = types.mapping.get(&span)?; - let ty = types.simplify(ty.clone(), false); + let ty = types.type_of_span(span)?; + let ty = types.simplify(ty, false); types.describe(&ty).map(From::from) }) .or_else(|| { @@ -470,9 +465,8 @@ fn describe_value(ctx: &mut AnalysisContext, v: &Value) -> EcoString { let sig = analyze_dyn_signature(ctx, f.clone()); sig.primary() - .sig_ty - .as_ref() - .and_then(|e| e.describe()) + .ty() + .describe() .unwrap_or_else(|| "function".into()) .into() } @@ -594,7 +588,7 @@ pub fn param_completions<'a>( for arg in args.items() { if let ast::Arg::Named(named) = arg { - ctx.seen_field(named.name().get().clone()); + ctx.seen_field(named.name().get().into()); } } @@ -615,7 +609,7 @@ pub fn param_completions<'a>( doc = Some(plain_docs_sentence(&pos.docs)); if pos.positional - && type_completion(ctx, pos.infer_type.as_ref(), doc.as_deref()).is_none() + && type_completion(ctx, pos.base_type.as_ref(), doc.as_deref()).is_none() { ctx.cast_completions(&pos.input); } @@ -637,16 +631,16 @@ pub fn param_completions<'a>( if param.named { let compl = Completion { - kind: CompletionKind::Param, - label: param.name.clone().into(), + kind: CompletionKind::Field, + label: param.name.as_ref().into(), apply: Some(eco_format!("{}: ${{}}", param.name)), detail: Some(plain_docs_sentence(¶m.docs)), label_detail: None, command: Some("tinymist.triggerNamedCompletion"), ..Completion::default() }; - match param.infer_type { - Some(FlowType::Builtin(FlowBuiltinType::TextSize)) => { + match param.base_type { + Some(Ty::Builtin(BuiltinTy::TextSize)) => { for size_template in &[ "10.5pt", "12pt", "9pt", "14pt", "8pt", "16pt", "18pt", "20pt", "22pt", "24pt", "28pt", @@ -659,7 +653,7 @@ pub fn param_completions<'a>( }); } } - Some(FlowType::Builtin(FlowBuiltinType::Dir)) => { + Some(Ty::Builtin(BuiltinTy::Dir)) => { for dir_template in &["ltr", "rtl", "ttb", "btt"] { let compl = compl.clone(); ctx.completions.push(Completion { @@ -677,7 +671,7 @@ pub fn param_completions<'a>( if param.positional && type_completion( ctx, - param.infer_type.as_ref(), + param.base_type.as_ref(), Some(&plain_docs_sentence(¶m.docs)), ) .is_none() @@ -694,7 +688,7 @@ pub fn param_completions<'a>( fn type_completion( ctx: &mut CompletionContext<'_, '_>, - infer_type: Option<&FlowType>, + infer_type: Option<&Ty>, docs: Option<&str>, ) -> Option<()> { // Prevent duplicate completions from appearing. @@ -705,44 +699,44 @@ fn type_completion( log::info!("type_completion: {:?}", infer_type); match infer_type? { - FlowType::Clause => return None, - FlowType::Undef => return None, - FlowType::Space => return None, - FlowType::Content => return None, - FlowType::Any => return None, - FlowType::Tuple(..) | FlowType::Array(..) => { + Ty::Clause => return None, + Ty::Undef => return None, + Ty::Space => return None, + Ty::Content => return None, + Ty::Any => return None, + Ty::Tuple(..) | Ty::Array(..) => { ctx.snippet_completion("()", "(${})", "An array."); } - FlowType::Dict(..) => { + Ty::Dict(..) => { ctx.snippet_completion("()", "(${})", "A dictionary."); } - FlowType::None => ctx.snippet_completion("none", "none", "Nothing."), - FlowType::Infer => return None, - FlowType::FlowNone => return None, - FlowType::Auto => { + Ty::None => ctx.snippet_completion("none", "none", "Nothing."), + Ty::Infer => return None, + Ty::FlowNone => return None, + Ty::Auto => { ctx.snippet_completion("auto", "auto", "A smart default."); } - FlowType::Boolean(_b) => { + Ty::Boolean(_b) => { ctx.snippet_completion("false", "false", "No / Disabled."); ctx.snippet_completion("true", "true", "Yes / Enabled."); } - FlowType::Field(f) => { - let f = f.0.clone(); + Ty::Field(f) => { + let f = &f.name; if ctx.seen_field(f.clone()) { return Some(()); } ctx.completions.push(Completion { kind: CompletionKind::Field, - label: f.clone(), + label: f.into(), apply: Some(eco_format!("{}: ${{}}", f)), detail: docs.map(Into::into), command: Some("tinymist.triggerNamedCompletion"), ..Completion::default() }); } - FlowType::Builtin(v) => match v { - FlowBuiltinType::Path(p) => { + Ty::Builtin(v) => match v { + BuiltinTy::Path(p) => { let source = ctx.ctx.source_by_id(ctx.root.span().id()?).ok()?; ctx.completions2.extend( @@ -751,14 +745,14 @@ fn type_completion( .flatten(), ); } - FlowBuiltinType::Args => return None, - FlowBuiltinType::Stroke => { + BuiltinTy::Args => return None, + BuiltinTy::Stroke => { ctx.snippet_completion("stroke()", "stroke(${})", "Stroke type."); ctx.snippet_completion("()", "(${})", "Stroke dictionary."); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Color)), docs); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Color)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Length)), docs); } - FlowBuiltinType::Color => { + BuiltinTy::Color => { ctx.snippet_completion("luma()", "luma(${v})", "A custom grayscale color."); ctx.snippet_completion( "rgb()", @@ -798,8 +792,8 @@ fn type_completion( let color_ty = Type::of::(); ctx.strict_scope_completions(false, |value| value.ty() == color_ty); } - FlowBuiltinType::TextSize => return None, - FlowBuiltinType::TextLang => { + BuiltinTy::TextSize => return None, + BuiltinTy::TextLang => { for (&key, desc) in rust_iso639::ALL_MAP.entries() { let detail = eco_format!("An ISO 639-1/2/3 language code, {}.", desc.name); ctx.completions.push(Completion { @@ -812,7 +806,7 @@ fn type_completion( }); } } - FlowBuiltinType::TextRegion => { + BuiltinTy::TextRegion => { for (&key, desc) in rust_iso3166::ALPHA2_MAP.entries() { let detail = eco_format!("An ISO 3166-1 alpha-2 region code, {}.", desc.name); ctx.completions.push(Completion { @@ -825,30 +819,30 @@ fn type_completion( }); } } - FlowBuiltinType::Dir => { + BuiltinTy::Dir => { let ty = Type::of::(); ctx.strict_scope_completions(false, |value| value.ty() == ty); } - FlowBuiltinType::TextFont => { + BuiltinTy::TextFont => { ctx.font_completions(); } - FlowBuiltinType::Margin => { + BuiltinTy::Margin => { ctx.snippet_completion("()", "(${})", "Margin dictionary."); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Length)), docs); } - FlowBuiltinType::Inset => { + BuiltinTy::Inset => { ctx.snippet_completion("()", "(${})", "Inset dictionary."); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Length)), docs); } - FlowBuiltinType::Outset => { + BuiltinTy::Outset => { ctx.snippet_completion("()", "(${})", "Outset dictionary."); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Length)), docs); } - FlowBuiltinType::Radius => { + BuiltinTy::Radius => { ctx.snippet_completion("()", "(${})", "Radius dictionary."); - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Length)), docs); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Length)), docs); } - FlowBuiltinType::Length => { + BuiltinTy::Length => { ctx.snippet_completion("pt", "${1}pt", "Point length unit."); ctx.snippet_completion("mm", "${1}mm", "Millimeter length unit."); ctx.snippet_completion("cm", "${1}cm", "Centimeter length unit."); @@ -856,21 +850,21 @@ fn type_completion( ctx.snippet_completion("em", "${1}em", "Em length unit."); let length_ty = Type::of::(); ctx.strict_scope_completions(false, |value| value.ty() == length_ty); - type_completion(ctx, Some(&FlowType::Auto), docs); + type_completion(ctx, Some(&Ty::Auto), docs); } - FlowBuiltinType::Float => { + BuiltinTy::Float => { ctx.snippet_completion("exponential notation", "${1}e${0}", "Exponential notation"); } - FlowBuiltinType::Type(ty) => { + BuiltinTy::Type(ty) => { if *ty == Type::of::() { - type_completion(ctx, Some(&FlowType::None), docs); + type_completion(ctx, Some(&Ty::None), docs); } else if *ty == Type::of::() { - type_completion(ctx, Some(&FlowType::Auto), docs); + type_completion(ctx, Some(&Ty::Auto), docs); } else if *ty == Type::of::() { ctx.snippet_completion("false", "false", "No / Disabled."); ctx.snippet_completion("true", "true", "Yes / Enabled."); } else if *ty == Type::of::() { - type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Color)), None); + type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Color)), None); } else if *ty == Type::of::