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:
Myriad-Dreamin 2024-05-11 21:12:49 +08:00 committed by GitHub
parent d9df64bca7
commit fff227f3ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 4011 additions and 3042 deletions

View 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,);

View file

@ -1 +1,2 @@
pub mod interner;
pub mod snapshot_map;

View file

@ -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(())

View file

@ -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 {

View file

@ -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()))
}
}

View file

@ -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

View 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()))),
// );
}
}

View file

@ -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("}")
}
}

View file

@ -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,
}
}

View 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,
})
}
}

View file

@ -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| {

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 } }

View file

@ -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 */)

View file

@ -0,0 +1,3 @@
// contains: paint,cap
#text(stroke: (/* range after 1..2 */ ))[]

View file

@ -0,0 +1,3 @@
// contains: paint,cap
#text(stroke: (paint: red, /* range after 1..2 */ ))[]

View file

@ -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": []
}
]

View file

@ -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": {

View file

@ -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
}
}
}
}
]
}
]

View file

@ -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
}
}
}
}
]
}
]

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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))

View file

@ -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)

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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"))

View file

@ -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)

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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)))

View file

@ -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)

View file

@ -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>)))

View file

@ -0,0 +1,4 @@
#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[]
#tmpl3(("Agency FB"),/* position */)

View file

@ -0,0 +1,4 @@
#let tmpl3(font, stroke) = text(font: font, stroke: stroke)[]
#tmpl3(("Agency FB"), (/* position */))

View file

@ -0,0 +1 @@
#let g = rgb.with(/* position */);

View file

@ -0,0 +1 @@
#let g = text.with(/* position */);

View file

@ -0,0 +1,2 @@
#let f(font, stroke) = text(font: font, stroke: stroke);
#let g = f.with(/* position */);

View file

@ -0,0 +1,3 @@
#let f(x) = {
assert(x in ("line", "number"))
};

View file

@ -0,0 +1,3 @@
#let f(x) = {
assert(x in "abc")
};

View file

@ -0,0 +1,3 @@
#let f(x) = {
assert(type(x) == int)
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -10,6 +10,7 @@
mod adt;
pub mod analysis;
pub mod syntax;
mod ty;
mod upstream;
pub(crate) mod diagnostics;

View file

@ -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"));
}

View file

@ -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,
});

View 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)
}
}

View 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),
}
}
}

View file

@ -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

View 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")]"###);
}
}

View 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()
}
}

View 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::*;

View 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)
}
}

View 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)
}
}

View 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())
}
}

View file

@ -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, &param, 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(),
})
}

View file

@ -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(&param.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(&param.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(&param.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(())