mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-23 04:35:00 +00:00
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
This commit is contained in:
parent
d9df64bca7
commit
fff227f3ae
94 changed files with 4011 additions and 3042 deletions
283
crates/tinymist-query/src/adt/interner.rs
Normal file
283
crates/tinymist-query/src/adt/interner.rs
Normal file
|
@ -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<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
|
||||
type Guard<T> = dashmap::RwLockWriteGuard<
|
||||
'static,
|
||||
HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
|
||||
>;
|
||||
|
||||
pub struct Interned<T: Internable + ?Sized> {
|
||||
arc: Arc<T>,
|
||||
}
|
||||
|
||||
impl<T: Internable> Interned<T> {
|
||||
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<str> {
|
||||
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<str> {
|
||||
fn from(s: &str) -> Self {
|
||||
Interned::new_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EcoString> for Interned<str> {
|
||||
fn from(s: &EcoString) -> Self {
|
||||
Interned::new_str(s)
|
||||
}
|
||||
}
|
||||
impl From<&Interned<str>> for EcoString {
|
||||
fn from(s: &Interned<str>) -> Self {
|
||||
s.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> Interned<T> {
|
||||
#[inline]
|
||||
fn select(obj: &T) -> (Guard<T>, 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<T: Internable + ?Sized> Drop for Interned<T> {
|
||||
#[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<T: Internable + ?Sized> Interned<T> {
|
||||
#[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<T: Internable> PartialEq for Interned<T> {
|
||||
// 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<T: Internable> Eq for Interned<T> {}
|
||||
|
||||
impl PartialOrd for Interned<str> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Interned<str> {
|
||||
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<str> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.arc, &other.arc)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Interned<str> {}
|
||||
|
||||
impl<T: Internable + ?Sized> Hash for Interned<T> {
|
||||
fn hash<H: Hasher>(&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<T: Internable + ?Sized> AsRef<T> for Interned<T> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.arc
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> Deref for Interned<T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.arc
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> Clone for Interned<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
arc: self.arc.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + Internable + ?Sized> Debug for Interned<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(*self.arc).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Internable + ?Sized> Display for Interned<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(*self.arc).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InternStorage<T: ?Sized> {
|
||||
map: OnceLock<InternMap<T>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)] // this a const fn, so it can't be default
|
||||
impl<T: ?Sized> InternStorage<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
map: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> InternStorage<T> {
|
||||
fn get(&self) -> &InternMap<T> {
|
||||
self.map.get_or_init(DashMap::default)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Internable: Hash + Eq + 'static {
|
||||
fn storage() -> &'static InternStorage<Self>;
|
||||
}
|
||||
|
||||
/// 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<Self> {
|
||||
static STORAGE: $crate::adt::interner::InternStorage<$t> = $crate::adt::interner::InternStorage::new();
|
||||
&STORAGE
|
||||
}
|
||||
}
|
||||
)+ };
|
||||
}
|
||||
|
||||
pub use crate::_impl_internable as impl_internable;
|
||||
|
||||
impl_internable!(str,);
|
|
@ -1 +1,2 @@
|
|||
pub mod interner;
|
||||
pub mod snapshot_map;
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<FlowType> {
|
||||
pub(crate) fn type_of(&mut self, rr: &SyntaxNode) -> Option<Ty> {
|
||||
self.type_of_span(rr.span())
|
||||
}
|
||||
|
||||
pub(crate) fn type_of_span(&mut self, s: Span) -> Option<FlowType> {
|
||||
pub(crate) fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<FlowType> {
|
||||
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<FlowType> {
|
||||
pub(crate) fn literal_type_of_node(&mut self, k: LinkedNode) -> Option<Ty> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<str>,
|
||||
/// 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<FlowType>,
|
||||
pub(crate) base_type: Option<Ty>,
|
||||
/// The parameter's default name as type.
|
||||
pub type_repr: Option<EcoString>,
|
||||
/// The parameter's default name as value.
|
||||
|
@ -56,10 +58,10 @@ pub struct ParamSpec {
|
|||
impl ParamSpec {
|
||||
fn from_static(f: &Func, p: &ParamInfo) -> Arc<Self> {
|
||||
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<SigTy> {
|
||||
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<Arc<ParamSpec>>,
|
||||
/// The named parameters.
|
||||
pub named: HashMap<Cow<'static, str>, Arc<ParamSpec>>,
|
||||
pub named: HashMap<Interned<str>, Arc<ParamSpec>>,
|
||||
/// Whether the function has fill, stroke, or size parameters.
|
||||
pub has_fill_or_size_or_stroke: bool,
|
||||
/// The rest parameter.
|
||||
pub rest: Option<Arc<ParamSpec>>,
|
||||
/// The return type.
|
||||
pub(crate) ret_ty: Option<FlowType>,
|
||||
pub(crate) ret_ty: Option<Ty>,
|
||||
/// The signature type.
|
||||
pub(crate) sig_ty: Option<FlowType>,
|
||||
pub(crate) sig_ty: Interned<SigTy>,
|
||||
_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<PrimarySignature> {
|
|||
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<PrimarySignature> {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
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<PrimarySignature> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut named_vec: Vec<(EcoString, FlowType)> = named
|
||||
let mut named_vec: Vec<(Interned<str>, 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::<Vec<_>>();
|
||||
|
||||
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<PrimarySignature> {
|
|||
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<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
|
|||
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<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
|
|||
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<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
|
|||
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<LazyHash<Closure>>) -> Vec<Arc<ParamSpec>> {
|
|||
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,
|
||||
|
|
File diff suppressed because it is too large
Load diff
112
crates/tinymist-query/src/analysis/ty/apply.rs
Normal file
112
crates/tinymist-query/src/analysis/ty/apply.rs
Normal file
|
@ -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<Ty>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'w> ApplyChecker for ApplyTypeChecker<'a, 'b, 'w> {
|
||||
fn bound_of_var(
|
||||
&mut self,
|
||||
var: &Interned<super::TypeVar>,
|
||||
_pol: bool,
|
||||
) -> Option<super::TypeBounds> {
|
||||
self.base
|
||||
.info
|
||||
.vars
|
||||
.get(&var.def)
|
||||
.map(|v| v.bounds.bounds().read().clone())
|
||||
}
|
||||
|
||||
fn call(&mut self, sig: Sig, args: &Interned<ArgsTy>, 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::<typst::foundations::Type>() {
|
||||
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()))),
|
||||
// );
|
||||
}
|
||||
}
|
|
@ -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<bool>),
|
||||
Builtin(FlowBuiltinType),
|
||||
Value(Box<(Value, Span)>),
|
||||
ValueDoc(Box<(Value, &'static str)>),
|
||||
Field(Box<(EcoString, FlowType, Span)>),
|
||||
Element(Element),
|
||||
|
||||
Var(Box<(DefId, EcoString)>),
|
||||
Func(Box<FlowSignature>),
|
||||
Dict(FlowRecord),
|
||||
Array(Box<FlowType>),
|
||||
// Note: may contains spread types
|
||||
Tuple(EcoVec<FlowType>),
|
||||
With(Box<(FlowType, Vec<FlowArgs>)>),
|
||||
Args(Box<FlowArgs>),
|
||||
At(FlowAt),
|
||||
Unary(FlowUnaryType),
|
||||
Binary(FlowBinaryType),
|
||||
If(Box<FlowIfType>),
|
||||
Union(Box<Vec<FlowType>>),
|
||||
Let(Arc<FlowVarStore>),
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
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<FlowType> {
|
||||
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<String> {
|
||||
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<Item = FlowType>) -> 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<std::slice::Iter<'a, CastInfo>>);
|
||||
|
||||
impl<'a> Iterator for UnionIter<'a> {
|
||||
type Item = &'a CastInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<FlowType>,
|
||||
}
|
||||
|
||||
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<FlowType>,
|
||||
pub ubs: EcoVec<FlowType>,
|
||||
}
|
||||
|
||||
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<RwLock<FlowVarStore>>),
|
||||
Weak(Arc<RwLock<FlowVarStore>>),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct FlowVar {
|
||||
pub name: EcoString,
|
||||
pub id: DefId,
|
||||
pub kind: FlowVarKind,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for FlowVar {
|
||||
fn hash<H: std::hash::Hasher>(&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<FlowType>,
|
||||
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<FlowType>,
|
||||
pub named: Vec<(EcoString, FlowType)>,
|
||||
pub rest: Option<FlowType>,
|
||||
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<Item = FlowType>,
|
||||
named: impl Iterator<Item = (EcoString, FlowType)>,
|
||||
rest: Option<FlowType>,
|
||||
ret_ty: Option<FlowType>,
|
||||
) -> 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<Item = (usize, usize)> + '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<Item = (&(EcoString, FlowType, Span), &(EcoString, FlowType, Span))> + '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("}")
|
||||
}
|
||||
}
|
|
@ -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<FlowType> {
|
||||
) -> Option<Ty> {
|
||||
let mut worker = PostTypeCheckWorker {
|
||||
ctx: _ctx,
|
||||
checked: HashMap::new(),
|
||||
|
@ -37,90 +33,41 @@ pub(crate) fn post_type_check(
|
|||
worker.check(&node)
|
||||
}
|
||||
|
||||
enum Abstracted<T, V> {
|
||||
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<Ty>,
|
||||
ubs_dedup: HashSet<Ty>,
|
||||
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<ArgsTy>], 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::<ast::Ident>()?;
|
||||
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::<usize>();
|
||||
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::<usize>();
|
||||
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<Span, Option<FlowType>>,
|
||||
checked: HashMap<Span, Option<Ty>>,
|
||||
info: &'a TypeCheckInfo,
|
||||
}
|
||||
|
||||
impl<'a, 'w> PostTypeCheckWorker<'a, 'w> {
|
||||
fn check(&mut self, node: &LinkedNode) -> Option<FlowType> {
|
||||
fn check(&mut self, node: &LinkedNode) -> Option<Ty> {
|
||||
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<FlowType> {
|
||||
fn check_(&mut self, node: &LinkedNode) -> Option<Ty> {
|
||||
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<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
fn check_context_or(&mut self, context: &LinkedNode, context_ty: Option<Ty>) -> Option<Ty> {
|
||||
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<CheckTarget>,
|
||||
context_ty: Option<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
fn check_target(&mut self, node: Option<CheckTarget>, context_ty: Option<Ty>) -> Option<Ty> {
|
||||
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<FlowType> {
|
||||
fn check_context(&mut self, context: &LinkedNode, node: &LinkedNode) -> Option<Ty> {
|
||||
match context.kind() {
|
||||
SyntaxKind::LetBinding => {
|
||||
let p = context.cast::<ast::LetBinding>()?;
|
||||
|
@ -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<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
context_ty: Option<Ty>,
|
||||
) -> Option<Ty> {
|
||||
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<FlowType> {
|
||||
fn destruct_let(&mut self, pattern: ast::Pattern, node: LinkedNode) -> Option<Ty> {
|
||||
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<ArgsTy>], 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<T>(&mut self, ty: &Ty, pol: bool, context: &LinkedNode, checker: &mut T)
|
||||
where
|
||||
T: FnMut(&mut Self, Sig, &[Interned<ArgsTy>], 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<FlowArgs>,
|
||||
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<FlowType> {
|
||||
fn simplify(&mut self, ty: &Ty) -> Option<Ty> {
|
||||
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::<ast::Array>();
|
||||
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<ArgsTy>], 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<crate::analysis::TypeVar>,
|
||||
_pol: bool,
|
||||
) -> Option<TypeBounds> {
|
||||
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::<ast::Array>();
|
||||
if c.is_some_and(|e| e.items().next().is_some()) {
|
||||
SigSurfaceKind::Array
|
||||
} else {
|
||||
SigSurfaceKind::ArrayOrDict
|
||||
}
|
||||
}
|
||||
SyntaxKind::Dict => SigSurfaceKind::Dict,
|
||||
_ => SigSurfaceKind::Array,
|
||||
}
|
||||
}
|
||||
|
|
586
crates/tinymist-query/src/analysis/ty/syntax.rs
Normal file
586
crates/tinymist-query/src/analysis/ty/syntax.rs
Normal file
|
@ -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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<str>, 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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
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<Ty> {
|
||||
let _module_import: ast::ModuleImport = root.cast()?;
|
||||
|
||||
// check all import items
|
||||
|
||||
Some(Ty::None)
|
||||
}
|
||||
|
||||
fn check_module_include(&mut self, _root: LinkedNode<'_>) -> Option<Ty> {
|
||||
Some(Ty::Content)
|
||||
}
|
||||
|
||||
fn check_destructuring(&mut self, _root: LinkedNode<'_>) -> Option<Ty> {
|
||||
Some(Ty::Any)
|
||||
}
|
||||
|
||||
fn check_destruct_assign(&mut self, _root: LinkedNode<'_>) -> Option<Ty> {
|
||||
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<Ty> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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<CompletionItem>| {
|
||||
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| {
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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 */)
|
|
@ -0,0 +1,3 @@
|
|||
// contains: paint,cap
|
||||
|
||||
#text(stroke: (/* range after 1..2 */ ))[]
|
|
@ -0,0 +1,3 @@
|
|||
// contains: paint,cap
|
||||
|
||||
#text(stroke: (paint: red, /* range after 1..2 */ ))[]
|
|
@ -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": []
|
||||
}
|
||||
]
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
( ⪰ ( ⪯ (TextFont | Array<TextFont>)) ⪯ TextFont)
|
||||
|
|
|
@ -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>)) | TextFont)
|
||||
( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array<TextFont>)) ⪯ TextFont)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
|
@ -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)
|
|
@ -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<TextFont>)))
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[]
|
||||
|
||||
#tmpl3(("Agency FB"),/* position */)
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[]
|
||||
|
||||
#tmpl3(("Agency FB"), (/* position */))
|
|
@ -0,0 +1 @@
|
|||
#let g = rgb.with(/* position */);
|
|
@ -0,0 +1 @@
|
|||
#let g = text.with(/* position */);
|
|
@ -0,0 +1,2 @@
|
|||
#let f(font, stroke) = text(font: font, stroke: stroke);
|
||||
#let g = f.with(/* position */);
|
|
@ -0,0 +1,3 @@
|
|||
#let f(x) = {
|
||||
assert(x in ("line", "number"))
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
#let f(x) = {
|
||||
assert(x in "abc")
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
#let f(x) = {
|
||||
assert(type(x) == int)
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<TextFont>)
|
||||
23..25 -> (TextFont | Array<TextFont>)
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<TextFont>)
|
||||
47..51 -> (@font | (TextFont | Array<TextFont>))
|
||||
47..51 -> @font
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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<TextFont>)
|
||||
12..18 -> (TextFont | Array<TextFont>)
|
||||
19..21 -> Type(content)
|
||||
23..27 -> Func(text)
|
||||
23..39 -> Element(text)
|
||||
28..36 -> (TextFont | Array<TextFont>)
|
||||
34..36 -> (TextFont | Array<TextFont>)
|
||||
37..39 -> Type(content)
|
||||
41..45 -> Func(text)
|
||||
41..64 -> Element(text)
|
||||
46..61 -> (TextFont | Array<TextFont>)
|
||||
52..61 -> (TextFont | Array<TextFont>)
|
||||
62..64 -> Type(content)
|
||||
70..71 -> @x
|
||||
82..86 -> Func(text)
|
||||
82..97 -> Element(text)
|
||||
87..94 -> (TextFont | Array<TextFont>)
|
||||
93..94 -> (@x | (TextFont | Array<TextFont>))
|
||||
95..97 -> Type(content)
|
||||
93..94 -> @x
|
||||
103..104 -> @y
|
||||
118..122 -> Func(text)
|
||||
118..133 -> Element(text)
|
||||
123..130 -> (TextFont | Array<TextFont>)
|
||||
129..130 -> (@y | (TextFont | Array<TextFont>))
|
||||
131..133 -> Type(content)
|
||||
129..130 -> @y
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
mod adt;
|
||||
pub mod analysis;
|
||||
pub mod syntax;
|
||||
mod ty;
|
||||
mod upstream;
|
||||
|
||||
pub(crate) mod diagnostics;
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
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"));
|
||||
}
|
||||
|
|
|
@ -101,14 +101,7 @@ pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option<DerefTarget<'
|
|||
|
||||
// Move to the first non-trivia node before the cursor.
|
||||
let mut node = node;
|
||||
if can_skip_trivia(&node, cursor) || {
|
||||
is_mark(node.kind())
|
||||
&& (!matches!(node.kind(), SyntaxKind::LeftParen)
|
||||
|| !matches!(
|
||||
node.parent_kind(),
|
||||
Some(SyntaxKind::Array | SyntaxKind::Dict | SyntaxKind::Parenthesized)
|
||||
))
|
||||
} {
|
||||
if can_skip_trivia(&node, cursor) {
|
||||
node = node.prev_sibling()?;
|
||||
}
|
||||
|
||||
|
@ -253,6 +246,7 @@ impl<'a> 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<CheckTarget<'a>> {
|
||||
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::<ast::Expr>() {
|
||||
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<CheckTarget<'_>> {
|
||||
let mut node = node;
|
||||
while node.kind().is_trivia() {
|
||||
|
@ -306,12 +334,13 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
|
|||
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,
|
||||
});
|
||||
|
|
65
crates/tinymist-query/src/ty/apply.rs
Normal file
65
crates/tinymist-query/src/ty/apply.rs
Normal file
|
@ -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<ArgsTy>, pol: bool);
|
||||
|
||||
fn bound_of_var(&mut self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
pub fn call(&self, args: &Interned<ArgsTy>, 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<Interned<ArgsTy>> =
|
||||
// Lazy::new(|| Interned::new(ArgsTy::default()));
|
||||
|
||||
// self.apply(SigSurfaceKind::ArrayOrDict, &EMPTY_ARGS, pol, checker)
|
||||
// }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
surface: SigSurfaceKind,
|
||||
args: &Interned<ArgsTy>,
|
||||
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<ArgsTy>);
|
||||
|
||||
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<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
self.0.bound_of_var(_var, _pol)
|
||||
}
|
||||
}
|
68
crates/tinymist-query/src/ty/bound.rs
Normal file
68
crates/tinymist-query/src/ty/bound.rs
Normal file
|
@ -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<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BoundChecker for T
|
||||
where
|
||||
T: FnMut(&Ty, bool) -> Option<TypeBounds>,
|
||||
{
|
||||
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<Item = &'a Ty>,
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Self> {
|
||||
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<Ty> {
|
||||
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<std::slice::Iter<'a, CastInfo>>);
|
||||
|
||||
impl<'a> Iterator for UnionIter<'a> {
|
||||
type Item = &'a CastInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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::<AutoValue>() {
|
||||
return FlowType::Auto;
|
||||
return Ty::Auto;
|
||||
}
|
||||
if builtin == Type::of::<NoneValue>() {
|
||||
return FlowType::None;
|
||||
return Ty::None;
|
||||
}
|
||||
if builtin == Type::of::<typst::visualize::Color>() {
|
||||
return Color.literally();
|
||||
}
|
||||
if builtin == Type::of::<bool>() {
|
||||
return FlowType::None;
|
||||
return Ty::None;
|
||||
}
|
||||
if builtin == Type::of::<f64>() {
|
||||
return Float.literally();
|
||||
|
@ -134,30 +239,31 @@ impl FlowBuiltinType {
|
|||
return Length.literally();
|
||||
}
|
||||
if builtin == Type::of::<Content>() {
|
||||
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<FlowType> {
|
||||
match (f.name().unwrap(), p.name) {
|
||||
pub(super) fn param_mapping(f: &Func, p: &ParamInfo) -> Option<Ty> {
|
||||
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<FlowType> = Lazy::new(|| {
|
||||
FlowType::Union(Box::new(vec![
|
||||
static FONT_TYPE: Lazy<Ty> = 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<FlowType> = Lazy::new(|| {
|
||||
static COLUMN_TYPE: Lazy<Ty> = Lazy::new(|| {
|
||||
flow_union!(
|
||||
FlowType::Value(Box::new((Value::Auto, Span::detached()))),
|
||||
FlowType::Value(Box::new((Value::Type(Type::of::<i64>()), Span::detached()))),
|
||||
Ty::Value(InsTy::new(Value::Auto)),
|
||||
Ty::Value(InsTy::new(Value::Type(Type::of::<i64>()))),
|
||||
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<FlowType> = Lazy::new(|| {
|
||||
static PATTERN_SIZE_TYPE: Lazy<Ty> = 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<FlowType> = Lazy::new(|| {
|
||||
static FLOW_STROKE_DASH_TYPE: Lazy<Ty> = Lazy::new(|| {
|
||||
flow_union!(
|
||||
"solid",
|
||||
"dotted",
|
||||
|
@ -325,15 +429,15 @@ static FLOW_STROKE_DASH_TYPE: Lazy<FlowType> = 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<FlowRecord> = Lazy::new(|| {
|
||||
pub static FLOW_STROKE_DICT: Lazy<Interned<RecordTy>> = Lazy::new(|| {
|
||||
flow_record!(
|
||||
"paint" => literally(Color),
|
||||
"thickness" => literally(Length),
|
||||
|
@ -344,7 +448,7 @@ pub static FLOW_STROKE_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
pub static FLOW_MARGIN_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
||||
pub static FLOW_MARGIN_DICT: Lazy<Interned<RecordTy>> = Lazy::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
|
@ -358,7 +462,7 @@ pub static FLOW_MARGIN_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
pub static FLOW_INSET_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
||||
pub static FLOW_INSET_DICT: Lazy<Interned<RecordTy>> = Lazy::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
|
@ -370,7 +474,7 @@ pub static FLOW_INSET_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
pub static FLOW_OUTSET_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
||||
pub static FLOW_OUTSET_DICT: Lazy<Interned<RecordTy>> = Lazy::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
|
@ -382,7 +486,7 @@ pub static FLOW_OUTSET_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
pub static FLOW_RADIUS_DICT: Lazy<FlowRecord> = Lazy::new(|| {
|
||||
pub static FLOW_RADIUS_DICT: Lazy<Interned<RecordTy>> = Lazy::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
|
@ -396,16 +500,12 @@ pub static FLOW_RADIUS_DICT: Lazy<FlowRecord> = 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
|
931
crates/tinymist-query/src/ty/def.rs
Normal file
931
crates/tinymist-query/src/ty/def.rs
Normal file
|
@ -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<Ty>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TypeCheckInfo {
|
||||
pub vars: HashMap<DefId, TypeVarBounds>,
|
||||
pub mapping: HashMap<Span, Vec<Ty>>,
|
||||
|
||||
pub(super) cano_cache: Mutex<TypeCanoStore>,
|
||||
}
|
||||
|
||||
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<Span, Vec<Ty>>) {
|
||||
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<Ty> {
|
||||
self.mapping
|
||||
.get(&site)
|
||||
.cloned()
|
||||
.map(|e| Ty::from_types(e.into_iter()))
|
||||
}
|
||||
|
||||
pub fn type_of_def(&self, def: DefId) -> Option<Ty> {
|
||||
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<DefId>,
|
||||
pub positives: HashSet<DefId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TypeSource {
|
||||
pub name_node: SyntaxNode,
|
||||
pub name_repr: OnceCell<Interned<str>>,
|
||||
pub span: Span,
|
||||
pub doc: Interned<str>,
|
||||
}
|
||||
|
||||
impl Hash for TypeSource {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name_node.hash(state);
|
||||
self.span.hash(state);
|
||||
self.doc.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeSource {
|
||||
pub fn name(&self) -> Interned<str> {
|
||||
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<NameBone>;
|
||||
fn interface(&self) -> impl Iterator<Item = (&Interned<str>, &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<Interned<TypeSource>>,
|
||||
}
|
||||
|
||||
// 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<Self> {
|
||||
Interned::new(Self { val, syntax: None })
|
||||
}
|
||||
pub fn new_at(val: Value, s: Span) -> Interned<Self> {
|
||||
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<Self> {
|
||||
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<Interned<str>>,
|
||||
}
|
||||
|
||||
impl NameBone {
|
||||
pub fn empty() -> Interned<Self> {
|
||||
Interned::new(Self { names: Vec::new() })
|
||||
}
|
||||
}
|
||||
|
||||
impl NameBone {
|
||||
fn find(&self, name: &Interned<str>) -> Option<usize> {
|
||||
// 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<Item = (usize, usize)> + '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<str>,
|
||||
pub field: Ty,
|
||||
|
||||
pub syntax: Option<Interned<TypeSource>>,
|
||||
}
|
||||
impl FieldTy {
|
||||
pub(crate) fn new_untyped(name: Interned<str>) -> Interned<Self> {
|
||||
Interned::new(Self {
|
||||
name,
|
||||
field: Ty::Any,
|
||||
syntax: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, Default)]
|
||||
pub struct TypeBounds {
|
||||
pub lbs: EcoVec<Ty>,
|
||||
pub ubs: EcoVec<Ty>,
|
||||
}
|
||||
|
||||
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<str>,
|
||||
pub def: DefId,
|
||||
|
||||
pub syntax: Option<Interned<TypeSource>>,
|
||||
}
|
||||
|
||||
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<RwLock<TypeBounds>>),
|
||||
Weak(Arc<RwLock<TypeBounds>>),
|
||||
}
|
||||
|
||||
impl FlowVarKind {
|
||||
pub fn bounds(&self) -> &RwLock<TypeBounds> {
|
||||
match self {
|
||||
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TypeVarBounds {
|
||||
pub var: Interned<TypeVar>,
|
||||
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<str> {
|
||||
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<str>, def: DefId) -> Interned<Self> {
|
||||
Interned::new(Self {
|
||||
name,
|
||||
def,
|
||||
syntax: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Interned<str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> DefId {
|
||||
self.def
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq)]
|
||||
pub struct RecordTy {
|
||||
pub types: Interned<Vec<Ty>>,
|
||||
pub names: Interned<NameBone>,
|
||||
pub syntax: Option<Interned<TypeSource>>,
|
||||
}
|
||||
|
||||
impl RecordTy {
|
||||
pub(crate) fn shape_fields(mut fields: Vec<(Interned<str>, Ty, Span)>) -> (NameBone, Vec<Ty>) {
|
||||
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::<Vec<_>>();
|
||||
|
||||
(names, types)
|
||||
}
|
||||
|
||||
pub(crate) fn new(fields: Vec<(Interned<str>, Ty, Span)>) -> Interned<Self> {
|
||||
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<Item = (&Interned<str>, &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<NameBone> {
|
||||
&self.names
|
||||
}
|
||||
|
||||
fn interface(&self) -> impl Iterator<Item = (&Interned<str>, &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<str>, &'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<Vec<Ty>>,
|
||||
pub ret: Option<Ty>,
|
||||
pub names: Interned<NameBone>,
|
||||
pub name_started: u32,
|
||||
pub spread_left: bool,
|
||||
pub spread_right: bool,
|
||||
pub has_free_variables: bool,
|
||||
|
||||
pub syntax: Option<Interned<TypeSource>>,
|
||||
}
|
||||
|
||||
impl SigTy {
|
||||
/// Array constructor
|
||||
#[comemo::memoize]
|
||||
pub(crate) fn array_cons(elem: Ty, anyify: bool) -> Interned<SigTy> {
|
||||
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<Item = &Ty> {
|
||||
self.types.iter()
|
||||
}
|
||||
|
||||
/// Dictionary constructor
|
||||
#[comemo::memoize]
|
||||
pub(crate) fn dict_cons(named: &Interned<RecordTy>, anyify: bool) -> Interned<SigTy> {
|
||||
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<Item = Ty>,
|
||||
named: impl Iterator<Item = (Interned<str>, Ty)>,
|
||||
rest: Option<Ty>,
|
||||
ret_ty: Option<Ty>,
|
||||
) -> Self {
|
||||
let named = named
|
||||
.map(|(name, ty)| (name, ty, Span::detached()))
|
||||
.collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<Item = &Ty> {
|
||||
self.types.iter().take(self.name_started as usize)
|
||||
}
|
||||
|
||||
pub fn named_params(&self) -> impl ExactSizeIterator<Item = (&Interned<str>, &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<str>) -> 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<Interned<crate::analysis::SigTy>>>,
|
||||
) -> impl Iterator<Item = (&'a Ty, &'a Ty)> + 'a {
|
||||
let with_len = withs
|
||||
.map(|w| w.iter().map(|w| w.positional_params().len()).sum::<usize>())
|
||||
.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<ArgsTy>,
|
||||
}
|
||||
|
||||
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<Ty>,
|
||||
pub select: Interned<str>,
|
||||
}
|
||||
|
||||
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<Ty>,
|
||||
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<Ty>,
|
||||
pub then: Interned<Ty>,
|
||||
pub else_: Interned<Ty>,
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum Ty {
|
||||
Clause,
|
||||
Undef,
|
||||
Content,
|
||||
Any,
|
||||
Space,
|
||||
None,
|
||||
Infer,
|
||||
FlowNone,
|
||||
Auto,
|
||||
Boolean(Option<bool>),
|
||||
Builtin(BuiltinTy),
|
||||
Value(Interned<InsTy>),
|
||||
Field(Interned<FieldTy>),
|
||||
|
||||
Var(Interned<TypeVar>),
|
||||
Union(Interned<Vec<Ty>>),
|
||||
Let(Interned<TypeBounds>),
|
||||
|
||||
Func(Interned<SigTy>),
|
||||
With(Interned<SigWithTy>),
|
||||
Args(Interned<ArgsTy>),
|
||||
Dict(Interned<RecordTy>),
|
||||
Array(Interned<Ty>),
|
||||
// Note: may contains spread types
|
||||
Tuple(Interned<Vec<Ty>>),
|
||||
Select(Interned<SelectTy>),
|
||||
Unary(Interned<TypeUnary>),
|
||||
Binary(Interned<TypeBinary>),
|
||||
If(Interned<IfTy>),
|
||||
}
|
||||
|
||||
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<Item = Ty>) -> 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<Ty>,);
|
||||
impl_internable!(TypeBounds,);
|
||||
impl_internable!(NameBone,);
|
||||
impl_internable!((Ty, Ty),);
|
||||
|
||||
fn interpersed<T: fmt::Debug>(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
iter: impl Iterator<Item = T>,
|
||||
) -> 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<SigTy> {
|
||||
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<SigTy>,
|
||||
args: Interned<SigTy>,
|
||||
withs: Option<Vec<Interned<ArgsTy>>>,
|
||||
) -> String {
|
||||
let res = sig.matches(&args, withs.as_ref()).collect::<Vec<_>>();
|
||||
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")]"###);
|
||||
}
|
||||
}
|
190
crates/tinymist-query/src/ty/describe.rs
Normal file
190
crates/tinymist-query/src/ty/describe.rs
Normal file
|
@ -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<String> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
worker.describe_root(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
pub fn describe(&self) -> Option<String> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
worker.describe_root(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TypeDescriber {
|
||||
described: HashMap<u128, String>,
|
||||
results: HashSet<String>,
|
||||
functions: Vec<Interned<SigTy>>,
|
||||
}
|
||||
|
||||
impl TypeDescriber {
|
||||
fn describe_root(&mut self, ty: &Ty) -> Option<String> {
|
||||
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::<Vec<_>>();
|
||||
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()
|
||||
}
|
||||
}
|
14
crates/tinymist-query/src/ty/mod.rs
Normal file
14
crates/tinymist-query/src/ty/mod.rs
Normal file
|
@ -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::*;
|
307
crates/tinymist-query/src/ty/sig.rs
Normal file
307
crates/tinymist-query/src/ty/sig.rs
Normal file
|
@ -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<SigTy>),
|
||||
TypeCons {
|
||||
val: &'a typst::foundations::Type,
|
||||
at: &'a Ty,
|
||||
},
|
||||
ArrayCons(&'a TyRef),
|
||||
DictCons(&'a Interned<RecordTy>),
|
||||
Value {
|
||||
val: &'a Func,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Partialize(&'a Sig<'a>),
|
||||
With {
|
||||
sig: &'a Sig<'a>,
|
||||
withs: &'a Vec<Interned<ArgsTy>>,
|
||||
at: &'a Ty,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct SigShape<'a> {
|
||||
pub sig: Interned<SigTy>,
|
||||
pub withs: Option<&'a Vec<Interned<SigTy>>>,
|
||||
}
|
||||
|
||||
impl<'a> Sig<'a> {
|
||||
pub fn ty(self) -> Option<Ty> {
|
||||
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<SigShape<'a>> {
|
||||
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<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> 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<Interned<SigTy>> {
|
||||
// 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<Interned<SigTy>>,
|
||||
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<TypeVar>, pol: bool) -> Option<TypeBounds> {
|
||||
self.checker.check_var(var, pol)
|
||||
}
|
||||
}
|
||||
|
||||
struct MethodDriver<'a, 'b>(&'a mut SigCheckDriver<'b>, &'a Interned<str>);
|
||||
|
||||
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<TypeVar>, pol: bool) -> Option<TypeBounds> {
|
||||
self.0.checker.check_var(var, pol)
|
||||
}
|
||||
}
|
304
crates/tinymist-query/src/ty/simplify.rs
Normal file
304
crates/tinymist-query/src/ty/simplify.rs
Normal file
|
@ -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<DefId>,
|
||||
primitives: HashSet<Ty>,
|
||||
recursives: HashMap<DefId, CompactTy>,
|
||||
signatures: Vec<Interned<SigTy>>,
|
||||
|
||||
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<DefId, TypeVarBounds>,
|
||||
|
||||
cano_cache: &'b mut HashMap<(Ty, bool), Ty>,
|
||||
cano_local_cache: &'b mut HashMap<(DefId, bool), Ty>,
|
||||
negatives: &'b mut HashSet<DefId>,
|
||||
positives: &'b mut HashSet<DefId>,
|
||||
}
|
||||
|
||||
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<Vec<Ty>> {
|
||||
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<SigTy> {
|
||||
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)
|
||||
}
|
||||
}
|
55
crates/tinymist-query/src/ty/subst.rs
Normal file
55
crates/tinymist-query/src/ty/subst.rs
Normal file
|
@ -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<ArgsTy>,
|
||||
pol: bool,
|
||||
ctx: Option<&mut AnalysisContext>,
|
||||
) -> Option<Ty> {
|
||||
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<ArgsTy>,
|
||||
ctx: Option<&mut AnalysisContext>,
|
||||
) -> Option<(HashMap<DefId, Ty>, Option<Ty>)> {
|
||||
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<DefId, Ty>,
|
||||
}
|
||||
impl SubstituteChecker {
|
||||
fn ty(&mut self, body: &Ty, pol: bool) -> Option<Ty> {
|
||||
let _ = self.bound_variables;
|
||||
let _ = pol;
|
||||
|
||||
Some(body.clone())
|
||||
}
|
||||
}
|
|
@ -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<Completion>, Vec<lsp_types::CompletionItem>)> {
|
||||
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<lsp_types::CompletionItem>,
|
||||
pub incomplete: bool,
|
||||
pub seen_casts: HashSet<u128>,
|
||||
pub seen_fields: HashSet<Interned<str>>,
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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<str>) -> 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::<Color>();
|
||||
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::<Dir>();
|
||||
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::<Length>();
|
||||
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::<NoneValue>() {
|
||||
type_completion(ctx, Some(&FlowType::None), docs);
|
||||
type_completion(ctx, Some(&Ty::None), docs);
|
||||
} else if *ty == Type::of::<AutoValue>() {
|
||||
type_completion(ctx, Some(&FlowType::Auto), docs);
|
||||
type_completion(ctx, Some(&Ty::Auto), docs);
|
||||
} else if *ty == Type::of::<bool>() {
|
||||
ctx.snippet_completion("false", "false", "No / Disabled.");
|
||||
ctx.snippet_completion("true", "true", "Yes / Enabled.");
|
||||
} else if *ty == Type::of::<Color>() {
|
||||
type_completion(ctx, Some(&FlowType::Builtin(FlowBuiltinType::Color)), None);
|
||||
type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Color)), None);
|
||||
} else if *ty == Type::of::<Label>() {
|
||||
ctx.label_completions()
|
||||
} else if *ty == Type::of::<Func>() {
|
||||
|
@ -890,17 +884,20 @@ fn type_completion(
|
|||
ctx.strict_scope_completions(false, |value| value.ty() == *ty);
|
||||
}
|
||||
}
|
||||
BuiltinTy::Element(e) => {
|
||||
ctx.value_completion(Some(e.name().into()), &Value::Func((*e).into()), true, docs);
|
||||
}
|
||||
},
|
||||
FlowType::Args(_) => return None,
|
||||
FlowType::Func(_) => return None,
|
||||
FlowType::With(_) => return None,
|
||||
FlowType::At(_) => return None,
|
||||
FlowType::Union(u) => {
|
||||
Ty::Args(_) => return None,
|
||||
Ty::Func(_) => return None,
|
||||
Ty::With(_) => return None,
|
||||
Ty::Select(_) => return None,
|
||||
Ty::Union(u) => {
|
||||
for info in u.as_ref() {
|
||||
type_completion(ctx, Some(info), docs);
|
||||
}
|
||||
}
|
||||
FlowType::Let(e) => {
|
||||
Ty::Let(e) => {
|
||||
for ut in e.ubs.iter() {
|
||||
type_completion(ctx, Some(ut), docs);
|
||||
}
|
||||
|
@ -908,58 +905,39 @@ fn type_completion(
|
|||
type_completion(ctx, Some(lt), docs);
|
||||
}
|
||||
}
|
||||
FlowType::Var(_) => return None,
|
||||
FlowType::Unary(_) => return None,
|
||||
FlowType::Binary(_) => return None,
|
||||
FlowType::If(_) => return None,
|
||||
FlowType::Value(v) => {
|
||||
Ty::Var(_) => return None,
|
||||
Ty::Unary(_) => return None,
|
||||
Ty::Binary(_) => return None,
|
||||
Ty::If(_) => return None,
|
||||
Ty::Value(v) => {
|
||||
let docs = v.syntax.as_ref().map(|s| s.doc.as_ref()).or(docs);
|
||||
|
||||
// Prevent duplicate completions from appearing.
|
||||
if !ctx.seen_casts.insert(typst::util::hash128(&v.0)) {
|
||||
if !ctx.seen_casts.insert(typst::util::hash128(&v.val)) {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
if let Value::Type(ty) = &v.0 {
|
||||
type_completion(
|
||||
ctx,
|
||||
Some(&FlowType::Builtin(FlowBuiltinType::Type(*ty))),
|
||||
docs,
|
||||
);
|
||||
} else if v.0.ty() == Type::of::<NoneValue>() {
|
||||
type_completion(ctx, Some(&FlowType::None), docs);
|
||||
} else if v.0.ty() == Type::of::<AutoValue>() {
|
||||
type_completion(ctx, Some(&FlowType::Auto), docs);
|
||||
if let Value::Type(ty) = &v.val {
|
||||
type_completion(ctx, Some(&Ty::Builtin(BuiltinTy::Type(*ty))), docs);
|
||||
} else if v.val.ty() == Type::of::<NoneValue>() {
|
||||
type_completion(ctx, Some(&Ty::None), docs);
|
||||
} else if v.val.ty() == Type::of::<AutoValue>() {
|
||||
type_completion(ctx, Some(&Ty::Auto), docs);
|
||||
} else {
|
||||
ctx.value_completion(None, &v.0, true, docs);
|
||||
ctx.value_completion(None, &v.val, true, docs);
|
||||
}
|
||||
}
|
||||
FlowType::ValueDoc(v) => {
|
||||
let (value, docs) = v.as_ref();
|
||||
type_completion(
|
||||
ctx,
|
||||
Some(&FlowType::Value(Box::new((
|
||||
value.clone(),
|
||||
Span::detached(),
|
||||
)))),
|
||||
Some(*docs),
|
||||
);
|
||||
}
|
||||
FlowType::Element(e) => {
|
||||
ctx.value_completion(Some(e.name().into()), &Value::Func((*e).into()), true, docs);
|
||||
} // CastInfo::Any => {}
|
||||
};
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
struct FieldName(EcoString);
|
||||
|
||||
/// Add completions for the values of a named function parameter.
|
||||
pub fn named_param_value_completions<'a>(
|
||||
ctx: &mut CompletionContext<'a, '_>,
|
||||
callee: ast::Expr<'a>,
|
||||
name: &str,
|
||||
ty: Option<&FlowType>,
|
||||
name: &Interned<str>,
|
||||
ty: Option<&Ty>,
|
||||
) {
|
||||
let Some(cc) = ctx
|
||||
.root
|
||||
|
@ -1030,7 +1008,7 @@ pub fn named_param_value_completions<'a>(
|
|||
|
||||
if type_completion(
|
||||
ctx,
|
||||
param.infer_type.as_ref(),
|
||||
param.base_type.as_ref(),
|
||||
Some(&plain_docs_sentence(¶m.docs)),
|
||||
)
|
||||
.is_none()
|
||||
|
@ -1044,175 +1022,48 @@ pub fn named_param_value_completions<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
|
||||
let parent = ctx.leaf.clone();
|
||||
log::info!("check complete_literal: {:?}", ctx.leaf);
|
||||
let parent = if parent.kind().is_trivia() {
|
||||
parent.prev_sibling()?
|
||||
} else {
|
||||
parent
|
||||
};
|
||||
log::debug!("check complete_literal 2: {:?}", parent);
|
||||
let parent = &parent;
|
||||
let parent = match parent.kind() {
|
||||
SyntaxKind::Ident | SyntaxKind::Colon => parent.parent()?,
|
||||
_ => parent,
|
||||
};
|
||||
let parent = match parent.kind() {
|
||||
SyntaxKind::Named => parent.parent()?,
|
||||
SyntaxKind::LeftParen | SyntaxKind::Comma => parent.parent()?,
|
||||
_ => parent,
|
||||
};
|
||||
log::debug!("check complete_literal 3: {:?}", parent);
|
||||
/// Complete call and set rule parameters.
|
||||
pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
|
||||
use crate::syntax::get_check_target;
|
||||
|
||||
// or empty array
|
||||
let lit_span;
|
||||
let (dict_lit, _tuple_lit) = match parent.kind() {
|
||||
SyntaxKind::Dict => {
|
||||
let dict_lit = parent.get().cast::<ast::Dict>()?;
|
||||
let check_target = get_check_target(ctx.leaf.clone());
|
||||
log::debug!("complete_type: pos {:?} -> {:#?}", ctx.leaf, check_target);
|
||||
|
||||
lit_span = dict_lit.span();
|
||||
(dict_lit, None)
|
||||
match check_target {
|
||||
Some(CheckTarget::Element { container, .. }) => {
|
||||
if let Some(container) = container.cast::<ast::Dict>() {
|
||||
for named in container.items() {
|
||||
if let ast::DictItem::Named(named) = named {
|
||||
ctx.seen_field(named.name().get().into());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
SyntaxKind::Array => {
|
||||
let w = parent.get().cast::<ast::Array>()?;
|
||||
lit_span = w.span();
|
||||
(ast::Dict::default(), Some(w))
|
||||
Some(CheckTarget::Param { args, .. }) => {
|
||||
let args = args.cast::<ast::Args>()?;
|
||||
for arg in args.items() {
|
||||
if let ast::Arg::Named(named) = arg {
|
||||
ctx.seen_field(named.name().get().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
SyntaxKind::Parenthesized => {
|
||||
lit_span = parent.span();
|
||||
(ast::Dict::default(), None)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
Some(CheckTarget::Paren { .. }) => {}
|
||||
Some(CheckTarget::Normal(..)) => return None,
|
||||
None => return None,
|
||||
}
|
||||
|
||||
// query type of the dict
|
||||
let named_ty = ctx
|
||||
let ty = ctx
|
||||
.ctx
|
||||
.literal_type_of_node(ctx.leaf.clone())
|
||||
.filter(|ty| !matches!(ty, FlowType::Any));
|
||||
let lit_ty = ctx.ctx.literal_type_of_span(lit_span);
|
||||
log::info!("complete_literal: {lit_ty:?} {named_ty:?}");
|
||||
.filter(|ty| !matches!(ty, Ty::Any))?;
|
||||
|
||||
enum LitComplAction<'a> {
|
||||
Dict(&'a FlowRecord),
|
||||
Positional(&'a FlowType),
|
||||
}
|
||||
let existing = OnceCell::new();
|
||||
log::debug!("complete_type: ty {:?} -> {:#?}", ctx.leaf, ty);
|
||||
// log::debug!("complete_type: before {:?}", ctx.before.chars().last());
|
||||
|
||||
struct LitComplWorker<'a, 'b, 'w> {
|
||||
ctx: &'a mut CompletionContext<'b, 'w>,
|
||||
dict_lit: ast::Dict<'a>,
|
||||
existing: &'a OnceCell<Mutex<HashSet<EcoString>>>,
|
||||
}
|
||||
|
||||
let mut ctx = LitComplWorker {
|
||||
ctx,
|
||||
dict_lit,
|
||||
existing: &existing,
|
||||
};
|
||||
|
||||
impl<'a, 'b, 'w> LitComplWorker<'a, 'b, 'w> {
|
||||
fn on_iface(&mut self, lit_interface: LitComplAction<'_>) {
|
||||
match lit_interface {
|
||||
LitComplAction::Positional(a) => {
|
||||
type_completion(self.ctx, Some(a), None);
|
||||
}
|
||||
LitComplAction::Dict(dict_iface) => {
|
||||
let existing = self.existing.get_or_init(|| {
|
||||
Mutex::new(
|
||||
self.dict_lit
|
||||
.items()
|
||||
.filter_map(|field| match field {
|
||||
ast::DictItem::Named(n) => Some(n.name().get().clone()),
|
||||
ast::DictItem::Keyed(k) => {
|
||||
let key = self.ctx.ctx.const_eval(k.key());
|
||||
if let Some(Value::Str(key)) = key {
|
||||
return Some(key.into());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
// todo: var dict union
|
||||
ast::DictItem::Spread(_s) => None,
|
||||
})
|
||||
.collect::<HashSet<_>>(),
|
||||
)
|
||||
});
|
||||
let mut existing = existing.lock();
|
||||
|
||||
for (key, _, _) in dict_iface.fields.iter() {
|
||||
if !existing.insert(key.clone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.ctx.completions.push(Completion {
|
||||
kind: CompletionKind::Field,
|
||||
label: key.clone(),
|
||||
apply: Some(eco_format!("{}: ${{}}", key)),
|
||||
// todo: only vscode and neovim (0.9.1) support this
|
||||
command: Some("editor.action.triggerSuggest"),
|
||||
..Completion::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_lit_ty(&mut self, ty: &FlowType) {
|
||||
match ty {
|
||||
FlowType::Builtin(FlowBuiltinType::Stroke) => {
|
||||
self.on_iface(LitComplAction::Dict(&FLOW_STROKE_DICT))
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Margin) => {
|
||||
self.on_iface(LitComplAction::Dict(&FLOW_MARGIN_DICT))
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Inset) => {
|
||||
self.on_iface(LitComplAction::Dict(&FLOW_INSET_DICT))
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Outset) => {
|
||||
self.on_iface(LitComplAction::Dict(&FLOW_OUTSET_DICT))
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Radius) => {
|
||||
self.on_iface(LitComplAction::Dict(&FLOW_RADIUS_DICT))
|
||||
}
|
||||
FlowType::Dict(d) => self.on_iface(LitComplAction::Dict(d)),
|
||||
FlowType::Array(a) => self.on_iface(LitComplAction::Positional(a)),
|
||||
FlowType::Union(u) => {
|
||||
for info in u.as_ref() {
|
||||
self.on_lit_ty(info);
|
||||
}
|
||||
}
|
||||
FlowType::Let(u) => {
|
||||
for ut in u.ubs.iter() {
|
||||
self.on_lit_ty(ut);
|
||||
}
|
||||
for lt in u.lbs.iter() {
|
||||
self.on_lit_ty(lt);
|
||||
}
|
||||
}
|
||||
// todo: var, let, etc.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn work(&mut self, named_ty: Option<FlowType>, lit_ty: Option<FlowType>) {
|
||||
if let Some(named_ty) = &named_ty {
|
||||
type_completion(self.ctx, Some(named_ty), None);
|
||||
} else if let Some(lit_ty) = &lit_ty {
|
||||
self.on_lit_ty(lit_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.work(named_ty, lit_ty);
|
||||
|
||||
let ctx = ctx.ctx;
|
||||
|
||||
if ctx.before.ends_with(',') {
|
||||
type_completion(ctx, Some(&ty), None);
|
||||
if ctx.before.ends_with(',') || ctx.before.ends_with(':') {
|
||||
ctx.enrich(" ", "");
|
||||
}
|
||||
ctx.incomplete = false;
|
||||
|
||||
sort_and_explicit_code_completion(ctx);
|
||||
Some(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue