mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-25 05:22:52 +00:00
refactor: move expr and ty defs to analysis crate (#1633)
This commit is contained in:
parent
72e33e461d
commit
ac506dcc31
58 changed files with 968 additions and 890 deletions
|
|
@ -14,43 +14,33 @@ rust-version.workspace = true
|
|||
[dependencies]
|
||||
|
||||
anyhow.workspace = true
|
||||
comemo.workspace = true
|
||||
dirs.workspace = true
|
||||
regex.workspace = true
|
||||
yaml-rust2.workspace = true
|
||||
base64.workspace = true
|
||||
biblatex.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
itertools.workspace = true
|
||||
strum.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
parking_lot.workspace = true
|
||||
comemo.workspace = true
|
||||
dashmap.workspace = true
|
||||
dirs.workspace = true
|
||||
ena.workspace = true
|
||||
toml.workspace = true
|
||||
walkdir.workspace = true
|
||||
indexmap.workspace = true
|
||||
ecow.workspace = true
|
||||
siphasher.workspace = true
|
||||
rpds.workspace = true
|
||||
rayon.workspace = true
|
||||
|
||||
typst.workspace = true
|
||||
|
||||
typst-shim.workspace = true
|
||||
|
||||
lsp-types.workspace = true
|
||||
if_chain.workspace = true
|
||||
itertools.workspace = true
|
||||
indexmap.workspace = true
|
||||
log.workspace = true
|
||||
lsp-types.workspace = true
|
||||
parking_lot.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
unscanny.workspace = true
|
||||
ttf-parser.workspace = true
|
||||
rayon.workspace = true
|
||||
regex.workspace = true
|
||||
rpds.workspace = true
|
||||
rust_iso639.workspace = true
|
||||
rust_iso3166.workspace = true
|
||||
dashmap.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
hashbrown.workspace = true
|
||||
triomphe.workspace = true
|
||||
base64.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
siphasher.workspace = true
|
||||
strum.workspace = true
|
||||
toml.workspace = true
|
||||
ttf-parser.workspace = true
|
||||
typlite.workspace = true
|
||||
tinymist-world = { workspace = true }
|
||||
tinymist-project = { workspace = true, features = ["lsp"] }
|
||||
|
|
@ -58,6 +48,12 @@ tinymist-analysis.workspace = true
|
|||
tinymist-derive.workspace = true
|
||||
tinymist-std.workspace = true
|
||||
tinymist-l10n.workspace = true
|
||||
typst.workspace = true
|
||||
typst-shim.workspace = true
|
||||
unscanny.workspace = true
|
||||
walkdir.workspace = true
|
||||
yaml-rust2.workspace = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
insta.workspace = true
|
||||
|
|
|
|||
|
|
@ -1,412 +0,0 @@
|
|||
//! 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::{LazyLock, OnceLock},
|
||||
};
|
||||
|
||||
use dashmap::{DashMap, SharedValue};
|
||||
use ecow::{EcoString, EcoVec};
|
||||
use hashbrown::{hash_map::RawEntryMut, HashMap};
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHasher;
|
||||
use triomphe::Arc;
|
||||
use typst::{foundations::Str, syntax::ast::Ident};
|
||||
|
||||
type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>;
|
||||
type Guard<T> = dashmap::RwLockWriteGuard<
|
||||
'static,
|
||||
HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>,
|
||||
>;
|
||||
|
||||
// https://news.ycombinator.com/item?id=22220342
|
||||
|
||||
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) => {
|
||||
T::storage().alloc().increment();
|
||||
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) => {
|
||||
str::storage().alloc().increment();
|
||||
|
||||
Self {
|
||||
arc: vac
|
||||
.insert_hashed_nocheck(hash, Arc::from(s), SharedValue::new(()))
|
||||
.0
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static EMPTY: LazyLock<Interned<str>> = LazyLock::new(|| Interned::new_str(""));
|
||||
impl Default for Interned<str> {
|
||||
fn default() -> Self {
|
||||
EMPTY.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Interned<str> {
|
||||
pub fn empty() -> &'static Self {
|
||||
&EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Interned<str> {
|
||||
fn from(s: &str) -> Self {
|
||||
Interned::new_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
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<&EcoString> for Interned<str> {
|
||||
fn from(s: &EcoString) -> Self {
|
||||
Interned::new_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ident<'_>> for Interned<str> {
|
||||
fn from(s: Ident<'_>) -> Self {
|
||||
Interned::new_str(s.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Interned<str>> for EcoString {
|
||||
fn from(s: &Interned<str>) -> Self {
|
||||
s.as_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable> From<T> for Interned<T> {
|
||||
fn from(s: T) -> Self {
|
||||
Interned::new(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + Clone> From<&T> for Interned<T> {
|
||||
fn from(s: &T) -> Self {
|
||||
Interned::new(s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
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!(),
|
||||
};
|
||||
|
||||
T::storage().alloc().decrement();
|
||||
|
||||
// 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<T: Internable + PartialOrd> PartialOrd for Interned<T> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.as_ref().partial_cmp(other.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + Ord> Ord for Interned<T> {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
if self == other {
|
||||
std::cmp::Ordering::Equal
|
||||
} else {
|
||||
self.as_ref().cmp(other.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 serde::Serialize for Interned<str> {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.arc.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Interned<str> {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct StrVisitor;
|
||||
|
||||
impl serde::de::Visitor<'_> for StrVisitor {
|
||||
type Value = Interned<str>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a string")
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
Ok(Interned::new_str(v))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(StrVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
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(crate) static MAPS: Mutex<EcoVec<(&'static str, usize, Arc<AllocStats>)>> =
|
||||
Mutex::new(EcoVec::new());
|
||||
|
||||
pub struct InternStorage<T: ?Sized> {
|
||||
alloc: OnceLock<Arc<AllocStats>>,
|
||||
map: OnceLock<InternMap<T>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)] // this a const fn, so it can't be default
|
||||
impl<T: InternSize + ?Sized> InternStorage<T> {
|
||||
const SIZE: usize = T::INTERN_SIZE;
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
alloc: OnceLock::new(),
|
||||
map: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Internable + ?Sized> InternStorage<T> {
|
||||
fn alloc(&self) -> &Arc<AllocStats> {
|
||||
self.alloc.get_or_init(Arc::default)
|
||||
}
|
||||
|
||||
fn get(&self) -> &InternMap<T> {
|
||||
self.map.get_or_init(|| {
|
||||
MAPS.lock()
|
||||
.push((std::any::type_name::<T>(), Self::SIZE, self.alloc().clone()));
|
||||
DashMap::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InternSize {
|
||||
const INTERN_SIZE: usize;
|
||||
}
|
||||
|
||||
impl<T: Sized> InternSize for T {
|
||||
const INTERN_SIZE: usize = std::mem::size_of::<T>();
|
||||
}
|
||||
|
||||
impl InternSize for str {
|
||||
const INTERN_SIZE: usize = std::mem::size_of::<usize>() * 2;
|
||||
}
|
||||
|
||||
pub trait Internable: InternSize + 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;
|
||||
use crate::analysis::AllocStats;
|
||||
|
||||
impl_internable!(str,);
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
pub mod interner;
|
||||
pub mod revision;
|
||||
pub mod snapshot_map;
|
||||
pub use tinymist_analysis::adt::*;
|
||||
|
|
|
|||
|
|
@ -1,170 +0,0 @@
|
|||
//! Upstream [rustc_data_structures::snapshot_map].
|
||||
//! Last checked commit: f4bb4500ddb4
|
||||
//! Last checked time: 2023-12-28
|
||||
//!
|
||||
//! [rustc_data_structures::snapshot_map]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_data_structures/src/snapshot_map/mod.rs
|
||||
|
||||
#![allow(missing_docs)]
|
||||
#![allow(unused)]
|
||||
|
||||
use ena::undo_log::{Rollback, Snapshots, UndoLogs, VecLog};
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops;
|
||||
|
||||
pub use ena::undo_log::Snapshot;
|
||||
|
||||
type FxHashMap<K, V> = rustc_hash::FxHashMap<K, V>;
|
||||
|
||||
pub type SnapshotMapStorage<K, V> = SnapshotMap<K, V, FxHashMap<K, V>, ()>;
|
||||
pub type SnapshotMapRef<'a, K, V, L> = SnapshotMap<K, V, &'a mut FxHashMap<K, V>, &'a mut L>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SnapshotMap<K, V, M = FxHashMap<K, V>, L = VecLog<UndoLog<K, V>>> {
|
||||
map: M,
|
||||
undo_log: L,
|
||||
_marker: PhantomData<(K, V)>,
|
||||
}
|
||||
|
||||
// HACK(eddyb) manual impl avoids `Default` bounds on `K` and `V`.
|
||||
impl<K, V, M, L> Default for SnapshotMap<K, V, M, L>
|
||||
where
|
||||
M: Default,
|
||||
L: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
SnapshotMap {
|
||||
map: Default::default(),
|
||||
undo_log: Default::default(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum UndoLog<K, V> {
|
||||
Inserted(K),
|
||||
Overwrite(K, V),
|
||||
Purged,
|
||||
}
|
||||
|
||||
impl<K, V, M, L> SnapshotMap<K, V, M, L> {
|
||||
#[inline]
|
||||
pub fn with_log<L2>(&mut self, undo_log: L2) -> SnapshotMap<K, V, &mut M, L2> {
|
||||
SnapshotMap {
|
||||
map: &mut self.map,
|
||||
undo_log,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, M, L> SnapshotMap<K, V, M, L>
|
||||
where
|
||||
K: Hash + Clone + Eq,
|
||||
M: BorrowMut<FxHashMap<K, V>> + Borrow<FxHashMap<K, V>>,
|
||||
L: UndoLogs<UndoLog<K, V>>,
|
||||
{
|
||||
pub fn clear(&mut self) {
|
||||
self.map.borrow_mut().clear();
|
||||
self.undo_log.clear();
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
self.map.borrow().iter()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.map.borrow().values()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: K, value: V) -> bool {
|
||||
match self.map.borrow_mut().insert(key.clone(), value) {
|
||||
None => {
|
||||
self.undo_log.push(UndoLog::Inserted(key));
|
||||
true
|
||||
}
|
||||
Some(old_value) => {
|
||||
self.undo_log.push(UndoLog::Overwrite(key, old_value));
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: K) -> bool {
|
||||
match self.map.borrow_mut().remove(&key) {
|
||||
Some(old_value) => {
|
||||
self.undo_log.push(UndoLog::Overwrite(key, old_value));
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<Q>(&self, k: &Q) -> Option<&V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
self.map.borrow().get(k)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> SnapshotMap<K, V>
|
||||
where
|
||||
K: Hash + Clone + Eq,
|
||||
{
|
||||
pub fn snapshot(&mut self) -> Snapshot {
|
||||
self.undo_log.start_snapshot()
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, snapshot: Snapshot) {
|
||||
self.undo_log.commit(snapshot)
|
||||
}
|
||||
|
||||
pub fn rollback_to(&mut self, snapshot: Snapshot) {
|
||||
let map = &mut self.map;
|
||||
self.undo_log.rollback_to(|| map, snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'k, K, V, M, L> ops::Index<&'k K> for SnapshotMap<K, V, M, L>
|
||||
where
|
||||
K: Hash + Clone + Eq,
|
||||
M: Borrow<FxHashMap<K, V>>,
|
||||
{
|
||||
type Output = V;
|
||||
fn index(&self, key: &'k K) -> &V {
|
||||
&self.map.borrow()[key]
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, M, L> Rollback<UndoLog<K, V>> for SnapshotMap<K, V, M, L>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
M: Rollback<UndoLog<K, V>>,
|
||||
{
|
||||
fn reverse(&mut self, undo: UndoLog<K, V>) {
|
||||
self.map.reverse(undo)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Rollback<UndoLog<K, V>> for FxHashMap<K, V>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
fn reverse(&mut self, undo: UndoLog<K, V>) {
|
||||
match undo {
|
||||
UndoLog::Inserted(key) => {
|
||||
self.remove(&key);
|
||||
}
|
||||
|
||||
UndoLog::Overwrite(key, old_value) => {
|
||||
self.insert(key, old_value);
|
||||
}
|
||||
|
||||
UndoLog::Purged => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,8 +34,6 @@ mod tyck;
|
|||
pub(crate) use crate::ty::*;
|
||||
pub(crate) use post_tyck::*;
|
||||
pub(crate) use tyck::*;
|
||||
pub mod track_values;
|
||||
pub use track_values::*;
|
||||
mod prelude;
|
||||
|
||||
mod global;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Hybrid analysis for function calls.
|
||||
|
||||
use super::prelude::*;
|
||||
use super::{Signature, StrRef};
|
||||
use super::Signature;
|
||||
use crate::analysis::{analyze_signature, PrimarySignature, SignatureTarget};
|
||||
|
||||
/// Describes kind of a parameter.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use lsp_types::InsertTextFormat;
|
|||
use regex::{Captures, Regex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_analysis::syntax::{bad_completion_cursor, BadCompletionCursor};
|
||||
use tinymist_analysis::{analyze_labels, func_signature, DynLabel};
|
||||
use tinymist_derive::BindTyCtx;
|
||||
use tinymist_project::LspWorld;
|
||||
use tinymist_std::path::unix_slash;
|
||||
|
|
@ -27,9 +28,7 @@ use typst_shim::{syntax::LinkedNodeExt, utils::hash128};
|
|||
use unscanny::Scanner;
|
||||
|
||||
use crate::adt::interner::Interned;
|
||||
use crate::analysis::{
|
||||
analyze_labels, func_signature, BuiltinTy, DynLabel, LocalContext, PathPreference, Ty,
|
||||
};
|
||||
use crate::analysis::{BuiltinTy, LocalContext, PathPreference, Ty};
|
||||
use crate::completion::{
|
||||
Completion, CompletionCommand, CompletionContextKey, CompletionItem, CompletionKind,
|
||||
EcoTextEdit, ParsedSnippet, PostfixSnippet, PostfixSnippetScope, PrefixSnippet,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,31 @@ impl Definition {
|
|||
}
|
||||
}
|
||||
|
||||
trait HasNameRange {
|
||||
/// Gets name range of the item.
|
||||
fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>>;
|
||||
}
|
||||
|
||||
impl HasNameRange for Decl {
|
||||
fn name_range(&self, ctx: &SharedContext) -> Option<Range<usize>> {
|
||||
if let Decl::BibEntry(decl) = self {
|
||||
return Some(decl.at.1.clone());
|
||||
}
|
||||
|
||||
if !self.is_def() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let span = self.span();
|
||||
if let Some(range) = span.range() {
|
||||
return Some(range.clone());
|
||||
}
|
||||
|
||||
let src = ctx.source_by_id(self.file_id()?).ok()?;
|
||||
src.range(span)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: field definition
|
||||
/// Finds the definition of a symbol.
|
||||
pub fn definition(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ use comemo::{Track, Tracked};
|
|||
use lsp_types::Url;
|
||||
use parking_lot::Mutex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tinymist_analysis::stats::AllocStats;
|
||||
use tinymist_analysis::ty::term_value;
|
||||
use tinymist_analysis::{analyze_expr_, analyze_import_};
|
||||
use tinymist_project::LspWorld;
|
||||
use tinymist_std::hash::{hash128, FxDashMap};
|
||||
use tinymist_std::typst::TypstDocument;
|
||||
|
|
@ -24,10 +27,9 @@ use typst_shim::eval::{eval_compat, Eval};
|
|||
use crate::adt::revision::{RevisionLock, RevisionManager, RevisionManagerLike, RevisionSlot};
|
||||
use crate::analysis::prelude::*;
|
||||
use crate::analysis::{
|
||||
analyze_expr_, analyze_import_, analyze_signature, bib_info, definition, post_type_check,
|
||||
AllocStats, AnalysisStats, BibInfo, CompletionFeat, Definition, PathPreference, QueryStatGuard,
|
||||
SemanticTokenCache, SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty,
|
||||
TypeInfo,
|
||||
analyze_signature, bib_info, definition, post_type_check, AnalysisStats, BibInfo,
|
||||
CompletionFeat, Definition, PathPreference, QueryStatGuard, SemanticTokenCache,
|
||||
SemanticTokenContext, SemanticTokens, Signature, SignatureTarget, Ty, TypeInfo,
|
||||
};
|
||||
use crate::docs::{DefDocs, TidyModuleDocs};
|
||||
use crate::syntax::{
|
||||
|
|
@ -147,7 +149,7 @@ impl Analysis {
|
|||
|
||||
/// Report the statistics of the allocation.
|
||||
pub fn report_alloc_stats(&self) -> String {
|
||||
AllocStats::report(self)
|
||||
AllocStats::report()
|
||||
}
|
||||
|
||||
/// Get configured trigger suggest command.
|
||||
|
|
@ -781,7 +783,7 @@ impl SharedContext {
|
|||
return cached;
|
||||
}
|
||||
|
||||
let res = crate::analysis::term_value(val);
|
||||
let res = term_value(val);
|
||||
|
||||
self.analysis
|
||||
.caches
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Infer more than the principal type of some expression.
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use std::collections::HashSet;
|
||||
use tinymist_derive::BindTyCtx;
|
||||
|
||||
use super::{prelude::*, DynTypeBounds, ParamAttrs, ParamTy, SharedContext};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
pub use core::fmt;
|
||||
pub use std::collections::{BTreeMap, HashMap};
|
||||
pub use std::hash::{Hash, Hasher};
|
||||
pub use std::ops::Range;
|
||||
|
|
@ -14,7 +13,7 @@ pub use typst::World;
|
|||
pub use typst_shim::syntax::LinkedNodeExt;
|
||||
pub use typst_shim::utils::LazyHash;
|
||||
|
||||
pub(crate) use super::StrRef;
|
||||
pub(crate) use super::{LocalContext, ToFunc};
|
||||
pub(crate) use crate::adt::interner::Interned;
|
||||
pub use crate::ty::Ty;
|
||||
pub(crate) use crate::StrRef;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Semantic tokens (highlighting) support for LSP.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
num::NonZeroUsize,
|
||||
ops::Range,
|
||||
|
|
@ -7,7 +8,6 @@ use std::{
|
|||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use lsp_types::SemanticToken;
|
||||
use lsp_types::{SemanticTokenModifier, SemanticTokenType};
|
||||
use parking_lot::Mutex;
|
||||
|
|
|
|||
|
|
@ -1,163 +1,19 @@
|
|||
//! Analysis of function signatures.
|
||||
|
||||
use itertools::Either;
|
||||
use tinymist_analysis::{func_signature, ArgInfo, ArgsInfo, PartialSignature};
|
||||
use tinymist_derive::BindTyCtx;
|
||||
use typst::foundations::Closure;
|
||||
|
||||
use super::{
|
||||
prelude::*, BoundChecker, Definition, DocSource, ParamTy, SharedContext, SigTy, SigWithTy,
|
||||
TypeInfo, TypeVar,
|
||||
};
|
||||
use super::{prelude::*, Definition, SharedContext};
|
||||
use crate::analysis::PostTypeChecker;
|
||||
use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs};
|
||||
use crate::syntax::classify_def_loosely;
|
||||
use crate::ty::{DynTypeBounds, ParamAttrs};
|
||||
use crate::ty::{InsTy, TyCtx};
|
||||
use crate::upstream::truncated_repr;
|
||||
use crate::ty::{
|
||||
BoundChecker, DocSource, DynTypeBounds, ParamAttrs, ParamTy, SigWithTy, TyCtx, TypeInfo,
|
||||
TypeVar,
|
||||
};
|
||||
|
||||
/// Describes a function signature.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Signature {
|
||||
/// A primary function signature.
|
||||
Primary(Arc<PrimarySignature>),
|
||||
/// A partially applied function signature.
|
||||
Partial(Arc<PartialSignature>),
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Returns the primary signature if it is one.
|
||||
pub fn primary(&self) -> &Arc<PrimarySignature> {
|
||||
match self {
|
||||
Signature::Primary(sig) => sig,
|
||||
Signature::Partial(sig) => &sig.signature,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the with bindings of the signature.
|
||||
pub fn bindings(&self) -> &[ArgsInfo] {
|
||||
match self {
|
||||
Signature::Primary(_) => &[],
|
||||
Signature::Partial(sig) => &sig.with_stack,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the all parameters of the function.
|
||||
pub(crate) fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
|
||||
let primary = self.primary().params();
|
||||
// todo: with stack
|
||||
primary
|
||||
}
|
||||
|
||||
pub(crate) fn type_sig(&self) -> Interned<SigTy> {
|
||||
let primary = self.primary().sig_ty.clone();
|
||||
// todo: with stack
|
||||
primary
|
||||
}
|
||||
|
||||
pub(crate) fn param_shift(&self) -> usize {
|
||||
match self {
|
||||
Signature::Primary(_) => 0,
|
||||
Signature::Partial(sig) => sig
|
||||
.with_stack
|
||||
.iter()
|
||||
.map(|ws| ws.items.len())
|
||||
.sum::<usize>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a primary function signature.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PrimarySignature {
|
||||
/// The documentation of the function
|
||||
pub docs: Option<EcoString>,
|
||||
/// The documentation of the parameter.
|
||||
pub param_specs: Vec<Interned<ParamTy>>,
|
||||
/// Whether the function has fill, stroke, or size parameters.
|
||||
pub has_fill_or_size_or_stroke: bool,
|
||||
/// The associated signature type.
|
||||
pub(crate) sig_ty: Interned<SigTy>,
|
||||
_broken: bool,
|
||||
}
|
||||
|
||||
impl PrimarySignature {
|
||||
/// Returns the number of positional parameters of the function.
|
||||
pub fn pos_size(&self) -> usize {
|
||||
self.sig_ty.name_started as usize
|
||||
}
|
||||
|
||||
/// Returns the positional parameters of the function.
|
||||
pub fn pos(&self) -> &[Interned<ParamTy>] {
|
||||
&self.param_specs[..self.pos_size()]
|
||||
}
|
||||
|
||||
/// Returns the positional parameters of the function.
|
||||
pub fn get_pos(&self, offset: usize) -> Option<&Interned<ParamTy>> {
|
||||
self.pos().get(offset)
|
||||
}
|
||||
|
||||
/// Returns the named parameters of the function.
|
||||
pub fn named(&self) -> &[Interned<ParamTy>] {
|
||||
&self.param_specs[self.pos_size()..self.pos_size() + self.sig_ty.names.names.len()]
|
||||
}
|
||||
|
||||
/// Returns the named parameters of the function.
|
||||
pub fn get_named(&self, name: &StrRef) -> Option<&Interned<ParamTy>> {
|
||||
self.named().get(self.sig_ty.names.find(name)?)
|
||||
}
|
||||
|
||||
/// Returns the name of the rest parameter of the function.
|
||||
pub fn has_spread_right(&self) -> bool {
|
||||
self.sig_ty.spread_right
|
||||
}
|
||||
|
||||
/// Returns the rest parameter of the function.
|
||||
pub fn rest(&self) -> Option<&Interned<ParamTy>> {
|
||||
self.has_spread_right()
|
||||
.then(|| &self.param_specs[self.pos_size() + self.sig_ty.names.names.len()])
|
||||
}
|
||||
|
||||
/// Returns the all parameters of the function.
|
||||
pub fn params(&self) -> impl Iterator<Item = (&Interned<ParamTy>, Option<&Ty>)> {
|
||||
let pos = self.pos();
|
||||
let named = self.named();
|
||||
let rest = self.rest();
|
||||
let type_sig = &self.sig_ty;
|
||||
let pos = pos
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, pos)| (pos, type_sig.pos(idx)));
|
||||
let named = named.iter().map(|x| (x, type_sig.named(&x.name)));
|
||||
let rest = rest.into_iter().map(|x| (x, type_sig.rest_param()));
|
||||
|
||||
pos.chain(named).chain(rest)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a function argument instance
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArgInfo {
|
||||
/// The argument's name.
|
||||
pub name: Option<StrRef>,
|
||||
/// The argument's term.
|
||||
pub term: Option<Ty>,
|
||||
}
|
||||
|
||||
/// Describes a function argument list.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArgsInfo {
|
||||
/// The arguments.
|
||||
pub items: EcoVec<ArgInfo>,
|
||||
}
|
||||
|
||||
/// Describes a function signature that is already partially applied.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PartialSignature {
|
||||
/// The positional parameters.
|
||||
pub signature: Arc<PrimarySignature>,
|
||||
/// The stack of `fn.with(..)` calls.
|
||||
pub with_stack: EcoVec<ArgsInfo>,
|
||||
}
|
||||
pub use tinymist_analysis::{PrimarySignature, Signature};
|
||||
|
||||
/// The language object that the signature is being analyzed for.
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -475,216 +331,3 @@ fn analyze_dyn_signature(
|
|||
|
||||
Some(func_signature(func))
|
||||
}
|
||||
|
||||
/// Gets the signature of a function.
|
||||
#[comemo::memoize]
|
||||
pub fn func_signature(func: Func) -> Signature {
|
||||
use typst::foundations::func::Repr;
|
||||
let mut with_stack = eco_vec![];
|
||||
let mut func = func;
|
||||
while let Repr::With(with) = func.inner() {
|
||||
let (inner, args) = with.as_ref();
|
||||
with_stack.push(ArgsInfo {
|
||||
items: args
|
||||
.items
|
||||
.iter()
|
||||
.map(|arg| ArgInfo {
|
||||
name: arg.name.clone().map(From::from),
|
||||
term: Some(Ty::Value(InsTy::new(arg.value.v.clone()))),
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
func = inner.clone();
|
||||
}
|
||||
|
||||
let mut pos_tys = vec![];
|
||||
let mut named_tys = Vec::new();
|
||||
let mut rest_ty = None;
|
||||
|
||||
let mut named_specs = BTreeMap::new();
|
||||
let mut param_specs = Vec::new();
|
||||
let mut rest_spec = None;
|
||||
|
||||
let mut broken = false;
|
||||
let mut has_fill_or_size_or_stroke = false;
|
||||
|
||||
let mut add_param = |param: Interned<ParamTy>| {
|
||||
let name = param.name.clone();
|
||||
if param.attrs.named {
|
||||
if matches!(name.as_ref(), "fill" | "stroke" | "size") {
|
||||
has_fill_or_size_or_stroke = true;
|
||||
}
|
||||
named_tys.push((name.clone(), param.ty.clone()));
|
||||
named_specs.insert(name.clone(), param.clone());
|
||||
}
|
||||
|
||||
if param.attrs.variadic {
|
||||
if rest_ty.is_some() {
|
||||
broken = true;
|
||||
} else {
|
||||
rest_ty = Some(param.ty.clone());
|
||||
rest_spec = Some(param);
|
||||
}
|
||||
} else if param.attrs.positional {
|
||||
// todo: we have some params that are both positional and named
|
||||
pos_tys.push(param.ty.clone());
|
||||
param_specs.push(param);
|
||||
}
|
||||
};
|
||||
|
||||
let ret_ty = match func.inner() {
|
||||
Repr::With(..) => unreachable!(),
|
||||
Repr::Closure(closure) => {
|
||||
analyze_closure_signature(closure.clone(), &mut add_param);
|
||||
None
|
||||
}
|
||||
Repr::Element(..) | Repr::Native(..) | Repr::Plugin(..) => {
|
||||
for param in func.params().unwrap_or_default() {
|
||||
add_param(Interned::new(ParamTy {
|
||||
name: param.name.into(),
|
||||
docs: Some(param.docs.into()),
|
||||
default: param.default.map(|default| truncated_repr(&default())),
|
||||
ty: Ty::from_param_site(&func, param),
|
||||
attrs: param.into(),
|
||||
}));
|
||||
}
|
||||
|
||||
func.returns().map(|r| Ty::from_return_site(&func, r))
|
||||
}
|
||||
};
|
||||
|
||||
let sig_ty = SigTy::new(pos_tys.into_iter(), named_tys, None, rest_ty, ret_ty);
|
||||
|
||||
for name in &sig_ty.names.names {
|
||||
let Some(param) = named_specs.get(name) else {
|
||||
continue;
|
||||
};
|
||||
param_specs.push(param.clone());
|
||||
}
|
||||
if let Some(doc) = rest_spec {
|
||||
param_specs.push(doc);
|
||||
}
|
||||
|
||||
let signature = Arc::new(PrimarySignature {
|
||||
docs: func.docs().map(From::from),
|
||||
param_specs,
|
||||
has_fill_or_size_or_stroke,
|
||||
sig_ty: sig_ty.into(),
|
||||
_broken: broken,
|
||||
});
|
||||
|
||||
log::trace!("got signature {signature:?}");
|
||||
|
||||
if with_stack.is_empty() {
|
||||
return Signature::Primary(signature);
|
||||
}
|
||||
|
||||
Signature::Partial(Arc::new(PartialSignature {
|
||||
signature,
|
||||
with_stack,
|
||||
}))
|
||||
}
|
||||
|
||||
fn analyze_closure_signature(
|
||||
closure: Arc<LazyHash<Closure>>,
|
||||
add_param: &mut impl FnMut(Interned<ParamTy>),
|
||||
) {
|
||||
log::trace!("closure signature for: {:?}", closure.node.kind());
|
||||
|
||||
let closure = &closure.node;
|
||||
let closure_ast = match closure.kind() {
|
||||
SyntaxKind::Closure => closure.cast::<ast::Closure>().unwrap(),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
for param in closure_ast.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(pos) => {
|
||||
let name = format!("{}", PatternDisplay(&pos));
|
||||
add_param(Interned::new(ParamTy {
|
||||
name: name.as_str().into(),
|
||||
docs: None,
|
||||
default: None,
|
||||
ty: Ty::Any,
|
||||
attrs: ParamAttrs::positional(),
|
||||
}));
|
||||
}
|
||||
// todo: pattern
|
||||
ast::Param::Named(named) => {
|
||||
let default = unwrap_parens(named.expr()).to_untyped().clone().into_text();
|
||||
add_param(Interned::new(ParamTy {
|
||||
name: named.name().get().into(),
|
||||
docs: Some(eco_format!("Default value: {default}")),
|
||||
default: Some(default),
|
||||
ty: Ty::Any,
|
||||
attrs: ParamAttrs::named(),
|
||||
}));
|
||||
}
|
||||
ast::Param::Spread(spread) => {
|
||||
let sink = spread.sink_ident().map(|sink| sink.as_str());
|
||||
add_param(Interned::new(ParamTy {
|
||||
name: sink.unwrap_or_default().into(),
|
||||
docs: None,
|
||||
default: None,
|
||||
ty: Ty::Any,
|
||||
attrs: ParamAttrs::variadic(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PatternDisplay<'a>(&'a ast::Pattern<'a>);
|
||||
|
||||
impl fmt::Display for PatternDisplay<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => f.write_str(ident.as_str()),
|
||||
ast::Pattern::Normal(_) => f.write_str("?"), // unreachable?
|
||||
ast::Pattern::Placeholder(_) => f.write_str("_"),
|
||||
ast::Pattern::Parenthesized(paren_expr) => {
|
||||
write!(f, "{}", PatternDisplay(&paren_expr.pattern()))
|
||||
}
|
||||
ast::Pattern::Destructuring(destructing) => {
|
||||
write!(f, "(")?;
|
||||
let mut first = true;
|
||||
for item in destructing.items() {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
match item {
|
||||
ast::DestructuringItem::Pattern(pos) => {
|
||||
write!(f, "{}", PatternDisplay(&pos))?
|
||||
}
|
||||
ast::DestructuringItem::Named(named) => write!(
|
||||
f,
|
||||
"{}: {}",
|
||||
named.name().as_str(),
|
||||
unwrap_parens(named.expr()).to_untyped().text()
|
||||
)?,
|
||||
ast::DestructuringItem::Spread(spread) => write!(
|
||||
f,
|
||||
"..{}",
|
||||
spread
|
||||
.sink_ident()
|
||||
.map(|sink| sink.as_str())
|
||||
.unwrap_or_default()
|
||||
)?,
|
||||
}
|
||||
}
|
||||
write!(f, ")")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_parens(mut expr: ast::Expr) -> ast::Expr {
|
||||
while let ast::Expr::Parenthesized(paren_expr) = expr {
|
||||
expr = paren_expr.expr();
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
//! Statistics about the analyzers
|
||||
|
||||
use std::{
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use tinymist_std::hash::FxDashMap;
|
||||
use typst::syntax::FileId;
|
||||
|
||||
use super::Analysis;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct QueryStatBucketData {
|
||||
pub query: u64,
|
||||
|
|
@ -117,84 +112,3 @@ table.analysis-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0
|
|||
html
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics about the allocation
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AllocStats {
|
||||
/// The number of allocated objects.
|
||||
pub allocated: AtomicUsize,
|
||||
/// The number of dropped objects.
|
||||
pub dropped: AtomicUsize,
|
||||
}
|
||||
|
||||
impl AllocStats {
|
||||
/// increment the statistics.
|
||||
pub fn increment(&self) {
|
||||
self.allocated
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// decrement the statistics.
|
||||
pub fn decrement(&self) {
|
||||
self.dropped
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl AllocStats {
|
||||
/// Report the statistics of the allocation.
|
||||
pub fn report(_a: &Analysis) -> String {
|
||||
let maps = crate::adt::interner::MAPS.lock().clone();
|
||||
let mut data = Vec::new();
|
||||
for (name, sz, map) in maps {
|
||||
let allocated = map.allocated.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let dropped = map.dropped.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let alive = allocated.saturating_sub(dropped);
|
||||
data.push((name, sz * alive, allocated, dropped, alive));
|
||||
}
|
||||
|
||||
// sort by total
|
||||
data.sort_by(|x, y| y.4.cmp(&x.4));
|
||||
|
||||
// format to html
|
||||
|
||||
let mut html = String::new();
|
||||
html.push_str(r#"<div>
|
||||
<style>
|
||||
table.alloc-stats { width: 100%; border-collapse: collapse; }
|
||||
table.alloc-stats th, table.alloc-stats td { border: 1px solid black; padding: 8px; text-align: center; }
|
||||
table.alloc-stats th.name-column, table.alloc-stats td.name-column { text-align: left; }
|
||||
table.alloc-stats tr:nth-child(odd) { background-color: rgba(242, 242, 242, 0.8); }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
table.alloc-stats tr:nth-child(odd) { background-color: rgba(50, 50, 50, 0.8); }
|
||||
}
|
||||
</style>
|
||||
<table class="alloc-stats"><tr><th class="name-column">Name</th><th>Alive</th><th>Allocated</th><th>Dropped</th><th>Size</th></tr>"#);
|
||||
|
||||
for (name, sz, allocated, dropped, alive) in data {
|
||||
html.push_str("<tr>");
|
||||
html.push_str(&format!(r#"<td class="name-column">{name}</td>"#));
|
||||
html.push_str(&format!("<td>{alive}</td>"));
|
||||
html.push_str(&format!("<td>{allocated}</td>"));
|
||||
html.push_str(&format!("<td>{dropped}</td>"));
|
||||
html.push_str(&format!("<td>{}</td>", human_size(sz)));
|
||||
html.push_str("</tr>");
|
||||
}
|
||||
html.push_str("</table>");
|
||||
html.push_str("</div>");
|
||||
|
||||
html
|
||||
}
|
||||
}
|
||||
|
||||
fn human_size(size: usize) -> String {
|
||||
let units = ["B", "KB", "MB", "GB", "TB"];
|
||||
let mut unit = 0;
|
||||
let mut size = size as f64;
|
||||
while size >= 768.0 && unit < units.len() {
|
||||
size /= 1024.0;
|
||||
unit += 1;
|
||||
}
|
||||
format!("{:.2} {}", size, units[unit])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
//! Dynamic analysis of an expression or import statement.
|
||||
|
||||
use comemo::Track;
|
||||
use ecow::*;
|
||||
use tinymist_std::typst::{TypstDocument, TypstPagedDocument};
|
||||
use typst::engine::{Engine, Route, Sink, Traced};
|
||||
use typst::foundations::{Context, Label, Scopes, Styles, Value};
|
||||
use typst::introspection::Introspector;
|
||||
use typst::model::BibliographyElem;
|
||||
use typst::syntax::{ast, LinkedNode, Span, SyntaxKind, SyntaxNode};
|
||||
use typst::World;
|
||||
use typst_shim::eval::Vm;
|
||||
|
||||
/// Try to determine a set of possible values for an expression.
|
||||
pub fn analyze_expr(world: &dyn World, node: &LinkedNode) -> EcoVec<(Value, Option<Styles>)> {
|
||||
if let Some(parent) = node.parent() {
|
||||
if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 {
|
||||
return analyze_expr(world, parent);
|
||||
}
|
||||
}
|
||||
|
||||
analyze_expr_(world, node.get())
|
||||
}
|
||||
|
||||
/// Try to determine a set of possible values for an expression.
|
||||
pub fn analyze_expr_(world: &dyn World, node: &SyntaxNode) -> EcoVec<(Value, Option<Styles>)> {
|
||||
let Some(expr) = node.cast::<ast::Expr>() else {
|
||||
return eco_vec![];
|
||||
};
|
||||
|
||||
let val = match expr {
|
||||
ast::Expr::None(_) => Value::None,
|
||||
ast::Expr::Auto(_) => Value::Auto,
|
||||
ast::Expr::Bool(v) => Value::Bool(v.get()),
|
||||
ast::Expr::Int(v) => Value::Int(v.get()),
|
||||
ast::Expr::Float(v) => Value::Float(v.get()),
|
||||
ast::Expr::Numeric(v) => Value::numeric(v.get()),
|
||||
ast::Expr::Str(v) => Value::Str(v.get().into()),
|
||||
_ => {
|
||||
if node.kind() == SyntaxKind::Contextual {
|
||||
if let Some(child) = node.children().last() {
|
||||
return analyze_expr_(world, child);
|
||||
}
|
||||
}
|
||||
|
||||
return typst::trace::<TypstPagedDocument>(world, node.span());
|
||||
}
|
||||
};
|
||||
|
||||
eco_vec![(val, None)]
|
||||
}
|
||||
|
||||
/// Try to load a module from the current source file.
|
||||
pub fn analyze_import_(world: &dyn World, source: &SyntaxNode) -> (Option<Value>, Option<Value>) {
|
||||
let source_span = source.span();
|
||||
let Some((source, _)) = analyze_expr_(world, source).into_iter().next() else {
|
||||
return (None, None);
|
||||
};
|
||||
if source.scope().is_some() {
|
||||
return (Some(source.clone()), Some(source));
|
||||
}
|
||||
|
||||
let introspector = Introspector::default();
|
||||
let traced = Traced::default();
|
||||
let mut sink = Sink::new();
|
||||
let engine = Engine {
|
||||
routines: &typst::ROUTINES,
|
||||
world: world.track(),
|
||||
route: Route::default(),
|
||||
introspector: introspector.track(),
|
||||
traced: traced.track(),
|
||||
sink: sink.track_mut(),
|
||||
};
|
||||
|
||||
let context = Context::none();
|
||||
let mut vm = Vm::new(
|
||||
engine,
|
||||
context.track(),
|
||||
Scopes::new(Some(world.library())),
|
||||
Span::detached(),
|
||||
);
|
||||
let module = match source.clone() {
|
||||
Value::Str(path) => typst_shim::eval::import(&mut vm.engine, &path, source_span)
|
||||
.ok()
|
||||
.map(Value::Module),
|
||||
Value::Module(module) => Some(Value::Module(module)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(Some(source), module)
|
||||
}
|
||||
|
||||
/// A label with a description and details.
|
||||
pub struct DynLabel {
|
||||
/// The label itself.
|
||||
pub label: Label,
|
||||
/// A description of the label.
|
||||
pub label_desc: Option<EcoString>,
|
||||
/// Additional details about the label.
|
||||
pub detail: Option<EcoString>,
|
||||
/// The title of the bibliography entry. Not present for non-bibliography
|
||||
/// labels.
|
||||
pub bib_title: Option<EcoString>,
|
||||
}
|
||||
|
||||
/// Find all labels and details for them.
|
||||
///
|
||||
/// Returns:
|
||||
/// - All labels and descriptions for them, if available
|
||||
/// - A split offset: All labels before this offset belong to nodes, all after
|
||||
/// belong to a bibliography.
|
||||
pub fn analyze_labels(document: &TypstDocument) -> (Vec<DynLabel>, usize) {
|
||||
let mut output = vec![];
|
||||
|
||||
// Labels in the document.
|
||||
for elem in document.introspector().all() {
|
||||
let Some(label) = elem.label() else { continue };
|
||||
let (is_derived, details) = {
|
||||
let derived = elem
|
||||
.get_by_name("caption")
|
||||
.or_else(|_| elem.get_by_name("body"));
|
||||
|
||||
match derived {
|
||||
Ok(Value::Content(content)) => (true, content.plain_text()),
|
||||
Ok(Value::Str(s)) => (true, s.into()),
|
||||
Ok(_) => (false, elem.plain_text()),
|
||||
Err(_) => (false, elem.plain_text()),
|
||||
}
|
||||
};
|
||||
output.push(DynLabel {
|
||||
label,
|
||||
label_desc: Some(if is_derived {
|
||||
details.clone()
|
||||
} else {
|
||||
eco_format!("{}(..)", elem.func().name())
|
||||
}),
|
||||
detail: Some(details),
|
||||
bib_title: None,
|
||||
});
|
||||
}
|
||||
|
||||
let split = output.len();
|
||||
|
||||
// Bibliography keys.
|
||||
for (label, detail) in BibliographyElem::keys(document.introspector().track()) {
|
||||
output.push(DynLabel {
|
||||
label,
|
||||
label_desc: detail.clone(),
|
||||
detail: detail.clone(),
|
||||
bib_title: detail,
|
||||
});
|
||||
}
|
||||
|
||||
(output, split)
|
||||
}
|
||||
|
|
@ -15,13 +15,11 @@ use crate::{
|
|||
};
|
||||
|
||||
mod apply;
|
||||
mod convert;
|
||||
mod docs;
|
||||
mod select;
|
||||
mod syntax;
|
||||
|
||||
pub(crate) use apply::*;
|
||||
pub(crate) use convert::*;
|
||||
pub(crate) use select::*;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
use crate::analysis::func_signature;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn is_plain_value(value: &Value) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
Value::Label(..)
|
||||
| Value::None
|
||||
| Value::Auto
|
||||
| Value::Bool(..)
|
||||
| Value::Int(..)
|
||||
| Value::Float(..)
|
||||
| Value::Decimal(..)
|
||||
| Value::Length(..)
|
||||
| Value::Angle(..)
|
||||
| Value::Ratio(..)
|
||||
| Value::Relative(..)
|
||||
| Value::Fraction(..)
|
||||
| Value::Color(..)
|
||||
| Value::Gradient(..)
|
||||
| Value::Tiling(..)
|
||||
| Value::Symbol(..)
|
||||
| Value::Version(..)
|
||||
| Value::Str(..)
|
||||
| Value::Bytes(..)
|
||||
| Value::Datetime(..)
|
||||
| Value::Duration(..)
|
||||
| Value::Content(..)
|
||||
| Value::Styles(..)
|
||||
)
|
||||
}
|
||||
|
||||
/// Gets the type of a value.
|
||||
#[comemo::memoize]
|
||||
pub fn term_value(value: &Value) -> Ty {
|
||||
match value {
|
||||
Value::Array(a) => {
|
||||
let values = a
|
||||
.iter()
|
||||
.map(|v| term_value_rec(v, Span::detached()))
|
||||
.collect::<Vec<_>>();
|
||||
Ty::Tuple(values.into())
|
||||
}
|
||||
// todo: term arguments
|
||||
Value::Args(..) => Ty::Builtin(BuiltinTy::Args),
|
||||
Value::Dict(dict) => {
|
||||
let values = dict
|
||||
.iter()
|
||||
.map(|(k, v)| (k.as_str().into(), term_value_rec(v, Span::detached())))
|
||||
.collect();
|
||||
Ty::Dict(RecordTy::new(values))
|
||||
}
|
||||
Value::Module(module) => {
|
||||
let values = module
|
||||
.scope()
|
||||
.iter()
|
||||
.map(|(k, b)| (k.into(), term_value_rec(b.read(), b.span())))
|
||||
.collect();
|
||||
Ty::Dict(RecordTy::new(values))
|
||||
}
|
||||
Value::Type(ty) => Ty::Builtin(BuiltinTy::TypeType(*ty)),
|
||||
Value::Dyn(dyn_val) => Ty::Builtin(BuiltinTy::Type(dyn_val.ty())),
|
||||
Value::Func(func) => Ty::Func(func_signature(func.clone()).type_sig()),
|
||||
_ if is_plain_value(value) => Ty::Value(InsTy::new(value.clone())),
|
||||
_ => Ty::Any,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn term_value_rec(value: &Value, s: Span) -> Ty {
|
||||
match value {
|
||||
Value::Type(ty) => Ty::Builtin(BuiltinTy::TypeType(*ty)),
|
||||
Value::Dyn(v) => Ty::Builtin(BuiltinTy::Type(v.ty())),
|
||||
Value::None
|
||||
| Value::Auto
|
||||
| Value::Array(..)
|
||||
| Value::Args(..)
|
||||
| Value::Dict(..)
|
||||
| Value::Module(..)
|
||||
| Value::Func(..)
|
||||
| Value::Label(..)
|
||||
| Value::Bool(..)
|
||||
| Value::Int(..)
|
||||
| Value::Float(..)
|
||||
| Value::Decimal(..)
|
||||
| Value::Length(..)
|
||||
| Value::Angle(..)
|
||||
| Value::Ratio(..)
|
||||
| Value::Relative(..)
|
||||
| Value::Fraction(..)
|
||||
| Value::Color(..)
|
||||
| Value::Gradient(..)
|
||||
| Value::Tiling(..)
|
||||
| Value::Symbol(..)
|
||||
| Value::Version(..)
|
||||
| Value::Str(..)
|
||||
| Value::Bytes(..)
|
||||
| Value::Datetime(..)
|
||||
| Value::Duration(..)
|
||||
| Value::Content(..)
|
||||
| Value::Styles(..) => {
|
||||
if !s.is_detached() {
|
||||
Ty::Value(InsTy::new_at(value.clone(), s))
|
||||
} else {
|
||||
Ty::Value(InsTy::new(value.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_analysis::analyze_expr;
|
||||
use tinymist_world::ShadowApi;
|
||||
use typst::foundations::{Bytes, IntoValue, StyleChain};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::{
|
||||
analysis::analyze_expr,
|
||||
prelude::*,
|
||||
syntax::{interpret_mode_at, InterpretMode},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ecow::EcoString;
|
|||
use lsp_types::InsertTextFormat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ty::Interned;
|
||||
use crate::StrRef;
|
||||
|
||||
use super::LspRange;
|
||||
|
||||
|
|
@ -284,11 +284,11 @@ pub struct LspCompletionCommand {
|
|||
/// The title of command.
|
||||
pub title: EcoString,
|
||||
/// The identifier of the actual command handler.
|
||||
pub command: Interned<str>,
|
||||
pub command: StrRef,
|
||||
}
|
||||
|
||||
impl From<Interned<str>> for LspCompletionCommand {
|
||||
fn from(command: Interned<str>) -> Self {
|
||||
impl From<StrRef> for LspCompletionCommand {
|
||||
fn from(command: StrRef) -> Self {
|
||||
Self {
|
||||
title: EcoString::default(),
|
||||
command,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::collections::HashSet;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use hashbrown::HashSet;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::adt::interner::Interned;
|
||||
use crate::prelude::*;
|
||||
use crate::syntax::{InterpretMode, SurroundingSyntax};
|
||||
use crate::ty::Interned;
|
||||
|
||||
/// This is the poorman's type filter, which is less powerful but more steady.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,303 +1,11 @@
|
|||
use core::fmt;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_analysis::docs::{format_ty, ParamDocs, SignatureDocs, VarDocs};
|
||||
use tinymist_analysis::ty::DocSource;
|
||||
use tinymist_analysis::Signature;
|
||||
use typst::syntax::Span;
|
||||
|
||||
use super::tidy::*;
|
||||
use crate::analysis::{ParamAttrs, ParamTy, Signature};
|
||||
use crate::prelude::*;
|
||||
use crate::ty::Ty;
|
||||
use crate::ty::{DocSource, Interned};
|
||||
use crate::upstream::plain_docs_sentence;
|
||||
|
||||
type TypeRepr = Option<(
|
||||
/* short */ EcoString,
|
||||
/* long */ EcoString,
|
||||
/* value */ EcoString,
|
||||
)>;
|
||||
|
||||
/// Documentation about a definition (without type information).
|
||||
pub type UntypedDefDocs = DefDocsT<()>;
|
||||
/// Documentation about a definition.
|
||||
pub type DefDocs = DefDocsT<TypeRepr>;
|
||||
|
||||
/// Documentation about a definition.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum DefDocsT<T> {
|
||||
/// Documentation about a function.
|
||||
#[serde(rename = "func")]
|
||||
Function(Box<SignatureDocsT<T>>),
|
||||
/// Documentation about a variable.
|
||||
#[serde(rename = "var")]
|
||||
Variable(VarDocsT<T>),
|
||||
/// Documentation about a module.
|
||||
#[serde(rename = "module")]
|
||||
Module(TidyModuleDocs),
|
||||
/// Other kinds of documentation.
|
||||
#[serde(rename = "plain")]
|
||||
Plain {
|
||||
/// The content of the documentation.
|
||||
docs: EcoString,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> DefDocsT<T> {
|
||||
/// Get the markdown representation of the documentation.
|
||||
pub fn docs(&self) -> &EcoString {
|
||||
match self {
|
||||
Self::Function(docs) => &docs.docs,
|
||||
Self::Variable(docs) => &docs.docs,
|
||||
Self::Module(docs) => &docs.docs,
|
||||
Self::Plain { docs } => docs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefDocs {
|
||||
/// Get full documentation for the signature.
|
||||
pub fn hover_docs(&self) -> EcoString {
|
||||
match self {
|
||||
DefDocs::Function(docs) => docs.hover_docs().clone(),
|
||||
_ => plain_docs_sentence(self.docs()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a primary function signature.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SignatureDocsT<T> {
|
||||
/// Documentation for the function.
|
||||
pub docs: EcoString,
|
||||
/// The positional parameters.
|
||||
pub pos: Vec<ParamDocsT<T>>,
|
||||
/// The named parameters.
|
||||
pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
|
||||
/// The rest parameter.
|
||||
pub rest: Option<ParamDocsT<T>>,
|
||||
/// The return type.
|
||||
pub ret_ty: T,
|
||||
/// The full documentation for the signature.
|
||||
#[serde(skip)]
|
||||
pub hover_docs: OnceLock<EcoString>,
|
||||
}
|
||||
|
||||
impl SignatureDocsT<TypeRepr> {
|
||||
/// Get full documentation for the signature.
|
||||
pub fn hover_docs(&self) -> &EcoString {
|
||||
self.hover_docs
|
||||
.get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
|
||||
}
|
||||
}
|
||||
|
||||
struct SigHoverDocs<'a>(&'a SignatureDocs);
|
||||
|
||||
impl fmt::Display for SigHoverDocs<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let docs = self.0;
|
||||
let base_docs = docs.docs.trim();
|
||||
|
||||
if !base_docs.is_empty() {
|
||||
f.write_str(base_docs)?;
|
||||
}
|
||||
|
||||
fn write_param_docs(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
docs: &ParamDocsT<TypeRepr>,
|
||||
kind: &str,
|
||||
is_first: &mut bool,
|
||||
) -> fmt::Result {
|
||||
if *is_first {
|
||||
*is_first = false;
|
||||
write!(f, "\n\n## {}\n\n", docs.name)?;
|
||||
} else {
|
||||
write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
|
||||
}
|
||||
|
||||
// p.cano_type.0
|
||||
if let Some(t) = &docs.cano_type {
|
||||
write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
|
||||
}
|
||||
|
||||
f.write_str(docs.docs.trim())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if !docs.pos.is_empty() {
|
||||
f.write_str("\n\n# Positional Parameters")?;
|
||||
|
||||
let mut is_first = true;
|
||||
for pos_docs in &docs.pos {
|
||||
write_param_docs(f, pos_docs, "positional", &mut is_first)?;
|
||||
}
|
||||
}
|
||||
|
||||
if docs.rest.is_some() {
|
||||
f.write_str("\n\n# Rest Parameters")?;
|
||||
|
||||
let mut is_first = true;
|
||||
if let Some(rest) = &docs.rest {
|
||||
write_param_docs(f, rest, "spread right", &mut is_first)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !docs.named.is_empty() {
|
||||
f.write_str("\n\n# Named Parameters")?;
|
||||
|
||||
let mut is_first = true;
|
||||
for named_docs in docs.named.values() {
|
||||
write_param_docs(f, named_docs, "named", &mut is_first)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation about a signature.
|
||||
pub type UntypedSignatureDocs = SignatureDocsT<()>;
|
||||
/// Documentation about a signature.
|
||||
pub type SignatureDocs = SignatureDocsT<TypeRepr>;
|
||||
|
||||
impl SignatureDocs {
|
||||
/// Get the markdown representation of the documentation.
|
||||
pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
|
||||
let mut is_first = true;
|
||||
let mut write_sep = |f: &mut dyn std::fmt::Write| {
|
||||
if is_first {
|
||||
is_first = false;
|
||||
return f.write_str("\n ");
|
||||
}
|
||||
f.write_str(",\n ")
|
||||
};
|
||||
|
||||
f.write_char('(')?;
|
||||
for pos_docs in &self.pos {
|
||||
write_sep(f)?;
|
||||
f.write_str(&pos_docs.name)?;
|
||||
if let Some(t) = &pos_docs.cano_type {
|
||||
write!(f, ": {}", t.0)?;
|
||||
}
|
||||
}
|
||||
if let Some(rest) = &self.rest {
|
||||
write_sep(f)?;
|
||||
f.write_str("..")?;
|
||||
f.write_str(&rest.name)?;
|
||||
if let Some(t) = &rest.cano_type {
|
||||
write!(f, ": {}", t.0)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.named.is_empty() {
|
||||
let mut name_prints = vec![];
|
||||
for v in self.named.values() {
|
||||
let ty = v.cano_type.as_ref().map(|t| &t.0);
|
||||
name_prints.push((v.name.clone(), ty, v.default.clone()))
|
||||
}
|
||||
name_prints.sort();
|
||||
for (name, ty, val) in name_prints {
|
||||
write_sep(f)?;
|
||||
let val = val.as_deref().unwrap_or("any");
|
||||
let mut default = val.trim();
|
||||
if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
|
||||
default = "{ .. }"
|
||||
}
|
||||
if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
|
||||
default = "raw"
|
||||
}
|
||||
if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
|
||||
default = "content"
|
||||
}
|
||||
f.write_str(&name)?;
|
||||
if let Some(ty) = ty {
|
||||
write!(f, ": {ty}")?;
|
||||
}
|
||||
if default.contains('\n') {
|
||||
write!(f, " = {}", default.replace("\n", "\n "))?;
|
||||
} else {
|
||||
write!(f, " = {default}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_first {
|
||||
f.write_str(",\n")?;
|
||||
}
|
||||
f.write_char(')')?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation about a variable (without type information).
|
||||
pub type UntypedVarDocs = VarDocsT<()>;
|
||||
/// Documentation about a variable.
|
||||
pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
|
||||
|
||||
/// Describes a primary pattern binding.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VarDocsT<T> {
|
||||
/// Documentation for the pattern binding.
|
||||
pub docs: EcoString,
|
||||
/// The inferred type of the pattern binding source.
|
||||
pub return_ty: T,
|
||||
/// Cached documentation for the definition.
|
||||
#[serde(skip)]
|
||||
pub def_docs: OnceLock<String>,
|
||||
}
|
||||
|
||||
impl VarDocs {
|
||||
/// Get the markdown representation of the documentation.
|
||||
pub fn def_docs(&self) -> &String {
|
||||
self.def_docs
|
||||
.get_or_init(|| plain_docs_sentence(&self.docs).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Documentation about a parameter (without type information).
|
||||
pub type TypelessParamDocs = ParamDocsT<()>;
|
||||
/// Documentation about a parameter.
|
||||
pub type ParamDocs = ParamDocsT<TypeRepr>;
|
||||
|
||||
/// Describes a function parameter.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ParamDocsT<T> {
|
||||
/// The parameter's name.
|
||||
pub name: Interned<str>,
|
||||
/// Documentation for the parameter.
|
||||
pub docs: EcoString,
|
||||
/// Inferred type of the parameter.
|
||||
pub cano_type: T,
|
||||
/// The parameter's default name as value.
|
||||
pub default: Option<EcoString>,
|
||||
/// The attribute of the parameter.
|
||||
#[serde(flatten)]
|
||||
pub attrs: ParamAttrs,
|
||||
}
|
||||
|
||||
impl ParamDocs {
|
||||
fn new(param: &ParamTy, ty: Option<&Ty>) -> Self {
|
||||
Self {
|
||||
name: param.name.as_ref().into(),
|
||||
docs: param.docs.clone().unwrap_or_default(),
|
||||
cano_type: format_ty(ty.or(Some(¶m.ty))),
|
||||
default: param.default.clone(),
|
||||
attrs: param.attrs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_ty(ty: Option<&Ty>) -> TypeRepr {
|
||||
let ty = ty?;
|
||||
let short = ty.repr().unwrap_or_else(|| "any".into());
|
||||
let long = eco_format!("{ty:?}");
|
||||
let value = ty.value_repr().unwrap_or_else(|| "".into());
|
||||
|
||||
Some((short, long, value))
|
||||
}
|
||||
use crate::LocalContext;
|
||||
|
||||
pub(crate) fn var_docs(ctx: &mut LocalContext, pos: Span) -> Option<VarDocs> {
|
||||
let source = ctx.source_by_id(pos.id()?).ok()?;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@ mod convert;
|
|||
mod def;
|
||||
mod module;
|
||||
mod package;
|
||||
mod tidy;
|
||||
|
||||
use tinymist_std::path::unix_slash;
|
||||
use typst::syntax::FileId;
|
||||
|
||||
pub(crate) use convert::convert_docs;
|
||||
pub use def::*;
|
||||
pub(crate) use def::*;
|
||||
pub use module::*;
|
||||
pub use package::*;
|
||||
pub(crate) use tidy::*;
|
||||
pub use tinymist_analysis::docs::*;
|
||||
|
||||
fn file_id_repr(fid: FileId) -> String {
|
||||
if let Some(spec) = fid.package() {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ use typst::diag::StrResult;
|
|||
use typst::syntax::package::PackageSpec;
|
||||
use typst::syntax::FileId;
|
||||
|
||||
use crate::adt::interner::Interned;
|
||||
use crate::docs::file_id_repr;
|
||||
use crate::package::{get_manifest_id, PackageInfo};
|
||||
use crate::syntax::{Decl, DefKind, Expr, ExprInfo};
|
||||
use crate::ty::Interned;
|
||||
use crate::LocalContext;
|
||||
|
||||
use super::DefDocs;
|
||||
|
|
|
|||
|
|
@ -1,317 +0,0 @@
|
|||
use ecow::EcoString;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::diag::StrResult;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyParamDocs {
|
||||
pub name: EcoString,
|
||||
pub docs: EcoString,
|
||||
pub types: EcoString,
|
||||
pub default: Option<EcoString>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyPatDocs {
|
||||
pub docs: EcoString,
|
||||
pub return_ty: Option<EcoString>,
|
||||
pub params: Vec<TidyParamDocs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyModuleDocs {
|
||||
pub docs: EcoString,
|
||||
}
|
||||
|
||||
pub fn identify_pat_docs(converted: &str) -> StrResult<TidyPatDocs> {
|
||||
let lines = converted.lines().collect::<Vec<_>>();
|
||||
|
||||
let mut matching_return_ty = true;
|
||||
let mut buf = vec![];
|
||||
let mut params = vec![];
|
||||
let mut return_ty = None;
|
||||
let mut break_line = None;
|
||||
|
||||
let mut line_width = lines.len();
|
||||
'search: loop {
|
||||
if line_width == 0 {
|
||||
break;
|
||||
}
|
||||
line_width -= 1;
|
||||
|
||||
let line = lines[line_width];
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
loop {
|
||||
if matching_return_ty {
|
||||
matching_return_ty = false;
|
||||
let Some(w) = line.trim_start().strip_prefix("->") else {
|
||||
// break_line = Some(i);
|
||||
continue;
|
||||
};
|
||||
|
||||
break_line = Some(line_width);
|
||||
return_ty = Some(w.trim().into());
|
||||
break;
|
||||
}
|
||||
|
||||
let Some(mut line) = line
|
||||
.trim_end()
|
||||
.strip_suffix("<!-- typlite:end:list-item 0 -->")
|
||||
else {
|
||||
break_line = Some(line_width + 1);
|
||||
break 'search;
|
||||
};
|
||||
let mut current_line_no = line_width;
|
||||
|
||||
loop {
|
||||
// <!-- typlite:begin:list-item -->
|
||||
let t = line
|
||||
.trim_start()
|
||||
.strip_prefix("- ")
|
||||
.and_then(|t| t.trim().strip_prefix("<!-- typlite:begin:list-item 0 -->"));
|
||||
|
||||
let line_content = match t {
|
||||
Some(t) => {
|
||||
buf.push(t);
|
||||
break;
|
||||
}
|
||||
None => line,
|
||||
};
|
||||
|
||||
buf.push(line_content);
|
||||
|
||||
if current_line_no == 0 {
|
||||
break_line = Some(line_width + 1);
|
||||
break 'search;
|
||||
}
|
||||
current_line_no -= 1;
|
||||
line = lines[current_line_no];
|
||||
}
|
||||
|
||||
let mut buf = std::mem::take(&mut buf);
|
||||
buf.reverse();
|
||||
|
||||
let Some(first_line) = buf.first_mut() else {
|
||||
break_line = Some(line_width + 1);
|
||||
break 'search;
|
||||
};
|
||||
*first_line = first_line.trim();
|
||||
|
||||
let Some(param_line) = None.or_else(|| {
|
||||
let (param_name, rest) = first_line.split_once(" ")?;
|
||||
let (type_content, rest) = match_brace(rest.trim_start().strip_prefix("(")?)?;
|
||||
let (_, rest) = rest.split_once(":")?;
|
||||
*first_line = rest.trim();
|
||||
Some((param_name.into(), type_content.into()))
|
||||
}) else {
|
||||
break_line = Some(line_width + 1);
|
||||
break 'search;
|
||||
};
|
||||
|
||||
line_width = current_line_no;
|
||||
params.push(TidyParamDocs {
|
||||
name: param_line.0,
|
||||
types: param_line.1,
|
||||
default: None,
|
||||
docs: buf.into_iter().join("\n").into(),
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let docs = match break_line {
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n").into(),
|
||||
None => converted.into(),
|
||||
};
|
||||
|
||||
params.reverse();
|
||||
Ok(TidyPatDocs {
|
||||
docs,
|
||||
return_ty,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
|
||||
Ok(TidyModuleDocs { docs })
|
||||
}
|
||||
|
||||
fn match_brace(trim_start: &str) -> Option<(&str, &str)> {
|
||||
let mut brace_count = 1;
|
||||
let mut end = 0;
|
||||
for (idx, ch) in trim_start.char_indices() {
|
||||
match ch {
|
||||
'(' => brace_count += 1,
|
||||
')' => brace_count -= 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if brace_count == 0 {
|
||||
end = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if brace_count != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (type_content, rest) = trim_start.split_at(end);
|
||||
Some((type_content, rest))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::TidyParamDocs;
|
||||
|
||||
fn func(s: &str) -> String {
|
||||
let docs = super::identify_pat_docs(s).unwrap();
|
||||
let mut res = format!(">> docs:\n{}\n<< docs", docs.docs);
|
||||
if let Some(t) = docs.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
}
|
||||
for TidyParamDocs {
|
||||
name,
|
||||
types,
|
||||
docs,
|
||||
default: _,
|
||||
} in docs.params
|
||||
{
|
||||
let _ = write!(res, "\n>>arg {name}: {types}\n{docs}\n<< arg");
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn var(s: &str) -> String {
|
||||
let docs = super::identify_pat_docs(s).unwrap();
|
||||
let mut res = format!(">> docs:\n{}\n<< docs", docs.docs);
|
||||
if let Some(t) = docs.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identify_tidy_docs() {
|
||||
insta::assert_snapshot!(func(r###"These again are dictionaries with the keys
|
||||
- <!-- typlite:begin:list-item 0 -->`description` (optional): The description for the argument.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->`types` (optional): A list of accepted argument types.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->`default` (optional): Default value for this argument.<!-- typlite:end:list-item 0 -->
|
||||
|
||||
See @@show-module() for outputting the results of this function.
|
||||
|
||||
- <!-- typlite:begin:list-item 0 -->content (string): Content of `.typ` file to analyze for docstrings.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->name (string): The name for the module.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->label-prefix (auto, string): The label-prefix for internal function
|
||||
references. If `auto`, the label-prefix name will be the module name.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->require-all-parameters (boolean): Require that all parameters of a
|
||||
functions are documented and fail if some are not.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->scope (dictionary): A dictionary of definitions that are then available
|
||||
in all function and parameter descriptions.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->preamble (string): Code to prepend to all code snippets shown with `#example()`.
|
||||
This can for instance be used to import something from the scope.<!-- typlite:end:list-item 0 -->
|
||||
-> string"###), @r"
|
||||
>> docs:
|
||||
These again are dictionaries with the keys
|
||||
- <!-- typlite:begin:list-item 0 -->`description` (optional): The description for the argument.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->`types` (optional): A list of accepted argument types.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->`default` (optional): Default value for this argument.<!-- typlite:end:list-item 0 -->
|
||||
|
||||
See @@show-module() for outputting the results of this function.
|
||||
<< docs
|
||||
>>return
|
||||
string
|
||||
<<return
|
||||
>>arg content: string
|
||||
Content of `.typ` file to analyze for docstrings.
|
||||
<< arg
|
||||
>>arg name: string
|
||||
The name for the module.
|
||||
<< arg
|
||||
>>arg label-prefix: auto, string
|
||||
The label-prefix for internal function
|
||||
references. If `auto`, the label-prefix name will be the module name.
|
||||
<< arg
|
||||
>>arg require-all-parameters: boolean
|
||||
Require that all parameters of a
|
||||
functions are documented and fail if some are not.
|
||||
<< arg
|
||||
>>arg scope: dictionary
|
||||
A dictionary of definitions that are then available
|
||||
in all function and parameter descriptions.
|
||||
<< arg
|
||||
>>arg preamble: string
|
||||
Code to prepend to all code snippets shown with `#example()`.
|
||||
This can for instance be used to import something from the scope.
|
||||
<< arg
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identify_tidy_docs_nested() {
|
||||
insta::assert_snapshot!(func(r###"These again are dictionaries with the keys
|
||||
- <!-- typlite:begin:list-item 0 -->`description` (optional): The description for the argument.<!-- typlite:end:list-item 0 -->
|
||||
|
||||
See @@show-module() for outputting the results of this function.
|
||||
|
||||
- <!-- typlite:begin:list-item 0 -->name (string): The name for the module.<!-- typlite:end:list-item 0 -->
|
||||
- <!-- typlite:begin:list-item 0 -->label-prefix (auto, string): The label-prefix for internal function
|
||||
references. If `auto`, the label-prefix name will be the module name.
|
||||
- <!-- typlite:begin:list-item 1 -->nested something<!-- typlite:end:list-item 1 -->
|
||||
- <!-- typlite:begin:list-item 1 -->nested something 2<!-- typlite:end:list-item 1 --><!-- typlite:end:list-item 0 -->
|
||||
-> string"###), @r"
|
||||
>> docs:
|
||||
These again are dictionaries with the keys
|
||||
- <!-- typlite:begin:list-item 0 -->`description` (optional): The description for the argument.<!-- typlite:end:list-item 0 -->
|
||||
|
||||
See @@show-module() for outputting the results of this function.
|
||||
<< docs
|
||||
>>return
|
||||
string
|
||||
<<return
|
||||
>>arg name: string
|
||||
The name for the module.
|
||||
<< arg
|
||||
>>arg label-prefix: auto, string
|
||||
The label-prefix for internal function
|
||||
references. If `auto`, the label-prefix name will be the module name.
|
||||
- <!-- typlite:begin:list-item 1 -->nested something<!-- typlite:end:list-item 1 -->
|
||||
- <!-- typlite:begin:list-item 1 -->nested something 2<!-- typlite:end:list-item 1 -->
|
||||
<< arg
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identify_tidy_docs3() {
|
||||
insta::assert_snapshot!(var(r###"See @@show-module() for outputting the results of this function.
|
||||
-> string"###), @r"
|
||||
>> docs:
|
||||
See @@show-module() for outputting the results of this function.
|
||||
<< docs
|
||||
>>return
|
||||
string
|
||||
<<return
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identify_tidy_docs4() {
|
||||
insta::assert_snapshot!(var(r###"
|
||||
- <!-- typlite:begin:list-item 0 -->name (string): The name for the module.<!-- typlite:end:list-item 0 -->
|
||||
-> string"###), @r"
|
||||
>> docs:
|
||||
|
||||
- <!-- typlite:begin:list-item 0 -->name (string): The name for the module.<!-- typlite:end:list-item 0 -->
|
||||
<< docs
|
||||
>>return
|
||||
string
|
||||
<<return
|
||||
");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use hashbrown::HashSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ pub mod docs;
|
|||
pub mod package;
|
||||
pub mod syntax;
|
||||
pub mod testing;
|
||||
pub mod ty;
|
||||
pub use tinymist_analysis::{ty, upstream};
|
||||
|
||||
/// The physical position in a document.
|
||||
pub type FramePosition = typst::layout::Position;
|
||||
|
|
@ -81,15 +81,17 @@ mod semantic_tokens_delta;
|
|||
mod semantic_tokens_full;
|
||||
mod signature_help;
|
||||
mod symbol;
|
||||
mod upstream;
|
||||
mod will_rename_files;
|
||||
mod workspace_label;
|
||||
|
||||
use typst::syntax::Source;
|
||||
|
||||
use tinymist_analysis::log_debug_ct;
|
||||
use tinymist_analysis::{adt::interner::Interned, log_debug_ct};
|
||||
use tinymist_project::LspComputeGraph;
|
||||
|
||||
/// A reference to the interned string
|
||||
pub(crate) type StrRef = Interned<str>;
|
||||
|
||||
/// A request handler with given syntax information.
|
||||
pub trait SyntaxRequest {
|
||||
/// The response type of the request.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use tinymist_analysis::adt::interner::Interned;
|
||||
use tinymist_std::typst::TypstDocument;
|
||||
use typst::syntax::Span;
|
||||
|
||||
|
|
@ -7,7 +8,7 @@ use crate::{
|
|||
analysis::{Definition, SearchCtx},
|
||||
prelude::*,
|
||||
syntax::{get_index_info, RefExpr, SyntaxClass},
|
||||
ty::Interned,
|
||||
StrRef,
|
||||
};
|
||||
|
||||
/// The [`textDocument/references`] request is sent from the client to the
|
||||
|
|
@ -73,7 +74,7 @@ struct ReferencesWorker<'a> {
|
|||
ctx: SearchCtx<'a>,
|
||||
references: Vec<LspLocation>,
|
||||
def: Definition,
|
||||
module_path: OnceLock<Interned<str>>,
|
||||
module_path: OnceLock<StrRef>,
|
||||
}
|
||||
|
||||
impl ReferencesWorker<'_> {
|
||||
|
|
@ -148,7 +149,7 @@ impl ReferencesWorker<'_> {
|
|||
}
|
||||
|
||||
// todo: references of package
|
||||
fn module_path(&self) -> &Interned<str> {
|
||||
fn module_path(&self) -> &StrRef {
|
||||
self.module_path.get_or_init(|| {
|
||||
self.def
|
||||
.decl
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ use typst::{
|
|||
syntax::Span,
|
||||
};
|
||||
|
||||
use crate::adt::interner::Interned;
|
||||
use crate::{
|
||||
analysis::{get_link_exprs, LinkObject, LinkTarget},
|
||||
find_references,
|
||||
prelude::*,
|
||||
prepare_renaming,
|
||||
syntax::{first_ancestor_expr, get_index_info, node_ancestors, Decl, RefExpr, SyntaxClass},
|
||||
ty::Interned,
|
||||
};
|
||||
|
||||
/// The [`textDocument/rename`] request is sent from the client to the server to
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,16 +7,13 @@ use std::{
|
|||
use ecow::eco_format;
|
||||
use typst::foundations::{IntoValue, Module, Str, Type};
|
||||
|
||||
use crate::{adt::interner::Interned, StrRef};
|
||||
use crate::{adt::snapshot_map::SnapshotMap, analysis::SharedContext};
|
||||
use crate::{
|
||||
adt::snapshot_map::SnapshotMap,
|
||||
analysis::SharedContext,
|
||||
docs::{convert_docs, identify_pat_docs, identify_tidy_module_docs, UntypedDefDocs, VarDocsT},
|
||||
prelude::*,
|
||||
syntax::{Decl, DefKind},
|
||||
ty::{
|
||||
BuiltinTy, DynTypeBounds, InsTy, Interned, PackageId, SigTy, StrRef, Ty, TypeVar,
|
||||
TypeVarBounds,
|
||||
},
|
||||
ty::{BuiltinTy, DynTypeBounds, InsTy, PackageId, SigTy, Ty, TypeVar, TypeVarBounds},
|
||||
};
|
||||
|
||||
use super::DeclExpr;
|
||||
|
|
@ -35,6 +32,7 @@ pub struct DocString {
|
|||
}
|
||||
|
||||
impl DocString {
|
||||
/// Gets the docstring as a variable doc
|
||||
pub fn as_var(&self) -> VarDoc {
|
||||
VarDoc {
|
||||
docs: self.docs.clone().unwrap_or_default(),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#![allow(missing_docs)]
|
||||
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use rpds::RedBlackTreeMapSync;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::ops::Deref;
|
||||
use tinymist_analysis::adt::interner::Interned;
|
||||
use tinymist_std::hash::hash128;
|
||||
use typst::{
|
||||
foundations::{Element, NativeElement, Type, Value},
|
||||
|
|
@ -17,7 +20,7 @@ use crate::{
|
|||
analysis::{QueryStatGuard, SharedContext},
|
||||
prelude::*,
|
||||
syntax::{find_module_level_docs, resolve_id_by_path, DefKind},
|
||||
ty::{BuiltinTy, InsTy, Interned, Ty},
|
||||
ty::{BuiltinTy, InsTy, Ty},
|
||||
};
|
||||
|
||||
use super::{compute_docstring, def::*, DocCommentMatcher, DocString, InterpretMode};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,8 @@
|
|||
//!
|
||||
//! This module must hide all **AST details** from the rest of the codebase.
|
||||
|
||||
// todo: remove this
|
||||
#![allow(missing_docs)]
|
||||
|
||||
pub use tinymist_analysis::syntax::comment::*;
|
||||
pub use tinymist_analysis::syntax::import::*;
|
||||
pub use tinymist_analysis::syntax::matcher::*;
|
||||
pub(crate) mod lexical_hierarchy;
|
||||
pub use lexical_hierarchy::*;
|
||||
pub(crate) mod module;
|
||||
|
|
@ -16,9 +12,6 @@ pub(crate) mod expr;
|
|||
pub use expr::*;
|
||||
pub(crate) mod docs;
|
||||
pub use docs::*;
|
||||
pub(crate) mod def;
|
||||
pub use def::*;
|
||||
pub(crate) mod repr;
|
||||
use repr::*;
|
||||
pub(crate) mod index;
|
||||
pub use index::*;
|
||||
pub use tinymist_analysis::syntax::*;
|
||||
|
|
|
|||
|
|
@ -1,607 +0,0 @@
|
|||
use core::fmt;
|
||||
|
||||
use super::def::*;
|
||||
use crate::ty::{Interned, Ty};
|
||||
|
||||
pub(in crate::syntax) struct ExprPrinter<'a, T: fmt::Write> {
|
||||
f: &'a mut T,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: fmt::Write> ExprPrinter<'a, T> {
|
||||
pub fn new(f: &'a mut T) -> Self {
|
||||
Self { f, indent: 0 }
|
||||
}
|
||||
|
||||
pub fn write_decl(&mut self, decl: &Decl) -> fmt::Result {
|
||||
write!(self.f, "{decl:?}")
|
||||
}
|
||||
|
||||
pub fn write_expr(&mut self, expr: &Expr) -> fmt::Result {
|
||||
match expr {
|
||||
Expr::Block(exprs) => self.write_seq(exprs),
|
||||
Expr::Array(elems) => self.write_array(&elems.args),
|
||||
Expr::Dict(elems) => self.write_dict(&elems.args),
|
||||
Expr::Args(args) => self.write_args(&args.args),
|
||||
Expr::Pattern(pat) => self.write_pattern(pat),
|
||||
Expr::Element(elem) => self.write_element(elem),
|
||||
Expr::Unary(unary) => self.write_unary(unary),
|
||||
Expr::Binary(binary) => self.write_binary(binary),
|
||||
Expr::Apply(apply) => self.write_apply(apply),
|
||||
Expr::Func(func) => self.write_func(func),
|
||||
Expr::Let(let_expr) => self.write_let(let_expr),
|
||||
Expr::Show(show) => self.write_show(show),
|
||||
Expr::Set(set) => self.write_set(set),
|
||||
Expr::Ref(reference) => self.write_ref(reference),
|
||||
Expr::ContentRef(content_ref) => self.write_content_ref(content_ref),
|
||||
Expr::Select(sel) => self.write_select(sel),
|
||||
Expr::Import(import) => self.write_import(import),
|
||||
Expr::Include(include) => self.write_include(include),
|
||||
Expr::Contextual(contextual) => self.write_contextual(contextual),
|
||||
Expr::Conditional(if_expr) => self.write_conditional(if_expr),
|
||||
Expr::WhileLoop(while_expr) => self.write_while_loop(while_expr),
|
||||
Expr::ForLoop(for_expr) => self.write_for_loop(for_expr),
|
||||
Expr::Type(ty) => self.write_type(ty),
|
||||
Expr::Decl(decl) => self.write_decl(decl),
|
||||
Expr::Star => self.write_star(),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_indent(&mut self) -> fmt::Result {
|
||||
write!(self.f, "{:indent$}", "", indent = self.indent)
|
||||
}
|
||||
|
||||
fn write_seq(&mut self, exprs: &Interned<Vec<Expr>>) -> fmt::Result {
|
||||
writeln!(self.f, "[")?;
|
||||
self.indent += 1;
|
||||
for expr in exprs.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_expr(expr)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
write!(self.f, "]")
|
||||
}
|
||||
|
||||
fn write_array(&mut self, elems: &[ArgExpr]) -> fmt::Result {
|
||||
writeln!(self.f, "(")?;
|
||||
self.indent += 1;
|
||||
for arg in elems.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_dict(&mut self, elems: &[ArgExpr]) -> fmt::Result {
|
||||
writeln!(self.f, "(:")?;
|
||||
self.indent += 1;
|
||||
for arg in elems.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_args(&mut self, args: &[ArgExpr]) -> fmt::Result {
|
||||
writeln!(self.f, "(")?;
|
||||
for arg in args.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_arg(&mut self, arg: &ArgExpr) -> fmt::Result {
|
||||
match arg {
|
||||
ArgExpr::Pos(pos) => self.write_expr(pos),
|
||||
ArgExpr::Named(named) => {
|
||||
let (name, val) = named.as_ref();
|
||||
write!(self.f, "{name:?}: ")?;
|
||||
self.write_expr(val)
|
||||
}
|
||||
ArgExpr::NamedRt(named) => {
|
||||
let (key, val) = named.as_ref();
|
||||
self.write_expr(key)?;
|
||||
write!(self.f, ": ")?;
|
||||
self.write_expr(val)
|
||||
}
|
||||
ArgExpr::Spread(spread) => {
|
||||
write!(self.f, "..")?;
|
||||
self.write_expr(spread)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pattern(&mut self, pat: &Pattern) -> fmt::Result {
|
||||
match pat {
|
||||
Pattern::Expr(expr) => self.write_expr(expr),
|
||||
Pattern::Simple(decl) => self.write_decl(decl),
|
||||
Pattern::Sig(sig) => self.write_pattern_sig(sig),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pattern_sig(&mut self, sig: &PatternSig) -> fmt::Result {
|
||||
self.f.write_str("pat(\n")?;
|
||||
self.indent += 1;
|
||||
for pos in &sig.pos {
|
||||
self.write_indent()?;
|
||||
self.write_pattern(pos)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
for (name, named) in &sig.named {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "{name:?} = ")?;
|
||||
self.write_pattern(named)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
if let Some((name, spread_left)) = &sig.spread_left {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "..{name:?}: ")?;
|
||||
self.write_pattern(spread_left)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
if let Some((name, spread_right)) = &sig.spread_right {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "..{name:?}: ")?;
|
||||
self.write_pattern(spread_right)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_element(&mut self, elem: &Interned<ElementExpr>) -> fmt::Result {
|
||||
self.f.write_str("elem(\n")?;
|
||||
self.indent += 1;
|
||||
for v in &elem.content {
|
||||
self.write_indent()?;
|
||||
self.write_expr(v)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_unary(&mut self, unary: &Interned<UnExpr>) -> fmt::Result {
|
||||
write!(self.f, "un({:?})(", unary.op)?;
|
||||
self.write_expr(&unary.lhs)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_binary(&mut self, binary: &Interned<BinExpr>) -> fmt::Result {
|
||||
let [lhs, rhs] = binary.operands();
|
||||
write!(self.f, "bin({:?})(", binary.op)?;
|
||||
self.write_expr(lhs)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(rhs)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_apply(&mut self, apply: &Interned<ApplyExpr>) -> fmt::Result {
|
||||
write!(self.f, "apply(")?;
|
||||
self.write_expr(&apply.callee)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(&apply.args)?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_func(&mut self, func: &Interned<FuncExpr>) -> fmt::Result {
|
||||
write!(self.f, "func[{:?}](", func.decl)?;
|
||||
self.write_pattern_sig(&func.params)?;
|
||||
write!(self.f, " = ")?;
|
||||
self.write_expr(&func.body)?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_let(&mut self, let_expr: &Interned<LetExpr>) -> fmt::Result {
|
||||
write!(self.f, "let(")?;
|
||||
self.write_pattern(&let_expr.pattern)?;
|
||||
if let Some(body) = &let_expr.body {
|
||||
write!(self.f, " = ")?;
|
||||
self.write_expr(body)?;
|
||||
}
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_show(&mut self, show: &Interned<ShowExpr>) -> fmt::Result {
|
||||
write!(self.f, "show(")?;
|
||||
if let Some(selector) = &show.selector {
|
||||
self.write_expr(selector)?;
|
||||
self.f.write_str(", ")?;
|
||||
}
|
||||
self.write_expr(&show.edit)?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_set(&mut self, set: &Interned<SetExpr>) -> fmt::Result {
|
||||
write!(self.f, "set(")?;
|
||||
self.write_expr(&set.target)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(&set.args)?;
|
||||
if let Some(cond) = &set.cond {
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(cond)?;
|
||||
}
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_ref(&mut self, reference: &Interned<RefExpr>) -> fmt::Result {
|
||||
write!(self.f, "ref({:?}", reference.decl)?;
|
||||
if let Some(step) = &reference.step {
|
||||
self.f.write_str(", step = ")?;
|
||||
self.write_expr(step)?;
|
||||
}
|
||||
if let Some(of) = &reference.root {
|
||||
self.f.write_str(", root = ")?;
|
||||
self.write_expr(of)?;
|
||||
}
|
||||
if let Some(val) = &reference.term {
|
||||
write!(self.f, ", val = {val:?}")?;
|
||||
}
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_content_ref(&mut self, content_ref: &Interned<ContentRefExpr>) -> fmt::Result {
|
||||
write!(self.f, "content_ref({:?}", content_ref.ident)?;
|
||||
if let Some(of) = &content_ref.of {
|
||||
self.f.write_str(", ")?;
|
||||
self.write_decl(of)?;
|
||||
}
|
||||
if let Some(val) = &content_ref.body {
|
||||
self.write_expr(val)?;
|
||||
}
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_select(&mut self, sel: &Interned<SelectExpr>) -> fmt::Result {
|
||||
write!(self.f, "(")?;
|
||||
self.write_expr(&sel.lhs)?;
|
||||
self.f.write_str(").")?;
|
||||
self.write_decl(&sel.key)
|
||||
}
|
||||
|
||||
fn write_import(&mut self, import: &Interned<ImportExpr>) -> fmt::Result {
|
||||
self.f.write_str("import(")?;
|
||||
self.write_decl(&import.decl.decl)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_include(&mut self, include: &Interned<IncludeExpr>) -> fmt::Result {
|
||||
self.f.write_str("include(")?;
|
||||
self.write_expr(&include.source)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_contextual(&mut self, contextual: &Interned<Expr>) -> fmt::Result {
|
||||
self.f.write_str("contextual(")?;
|
||||
self.write_expr(contextual)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_conditional(&mut self, if_expr: &Interned<IfExpr>) -> fmt::Result {
|
||||
self.f.write_str("if(")?;
|
||||
self.write_expr(&if_expr.cond)?;
|
||||
self.f.write_str(", then = ")?;
|
||||
self.write_expr(&if_expr.then)?;
|
||||
self.f.write_str(", else = ")?;
|
||||
self.write_expr(&if_expr.else_)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_while_loop(&mut self, while_expr: &Interned<WhileExpr>) -> fmt::Result {
|
||||
self.f.write_str("while(")?;
|
||||
self.write_expr(&while_expr.cond)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(&while_expr.body)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_for_loop(&mut self, for_expr: &Interned<ForExpr>) -> fmt::Result {
|
||||
self.f.write_str("for(")?;
|
||||
self.write_pattern(&for_expr.pattern)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(&for_expr.iter)?;
|
||||
self.f.write_str(", ")?;
|
||||
self.write_expr(&for_expr.body)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_type(&mut self, ty: &Ty) -> fmt::Result {
|
||||
let formatted = ty.describe();
|
||||
let formatted = formatted.as_deref().unwrap_or("any");
|
||||
self.f.write_str(formatted)
|
||||
}
|
||||
|
||||
fn write_star(&mut self) -> fmt::Result {
|
||||
self.f.write_str("*")
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::syntax) struct ExprDescriber<'a, T: fmt::Write> {
|
||||
f: &'a mut T,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl<'a, T: fmt::Write> ExprDescriber<'a, T> {
|
||||
pub fn new(f: &'a mut T) -> Self {
|
||||
Self { f, indent: 0 }
|
||||
}
|
||||
|
||||
pub fn write_decl(&mut self, decl: &Decl) -> fmt::Result {
|
||||
use DefKind::*;
|
||||
let shorter = matches!(decl.kind(), Function | Variable | Module);
|
||||
if shorter && !decl.name().is_empty() {
|
||||
return write!(self.f, "{}", decl.name());
|
||||
}
|
||||
|
||||
write!(self.f, "{decl:?}")
|
||||
}
|
||||
|
||||
pub fn write_expr(&mut self, expr: &Expr) -> fmt::Result {
|
||||
match expr {
|
||||
Expr::Block(..) => self.f.write_str("Expr(..)"),
|
||||
Expr::Array(elems) => self.write_array(&elems.args),
|
||||
Expr::Dict(elems) => self.write_dict(&elems.args),
|
||||
Expr::Args(args) => self.write_args(&args.args),
|
||||
Expr::Pattern(pat) => self.write_pattern(pat),
|
||||
Expr::Element(elem) => self.write_element(elem),
|
||||
Expr::Unary(unary) => self.write_unary(unary),
|
||||
Expr::Binary(binary) => self.write_binary(binary),
|
||||
Expr::Apply(apply) => self.write_apply(apply),
|
||||
Expr::Func(func) => self.write_func(func),
|
||||
Expr::Ref(ref_expr) => self.write_ref(ref_expr),
|
||||
Expr::ContentRef(content_ref) => self.write_content_ref(content_ref),
|
||||
Expr::Select(sel) => self.write_select(sel),
|
||||
Expr::Import(import) => self.write_import(import),
|
||||
Expr::Include(include) => self.write_include(include),
|
||||
Expr::Contextual(..) => self.f.write_str("content"),
|
||||
Expr::Let(..) | Expr::Show(..) | Expr::Set(..) => self.f.write_str("Expr(..)"),
|
||||
Expr::Conditional(..) | Expr::WhileLoop(..) | Expr::ForLoop(..) => {
|
||||
self.f.write_str("Expr(..)")
|
||||
}
|
||||
Expr::Type(ty) => self.write_type(ty),
|
||||
Expr::Decl(decl) => self.write_decl(decl),
|
||||
Expr::Star => self.f.write_str("*"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_indent(&mut self) -> fmt::Result {
|
||||
write!(self.f, "{:indent$}", "", indent = self.indent)
|
||||
}
|
||||
|
||||
fn write_array(&mut self, elems: &[ArgExpr]) -> fmt::Result {
|
||||
if elems.len() <= 1 {
|
||||
self.f.write_char('(')?;
|
||||
if let Some(arg) = elems.first() {
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",")?
|
||||
}
|
||||
return self.f.write_str(")");
|
||||
}
|
||||
|
||||
writeln!(self.f, "(")?;
|
||||
self.indent += 1;
|
||||
for arg in elems.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_dict(&mut self, elems: &[ArgExpr]) -> fmt::Result {
|
||||
if elems.len() <= 1 {
|
||||
self.f.write_char('(')?;
|
||||
if let Some(arg) = elems.first() {
|
||||
self.write_arg(arg)?;
|
||||
} else {
|
||||
self.f.write_str(":")?
|
||||
}
|
||||
return self.f.write_str(")");
|
||||
}
|
||||
|
||||
writeln!(self.f, "(:")?;
|
||||
self.indent += 1;
|
||||
for arg in elems.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_args(&mut self, args: &[ArgExpr]) -> fmt::Result {
|
||||
writeln!(self.f, "(")?;
|
||||
for arg in args.iter() {
|
||||
self.write_indent()?;
|
||||
self.write_arg(arg)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.write_indent()?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_arg(&mut self, arg: &ArgExpr) -> fmt::Result {
|
||||
match arg {
|
||||
ArgExpr::Pos(pos) => self.write_expr(pos),
|
||||
ArgExpr::Named(named) => {
|
||||
let (k, v) = named.as_ref();
|
||||
self.write_decl(k)?;
|
||||
write!(self.f, ": ")?;
|
||||
self.write_expr(v)
|
||||
}
|
||||
ArgExpr::NamedRt(named) => {
|
||||
let n = named.as_ref();
|
||||
self.write_expr(&n.0)?;
|
||||
write!(self.f, ": ")?;
|
||||
self.write_expr(&n.1)
|
||||
}
|
||||
ArgExpr::Spread(spread) => {
|
||||
write!(self.f, "..")?;
|
||||
self.write_expr(spread)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_pattern(&mut self, pat: &Pattern) -> fmt::Result {
|
||||
match pat {
|
||||
Pattern::Expr(expr) => self.write_expr(expr),
|
||||
Pattern::Simple(decl) => self.write_decl(decl),
|
||||
Pattern::Sig(sig) => self.write_pattern_sig(sig),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pattern_sig(&mut self, sig: &PatternSig) -> fmt::Result {
|
||||
self.f.write_str("pat(\n")?;
|
||||
self.indent += 1;
|
||||
for pos in &sig.pos {
|
||||
self.write_indent()?;
|
||||
self.write_pattern(pos)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
for (name, pat) in &sig.named {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "{name:?} = ")?;
|
||||
self.write_pattern(pat)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
if let Some((k, rest)) = &sig.spread_left {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "..{k:?}: ")?;
|
||||
self.write_pattern(rest)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
if let Some((k, rest)) = &sig.spread_right {
|
||||
self.write_indent()?;
|
||||
write!(self.f, "..{k:?}: ")?;
|
||||
self.write_pattern(rest)?;
|
||||
self.f.write_str(",\n")?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.write_indent()?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_element(&mut self, elem: &Interned<ElementExpr>) -> fmt::Result {
|
||||
write!(self.f, "{:?}", elem.elem.name())
|
||||
}
|
||||
|
||||
fn write_unary(&mut self, unary: &Interned<UnExpr>) -> fmt::Result {
|
||||
use UnaryOp::*;
|
||||
match unary.op {
|
||||
Pos => {
|
||||
self.f.write_str("+")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
Neg => {
|
||||
self.f.write_str("-")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
Not => {
|
||||
self.f.write_str("not ")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
Return => {
|
||||
self.f.write_str("return ")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
Context => {
|
||||
self.f.write_str("context ")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
Spread => {
|
||||
self.f.write_str("..")?;
|
||||
self.write_expr(&unary.lhs)
|
||||
}
|
||||
NotElementOf => {
|
||||
self.f.write_str("not elementOf(")?;
|
||||
self.write_expr(&unary.lhs)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
ElementOf => {
|
||||
self.f.write_str("elementOf(")?;
|
||||
self.write_expr(&unary.lhs)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
TypeOf => {
|
||||
self.f.write_str("typeOf(")?;
|
||||
self.write_expr(&unary.lhs)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_binary(&mut self, binary: &Interned<BinExpr>) -> fmt::Result {
|
||||
let [lhs, rhs] = binary.operands();
|
||||
self.write_expr(lhs)?;
|
||||
write!(self.f, " {} ", binary.op.as_str())?;
|
||||
self.write_expr(rhs)
|
||||
}
|
||||
|
||||
fn write_apply(&mut self, apply: &Interned<ApplyExpr>) -> fmt::Result {
|
||||
self.write_expr(&apply.callee)?;
|
||||
write!(self.f, "(")?;
|
||||
self.write_expr(&apply.args)?;
|
||||
write!(self.f, ")")
|
||||
}
|
||||
|
||||
fn write_func(&mut self, func: &Interned<FuncExpr>) -> fmt::Result {
|
||||
self.write_decl(&func.decl)
|
||||
}
|
||||
|
||||
fn write_ref(&mut self, resolved: &Interned<RefExpr>) -> fmt::Result {
|
||||
if let Some(root) = &resolved.root {
|
||||
return self.write_expr(root);
|
||||
}
|
||||
if let Some(term) = &resolved.term {
|
||||
return self.write_type(term);
|
||||
}
|
||||
|
||||
write!(self.f, "undefined({:?})", resolved.decl)
|
||||
}
|
||||
|
||||
fn write_content_ref(&mut self, content_ref: &Interned<ContentRefExpr>) -> fmt::Result {
|
||||
write!(self.f, "@{:?}", content_ref.ident)
|
||||
}
|
||||
|
||||
fn write_select(&mut self, sel: &Interned<SelectExpr>) -> fmt::Result {
|
||||
write!(self.f, "")?;
|
||||
self.write_expr(&sel.lhs)?;
|
||||
self.f.write_str(".")?;
|
||||
self.write_decl(&sel.key)
|
||||
}
|
||||
|
||||
fn write_import(&mut self, import: &Interned<ImportExpr>) -> fmt::Result {
|
||||
self.f.write_str("import(")?;
|
||||
self.write_decl(&import.decl.decl)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_include(&mut self, include: &Interned<IncludeExpr>) -> fmt::Result {
|
||||
self.f.write_str("include(")?;
|
||||
self.write_expr(&include.source)?;
|
||||
self.f.write_str(")")
|
||||
}
|
||||
|
||||
fn write_type(&mut self, ty: &Ty) -> fmt::Result {
|
||||
let formatted = ty.describe();
|
||||
let formatted = formatted.as_deref().unwrap_or("any");
|
||||
self.f.write_str(formatted)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use super::{Sig, SigChecker, SigSurfaceKind, TyCtx};
|
||||
use crate::ty::prelude::*;
|
||||
|
||||
pub trait ApplyChecker: TyCtx {
|
||||
fn apply(&mut self, sig: Sig, arguments: &Interned<ArgsTy>, pol: bool);
|
||||
}
|
||||
|
||||
static EMPTY_ARGS: LazyLock<Interned<ArgsTy>> = LazyLock::new(|| ArgsTy::default().into());
|
||||
|
||||
impl Ty {
|
||||
/// Call the given type with the given arguments.
|
||||
pub fn call(&self, args: &Interned<ArgsTy>, pol: bool, c: &mut impl ApplyChecker) {
|
||||
ApplySigChecker(c, args).ty(self, SigSurfaceKind::Call, pol);
|
||||
}
|
||||
|
||||
/// Get the tuple element type of the given type.
|
||||
pub fn tuple_element_of(&self, pol: bool, c: &mut impl ApplyChecker) {
|
||||
ApplySigChecker(c, &EMPTY_ARGS).ty(self, SigSurfaceKind::Array, pol);
|
||||
}
|
||||
|
||||
/// Get the element type of the given type.
|
||||
pub fn element_of(&self, pol: bool, c: &mut impl ApplyChecker) {
|
||||
ApplySigChecker(c, &EMPTY_ARGS).ty(self, SigSurfaceKind::ArrayOrDict, pol);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(0)]
|
||||
pub struct ApplySigChecker<'a, T: ApplyChecker>(&'a mut T, &'a Interned<ArgsTy>);
|
||||
|
||||
impl<T: ApplyChecker> ApplySigChecker<'_, T> {
|
||||
fn ty(&mut self, ty: &Ty, surface: SigSurfaceKind, pol: bool) {
|
||||
ty.sig_surface(pol, surface, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ApplyChecker> SigChecker for ApplySigChecker<'_, T> {
|
||||
fn check(&mut self, cano_sig: Sig, ctx: &mut super::SigCheckContext, pol: bool) -> Option<()> {
|
||||
let (cano_sig, is_partialize) = match cano_sig {
|
||||
Sig::Partialize(sig) => (*sig, true),
|
||||
sig => (sig, false),
|
||||
};
|
||||
// Bind the arguments to the canonical signature.
|
||||
let partial_sig = if ctx.args.is_empty() {
|
||||
cano_sig
|
||||
} else {
|
||||
Sig::With {
|
||||
sig: &cano_sig,
|
||||
withs: &ctx.args,
|
||||
at: &ctx.at,
|
||||
}
|
||||
};
|
||||
let partial_sig = if is_partialize {
|
||||
Sig::Partialize(&partial_sig)
|
||||
} else {
|
||||
partial_sig
|
||||
};
|
||||
|
||||
self.0.apply(partial_sig, self.1, pol);
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use typst::foundations::{self, Func};
|
||||
|
||||
use crate::ty::prelude::*;
|
||||
|
||||
pub trait BoundChecker: Sized + TyCtx {
|
||||
fn collect(&mut self, ty: &Ty, pol: bool);
|
||||
|
||||
fn check_var(&mut self, u: &Interned<TypeVar>, pol: bool) {
|
||||
self.check_var_rec(u, pol);
|
||||
}
|
||||
|
||||
fn check_var_rec(&mut self, u: &Interned<TypeVar>, pol: bool) {
|
||||
let Some(w) = self.global_bounds(u, pol) else {
|
||||
return;
|
||||
};
|
||||
let mut ctx = BoundCheckContext;
|
||||
ctx.tys(w.ubs.iter(), pol, self);
|
||||
ctx.tys(w.lbs.iter(), !pol, self);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(0)]
|
||||
pub struct BoundPred<'a, T: TyCtx, F>(pub &'a T, pub F);
|
||||
|
||||
impl<'a, T: TyCtx, F> BoundPred<'a, T, F> {
|
||||
pub fn new(t: &'a T, f: F) -> Self {
|
||||
Self(t, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TyCtx, F> BoundChecker for BoundPred<'_, T, F>
|
||||
where
|
||||
F: FnMut(&Ty, bool),
|
||||
{
|
||||
fn collect(&mut self, ty: &Ty, pol: bool) {
|
||||
self.1(ty, pol);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DocSource {
|
||||
Var(Interned<TypeVar>),
|
||||
Ins(Interned<InsTy>),
|
||||
Builtin(BuiltinTy),
|
||||
}
|
||||
|
||||
impl DocSource {
|
||||
/// Regard doc source as function.
|
||||
pub fn as_func(&self) -> Option<Func> {
|
||||
match self {
|
||||
Self::Var(..) => None,
|
||||
Self::Builtin(BuiltinTy::Type(ty)) => Some(ty.constructor().ok()?),
|
||||
Self::Builtin(BuiltinTy::Element(ty)) => Some((*ty).into()),
|
||||
Self::Builtin(..) => None,
|
||||
Self::Ins(ins_ty) => match &ins_ty.val {
|
||||
foundations::Value::Func(func) => Some(func.clone()),
|
||||
foundations::Value::Type(ty) => Some(ty.constructor().ok()?),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Check if the given type has bounds (is combinated).
|
||||
pub fn has_bounds(&self) -> bool {
|
||||
matches!(self, Ty::Union(_) | Ty::Let(_) | Ty::Var(_))
|
||||
}
|
||||
|
||||
/// Convert type to doc source
|
||||
pub fn as_source(&self) -> Option<DocSource> {
|
||||
match self {
|
||||
Ty::Builtin(ty @ (BuiltinTy::Type(..) | BuiltinTy::Element(..))) => {
|
||||
Some(DocSource::Builtin(ty.clone()))
|
||||
}
|
||||
Ty::Value(ty) => match &ty.val {
|
||||
foundations::Value::Type(..) | foundations::Value::Func(..) => {
|
||||
Some(DocSource::Ins(ty.clone()))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the sources of the given type.
|
||||
pub fn sources(&self) -> Vec<DocSource> {
|
||||
let mut results = vec![];
|
||||
fn collect(ty: &Ty, results: &mut Vec<DocSource>) {
|
||||
use Ty::*;
|
||||
if let Some(src) = ty.as_source() {
|
||||
results.push(src);
|
||||
return;
|
||||
}
|
||||
match ty {
|
||||
Any | Boolean(_) | If(..) | Builtin(..) | Value(..) => {}
|
||||
Dict(..) | Array(..) | Tuple(..) | Func(..) | Args(..) | Pattern(..) => {}
|
||||
Unary(..) | Binary(..) => {}
|
||||
Param(ty) => {
|
||||
// todo: doc source can be param ty
|
||||
collect(&ty.ty, results);
|
||||
}
|
||||
Union(ty) => {
|
||||
for ty in ty.iter() {
|
||||
collect(ty, results);
|
||||
}
|
||||
}
|
||||
Let(ty) => {
|
||||
for ty in ty.ubs.iter() {
|
||||
collect(ty, results);
|
||||
}
|
||||
for ty in ty.lbs.iter() {
|
||||
collect(ty, results);
|
||||
}
|
||||
}
|
||||
Var(ty) => {
|
||||
results.push(DocSource::Var(ty.clone()));
|
||||
}
|
||||
With(ty) => collect(&ty.sig, results),
|
||||
Select(ty) => {
|
||||
// todo: do this correctly
|
||||
if matches!(ty.select.deref(), "with" | "where") {
|
||||
collect(&ty.ty, results);
|
||||
}
|
||||
|
||||
// collect(&ty.ty, results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collect(self, &mut results);
|
||||
results
|
||||
}
|
||||
|
||||
/// Profile the bounds of the given type.
|
||||
pub fn bounds(&self, pol: bool, checker: &mut impl BoundChecker) {
|
||||
BoundCheckContext.ty(self, pol, checker);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoundCheckContext;
|
||||
|
||||
impl BoundCheckContext {
|
||||
fn tys<'a>(&mut self, tys: impl Iterator<Item = &'a Ty>, pol: bool, c: &mut impl BoundChecker) {
|
||||
for ty in tys {
|
||||
self.ty(ty, pol, c);
|
||||
}
|
||||
}
|
||||
|
||||
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) => checker.check_var(u, pol),
|
||||
// todo: calculate these operators
|
||||
// Ty::Select(_) => {}
|
||||
// Ty::Unary(_) => {}
|
||||
// Ty::Binary(_) => {}
|
||||
// Ty::If(_) => {}
|
||||
ty => checker.collect(ty, pol),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,762 +0,0 @@
|
|||
use core::fmt;
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use regex::RegexSet;
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use typst::foundations::{CastInfo, Regex};
|
||||
use typst::layout::Ratio;
|
||||
use typst::syntax::FileId;
|
||||
use typst::{
|
||||
foundations::{AutoValue, Content, Func, NoneValue, ParamInfo, Type, Value},
|
||||
layout::Length,
|
||||
};
|
||||
|
||||
use crate::syntax::Decl;
|
||||
use crate::ty::*;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
|
||||
pub enum PathPreference {
|
||||
Source { allow_package: bool },
|
||||
Wasm,
|
||||
Csv,
|
||||
Image,
|
||||
Json,
|
||||
Yaml,
|
||||
Xml,
|
||||
Toml,
|
||||
Csl,
|
||||
Bibliography,
|
||||
RawTheme,
|
||||
RawSyntax,
|
||||
Special,
|
||||
None,
|
||||
}
|
||||
|
||||
impl PathPreference {
|
||||
pub fn ext_matcher(&self) -> &'static RegexSet {
|
||||
type RegSet = LazyLock<RegexSet>;
|
||||
|
||||
fn make_regex(patterns: &[&str]) -> RegexSet {
|
||||
let patterns = patterns.iter().map(|pattern| format!("(?i)^{pattern}$"));
|
||||
RegexSet::new(patterns).unwrap()
|
||||
}
|
||||
|
||||
static SOURCE_REGSET: RegSet = RegSet::new(|| make_regex(&["typ", "typc"]));
|
||||
static WASM_REGSET: RegSet = RegSet::new(|| make_regex(&["wasm"]));
|
||||
static IMAGE_REGSET: RegSet = RegSet::new(|| {
|
||||
make_regex(&[
|
||||
"ico", "bmp", "png", "webp", "jpg", "jpeg", "jfif", "tiff", "gif", "svg", "svgz",
|
||||
])
|
||||
});
|
||||
static JSON_REGSET: RegSet = RegSet::new(|| make_regex(&["json", "jsonc", "json5"]));
|
||||
static YAML_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml"]));
|
||||
static XML_REGSET: RegSet = RegSet::new(|| make_regex(&["xml"]));
|
||||
static TOML_REGSET: RegSet = RegSet::new(|| make_regex(&["toml"]));
|
||||
static CSV_REGSET: RegSet = RegSet::new(|| make_regex(&["csv"]));
|
||||
static BIB_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml", "bib"]));
|
||||
static CSL_REGSET: RegSet = RegSet::new(|| make_regex(&["csl"]));
|
||||
static RAW_THEME_REGSET: RegSet = RegSet::new(|| make_regex(&["tmTheme", "xml"]));
|
||||
static RAW_SYNTAX_REGSET: RegSet =
|
||||
RegSet::new(|| make_regex(&["tmLanguage", "sublime-syntax"]));
|
||||
|
||||
static ALL_REGSET: RegSet = RegSet::new(|| RegexSet::new([r".*"]).unwrap());
|
||||
static ALL_SPECIAL_REGSET: RegSet = RegSet::new(|| {
|
||||
RegexSet::new({
|
||||
let patterns = SOURCE_REGSET.patterns();
|
||||
let patterns = patterns.iter().chain(WASM_REGSET.patterns());
|
||||
let patterns = patterns.chain(IMAGE_REGSET.patterns());
|
||||
let patterns = patterns.chain(JSON_REGSET.patterns());
|
||||
let patterns = patterns.chain(YAML_REGSET.patterns());
|
||||
let patterns = patterns.chain(XML_REGSET.patterns());
|
||||
let patterns = patterns.chain(TOML_REGSET.patterns());
|
||||
let patterns = patterns.chain(CSV_REGSET.patterns());
|
||||
let patterns = patterns.chain(BIB_REGSET.patterns());
|
||||
let patterns = patterns.chain(CSL_REGSET.patterns());
|
||||
let patterns = patterns.chain(RAW_THEME_REGSET.patterns());
|
||||
patterns.chain(RAW_SYNTAX_REGSET.patterns())
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
match self {
|
||||
PathPreference::Source { .. } => &SOURCE_REGSET,
|
||||
PathPreference::Wasm => &WASM_REGSET,
|
||||
PathPreference::Csv => &CSV_REGSET,
|
||||
PathPreference::Image => &IMAGE_REGSET,
|
||||
PathPreference::Json => &JSON_REGSET,
|
||||
PathPreference::Yaml => &YAML_REGSET,
|
||||
PathPreference::Xml => &XML_REGSET,
|
||||
PathPreference::Toml => &TOML_REGSET,
|
||||
PathPreference::Csl => &CSL_REGSET,
|
||||
PathPreference::Bibliography => &BIB_REGSET,
|
||||
PathPreference::RawTheme => &RAW_THEME_REGSET,
|
||||
PathPreference::RawSyntax => &RAW_SYNTAX_REGSET,
|
||||
PathPreference::Special => &ALL_SPECIAL_REGSET,
|
||||
PathPreference::None => &ALL_REGSET,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_match(&self, path: &Path) -> bool {
|
||||
let ext = path.extension().and_then(|ext| ext.to_str());
|
||||
ext.is_some_and(|ext| self.ext_matcher().is_match(ext))
|
||||
}
|
||||
|
||||
pub fn from_ext(path: &str) -> Option<Self> {
|
||||
PathPreference::iter().find(|preference| preference.is_match(std::path::Path::new(path)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
pub(crate) fn from_cast_info(ty: &CastInfo) -> Ty {
|
||||
match &ty {
|
||||
CastInfo::Any => Ty::Any,
|
||||
CastInfo::Value(val, doc) => Ty::Value(InsTy::new_doc(val.clone(), *doc)),
|
||||
CastInfo::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)),
|
||||
CastInfo::Union(types) => {
|
||||
Ty::iter_union(UnionIter(vec![types.as_slice().iter()]).map(Self::from_cast_info))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_param_site(func: &Func, param: &ParamInfo) -> Ty {
|
||||
use typst::foundations::func::Repr;
|
||||
match func.inner() {
|
||||
Repr::Element(..) | Repr::Native(..) | Repr::Plugin(..) => {
|
||||
if let Some(ty) = param_mapping(func, param) {
|
||||
return ty;
|
||||
}
|
||||
}
|
||||
Repr::Closure(_) => {}
|
||||
Repr::With(w) => return Ty::from_param_site(&w.0, param),
|
||||
};
|
||||
|
||||
Self::from_cast_info(¶m.input)
|
||||
}
|
||||
|
||||
pub(crate) fn from_return_site(func: &Func, ty: &'_ CastInfo) -> Self {
|
||||
use typst::foundations::func::Repr;
|
||||
match func.inner() {
|
||||
Repr::Element(elem) => return Ty::Builtin(BuiltinTy::Content(Some(*elem))),
|
||||
Repr::Closure(_) | Repr::Plugin(_) => {}
|
||||
Repr::With(w) => return Ty::from_return_site(&w.0, ty),
|
||||
Repr::Native(_) => {}
|
||||
};
|
||||
|
||||
Self::from_cast_info(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(ty) = iter.next() {
|
||||
match ty {
|
||||
CastInfo::Union(types) => {
|
||||
self.0.push(types.as_slice().iter());
|
||||
}
|
||||
_ => return Some(ty),
|
||||
}
|
||||
} else {
|
||||
self.0.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: we can write some proto files for builtin sigs
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum BuiltinSig<'a> {
|
||||
/// Map a function over a tuple.
|
||||
TupleMap(&'a Ty),
|
||||
/// Get element of a tuple.
|
||||
TupleAt(&'a Ty),
|
||||
}
|
||||
|
||||
/// A package identifier.
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PackageId {
|
||||
pub namespace: StrRef,
|
||||
pub name: StrRef,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PackageId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "@{}/{}", self.namespace, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<FileId> for PackageId {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: FileId) -> Result<Self, Self::Error> {
|
||||
let Some(spec) = value.package() else {
|
||||
return Err(());
|
||||
};
|
||||
Ok(PackageId {
|
||||
namespace: spec.namespace.as_str().into(),
|
||||
name: spec.name.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum BuiltinTy {
|
||||
Clause,
|
||||
Undef,
|
||||
Space,
|
||||
None,
|
||||
Break,
|
||||
Continue,
|
||||
Infer,
|
||||
FlowNone,
|
||||
Auto,
|
||||
|
||||
Args,
|
||||
Color,
|
||||
TextSize,
|
||||
TextFont,
|
||||
TextFeature,
|
||||
TextLang,
|
||||
TextRegion,
|
||||
|
||||
Label,
|
||||
CiteLabel,
|
||||
RefLabel,
|
||||
Dir,
|
||||
Length,
|
||||
Float,
|
||||
|
||||
Stroke,
|
||||
Margin,
|
||||
Inset,
|
||||
Outset,
|
||||
Radius,
|
||||
|
||||
Tag(Box<(StrRef, Option<Interned<PackageId>>)>),
|
||||
|
||||
/// A value having a specific type.
|
||||
Type(typst::foundations::Type),
|
||||
/// A value of some type.
|
||||
TypeType(typst::foundations::Type),
|
||||
/// A content having a specific element type.
|
||||
Content(Option<typst::foundations::Element>),
|
||||
/// A value of some element type.
|
||||
Element(typst::foundations::Element),
|
||||
|
||||
Module(Interned<Decl>),
|
||||
Path(PathPreference),
|
||||
}
|
||||
|
||||
impl fmt::Debug for BuiltinTy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BuiltinTy::Clause => f.write_str("Clause"),
|
||||
BuiltinTy::Undef => f.write_str("Undef"),
|
||||
BuiltinTy::Content(ty) => {
|
||||
if let Some(ty) = ty {
|
||||
write!(f, "Content({})", ty.name())
|
||||
} else {
|
||||
f.write_str("Content")
|
||||
}
|
||||
}
|
||||
BuiltinTy::Space => f.write_str("Space"),
|
||||
BuiltinTy::None => f.write_str("None"),
|
||||
BuiltinTy::Break => f.write_str("Break"),
|
||||
BuiltinTy::Continue => f.write_str("Continue"),
|
||||
BuiltinTy::Infer => f.write_str("Infer"),
|
||||
BuiltinTy::FlowNone => f.write_str("FlowNone"),
|
||||
BuiltinTy::Auto => f.write_str("Auto"),
|
||||
|
||||
BuiltinTy::Args => write!(f, "Args"),
|
||||
BuiltinTy::Color => write!(f, "Color"),
|
||||
BuiltinTy::TextSize => write!(f, "TextSize"),
|
||||
BuiltinTy::TextFont => write!(f, "TextFont"),
|
||||
BuiltinTy::TextFeature => write!(f, "TextFeature"),
|
||||
BuiltinTy::TextLang => write!(f, "TextLang"),
|
||||
BuiltinTy::TextRegion => write!(f, "TextRegion"),
|
||||
BuiltinTy::Dir => write!(f, "Dir"),
|
||||
BuiltinTy::Length => write!(f, "Length"),
|
||||
BuiltinTy::Label => write!(f, "Label"),
|
||||
BuiltinTy::CiteLabel => write!(f, "CiteLabel"),
|
||||
BuiltinTy::RefLabel => write!(f, "RefLabel"),
|
||||
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::TypeType(ty) => write!(f, "TypeType({})", ty.short_name()),
|
||||
BuiltinTy::Type(ty) => write!(f, "Type({})", ty.short_name()),
|
||||
BuiltinTy::Element(elem) => elem.fmt(f),
|
||||
BuiltinTy::Tag(tag) => {
|
||||
let (name, id) = tag.as_ref();
|
||||
if let Some(id) = id {
|
||||
write!(f, "Tag({name:?}) of {id:?}")
|
||||
} else {
|
||||
write!(f, "Tag({name:?})")
|
||||
}
|
||||
}
|
||||
BuiltinTy::Module(decl) => write!(f, "{decl:?}"),
|
||||
BuiltinTy::Path(preference) => write!(f, "Path({preference:?})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltinTy {
|
||||
pub fn from_value(builtin: &Value) -> Ty {
|
||||
if let Value::Bool(v) = builtin {
|
||||
return Ty::Boolean(Some(*v));
|
||||
}
|
||||
|
||||
Self::from_builtin(builtin.ty())
|
||||
}
|
||||
|
||||
pub fn from_builtin(builtin: Type) -> Ty {
|
||||
if builtin == Type::of::<AutoValue>() {
|
||||
return Ty::Builtin(BuiltinTy::Auto);
|
||||
}
|
||||
if builtin == Type::of::<NoneValue>() {
|
||||
return Ty::Builtin(BuiltinTy::None);
|
||||
}
|
||||
if builtin == Type::of::<typst::visualize::Color>() {
|
||||
return Color.literally();
|
||||
}
|
||||
if builtin == Type::of::<bool>() {
|
||||
return Ty::Builtin(BuiltinTy::None);
|
||||
}
|
||||
if builtin == Type::of::<f64>() {
|
||||
return Float.literally();
|
||||
}
|
||||
if builtin == Type::of::<Length>() {
|
||||
return Length.literally();
|
||||
}
|
||||
if builtin == Type::of::<Content>() {
|
||||
return Ty::Builtin(BuiltinTy::Content(Option::None));
|
||||
}
|
||||
|
||||
BuiltinTy::Type(builtin).literally()
|
||||
}
|
||||
|
||||
pub(crate) fn describe(&self) -> EcoString {
|
||||
let res = match self {
|
||||
BuiltinTy::Clause => "any",
|
||||
BuiltinTy::Undef => "any",
|
||||
BuiltinTy::Content(ty) => {
|
||||
return if let Some(ty) = ty {
|
||||
eco_format!("content({})", ty.name())
|
||||
} else {
|
||||
"content".into()
|
||||
};
|
||||
}
|
||||
BuiltinTy::Space => "content",
|
||||
BuiltinTy::None => "none",
|
||||
BuiltinTy::Break => "break",
|
||||
BuiltinTy::Continue => "continue",
|
||||
BuiltinTy::Infer => "any",
|
||||
BuiltinTy::FlowNone => "none",
|
||||
BuiltinTy::Auto => "auto",
|
||||
|
||||
BuiltinTy::Args => "arguments",
|
||||
BuiltinTy::Color => "color",
|
||||
BuiltinTy::TextSize => "text.size",
|
||||
BuiltinTy::TextFont => "text.font",
|
||||
BuiltinTy::TextFeature => "text.feature",
|
||||
BuiltinTy::TextLang => "text.lang",
|
||||
BuiltinTy::TextRegion => "text.region",
|
||||
BuiltinTy::Dir => "dir",
|
||||
BuiltinTy::Length => "length",
|
||||
BuiltinTy::Float => "float",
|
||||
BuiltinTy::Label => "label",
|
||||
BuiltinTy::CiteLabel => "cite-label",
|
||||
BuiltinTy::RefLabel => "ref-label",
|
||||
BuiltinTy::Stroke => "stroke",
|
||||
BuiltinTy::Margin => "margin",
|
||||
BuiltinTy::Inset => "inset",
|
||||
BuiltinTy::Outset => "outset",
|
||||
BuiltinTy::Radius => "radius",
|
||||
BuiltinTy::TypeType(..) => "type",
|
||||
BuiltinTy::Type(ty) => ty.short_name(),
|
||||
BuiltinTy::Element(ty) => ty.name(),
|
||||
BuiltinTy::Tag(tag) => {
|
||||
let (name, id) = tag.as_ref();
|
||||
return if let Some(id) = id {
|
||||
eco_format!("tag {name} of {id:?}")
|
||||
} else {
|
||||
eco_format!("tag {name}")
|
||||
};
|
||||
}
|
||||
BuiltinTy::Module(m) => return eco_format!("module({})", m.name()),
|
||||
BuiltinTy::Path(s) => match s {
|
||||
PathPreference::None => "[any]",
|
||||
PathPreference::Special => "[any]",
|
||||
PathPreference::Source { .. } => "[source]",
|
||||
PathPreference::Wasm => "[wasm]",
|
||||
PathPreference::Csv => "[csv]",
|
||||
PathPreference::Image => "[image]",
|
||||
PathPreference::Json => "[json]",
|
||||
PathPreference::Yaml => "[yaml]",
|
||||
PathPreference::Xml => "[xml]",
|
||||
PathPreference::Toml => "[toml]",
|
||||
PathPreference::Csl => "[csl]",
|
||||
PathPreference::Bibliography => "[bib]",
|
||||
PathPreference::RawTheme => "[theme]",
|
||||
PathPreference::RawSyntax => "[syntax]",
|
||||
},
|
||||
};
|
||||
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
|
||||
use BuiltinTy::*;
|
||||
|
||||
fn literally(s: impl FlowBuiltinLiterally) -> Ty {
|
||||
s.literally()
|
||||
}
|
||||
|
||||
trait FlowBuiltinLiterally {
|
||||
fn literally(self) -> Ty;
|
||||
}
|
||||
|
||||
impl FlowBuiltinLiterally for &str {
|
||||
fn literally(self) -> Ty {
|
||||
Ty::Value(InsTy::new(Value::Str(self.into())))
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowBuiltinLiterally for BuiltinTy {
|
||||
fn literally(self) -> Ty {
|
||||
Ty::Builtin(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowBuiltinLiterally for Ty {
|
||||
fn literally(self) -> Ty {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// separate by middle
|
||||
macro_rules! flow_builtin_union_inner {
|
||||
($literal_kind:expr) => {
|
||||
literally($literal_kind)
|
||||
};
|
||||
($($x:expr),+ $(,)?) => {
|
||||
Vec::from_iter([
|
||||
$(flow_builtin_union_inner!($x)),*
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! flow_union {
|
||||
// the first one is string
|
||||
($($b:tt)*) => {
|
||||
Ty::iter_union(flow_builtin_union_inner!( $($b)* ).into_iter())
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
macro_rules! flow_record {
|
||||
($($name:expr => $ty:expr),* $(,)?) => {
|
||||
RecordTy::new(vec![
|
||||
$(
|
||||
(
|
||||
$name.into(),
|
||||
$ty,
|
||||
),
|
||||
)*
|
||||
])
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) fn param_mapping(func: &Func, param: &ParamInfo) -> Option<Ty> {
|
||||
// todo: remove path params which is compatible with 0.12.0
|
||||
match (func.name()?, param.name) {
|
||||
// todo: pdf.embed
|
||||
("embed", "path") => Some(literally(Path(PathPreference::None))),
|
||||
("cbor", "path" | "source") => Some(literally(Path(PathPreference::None))),
|
||||
("plugin", "source") => Some(literally(Path(PathPreference::Wasm))),
|
||||
("csv", "path" | "source") => Some(literally(Path(PathPreference::Csv))),
|
||||
("image", "path" | "source") => Some(literally(Path(PathPreference::Image))),
|
||||
("read", "path" | "source") => Some(literally(Path(PathPreference::None))),
|
||||
("json", "path" | "source") => Some(literally(Path(PathPreference::Json))),
|
||||
("yaml", "path" | "source") => Some(literally(Path(PathPreference::Yaml))),
|
||||
("xml", "path" | "source") => Some(literally(Path(PathPreference::Xml))),
|
||||
("toml", "path" | "source") => Some(literally(Path(PathPreference::Toml))),
|
||||
("raw", "theme") => Some(literally(Path(PathPreference::RawTheme))),
|
||||
("raw", "syntaxes") => Some(literally(Path(PathPreference::RawSyntax))),
|
||||
("bibliography" | "cite", "style") => Some(Ty::iter_union([
|
||||
literally(Path(PathPreference::Csl)),
|
||||
Ty::from_cast_info(¶m.input),
|
||||
])),
|
||||
("cite", "key") => Some(Ty::iter_union([literally(CiteLabel)])),
|
||||
("ref", "target") => Some(Ty::iter_union([literally(RefLabel)])),
|
||||
("footnote", "body") => Some(Ty::iter_union([
|
||||
literally(RefLabel),
|
||||
Ty::from_cast_info(¶m.input),
|
||||
])),
|
||||
("link", "dest") => {
|
||||
static LINK_DEST_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
flow_union!(
|
||||
literally(RefLabel),
|
||||
Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Str>())),
|
||||
Ty::Builtin(BuiltinTy::Type(Type::of::<typst::introspection::Location>())),
|
||||
Ty::Dict(RecordTy::new(vec![
|
||||
("x".into(), literally(Length)),
|
||||
("y".into(), literally(Length)),
|
||||
])),
|
||||
)
|
||||
});
|
||||
Some(LINK_DEST_TYPE.clone())
|
||||
}
|
||||
("bibliography", "path" | "sources") => {
|
||||
static BIB_PATH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
let bib_path_ty = literally(Path(PathPreference::Bibliography));
|
||||
Ty::iter_union([bib_path_ty.clone(), Ty::Array(bib_path_ty.into())])
|
||||
});
|
||||
Some(BIB_PATH_TYPE.clone())
|
||||
}
|
||||
("text", "size") => Some(literally(TextSize)),
|
||||
("text", "font") => {
|
||||
// todo: the dict can be completed, but we have bugs...
|
||||
static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
Ty::iter_union([literally(TextFont), Ty::Array(literally(TextFont).into())])
|
||||
});
|
||||
Some(FONT_TYPE.clone())
|
||||
}
|
||||
("text", "feature") => {
|
||||
static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
Ty::iter_union([
|
||||
// todo: the key can only be the text feature
|
||||
Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Dict>())),
|
||||
Ty::Array(literally(TextFeature).into()),
|
||||
])
|
||||
});
|
||||
Some(FONT_TYPE.clone())
|
||||
}
|
||||
("text", "costs") => {
|
||||
static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
Ty::Dict(flow_record!(
|
||||
"hyphenation" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
|
||||
"runt" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
|
||||
"widow" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
|
||||
"orphan" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
|
||||
))
|
||||
});
|
||||
Some(FONT_TYPE.clone())
|
||||
}
|
||||
("text", "lang") => Some(literally(TextLang)),
|
||||
("text", "region") => Some(literally(TextRegion)),
|
||||
("text" | "stack", "dir") => Some(literally(Dir)),
|
||||
("par", "first-line-indent") => {
|
||||
static FIRST_LINE_INDENT: LazyLock<Ty> = LazyLock::new(|| {
|
||||
Ty::iter_union([
|
||||
literally(Length),
|
||||
Ty::Dict(RecordTy::new(vec![
|
||||
("amount".into(), literally(Length)),
|
||||
("all".into(), Ty::Boolean(Option::None)),
|
||||
])),
|
||||
])
|
||||
});
|
||||
Some(FIRST_LINE_INDENT.clone())
|
||||
}
|
||||
(
|
||||
// todo: polygon.regular
|
||||
"page" | "highlight" | "text" | "path" | "curve" | "rect" | "ellipse" | "circle"
|
||||
| "polygon" | "box" | "block" | "table" | "regular",
|
||||
"fill",
|
||||
) => Some(literally(Color)),
|
||||
(
|
||||
// todo: table.cell
|
||||
"table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
|
||||
"inset",
|
||||
) => Some(literally(Inset)),
|
||||
("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
|
||||
Some(literally(Outset))
|
||||
}
|
||||
("block" | "box" | "rect" | "square" | "highlight", "radius") => Some(literally(Radius)),
|
||||
("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => {
|
||||
static COLUMN_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
flow_union!(
|
||||
Ty::Value(InsTy::new(Value::Auto)),
|
||||
Ty::Value(InsTy::new(Value::Type(Type::of::<i64>()))),
|
||||
literally(Length),
|
||||
Ty::Array(literally(Length).into()),
|
||||
)
|
||||
});
|
||||
Some(COLUMN_TYPE.clone())
|
||||
}
|
||||
("pattern" | "tiling", "size") => {
|
||||
static PATTERN_SIZE_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
flow_union!(
|
||||
Ty::Value(InsTy::new(Value::Auto)),
|
||||
Ty::Array(Ty::Builtin(Length).into()),
|
||||
)
|
||||
});
|
||||
Some(PATTERN_SIZE_TYPE.clone())
|
||||
}
|
||||
("stroke", "dash") => Some(FLOW_STROKE_DASH_TYPE.clone()),
|
||||
(
|
||||
//todo: table.cell, table.hline, table.vline, math.cancel, grid.cell, polygon.regular
|
||||
"cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path"
|
||||
| "curve" | "rect" | "ellipse" | "circle" | "polygon" | "box" | "block" | "table"
|
||||
| "line" | "cell" | "hline" | "vline" | "regular",
|
||||
"stroke",
|
||||
) => Some(Ty::Builtin(Stroke)),
|
||||
("page", "margin") => Some(Ty::Builtin(Margin)),
|
||||
_ => Option::None,
|
||||
}
|
||||
}
|
||||
|
||||
static FLOW_STROKE_DASH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
|
||||
flow_union!(
|
||||
"solid",
|
||||
"dotted",
|
||||
"densely-dotted",
|
||||
"loosely-dotted",
|
||||
"dashed",
|
||||
"densely-dashed",
|
||||
"loosely-dashed",
|
||||
"dash-dotted",
|
||||
"densely-dash-dotted",
|
||||
"loosely-dash-dotted",
|
||||
Ty::Array(flow_union!("dot", literally(Float)).into()),
|
||||
Ty::Dict(flow_record!(
|
||||
"array" => Ty::Array(flow_union!("dot", literally(Float)).into()),
|
||||
"phase" => literally(Length),
|
||||
))
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_STROKE_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"paint" => literally(Color),
|
||||
"thickness" => literally(Length),
|
||||
"cap" => flow_union!("butt", "round", "square"),
|
||||
"join" => flow_union!("miter", "round", "bevel"),
|
||||
"dash" => FLOW_STROKE_DASH_TYPE.clone(),
|
||||
"miter-limit" => literally(Float),
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_MARGIN_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
"bottom" => literally(Length),
|
||||
"left" => literally(Length),
|
||||
"inside" => literally(Length),
|
||||
"outside" => literally(Length),
|
||||
"x" => literally(Length),
|
||||
"y" => literally(Length),
|
||||
"rest" => literally(Length),
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_INSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
"bottom" => literally(Length),
|
||||
"left" => literally(Length),
|
||||
"x" => literally(Length),
|
||||
"y" => literally(Length),
|
||||
"rest" => literally(Length),
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_OUTSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
"bottom" => literally(Length),
|
||||
"left" => literally(Length),
|
||||
"x" => literally(Length),
|
||||
"y" => literally(Length),
|
||||
"rest" => literally(Length),
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_RADIUS_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"top" => literally(Length),
|
||||
"right" => literally(Length),
|
||||
"bottom" => literally(Length),
|
||||
"left" => literally(Length),
|
||||
"top-left" => literally(Length),
|
||||
"top-right" => literally(Length),
|
||||
"bottom-left" => literally(Length),
|
||||
"bottom-right" => literally(Length),
|
||||
"rest" => literally(Length),
|
||||
)
|
||||
});
|
||||
|
||||
pub static FLOW_TEXT_FONT_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
|
||||
flow_record!(
|
||||
"name" => literally(TextFont),
|
||||
"covers" => flow_union!("latin-in-cjk", BuiltinTy::Type(Type::of::<Regex>())),
|
||||
)
|
||||
});
|
||||
|
||||
// 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.fill/align/stroke/inset can be a function
|
||||
// todo: math.cancel.angle can be a function
|
||||
// todo: math.mat.augment
|
||||
// todo: csv.row-type can be an array or a dictionary
|
||||
// todo: text.stylistic-set is an array of integer
|
||||
// todo: raw.lang can be completed
|
||||
// todo: smartquote.quotes can be an array or a dictionary
|
||||
// todo: mat.augment can be a dictionary
|
||||
// todo: pdf.embed mime-type can be special
|
||||
|
||||
// ISO 639
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::syntax::Decl;
|
||||
|
||||
use super::{SigTy, Ty, TypeVar};
|
||||
|
||||
#[test]
|
||||
fn test_image_extension() {
|
||||
let path = "test.png";
|
||||
let preference = super::PathPreference::from_ext(path).unwrap();
|
||||
assert_eq!(preference, super::PathPreference::Image);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_image_extension_uppercase() {
|
||||
let path = "TEST.PNG";
|
||||
let preference = super::PathPreference::from_ext(path).unwrap();
|
||||
assert_eq!(preference, super::PathPreference::Image);
|
||||
}
|
||||
|
||||
// todo: map function
|
||||
// Technical Note for implementing a map function:
|
||||
// `u`, `v` is in level 2
|
||||
// instantiate a `v` as the return type of the map function.
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let u = Ty::Var(TypeVar::new("u".into(), Decl::lit("u").into()));
|
||||
let v = Ty::Var(TypeVar::new("v".into(), Decl::lit("v").into()));
|
||||
let mapper_fn =
|
||||
Ty::Func(SigTy::new([u].into_iter(), None, None, None, Some(v.clone())).into());
|
||||
let map_fn =
|
||||
Ty::Func(SigTy::new([mapper_fn].into_iter(), None, None, None, Some(v)).into());
|
||||
let _ = map_fn;
|
||||
// println!("{map_fn:?}");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,229 +0,0 @@
|
|||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_std::hash::hash128;
|
||||
use tinymist_world::vfs::WorkspaceResolver;
|
||||
use typst::foundations::Repr;
|
||||
|
||||
use crate::{
|
||||
analysis::{is_plain_value, term_value},
|
||||
ty::prelude::*,
|
||||
upstream::truncated_repr_,
|
||||
};
|
||||
|
||||
impl Ty {
|
||||
/// Describe the given type.
|
||||
pub fn repr(&self) -> Option<EcoString> {
|
||||
let mut worker = TypeDescriber {
|
||||
repr: true,
|
||||
..Default::default()
|
||||
};
|
||||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
/// Describe available value instances of the given type.
|
||||
pub fn value_repr(&self) -> Option<EcoString> {
|
||||
let mut worker = TypeDescriber {
|
||||
repr: true,
|
||||
value: true,
|
||||
..Default::default()
|
||||
};
|
||||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
/// Describe the given type.
|
||||
pub fn describe(&self) -> Option<EcoString> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
// todo: extend this cache idea for all crate?
|
||||
// #[allow(clippy::mutable_key_type)]
|
||||
// let mut describe_cache = HashMap::<Ty, String>::new();
|
||||
// let doc_ty = |ty: Option<&Ty>| {
|
||||
// let ty = ty?;
|
||||
// let short = {
|
||||
// describe_cache
|
||||
// .entry(ty.clone())
|
||||
// .or_insert_with(|| ty.describe().unwrap_or_else(||
|
||||
// "unknown".to_string())) .clone()
|
||||
// };
|
||||
|
||||
// Some((short, format!("{ty:?}")))
|
||||
// };
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TypeDescriber {
|
||||
repr: bool,
|
||||
value: bool,
|
||||
described: HashMap<u128, EcoString>,
|
||||
results: HashSet<EcoString>,
|
||||
functions: Vec<Interned<SigTy>>,
|
||||
}
|
||||
|
||||
impl TypeDescriber {
|
||||
fn describe_root(&mut self, ty: &Ty) -> Option<EcoString> {
|
||||
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".into());
|
||||
|
||||
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 func = functions[0].clone();
|
||||
|
||||
let mut res = EcoString::new();
|
||||
res.push('(');
|
||||
let mut not_first = false;
|
||||
for ty in func.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 (name, ty) in func.named_params() {
|
||||
if not_first {
|
||||
res.push_str(", ");
|
||||
} else {
|
||||
not_first = true;
|
||||
}
|
||||
res.push_str(name);
|
||||
res.push_str(": ");
|
||||
res.push_str(self.describe_root(ty).as_deref().unwrap_or("any"));
|
||||
}
|
||||
if let Some(spread_right) = func.rest_param() {
|
||||
if not_first {
|
||||
res.push_str(", ");
|
||||
}
|
||||
res.push_str("..: ");
|
||||
res.push_str(self.describe_root(spread_right).as_deref().unwrap_or("any"));
|
||||
}
|
||||
res.push_str(") => ");
|
||||
res.push_str(
|
||||
func.body
|
||||
.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".into());
|
||||
return None;
|
||||
}
|
||||
|
||||
results.sort();
|
||||
results.dedup();
|
||||
let res: EcoString = results.join(" | ").into();
|
||||
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) -> EcoString {
|
||||
match ty {
|
||||
Ty::Var(..) => {}
|
||||
Ty::Union(types) => {
|
||||
self.describe_iter(types);
|
||||
}
|
||||
Ty::Let(bounds) => {
|
||||
self.describe_iter(&bounds.lbs);
|
||||
self.describe_iter(&bounds.ubs);
|
||||
}
|
||||
Ty::Func(func) => {
|
||||
self.functions.push(func.clone());
|
||||
}
|
||||
Ty::Dict(..) => {
|
||||
return "dictionary".into();
|
||||
}
|
||||
Ty::Tuple(..) => {
|
||||
return "array".into();
|
||||
}
|
||||
Ty::Array(..) => {
|
||||
return "array".into();
|
||||
}
|
||||
// todo: sig with
|
||||
Ty::With(w) => {
|
||||
return self.describe(&w.sig);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Content(Some(elem))) => {
|
||||
return elem.name().into();
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Content(None) | BuiltinTy::Space) => {
|
||||
return "content".into();
|
||||
}
|
||||
// Doesn't provide any information, hence we doesn't describe it intermediately here.
|
||||
Ty::Any | Ty::Builtin(BuiltinTy::Clause | BuiltinTy::Undef | BuiltinTy::Infer) => {}
|
||||
Ty::Builtin(BuiltinTy::FlowNone | BuiltinTy::None) => {
|
||||
return "none".into();
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Auto) => {
|
||||
return "auto".into();
|
||||
}
|
||||
Ty::Boolean(..) if self.repr => {
|
||||
return "bool".into();
|
||||
}
|
||||
Ty::Boolean(None) => {
|
||||
return "bool".into();
|
||||
}
|
||||
Ty::Boolean(Some(b)) => {
|
||||
return eco_format!("{b}");
|
||||
}
|
||||
Ty::Builtin(b) => {
|
||||
return b.describe();
|
||||
}
|
||||
Ty::Value(v) if matches!(v.val, Value::Module(..)) => {
|
||||
let Value::Module(m) = &v.val else {
|
||||
return "module".into();
|
||||
};
|
||||
|
||||
return match (m.name(), m.file_id()) {
|
||||
(Some(name), ..) => eco_format!("module({name:?})"),
|
||||
(None, file_id) => {
|
||||
eco_format!("module({:?})", WorkspaceResolver::display(file_id))
|
||||
}
|
||||
};
|
||||
}
|
||||
Ty::Value(v) if !is_plain_value(&v.val) => return self.describe(&term_value(&v.val)),
|
||||
Ty::Value(v) if self.value => return truncated_repr_::<181>(&v.val),
|
||||
Ty::Value(v) if self.repr => return v.val.ty().short_name().into(),
|
||||
Ty::Value(v) => return v.val.repr(),
|
||||
Ty::Param(..) => {
|
||||
return "param".into();
|
||||
}
|
||||
Ty::Args(..) => {
|
||||
return "arguments".into();
|
||||
}
|
||||
Ty::Pattern(..) => {
|
||||
return "pattern".into();
|
||||
}
|
||||
Ty::Select(..) | Ty::Unary(..) | Ty::Binary(..) | Ty::If(..) => return "any".into(),
|
||||
}
|
||||
|
||||
EcoString::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
use typst::foundations::{Dict, Func, Module, Scope, Type};
|
||||
use typst::syntax::FileId;
|
||||
|
||||
use super::BoundChecker;
|
||||
use crate::{syntax::Decl, ty::prelude::*};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Iface<'a> {
|
||||
Array(&'a Interned<Ty>),
|
||||
Tuple(&'a Interned<Vec<Ty>>),
|
||||
Dict(&'a Interned<RecordTy>),
|
||||
Content {
|
||||
val: &'a typst::foundations::Element,
|
||||
at: &'a Ty,
|
||||
},
|
||||
TypeType {
|
||||
val: &'a typst::foundations::Type,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Type {
|
||||
val: &'a typst::foundations::Type,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Func {
|
||||
val: &'a typst::foundations::Func,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Value {
|
||||
val: &'a Dict,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Module {
|
||||
val: FileId,
|
||||
at: &'a Ty,
|
||||
},
|
||||
ModuleVal {
|
||||
val: &'a Module,
|
||||
at: &'a Ty,
|
||||
},
|
||||
}
|
||||
|
||||
impl Iface<'_> {
|
||||
pub fn to_type(self) -> Ty {
|
||||
match self {
|
||||
Iface::Array(ty) => Ty::Array(ty.clone()),
|
||||
Iface::Tuple(tys) => Ty::Tuple(tys.clone()),
|
||||
Iface::Dict(dict) => Ty::Dict(dict.clone()),
|
||||
Iface::Content { at, .. }
|
||||
| Iface::TypeType { at, .. }
|
||||
| Iface::Type { at, .. }
|
||||
| Iface::Func { at, .. }
|
||||
| Iface::Value { at, .. }
|
||||
| Iface::Module { at, .. }
|
||||
| Iface::ModuleVal { at, .. } => at.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// IfaceShape { iface }
|
||||
pub fn select(self, ctx: &mut impl TyCtxMut, key: &StrRef) -> Option<Ty> {
|
||||
crate::log_debug_ct!("iface shape: {self:?}");
|
||||
|
||||
match self {
|
||||
Iface::Array(..) | Iface::Tuple(..) => {
|
||||
select_scope(Some(Type::of::<typst::foundations::Array>().scope()), key)
|
||||
}
|
||||
Iface::Dict(dict) => dict.field_by_name(key).cloned(),
|
||||
Iface::Content { val, .. } => select_scope(Some(val.scope()), key),
|
||||
// todo: distinguish TypeType and Type
|
||||
Iface::TypeType { val, .. } | Iface::Type { val, .. } => {
|
||||
select_scope(Some(val.scope()), key)
|
||||
}
|
||||
Iface::Func { val, .. } => select_scope(val.scope(), key),
|
||||
Iface::Value { val, at: _ } => ctx.type_of_dict(val).field_by_name(key).cloned(),
|
||||
Iface::Module { val, at: _ } => ctx.check_module_item(val, key),
|
||||
Iface::ModuleVal { val, at: _ } => ctx.type_of_module(val).field_by_name(key).cloned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_scope(scope: Option<&Scope>, key: &str) -> Option<Ty> {
|
||||
let scope = scope?;
|
||||
let sub = scope.get(key)?;
|
||||
let sub_span = sub.span();
|
||||
Some(Ty::Value(InsTy::new_at(sub.read().clone(), sub_span)))
|
||||
}
|
||||
|
||||
pub trait IfaceChecker: TyCtx {
|
||||
fn check(&mut self, iface: Iface, ctx: &mut IfaceCheckContext, pol: bool) -> Option<()>;
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Iterate over the signatures of the given type.
|
||||
pub fn iface_surface(
|
||||
&self,
|
||||
pol: bool,
|
||||
// iface_kind: IfaceSurfaceKind,
|
||||
checker: &mut impl IfaceChecker,
|
||||
) {
|
||||
let context = IfaceCheckContext { args: Vec::new() };
|
||||
let mut worker = IfaceCheckDriver {
|
||||
ctx: context,
|
||||
checker,
|
||||
};
|
||||
|
||||
worker.ty(self, pol);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IfaceCheckContext {
|
||||
pub args: Vec<Interned<SigTy>>,
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(checker)]
|
||||
pub struct IfaceCheckDriver<'a> {
|
||||
ctx: IfaceCheckContext,
|
||||
checker: &'a mut dyn IfaceChecker,
|
||||
}
|
||||
|
||||
impl BoundChecker for IfaceCheckDriver<'_> {
|
||||
fn collect(&mut self, ty: &Ty, pol: bool) {
|
||||
self.ty(ty, pol);
|
||||
}
|
||||
}
|
||||
|
||||
impl IfaceCheckDriver<'_> {
|
||||
fn array_as_iface(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn dict_as_iface(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn value_as_iface(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn ty(&mut self, at: &Ty, pol: bool) {
|
||||
crate::log_debug_ct!("check iface ty: {at:?}");
|
||||
|
||||
match at {
|
||||
Ty::Builtin(BuiltinTy::Stroke) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_STROKE_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Margin) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_MARGIN_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Inset) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_INSET_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Outset) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_OUTSET_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Radius) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_RADIUS_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::TextFont) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_TEXT_FONT_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Value(ins_ty) => {
|
||||
// todo: deduplicate checking early
|
||||
if self.value_as_iface() {
|
||||
match &ins_ty.val {
|
||||
Value::Module(val) => {
|
||||
self.checker
|
||||
.check(Iface::ModuleVal { val, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::Dict(dict) => {
|
||||
self.checker
|
||||
.check(Iface::Value { val: dict, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::Type(ty) => {
|
||||
self.checker
|
||||
.check(Iface::TypeType { val: ty, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::Func(func) => {
|
||||
self.checker
|
||||
.check(Iface::Func { val: func, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::None
|
||||
| Value::Auto
|
||||
| Value::Bool(_)
|
||||
| Value::Int(_)
|
||||
| Value::Float(_)
|
||||
| Value::Length(..)
|
||||
| Value::Angle(..)
|
||||
| Value::Ratio(..)
|
||||
| Value::Relative(..)
|
||||
| Value::Fraction(..)
|
||||
| Value::Color(..)
|
||||
| Value::Gradient(..)
|
||||
| Value::Tiling(..)
|
||||
| Value::Symbol(..)
|
||||
| Value::Version(..)
|
||||
| Value::Str(..)
|
||||
| Value::Bytes(..)
|
||||
| Value::Label(..)
|
||||
| Value::Datetime(..)
|
||||
| Value::Decimal(..)
|
||||
| Value::Duration(..)
|
||||
| Value::Content(..)
|
||||
| Value::Styles(..)
|
||||
| Value::Array(..)
|
||||
| Value::Args(..)
|
||||
| Value::Dyn(..) => {
|
||||
self.checker.check(
|
||||
Iface::Type {
|
||||
val: &ins_ty.val.ty(),
|
||||
at,
|
||||
},
|
||||
&mut self.ctx,
|
||||
pol,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: more builtin types to check
|
||||
Ty::Builtin(BuiltinTy::Content(Some(elem))) if self.value_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Content { val: elem, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Content(..)) if self.value_as_iface() => {
|
||||
let ty = Type::of::<typst::foundations::Content>();
|
||||
self.checker
|
||||
.check(Iface::Type { val: &ty, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Type(ty)) if self.value_as_iface() => {
|
||||
// todo: distinguish between element and function
|
||||
self.checker
|
||||
.check(Iface::Type { val: ty, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Element(elem)) if self.value_as_iface() => {
|
||||
self.checker.check(
|
||||
Iface::Func {
|
||||
val: &Func::from(*elem),
|
||||
at,
|
||||
},
|
||||
&mut self.ctx,
|
||||
pol,
|
||||
);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Module(module)) => {
|
||||
if let Decl::Module(m) = module.as_ref() {
|
||||
self.checker
|
||||
.check(Iface::Module { val: m.fid, at }, &mut self.ctx, pol);
|
||||
}
|
||||
}
|
||||
// Ty::Func(..) if self.value_as_iface() => {
|
||||
// self.checker.check(Iface::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(Iface::ArrayCons(sig), &mut self.ctx, pol);
|
||||
// }
|
||||
// // todo: tuple
|
||||
// Ty::Tuple(_) => {}
|
||||
Ty::Dict(sig) if self.dict_as_iface() => {
|
||||
// self.check_dict_signature(sig, pol, self.checker);
|
||||
self.checker.check(Iface::Dict(sig), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Tuple(sig) if self.array_as_iface() => {
|
||||
// self.check_dict_signature(sig, pol, self.checker);
|
||||
self.checker.check(Iface::Tuple(sig), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Array(sig) if self.array_as_iface() => {
|
||||
// self.check_dict_signature(sig, pol, self.checker);
|
||||
self.checker.check(Iface::Array(sig), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Dict(..) => {
|
||||
self.checker.check(
|
||||
Iface::Type {
|
||||
val: &Type::of::<typst::foundations::Dict>(),
|
||||
at,
|
||||
},
|
||||
&mut self.ctx,
|
||||
pol,
|
||||
);
|
||||
}
|
||||
Ty::Tuple(..) | Ty::Array(..) => {
|
||||
self.checker.check(
|
||||
Iface::Type {
|
||||
val: &Type::of::<typst::foundations::Array>(),
|
||||
at,
|
||||
},
|
||||
&mut self.ctx,
|
||||
pol,
|
||||
);
|
||||
}
|
||||
Ty::Var(..) => at.bounds(pol, self),
|
||||
_ if at.has_bounds() => at.bounds(pol, self),
|
||||
_ => {}
|
||||
}
|
||||
// Ty::Select(sel) => sel.ty.bounds(pol, &mut MethodDriver(self,
|
||||
// &sel.select)), // todo: calculate these operators
|
||||
// Ty::Unary(_) => {}
|
||||
// Ty::Binary(_) => {}
|
||||
// Ty::If(_) => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
//! Types and type operations for Typst.
|
||||
|
||||
mod apply;
|
||||
mod bound;
|
||||
mod builtin;
|
||||
mod def;
|
||||
mod describe;
|
||||
mod iface;
|
||||
mod mutate;
|
||||
mod prelude;
|
||||
mod select;
|
||||
mod sig;
|
||||
mod simplify;
|
||||
mod subst;
|
||||
|
||||
pub(crate) use apply::*;
|
||||
pub(crate) use bound::*;
|
||||
pub(crate) use builtin::*;
|
||||
pub use def::*;
|
||||
pub(crate) use iface::*;
|
||||
pub(crate) use mutate::*;
|
||||
pub(crate) use select::*;
|
||||
pub(crate) use sig::*;
|
||||
use typst::foundations::{self, Func, Module, Value};
|
||||
use typst::syntax::FileId;
|
||||
|
||||
/// A type context.
|
||||
pub trait TyCtx {
|
||||
/// Get local binding of a variable.
|
||||
fn local_bind_of(&self, _var: &Interned<TypeVar>) -> Option<Ty>;
|
||||
/// Get the type of a variable.
|
||||
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<DynTypeBounds>;
|
||||
}
|
||||
|
||||
impl TyCtx for () {
|
||||
fn local_bind_of(&self, _var: &Interned<TypeVar>) -> Option<Ty> {
|
||||
None
|
||||
}
|
||||
|
||||
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<DynTypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A mutable type context.
|
||||
pub trait TyCtxMut: TyCtx {
|
||||
/// The type of a snapshot of the scope.
|
||||
type Snap;
|
||||
|
||||
/// Start a new scope.
|
||||
#[must_use]
|
||||
fn start_scope(&mut self) -> Self::Snap;
|
||||
/// End the current scope.
|
||||
fn end_scope(&mut self, snap: Self::Snap);
|
||||
/// Execute a function with a new scope.
|
||||
fn with_scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let snap = self.start_scope();
|
||||
let res = f(self);
|
||||
self.end_scope(snap);
|
||||
res
|
||||
}
|
||||
|
||||
/// Bind a variable locally.
|
||||
fn bind_local(&mut self, var: &Interned<TypeVar>, ty: Ty);
|
||||
/// Get the type of a runtime function.
|
||||
fn type_of_func(&mut self, func: &Func) -> Option<Interned<SigTy>>;
|
||||
/// Get the type of a runtime value.
|
||||
fn type_of_value(&mut self, val: &Value) -> Ty;
|
||||
/// Get the type of a runtime dict.
|
||||
fn type_of_dict(&mut self, dict: &foundations::Dict) -> Interned<RecordTy> {
|
||||
let ty = self.type_of_value(&Value::Dict(dict.clone()));
|
||||
let Ty::Dict(ty) = ty else {
|
||||
panic!("expected dict type, found {ty:?}");
|
||||
};
|
||||
ty
|
||||
}
|
||||
/// Get the type of a runtime module.
|
||||
fn type_of_module(&mut self, module: &Module) -> Interned<RecordTy> {
|
||||
let ty = self.type_of_value(&Value::Module(module.clone()));
|
||||
let Ty::Dict(ty) = ty else {
|
||||
panic!("expected dict type, found {ty:?}");
|
||||
};
|
||||
ty
|
||||
}
|
||||
/// Check a module item.
|
||||
fn check_module_item(&mut self, module: FileId, key: &StrRef) -> Option<Ty>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::adt::interner::Interned;
|
||||
use crate::syntax::Decl;
|
||||
|
||||
pub fn var_ins(s: &str) -> Ty {
|
||||
Ty::Var(TypeVar::new(s.into(), Decl::lit(s).into()))
|
||||
}
|
||||
|
||||
pub fn str_sig(
|
||||
pos: &[&str],
|
||||
named: &[(&str, &str)],
|
||||
rest: Option<&str>,
|
||||
ret: Option<&str>,
|
||||
) -> Interned<SigTy> {
|
||||
let pos = pos.iter().map(|s| var_ins(s));
|
||||
let named = named.iter().map(|(n, t)| ((*n).into(), var_ins(t)));
|
||||
let rest = rest.map(var_ins);
|
||||
let ret = ret.map(var_ins);
|
||||
SigTy::new(pos, named, None, rest, ret).into()
|
||||
}
|
||||
|
||||
// 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)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use literal_sig;
|
||||
pub(crate) use literal_sig as literal_args;
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
use crate::ty::def::*;
|
||||
|
||||
pub trait TyMutator {
|
||||
fn mutate(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
|
||||
self.mutate_rec(ty, pol)
|
||||
}
|
||||
fn mutate_rec(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
|
||||
use Ty::*;
|
||||
match ty {
|
||||
Value(..) | Any | Boolean(..) | Builtin(..) => None,
|
||||
Union(v) => Some(Union(self.mutate_vec(v, pol)?)),
|
||||
Var(..) | Let(..) => None,
|
||||
Array(arr) => Some(Array(self.mutate(arr, pol)?.into())),
|
||||
Dict(dict) => Some(Dict(self.mutate_record(dict, pol)?.into())),
|
||||
Tuple(tup) => Some(Tuple(self.mutate_vec(tup, pol)?)),
|
||||
Func(func) => Some(Func(self.mutate_func(func, pol)?.into())),
|
||||
Args(args) => Some(Args(self.mutate_func(args, pol)?.into())),
|
||||
Pattern(pat) => Some(Pattern(self.mutate_func(pat, pol)?.into())),
|
||||
Param(param) => Some(Param(self.mutate_param(param, pol)?.into())),
|
||||
Select(sel) => Some(Select(self.mutate_select(sel, pol)?.into())),
|
||||
With(sig) => Some(With(self.mutate_with_sig(sig, pol)?.into())),
|
||||
Unary(unary) => Some(Unary(self.mutate_unary(unary, pol)?.into())),
|
||||
Binary(binary) => Some(Binary(self.mutate_binary(binary, pol)?.into())),
|
||||
If(if_expr) => Some(If(self.mutate_if(if_expr, pol)?.into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate_vec(&mut self, ty: &[Ty], pol: bool) -> Option<Interned<Vec<Ty>>> {
|
||||
let mut mutated = false;
|
||||
|
||||
let mut types = Vec::with_capacity(ty.len());
|
||||
for ty in ty.iter() {
|
||||
match self.mutate(ty, pol) {
|
||||
Some(ty) => {
|
||||
types.push(ty);
|
||||
mutated = true;
|
||||
}
|
||||
None => types.push(ty.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
if mutated {
|
||||
Some(types.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate_option(&mut self, ty: Option<&Ty>, pol: bool) -> Option<Option<Ty>> {
|
||||
match ty {
|
||||
Some(ty) => self.mutate(ty, pol).map(Some),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate_func(&mut self, ty: &Interned<SigTy>, pol: bool) -> Option<SigTy> {
|
||||
let types = self.mutate_vec(&ty.inputs, pol);
|
||||
let ret = self.mutate_option(ty.body.as_ref(), pol);
|
||||
|
||||
if types.is_none() && ret.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sig = ty.as_ref().clone();
|
||||
let types = types.unwrap_or_else(|| ty.inputs.clone());
|
||||
let ret = ret.unwrap_or_else(|| ty.body.clone());
|
||||
Some(SigTy {
|
||||
inputs: types,
|
||||
body: ret,
|
||||
..sig
|
||||
})
|
||||
}
|
||||
|
||||
fn mutate_param(&mut self, param: &Interned<ParamTy>, pol: bool) -> Option<ParamTy> {
|
||||
let ty = self.mutate(¶m.ty, pol)?;
|
||||
let mut param = param.as_ref().clone();
|
||||
param.ty = ty;
|
||||
Some(param)
|
||||
}
|
||||
|
||||
fn mutate_record(&mut self, record: &Interned<RecordTy>, pol: bool) -> Option<RecordTy> {
|
||||
let types = self.mutate_vec(&record.types, pol)?;
|
||||
|
||||
let rec = record.as_ref().clone();
|
||||
Some(RecordTy { types, ..rec })
|
||||
}
|
||||
|
||||
fn mutate_with_sig(&mut self, ty: &Interned<SigWithTy>, pol: bool) -> Option<SigWithTy> {
|
||||
let sig = self.mutate(ty.sig.as_ref(), pol);
|
||||
let with = self.mutate_func(&ty.with, pol);
|
||||
|
||||
if sig.is_none() && with.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sig = sig.map(Interned::new).unwrap_or_else(|| ty.sig.clone());
|
||||
let with = with.map(Interned::new).unwrap_or_else(|| ty.with.clone());
|
||||
|
||||
Some(SigWithTy { sig, with })
|
||||
}
|
||||
|
||||
fn mutate_unary(&mut self, ty: &Interned<TypeUnary>, pol: bool) -> Option<TypeUnary> {
|
||||
let lhs = self.mutate(&ty.lhs, pol)?;
|
||||
|
||||
Some(TypeUnary { lhs, op: ty.op })
|
||||
}
|
||||
|
||||
fn mutate_binary(&mut self, ty: &Interned<TypeBinary>, pol: bool) -> Option<TypeBinary> {
|
||||
let (lhs, rhs) = &ty.operands;
|
||||
|
||||
let x = self.mutate(lhs, pol);
|
||||
let y = self.mutate(rhs, pol);
|
||||
|
||||
if x.is_none() && y.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let lhs = x.unwrap_or_else(|| lhs.clone());
|
||||
let rhs = y.unwrap_or_else(|| rhs.clone());
|
||||
|
||||
Some(TypeBinary {
|
||||
operands: (lhs, rhs),
|
||||
op: ty.op,
|
||||
})
|
||||
}
|
||||
|
||||
fn mutate_if(&mut self, ty: &Interned<IfTy>, pol: bool) -> Option<IfTy> {
|
||||
let cond = self.mutate(ty.cond.as_ref(), pol);
|
||||
let then = self.mutate(ty.then.as_ref(), pol);
|
||||
let else_ = self.mutate(ty.else_.as_ref(), pol);
|
||||
|
||||
if cond.is_none() && then.is_none() && else_.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cond = cond.map(Interned::new).unwrap_or_else(|| ty.cond.clone());
|
||||
let then = then.map(Interned::new).unwrap_or_else(|| ty.then.clone());
|
||||
let else_ = else_.map(Interned::new).unwrap_or_else(|| ty.else_.clone());
|
||||
|
||||
Some(IfTy { cond, then, else_ })
|
||||
}
|
||||
|
||||
fn mutate_select(&mut self, ty: &Interned<SelectTy>, pol: bool) -> Option<SelectTy> {
|
||||
let target = self.mutate(ty.ty.as_ref(), pol)?.into();
|
||||
|
||||
Some(SelectTy {
|
||||
ty: target,
|
||||
select: ty.select.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TyMutator for T
|
||||
where
|
||||
T: FnMut(&Ty, bool) -> Option<Ty>,
|
||||
{
|
||||
fn mutate(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
|
||||
self(ty, pol)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Mutate the given type.
|
||||
pub fn mutate(&self, pol: bool, checker: &mut impl TyMutator) -> Option<Ty> {
|
||||
checker.mutate(self, pol)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
pub use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub use rustc_hash::{FxHashMap, FxHashSet};
|
||||
pub use tinymist_std::DefId;
|
||||
pub use typst::foundations::Value;
|
||||
|
||||
pub use super::builtin::*;
|
||||
pub use super::def::*;
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
use super::{Iface, IfaceChecker};
|
||||
use crate::ty::def::*;
|
||||
|
||||
pub trait SelectChecker: TyCtx {
|
||||
fn select(&mut self, sig: Iface, key: &Interned<str>, pol: bool);
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Select the given type with the given key.
|
||||
pub fn select(&self, key: &Interned<str>, pol: bool, checker: &mut impl SelectChecker) {
|
||||
SelectKeyChecker(checker, key).ty(self, pol);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(0)]
|
||||
pub struct SelectKeyChecker<'a, T: TyCtx>(&'a mut T, &'a Interned<str>);
|
||||
|
||||
impl<T: SelectChecker> SelectKeyChecker<'_, T> {
|
||||
fn ty(&mut self, ty: &Ty, pol: bool) {
|
||||
ty.iface_surface(pol, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: SelectChecker> IfaceChecker for SelectKeyChecker<'_, T> {
|
||||
fn check(
|
||||
&mut self,
|
||||
iface: Iface,
|
||||
_ctx: &mut super::IfaceCheckContext,
|
||||
pol: bool,
|
||||
) -> Option<()> {
|
||||
self.0.select(iface, self.1, pol);
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,337 +0,0 @@
|
|||
use typst::foundations::{Func, Value};
|
||||
|
||||
use super::BoundChecker;
|
||||
use crate::ty::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Sig<'a> {
|
||||
Builtin(BuiltinSig<'a>),
|
||||
Type(&'a Interned<SigTy>),
|
||||
TypeCons {
|
||||
val: &'a typst::foundations::Type,
|
||||
at: &'a Ty,
|
||||
},
|
||||
ArrayCons(&'a TyRef),
|
||||
TupleCons(&'a Interned<Vec<Ty>>),
|
||||
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::Builtin(_) => return None,
|
||||
Sig::Type(t) => Ty::Func(t.clone()),
|
||||
Sig::ArrayCons(t) => Ty::Array(t.clone()),
|
||||
Sig::TupleCons(t) => Ty::Tuple(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: &mut impl TyCtxMut) -> Option<SigShape<'a>> {
|
||||
let (sig, _is_partialize) = match self {
|
||||
Sig::Partialize(sig) => (*sig, true),
|
||||
sig => (sig, false),
|
||||
};
|
||||
|
||||
let (cano_sig, withs) = match sig {
|
||||
Sig::With { sig, withs, .. } => (*sig, Some(withs)),
|
||||
sig => (sig, None),
|
||||
};
|
||||
|
||||
let sig = match cano_sig {
|
||||
Sig::Builtin(_) => return None,
|
||||
Sig::ArrayCons(arr) => SigTy::array_cons(arr.as_ref().clone(), false),
|
||||
Sig::TupleCons(tup) => SigTy::tuple_cons(tup.clone(), false),
|
||||
Sig::DictCons(dict) => SigTy::dict_cons(dict, 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(ty) => ty.clone(),
|
||||
};
|
||||
|
||||
Some(SigShape { sig, withs })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SigSurfaceKind {
|
||||
Call,
|
||||
Array,
|
||||
Dict,
|
||||
ArrayOrDict,
|
||||
}
|
||||
|
||||
pub trait SigChecker: TyCtx {
|
||||
fn check(&mut self, sig: Sig, args: &mut SigCheckContext, pol: bool) -> Option<()>;
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Iterate over the signatures of the given type.
|
||||
pub fn sig_surface(&self, pol: bool, sig_kind: SigSurfaceKind, checker: &mut impl SigChecker) {
|
||||
let ctx = SigCheckContext {
|
||||
sig_kind,
|
||||
args: Vec::new(),
|
||||
at: TyRef::new(Ty::Any),
|
||||
};
|
||||
|
||||
SigCheckDriver { ctx, checker }.ty(self, pol);
|
||||
}
|
||||
|
||||
/// Get the signature representation of the given type.
|
||||
pub fn sig_repr(&self, pol: bool, ctx: &mut impl TyCtxMut) -> 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;
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(0)]
|
||||
struct SigReprDriver<'a, C: TyCtxMut>(&'a mut C, &'a mut Option<Interned<SigTy>>);
|
||||
|
||||
impl<C: TyCtxMut> SigChecker for SigReprDriver<'_, C> {
|
||||
fn check(&mut self, sig: Sig, _ctx: &mut SigCheckContext, _pol: bool) -> Option<()> {
|
||||
let sig = sig.shape(self.0)?;
|
||||
*self.1 = Some(sig.sig.clone());
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
self.sig_surface(
|
||||
pol,
|
||||
SigSurfaceKind::Call,
|
||||
// todo: bind type context
|
||||
&mut SigReprDriver(ctx, &mut primary),
|
||||
);
|
||||
|
||||
primary
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SigCheckContext {
|
||||
pub sig_kind: SigSurfaceKind,
|
||||
pub args: Vec<Interned<SigTy>>,
|
||||
pub at: TyRef,
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(checker)]
|
||||
pub struct SigCheckDriver<'a> {
|
||||
ctx: SigCheckContext,
|
||||
checker: &'a mut dyn SigChecker,
|
||||
}
|
||||
|
||||
impl SigCheckDriver<'_> {
|
||||
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, at: &Ty, pol: bool) {
|
||||
crate::log_debug_ct!("check sig: {at:?}");
|
||||
match at {
|
||||
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);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::TextFont) if self.dict_as_sig() => {
|
||||
self.checker
|
||||
.check(Sig::DictCons(&FLOW_TEXT_FONT_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
// todo: deduplicate checking early
|
||||
Ty::Value(ins_ty) => {
|
||||
if self.func_as_sig() {
|
||||
match &ins_ty.val {
|
||||
Value::Func(func) => {
|
||||
self.checker
|
||||
.check(Sig::Value { val: func, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::Type(ty) => {
|
||||
self.checker
|
||||
.check(Sig::TypeCons { val: ty, at }, &mut self.ctx, pol);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Type(b_ty)) if self.func_as_sig() => {
|
||||
// todo: distinguish between element and function
|
||||
self.checker
|
||||
.check(Sig::TypeCons { val: b_ty, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Element(elem)) if self.func_as_sig() => {
|
||||
// todo: distinguish between element and function
|
||||
let func = (*elem).into();
|
||||
self.checker
|
||||
.check(Sig::Value { val: &func, at }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Func(sig) if self.func_as_sig() => {
|
||||
self.checker.check(Sig::Type(sig), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Array(arr) if self.array_as_sig() => {
|
||||
self.checker.check(Sig::ArrayCons(arr), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Tuple(elems) if self.array_as_sig() => {
|
||||
self.checker
|
||||
.check(Sig::TupleCons(elems), &mut self.ctx, pol);
|
||||
}
|
||||
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(sig) if self.func_as_sig() => {
|
||||
self.ctx.args.push(sig.with.clone());
|
||||
self.ty(&sig.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(_) => {}
|
||||
Ty::Param(param) => {
|
||||
// todo: keep type information
|
||||
self.ty(¶m.ty, pol);
|
||||
}
|
||||
_ if at.has_bounds() => at.bounds(pol, self),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundChecker for SigCheckDriver<'_> {
|
||||
fn collect(&mut self, ty: &Ty, pol: bool) {
|
||||
crate::log_debug_ct!("sig bounds: {ty:?}");
|
||||
self.ty(ty, pol);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BindTyCtx)]
|
||||
#[bind(0)]
|
||||
struct MethodDriver<'a, 'b>(&'a mut SigCheckDriver<'b>, &'a StrRef);
|
||||
|
||||
impl MethodDriver<'_, '_> {
|
||||
fn is_binder(&self) -> bool {
|
||||
matches!(self.1.as_ref(), "with" | "where")
|
||||
}
|
||||
|
||||
fn array_method(&mut self, ty: &Ty, pol: bool) {
|
||||
let method = match self.1.as_ref() {
|
||||
"map" => BuiltinSig::TupleMap(ty),
|
||||
"at" => BuiltinSig::TupleAt(ty),
|
||||
_ => return,
|
||||
};
|
||||
self.0
|
||||
.checker
|
||||
.check(Sig::Builtin(method), &mut self.0.ctx, pol);
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundChecker for MethodDriver<'_, '_> {
|
||||
fn collect(&mut self, ty: &Ty, pol: bool) {
|
||||
crate::log_debug_ct!("check method: {ty:?}.{}", self.1.as_ref());
|
||||
match ty {
|
||||
// todo: deduplicate checking early
|
||||
Ty::Value(v) => {
|
||||
match &v.val {
|
||||
Value::Func(func) => {
|
||||
if self.is_binder() {
|
||||
self.0.checker.check(
|
||||
Sig::Partialize(&Sig::Value { val: func, at: ty }),
|
||||
&mut self.0.ctx,
|
||||
pol,
|
||||
);
|
||||
} else {
|
||||
// todo: general select operator
|
||||
}
|
||||
}
|
||||
Value::Array(..) => self.array_method(ty, pol),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Element(elem)) => {
|
||||
// todo: distinguish between element and function
|
||||
if self.is_binder() {
|
||||
let func = (*elem).into();
|
||||
self.0.checker.check(
|
||||
Sig::Partialize(&Sig::Value { val: &func, 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
|
||||
}
|
||||
}
|
||||
Ty::With(w) => {
|
||||
self.0.ctx.args.push(w.with.clone());
|
||||
w.sig.bounds(pol, self);
|
||||
self.0.ctx.args.pop();
|
||||
}
|
||||
Ty::Tuple(..) => self.array_method(ty, pol),
|
||||
Ty::Array(..) => self.array_method(ty, pol),
|
||||
// todo: general select operator
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use ecow::EcoVec;
|
||||
|
||||
use crate::{syntax::DeclExpr, ty::prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
struct CompactTy {
|
||||
equiv_vars: HashSet<DefId>,
|
||||
primitives: HashSet<Ty>,
|
||||
recursives: HashMap<DefId, CompactTy>,
|
||||
signatures: Vec<Interned<SigTy>>,
|
||||
|
||||
is_final: bool,
|
||||
}
|
||||
|
||||
impl TypeInfo {
|
||||
/// Simplify (Canonicalize) the given type with the given type scheme.
|
||||
pub fn simplify(&self, ty: Ty, principal: bool) -> Ty {
|
||||
let mut cache = self.cano_cache.lock();
|
||||
let cache = &mut *cache;
|
||||
|
||||
cache.cano_local_cache.clear();
|
||||
cache.positives.clear();
|
||||
cache.negatives.clear();
|
||||
|
||||
let mut worker = TypeSimplifier {
|
||||
principal,
|
||||
vars: &self.vars,
|
||||
cano_cache: &mut cache.cano_cache,
|
||||
cano_local_cache: &mut cache.cano_local_cache,
|
||||
|
||||
positives: &mut cache.positives,
|
||||
negatives: &mut cache.negatives,
|
||||
};
|
||||
|
||||
worker.simplify(ty, principal)
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeSimplifier<'a, 'b> {
|
||||
principal: bool,
|
||||
|
||||
vars: &'a FxHashMap<DeclExpr, TypeVarBounds>,
|
||||
|
||||
cano_cache: &'b mut FxHashMap<(Ty, bool), Ty>,
|
||||
cano_local_cache: &'b mut FxHashMap<(DeclExpr, bool), Ty>,
|
||||
negatives: &'b mut FxHashSet<DeclExpr>,
|
||||
positives: &'b mut FxHashSet<DeclExpr>,
|
||||
}
|
||||
|
||||
impl TypeSimplifier<'_, '_> {
|
||||
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(var) => {
|
||||
let w = self.vars.get(&var.def).unwrap();
|
||||
match &w.bounds {
|
||||
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
|
||||
let bounds = w.read();
|
||||
let inserted = if pol {
|
||||
self.positives.insert(var.def.clone())
|
||||
} else {
|
||||
self.negatives.insert(var.def.clone())
|
||||
};
|
||||
if !inserted {
|
||||
return;
|
||||
}
|
||||
|
||||
if pol {
|
||||
for lb in bounds.lbs.iter() {
|
||||
self.analyze(lb, pol);
|
||||
}
|
||||
} else {
|
||||
for ub in bounds.ubs.iter() {
|
||||
self.analyze(ub, pol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ty::Func(func) => {
|
||||
for input_ty in func.inputs() {
|
||||
self.analyze(input_ty, !pol);
|
||||
}
|
||||
if let Some(ret_ty) = &func.body {
|
||||
self.analyze(ret_ty, pol);
|
||||
}
|
||||
}
|
||||
Ty::Dict(record) => {
|
||||
for member in record.types.iter() {
|
||||
self.analyze(member, pol);
|
||||
}
|
||||
}
|
||||
Ty::Tuple(elems) => {
|
||||
for elem in elems.iter() {
|
||||
self.analyze(elem, pol);
|
||||
}
|
||||
}
|
||||
Ty::Array(arr) => {
|
||||
self.analyze(arr, pol);
|
||||
}
|
||||
Ty::With(with) => {
|
||||
self.analyze(&with.sig, pol);
|
||||
for input in with.with.inputs() {
|
||||
self.analyze(input, pol);
|
||||
}
|
||||
}
|
||||
Ty::Args(args) => {
|
||||
for input in args.inputs() {
|
||||
self.analyze(input, pol);
|
||||
}
|
||||
}
|
||||
Ty::Pattern(pat) => {
|
||||
for input in pat.inputs() {
|
||||
self.analyze(input, pol);
|
||||
}
|
||||
}
|
||||
Ty::Unary(unary) => self.analyze(&unary.lhs, pol),
|
||||
Ty::Binary(binary) => {
|
||||
let [lhs, rhs] = binary.operands();
|
||||
self.analyze(lhs, pol);
|
||||
self.analyze(rhs, pol);
|
||||
}
|
||||
Ty::If(if_expr) => {
|
||||
self.analyze(&if_expr.cond, pol);
|
||||
self.analyze(&if_expr.then, pol);
|
||||
self.analyze(&if_expr.else_, pol);
|
||||
}
|
||||
Ty::Union(types) => {
|
||||
for ty in types.iter() {
|
||||
self.analyze(ty, pol);
|
||||
}
|
||||
}
|
||||
Ty::Select(select) => {
|
||||
self.analyze(&select.ty, pol);
|
||||
}
|
||||
Ty::Let(bounds) => {
|
||||
for lb in bounds.lbs.iter() {
|
||||
self.analyze(lb, !pol);
|
||||
}
|
||||
for ub in bounds.ubs.iter() {
|
||||
self.analyze(ub, pol);
|
||||
}
|
||||
}
|
||||
Ty::Param(param) => {
|
||||
self.analyze(¶m.ty, pol);
|
||||
}
|
||||
Ty::Value(_v) => {}
|
||||
Ty::Any => {}
|
||||
Ty::Boolean(_) => {}
|
||||
Ty::Builtin(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(&mut self, ty: &Ty, pol: bool) -> Ty {
|
||||
match ty {
|
||||
Ty::Let(bounds) => self.transform_let(bounds.lbs.iter(), bounds.ubs.iter(), None, pol),
|
||||
Ty::Var(var) => {
|
||||
if let Some(cano) = self
|
||||
.cano_local_cache
|
||||
.get(&(var.def.clone(), self.principal))
|
||||
{
|
||||
return cano.clone();
|
||||
}
|
||||
// todo: avoid cycle
|
||||
self.cano_local_cache
|
||||
.insert((var.def.clone(), self.principal), Ty::Any);
|
||||
|
||||
let res = match &self.vars.get(&var.def).unwrap().bounds {
|
||||
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
|
||||
let w = w.read();
|
||||
|
||||
self.transform_let(w.lbs.iter(), w.ubs.iter(), Some(&var.def), pol)
|
||||
}
|
||||
};
|
||||
|
||||
self.cano_local_cache
|
||||
.insert((var.def.clone(), self.principal), res.clone());
|
||||
|
||||
res
|
||||
}
|
||||
Ty::Func(func) => Ty::Func(self.transform_sig(func, pol)),
|
||||
Ty::Dict(record) => {
|
||||
let mut mutated = record.as_ref().clone();
|
||||
mutated.types = self.transform_seq(&mutated.types, pol);
|
||||
|
||||
Ty::Dict(mutated.into())
|
||||
}
|
||||
Ty::Tuple(tup) => Ty::Tuple(self.transform_seq(tup, pol)),
|
||||
Ty::Array(arr) => Ty::Array(self.transform(arr, pol).into()),
|
||||
Ty::With(with) => {
|
||||
let sig = self.transform(&with.sig, pol).into();
|
||||
// Negate the pol to make correct covariance
|
||||
let mutated = self.transform_sig(&with.with, !pol);
|
||||
|
||||
Ty::With(SigWithTy::new(sig, mutated))
|
||||
}
|
||||
// Negate the pol to make correct covariance
|
||||
// todo: negate?
|
||||
Ty::Args(args) => Ty::Args(self.transform_sig(args, !pol)),
|
||||
Ty::Pattern(pat) => Ty::Pattern(self.transform_sig(pat, !pol)),
|
||||
Ty::Unary(unary) => {
|
||||
Ty::Unary(TypeUnary::new(unary.op, self.transform(&unary.lhs, pol)))
|
||||
}
|
||||
Ty::Binary(binary) => {
|
||||
let [lhs, rhs] = binary.operands();
|
||||
let lhs = self.transform(lhs, pol);
|
||||
let rhs = self.transform(rhs, pol);
|
||||
|
||||
Ty::Binary(TypeBinary::new(binary.op, lhs, rhs))
|
||||
}
|
||||
Ty::If(if_ty) => Ty::If(IfTy::new(
|
||||
self.transform(&if_ty.cond, pol).into(),
|
||||
self.transform(&if_ty.then, pol).into(),
|
||||
self.transform(&if_ty.else_, pol).into(),
|
||||
)),
|
||||
Ty::Union(types) => {
|
||||
let seq = types.iter().map(|ty| self.transform(ty, pol));
|
||||
let seq_no_any = seq.filter(|ty| !matches!(ty, Ty::Any));
|
||||
let seq = seq_no_any.collect::<Vec<_>>();
|
||||
Ty::from_types(seq.into_iter())
|
||||
}
|
||||
Ty::Param(param) => {
|
||||
let mut param = param.as_ref().clone();
|
||||
param.ty = self.transform(¶m.ty, pol);
|
||||
|
||||
Ty::Param(param.into())
|
||||
}
|
||||
Ty::Select(sel) => {
|
||||
let mut sel = sel.as_ref().clone();
|
||||
sel.ty = self.transform(&sel.ty, pol).into();
|
||||
|
||||
Ty::Select(sel.into())
|
||||
}
|
||||
|
||||
Ty::Value(ins_ty) => Ty::Value(ins_ty.clone()),
|
||||
Ty::Any => Ty::Any,
|
||||
Ty::Boolean(truthiness) => Ty::Boolean(*truthiness),
|
||||
Ty::Builtin(ty) => Ty::Builtin(ty.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_seq(&mut self, types: &[Ty], pol: bool) -> Interned<Vec<Ty>> {
|
||||
let seq = types.iter().map(|ty| self.transform(ty, pol));
|
||||
seq.collect::<Vec<_>>().into()
|
||||
}
|
||||
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
fn transform_let<'a>(
|
||||
&mut self,
|
||||
lbs_iter: impl ExactSizeIterator<Item = &'a Ty>,
|
||||
ubs_iter: impl ExactSizeIterator<Item = &'a Ty>,
|
||||
decl: Option<&DeclExpr>,
|
||||
pol: bool,
|
||||
) -> Ty {
|
||||
let mut lbs = HashSet::with_capacity(lbs_iter.len());
|
||||
let mut ubs = HashSet::with_capacity(ubs_iter.len());
|
||||
|
||||
crate::log_debug_ct!("transform let [principal={}]", self.principal);
|
||||
|
||||
if !self.principal || ((pol) && !decl.is_some_and(|decl| self.negatives.contains(decl))) {
|
||||
for lb in lbs_iter {
|
||||
lbs.insert(self.transform(lb, pol));
|
||||
}
|
||||
}
|
||||
if !self.principal || ((!pol) && !decl.is_some_and(|decl| self.positives.contains(decl))) {
|
||||
for ub in ubs_iter {
|
||||
ubs.insert(self.transform(ub, !pol));
|
||||
}
|
||||
}
|
||||
|
||||
if ubs.is_empty() {
|
||||
if lbs.len() == 1 {
|
||||
return lbs.into_iter().next().unwrap();
|
||||
}
|
||||
if lbs.is_empty() {
|
||||
return Ty::Any;
|
||||
}
|
||||
} else if lbs.is_empty() && ubs.len() == 1 {
|
||||
return ubs.into_iter().next().unwrap();
|
||||
}
|
||||
|
||||
// todo: bad performance
|
||||
let mut lbs: Vec<_> = lbs.into_iter().collect();
|
||||
lbs.sort();
|
||||
let mut ubs: Vec<_> = ubs.into_iter().collect();
|
||||
ubs.sort();
|
||||
|
||||
Ty::Let(TypeBounds { lbs, ubs }.into())
|
||||
}
|
||||
|
||||
fn transform_sig(&mut self, sig: &SigTy, pol: bool) -> Interned<SigTy> {
|
||||
let mut sig = sig.clone();
|
||||
sig.inputs = self.transform_seq(&sig.inputs, !pol);
|
||||
if let Some(ret) = &sig.body {
|
||||
sig.body = Some(self.transform(ret, pol));
|
||||
}
|
||||
|
||||
// todo: we can reduce one clone by early compare on sig.types
|
||||
sig.into()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
use super::{Sig, SigShape, TyMutator};
|
||||
use crate::ty::prelude::*;
|
||||
|
||||
impl Sig<'_> {
|
||||
pub fn call(&self, args: &Interned<ArgsTy>, pol: bool, ctx: &mut impl TyCtxMut) -> Option<Ty> {
|
||||
crate::log_debug_ct!("call {self:?} {args:?} {pol:?}");
|
||||
ctx.with_scope(|ctx| {
|
||||
let body = self.check_bind(args, ctx)?;
|
||||
|
||||
// Substitute the bound variables in the body or just body
|
||||
let mut checker = SubstituteChecker { ctx };
|
||||
Some(checker.ty(&body, pol).unwrap_or(body))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn check_bind(&self, args: &Interned<ArgsTy>, ctx: &mut impl TyCtxMut) -> Option<Ty> {
|
||||
let SigShape { sig, withs } = self.shape(ctx)?;
|
||||
|
||||
// todo: check if the signature has free variables
|
||||
// let has_free_vars = sig.has_free_variables;
|
||||
|
||||
for (arg_recv, arg_ins) in sig.matches(args, withs) {
|
||||
if let Ty::Var(arg_recv) = arg_recv {
|
||||
crate::log_debug_ct!("bind {arg_recv:?} {arg_ins:?}");
|
||||
ctx.bind_local(arg_recv, arg_ins.clone());
|
||||
}
|
||||
}
|
||||
|
||||
sig.body.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct SubstituteChecker<'a, T: TyCtxMut> {
|
||||
ctx: &'a mut T,
|
||||
}
|
||||
|
||||
impl<T: TyCtxMut> SubstituteChecker<'_, T> {
|
||||
fn ty(&mut self, body: &Ty, pol: bool) -> Option<Ty> {
|
||||
body.mutate(pol, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TyCtxMut> TyMutator for SubstituteChecker<'_, T> {
|
||||
fn mutate(&mut self, ty: &Ty, pol: bool) -> Option<Ty> {
|
||||
// todo: extrude the type into a polarized type
|
||||
let _ = pol;
|
||||
|
||||
if let Ty::Var(v) = ty {
|
||||
self.ctx.local_bind_of(v)
|
||||
} else {
|
||||
self.mutate_rec(ty, pol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use insta::{assert_debug_snapshot, assert_snapshot};
|
||||
use tinymist_derive::BindTyCtx;
|
||||
|
||||
use super::{DynTypeBounds, Interned, Ty, TyCtx, TypeInfo, TypeVar};
|
||||
use crate::ty::tests::*;
|
||||
use crate::ty::ApplyChecker;
|
||||
#[test]
|
||||
fn test_ty() {
|
||||
use super::*;
|
||||
let ty = Ty::Builtin(BuiltinTy::Clause);
|
||||
let ty_ref = TyRef::new(ty.clone());
|
||||
assert_debug_snapshot!(ty_ref, @"Clause");
|
||||
}
|
||||
|
||||
#[derive(Default, BindTyCtx)]
|
||||
#[bind(0)]
|
||||
struct CallCollector(TypeInfo, Vec<Ty>);
|
||||
|
||||
impl ApplyChecker for CallCollector {
|
||||
fn apply(
|
||||
&mut self,
|
||||
sig: super::Sig,
|
||||
arguments: &crate::adt::interner::Interned<super::ArgsTy>,
|
||||
pol: bool,
|
||||
) {
|
||||
let ty = sig.call(arguments, pol, &mut self.0);
|
||||
if let Some(ty) = ty {
|
||||
self.1.push(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sig_call() {
|
||||
use super::*;
|
||||
|
||||
fn call(sig: Interned<SigTy>, args: Interned<SigTy>) -> String {
|
||||
let sig_ty = Ty::Func(sig);
|
||||
let mut collector = CallCollector::default();
|
||||
sig_ty.call(&args, false, &mut collector);
|
||||
|
||||
collector.1.iter().fold(String::new(), |mut acc, ty| {
|
||||
if !acc.is_empty() {
|
||||
acc.push_str(", ");
|
||||
}
|
||||
|
||||
acc.push_str(&format!("{ty:?}"));
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
assert_snapshot!(call(literal_sig!(p1 -> p1), literal_args!(q1)), @"@q1");
|
||||
assert_snapshot!(call(literal_sig!(!u1: w1 -> w1), literal_args!(!u1: w2)), @"@w2");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
# This is responsible for the fact that certain math functions are grouped
|
||||
# together into one documentation page although they are not part of any scope.
|
||||
|
||||
- name: variants
|
||||
title: Variants
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["serif", "sans", "frak", "mono", "bb", "cal"]
|
||||
details: |
|
||||
Alternate typefaces within formulas.
|
||||
|
||||
These functions are distinct from the [`text`] function because math fonts
|
||||
contain multiple variants of each letter.
|
||||
|
||||
- name: styles
|
||||
title: Styles
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["upright", "italic", "bold"]
|
||||
details: |
|
||||
Alternate letterforms within formulas.
|
||||
|
||||
These functions are distinct from the [`text`] function because math fonts
|
||||
contain multiple variants of each letter.
|
||||
|
||||
- name: sizes
|
||||
title: Sizes
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["display", "inline", "script", "sscript"]
|
||||
details: |
|
||||
Forced size styles for expressions within formulas.
|
||||
|
||||
These functions allow manual configuration of the size of equation elements
|
||||
to make them look as in a display/inline equation or as if used in a root or
|
||||
sub/superscripts.
|
||||
|
||||
- name: underover
|
||||
title: Under/Over
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: [
|
||||
"underline",
|
||||
"overline",
|
||||
"underbrace",
|
||||
"overbrace",
|
||||
"underbracket",
|
||||
"overbracket",
|
||||
"underparen",
|
||||
"overparen",
|
||||
"undershell",
|
||||
"overshell",
|
||||
]
|
||||
details: |
|
||||
Delimiters above or below parts of an equation.
|
||||
|
||||
The braces and brackets further allow you to add an optional annotation
|
||||
below or above themselves.
|
||||
|
||||
- name: roots
|
||||
title: Roots
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["root", "sqrt"]
|
||||
details: |
|
||||
Square and non-square roots.
|
||||
|
||||
# Example
|
||||
```example
|
||||
$ sqrt(3 - 2 sqrt(2)) = sqrt(2) - 1 $
|
||||
$ root(3, x) $
|
||||
```
|
||||
|
||||
- name: attach
|
||||
title: Attach
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["attach", "scripts", "limits"]
|
||||
details: |
|
||||
Subscript, superscripts, and limits.
|
||||
|
||||
Attachments can be displayed either as sub/superscripts, or limits. Typst
|
||||
automatically decides which is more suitable depending on the base, but you
|
||||
can also control this manually with the `scripts` and `limits` functions.
|
||||
|
||||
If you want the base to stretch to fit long top and bottom attachments (for
|
||||
example, an arrow with text above it), use the [`stretch`]($math.stretch)
|
||||
function.
|
||||
|
||||
# Example
|
||||
```example
|
||||
$ sum_(i=0)^n a_i = 2^(1+i) $
|
||||
```
|
||||
|
||||
# Syntax
|
||||
This function also has dedicated syntax for attachments after the base: Use
|
||||
the underscore (`_`) to indicate a subscript i.e. bottom attachment and the
|
||||
hat (`^`) to indicate a superscript i.e. top attachment.
|
||||
|
||||
- name: lr
|
||||
title: Left/Right
|
||||
category: math
|
||||
path: ["math"]
|
||||
filter: ["lr", "mid", "abs", "norm", "floor", "ceil", "round"]
|
||||
details: |
|
||||
Delimiter matching.
|
||||
|
||||
The `lr` function allows you to match two delimiters and scale them with the
|
||||
content they contain. While this also happens automatically for delimiters
|
||||
that match syntactically, `lr` allows you to match two arbitrary delimiters
|
||||
and control their size exactly. Apart from the `lr` function, Typst provides
|
||||
a few more functions that create delimiter pairings for absolute, ceiled,
|
||||
and floored values as well as norms.
|
||||
|
||||
# Example
|
||||
```example
|
||||
$ [a, b/2] $
|
||||
$ lr(]sum_(x=1)^n], size: #50%) x $
|
||||
$ abs((x + y) / 2) $
|
||||
```
|
||||
|
||||
- name: calc
|
||||
title: Calculation
|
||||
category: foundations
|
||||
path: ["calc"]
|
||||
details: |
|
||||
Module for calculations and processing of numeric values.
|
||||
|
||||
These definitions are part of the `calc` module and not imported by default.
|
||||
In addition to the functions listed below, the `calc` module also defines
|
||||
the constants `pi`, `tau`, `e`, and `inf`.
|
||||
|
||||
- name: sys
|
||||
title: System
|
||||
category: foundations
|
||||
path: ["sys"]
|
||||
details: |
|
||||
Module for system interactions.
|
||||
|
||||
This module defines the following items:
|
||||
|
||||
- The `sys.version` constant (of type [`version`]) that specifies
|
||||
the currently active Typst compiler version.
|
||||
|
||||
- The `sys.inputs` [dictionary], which makes external inputs
|
||||
available to the project. An input specified in the command line as
|
||||
`--input key=value` becomes available under `sys.inputs.key` as
|
||||
`{"value"}`. To include spaces in the value, it may be enclosed with
|
||||
single or double quotes.
|
||||
|
||||
The value is always of type [string]($str). More complex data
|
||||
may be parsed manually using functions like [`json.decode`]($json.decode).
|
||||
|
||||
- name: sym
|
||||
title: General
|
||||
category: symbols
|
||||
path: ["sym"]
|
||||
details: |
|
||||
Named general symbols.
|
||||
|
||||
For example, `#sym.arrow` produces the → symbol. Within
|
||||
[formulas]($category/math), these symbols can be used without the `#sym.`
|
||||
prefix.
|
||||
|
||||
The `d` in an integral's `dx` can be written as `[$dif x$]`.
|
||||
Outside math formulas, `dif` can be accessed as `math.dif`.
|
||||
|
||||
- name: emoji
|
||||
title: Emoji
|
||||
category: symbols
|
||||
path: ["emoji"]
|
||||
details: |
|
||||
Named emoji.
|
||||
|
||||
For example, `#emoji.face` produces the 😀 emoji. If you frequently use
|
||||
certain emojis, you can also import them from the `emoji` module (`[#import
|
||||
emoji: face]`) to use them without the `#emoji.` prefix.
|
||||
|
|
@ -1,483 +0,0 @@
|
|||
use std::{collections::HashMap, fmt::Write, sync::LazyLock};
|
||||
|
||||
use comemo::Tracked;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::Deserialize;
|
||||
use serde_yaml as yaml;
|
||||
use typst::{
|
||||
diag::{bail, StrResult},
|
||||
foundations::{Binding, Content, Func, Module, Type, Value},
|
||||
introspection::MetadataElem,
|
||||
syntax::Span,
|
||||
text::{FontInfo, FontStyle},
|
||||
Library, World,
|
||||
};
|
||||
|
||||
mod tooltip;
|
||||
pub use tooltip::*;
|
||||
|
||||
/// Extract the first sentence of plain text of a piece of documentation.
|
||||
///
|
||||
/// Removes Markdown formatting.
|
||||
pub fn plain_docs_sentence(docs: &str) -> EcoString {
|
||||
crate::log_debug_ct!("plain docs {docs:?}");
|
||||
let docs = docs.replace("```example", "```typ");
|
||||
let mut scanner = unscanny::Scanner::new(&docs);
|
||||
let mut output = EcoString::new();
|
||||
let mut link = false;
|
||||
while let Some(ch) = scanner.eat() {
|
||||
match ch {
|
||||
'`' => {
|
||||
let mut raw = scanner.eat_until('`');
|
||||
if (raw.starts_with('{') && raw.ends_with('}'))
|
||||
|| (raw.starts_with('[') && raw.ends_with(']'))
|
||||
{
|
||||
raw = &raw[1..raw.len() - 1];
|
||||
}
|
||||
|
||||
scanner.eat();
|
||||
output.push('`');
|
||||
output.push_str(raw);
|
||||
output.push('`');
|
||||
}
|
||||
'[' => {
|
||||
link = true;
|
||||
output.push('[');
|
||||
}
|
||||
']' if link => {
|
||||
output.push(']');
|
||||
let cursor = scanner.cursor();
|
||||
if scanner.eat_if('(') {
|
||||
scanner.eat_until(')');
|
||||
let link_content = scanner.from(cursor + 1);
|
||||
scanner.eat();
|
||||
|
||||
crate::log_debug_ct!("Intra Link: {link_content}");
|
||||
let link = resolve(link_content, "https://typst.app/docs/").ok();
|
||||
let link = link.unwrap_or_else(|| {
|
||||
log::warn!("Failed to resolve link: {link_content}");
|
||||
"https://typst.app/docs/404.html".to_string()
|
||||
});
|
||||
|
||||
output.push('(');
|
||||
output.push_str(&link);
|
||||
output.push(')');
|
||||
} else if scanner.eat_if('[') {
|
||||
scanner.eat_until(']');
|
||||
scanner.eat();
|
||||
output.push_str(scanner.from(cursor));
|
||||
}
|
||||
link = false
|
||||
}
|
||||
// '*' | '_' => {}
|
||||
// '.' => {
|
||||
// output.push('.');
|
||||
// break;
|
||||
// }
|
||||
_ => output.push(ch),
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Data about a collection of functions.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct GroupData {
|
||||
name: EcoString,
|
||||
// title: EcoString,
|
||||
category: EcoString,
|
||||
#[serde(default)]
|
||||
path: Vec<EcoString>,
|
||||
#[serde(default)]
|
||||
filter: Vec<EcoString>,
|
||||
// details: EcoString,
|
||||
}
|
||||
|
||||
impl GroupData {
|
||||
fn module(&self) -> &'static Module {
|
||||
let mut focus = &LIBRARY.global;
|
||||
for path in &self.path {
|
||||
focus = get_module(focus, path).unwrap();
|
||||
}
|
||||
focus
|
||||
}
|
||||
}
|
||||
|
||||
static GROUPS: LazyLock<Vec<GroupData>> = LazyLock::new(|| {
|
||||
let mut groups: Vec<GroupData> = yaml::from_str(include_str!("groups.yml")).unwrap();
|
||||
for group in &mut groups {
|
||||
if group.filter.is_empty() {
|
||||
group.filter = group
|
||||
.module()
|
||||
.scope()
|
||||
.iter()
|
||||
.filter(|(_, v)| matches!(v.read(), Value::Func(_)))
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
groups
|
||||
});
|
||||
|
||||
/// Resolve an intra-doc link.
|
||||
pub fn resolve(link: &str, base: &str) -> StrResult<String> {
|
||||
if link.starts_with('#') || link.starts_with("http") {
|
||||
return Ok(link.to_string());
|
||||
}
|
||||
|
||||
let (head, tail) = split_link(link)?;
|
||||
let mut route = match resolve_known(head, base) {
|
||||
Some(route) => route,
|
||||
None => resolve_definition(head, base)?,
|
||||
};
|
||||
|
||||
if !tail.is_empty() {
|
||||
route.push('/');
|
||||
route.push_str(tail);
|
||||
}
|
||||
|
||||
if !route.contains(['#', '?']) && !route.ends_with('/') {
|
||||
route.push('/');
|
||||
}
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
/// Split a link at the first slash.
|
||||
fn split_link(link: &str) -> StrResult<(&str, &str)> {
|
||||
let first = link.split('/').next().unwrap_or(link);
|
||||
let rest = link[first.len()..].trim_start_matches('/');
|
||||
Ok((first, rest))
|
||||
}
|
||||
|
||||
/// Resolve a `$` link head to a known destination.
|
||||
fn resolve_known(head: &str, base: &str) -> Option<String> {
|
||||
Some(match head {
|
||||
"$tutorial" => format!("{base}tutorial"),
|
||||
"$reference" => format!("{base}reference"),
|
||||
"$category" => format!("{base}reference"),
|
||||
"$syntax" => format!("{base}reference/syntax"),
|
||||
"$styling" => format!("{base}reference/styling"),
|
||||
"$scripting" => format!("{base}reference/scripting"),
|
||||
"$context" => format!("{base}reference/context"),
|
||||
"$guides" => format!("{base}guides"),
|
||||
"$changelog" => format!("{base}changelog"),
|
||||
"$community" => format!("{base}community"),
|
||||
"$universe" => "https://typst.app/universe".into(),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
static LIBRARY: LazyLock<Library> = LazyLock::new(Library::default);
|
||||
|
||||
/// Extract a module from another module.
|
||||
#[track_caller]
|
||||
fn get_module<'a>(parent: &'a Module, name: &str) -> StrResult<&'a Module> {
|
||||
match parent.scope().get(name).map(|x| x.read()) {
|
||||
Some(Value::Module(module)) => Ok(module),
|
||||
_ => bail!("module doesn't contain module `{name}`"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a `$` link to a global definition.
|
||||
fn resolve_definition(head: &str, base: &str) -> StrResult<String> {
|
||||
let mut parts = head.trim_start_matches('$').split('.').peekable();
|
||||
let mut focus = &LIBRARY.global;
|
||||
let mut category = None;
|
||||
|
||||
while let Some(name) = parts.peek() {
|
||||
if category.is_none() {
|
||||
category = focus.scope().get(name).and_then(Binding::category);
|
||||
}
|
||||
let Ok(module) = get_module(focus, name) else {
|
||||
break;
|
||||
};
|
||||
focus = module;
|
||||
parts.next();
|
||||
}
|
||||
|
||||
let Some(category) = category else {
|
||||
bail!("{head} has no category")
|
||||
};
|
||||
|
||||
let name = parts.next().ok_or("link is missing first part")?;
|
||||
let value = focus.field(name, ())?;
|
||||
|
||||
// Handle grouped functions.
|
||||
if let Some(group) = GROUPS.iter().find(|group| {
|
||||
group.category == category.name() && group.filter.iter().any(|func| func == name)
|
||||
}) {
|
||||
let mut route = format!(
|
||||
"{}reference/{}/{}/#functions-{}",
|
||||
base, group.category, group.name, name
|
||||
);
|
||||
if let Some(param) = parts.next() {
|
||||
route.push('-');
|
||||
route.push_str(param);
|
||||
}
|
||||
return Ok(route);
|
||||
}
|
||||
|
||||
let mut route = format!("{}reference/{}/{name}", base, category.name());
|
||||
if let Some(next) = parts.next() {
|
||||
if let Ok(field) = value.field(next, ()) {
|
||||
route.push_str("/#definitions-");
|
||||
route.push_str(next);
|
||||
if let Some(next) = parts.next() {
|
||||
if field
|
||||
.cast::<Func>()
|
||||
.is_ok_and(|func| func.param(next).is_some())
|
||||
{
|
||||
route.push('-');
|
||||
route.push_str(next);
|
||||
}
|
||||
}
|
||||
} else if value
|
||||
.clone()
|
||||
.cast::<Func>()
|
||||
.is_ok_and(|func| func.param(next).is_some())
|
||||
{
|
||||
route.push_str("/#parameters-");
|
||||
route.push_str(next);
|
||||
} else {
|
||||
bail!("field {next} not found");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
enum CatKey {
|
||||
Func(Func),
|
||||
Type(Type),
|
||||
}
|
||||
|
||||
impl PartialEq for CatKey {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use typst::foundations::func::Repr::*;
|
||||
match (self, other) {
|
||||
(CatKey::Func(a), CatKey::Func(b)) => match (a.inner(), b.inner()) {
|
||||
(Native(a), Native(b)) => a == b,
|
||||
(Element(a), Element(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(CatKey::Type(a), CatKey::Type(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for CatKey {}
|
||||
|
||||
// todo: category of types
|
||||
static ROUTE_MAPS: LazyLock<HashMap<CatKey, String>> = LazyLock::new(|| {
|
||||
// todo: this is a false positive for clippy on LazyHash
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let mut map = HashMap::new();
|
||||
let mut scope_to_finds = vec![
|
||||
(LIBRARY.global.scope(), None, None),
|
||||
(LIBRARY.math.scope(), None, None),
|
||||
];
|
||||
while let Some((scope, parent_name, cat)) = scope_to_finds.pop() {
|
||||
for (name, bind) in scope.iter() {
|
||||
let cat = cat.or_else(|| bind.category());
|
||||
let name = urlify(name);
|
||||
match bind.read() {
|
||||
Value::Func(func) => {
|
||||
if let Some(cat) = cat {
|
||||
let Some(name) = func.name() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Handle grouped functions.
|
||||
if let Some(group) = GROUPS.iter().find(|group| {
|
||||
group.category == cat.name()
|
||||
&& group.filter.iter().any(|func| func == name)
|
||||
}) {
|
||||
let route = format!(
|
||||
"reference/{}/{}/#functions-{name}",
|
||||
group.category, group.name
|
||||
);
|
||||
map.insert(CatKey::Func(func.clone()), route);
|
||||
continue;
|
||||
}
|
||||
|
||||
crate::log_debug_ct!("func: {func:?} -> {cat:?}");
|
||||
|
||||
let route = if let Some(parent_name) = &parent_name {
|
||||
format!("reference/{}/{parent_name}/#definitions-{name}", cat.name())
|
||||
} else {
|
||||
format!("reference/{}/{name}/", cat.name())
|
||||
};
|
||||
|
||||
map.insert(CatKey::Func(func.clone()), route);
|
||||
}
|
||||
if let Some(s) = func.scope() {
|
||||
scope_to_finds.push((s, Some(name), cat));
|
||||
}
|
||||
}
|
||||
Value::Type(t) => {
|
||||
if let Some(cat) = cat {
|
||||
crate::log_debug_ct!("type: {t:?} -> {cat:?}");
|
||||
|
||||
let route = if let Some(parent_name) = &parent_name {
|
||||
format!("reference/{}/{parent_name}/#definitions-{name}", cat.name())
|
||||
} else {
|
||||
format!("reference/{}/{name}/", cat.name())
|
||||
};
|
||||
map.insert(CatKey::Type(*t), route);
|
||||
}
|
||||
scope_to_finds.push((t.scope(), Some(name), cat));
|
||||
}
|
||||
Value::Module(module) => {
|
||||
scope_to_finds.push((module.scope(), Some(name), cat));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
});
|
||||
|
||||
/// Turn a title into an URL fragment.
|
||||
pub(crate) fn urlify(title: &str) -> EcoString {
|
||||
title
|
||||
.chars()
|
||||
.map(|ch| ch.to_ascii_lowercase())
|
||||
.map(|ch| match ch {
|
||||
'a'..='z' | '0'..='9' => ch,
|
||||
_ => '-',
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn route_of_value(val: &Value) -> Option<&'static String> {
|
||||
// ROUTE_MAPS.get(&CatKey::Func(k.clone()))
|
||||
let key = match val {
|
||||
Value::Func(func) => CatKey::Func(func.clone()),
|
||||
Value::Type(ty) => CatKey::Type(*ty),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
ROUTE_MAPS.get(&key)
|
||||
}
|
||||
|
||||
/// Create a short description of a font family.
|
||||
pub fn summarize_font_family<'a>(variants: impl Iterator<Item = &'a FontInfo>) -> EcoString {
|
||||
let mut infos: Vec<_> = variants.collect();
|
||||
infos.sort_by_key(|info: &&FontInfo| info.variant);
|
||||
|
||||
let mut has_italic = false;
|
||||
let mut min_weight = u16::MAX;
|
||||
let mut max_weight = 0;
|
||||
for info in &infos {
|
||||
let weight = info.variant.weight.to_number();
|
||||
has_italic |= info.variant.style == FontStyle::Italic;
|
||||
min_weight = min_weight.min(weight);
|
||||
max_weight = min_weight.max(weight);
|
||||
}
|
||||
|
||||
let count = infos.len();
|
||||
let mut detail = eco_format!("{count} variant{}.", if count == 1 { "" } else { "s" });
|
||||
|
||||
if min_weight == max_weight {
|
||||
write!(detail, " Weight {min_weight}.").unwrap();
|
||||
} else {
|
||||
write!(detail, " Weights {min_weight}–{max_weight}.").unwrap();
|
||||
}
|
||||
|
||||
if has_italic {
|
||||
detail.push_str(" Has italics.");
|
||||
}
|
||||
|
||||
detail
|
||||
}
|
||||
|
||||
pub fn truncated_repr_<const SZ_LIMIT: usize>(value: &Value) -> EcoString {
|
||||
use typst::foundations::Repr;
|
||||
|
||||
let data: Option<Content> = value.clone().cast().ok();
|
||||
let metadata: Option<MetadataElem> = data.and_then(|content| content.unpack().ok());
|
||||
|
||||
// todo: early truncation
|
||||
let repr = if let Some(metadata) = metadata {
|
||||
metadata.value.repr()
|
||||
} else {
|
||||
value.repr()
|
||||
};
|
||||
|
||||
if repr.len() > SZ_LIMIT {
|
||||
eco_format!("[truncated-repr: {} bytes]", repr.len())
|
||||
} else {
|
||||
repr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncated_repr(value: &Value) -> EcoString {
|
||||
const _10MB: usize = 100 * 1024 * 1024;
|
||||
truncated_repr_::<_10MB>(value)
|
||||
}
|
||||
|
||||
/// Run a function with a VM instance in the world
|
||||
pub fn with_vm<T>(
|
||||
world: Tracked<dyn World + '_>,
|
||||
f: impl FnOnce(&mut typst_shim::eval::Vm) -> T,
|
||||
) -> T {
|
||||
use comemo::Track;
|
||||
use typst::engine::*;
|
||||
use typst::foundations::*;
|
||||
use typst::introspection::*;
|
||||
use typst_shim::eval::*;
|
||||
|
||||
let introspector = Introspector::default();
|
||||
let traced = Traced::default();
|
||||
let mut sink = Sink::new();
|
||||
let engine = Engine {
|
||||
routines: &typst::ROUTINES,
|
||||
world,
|
||||
route: Route::default(),
|
||||
introspector: introspector.track(),
|
||||
traced: traced.track(),
|
||||
sink: sink.track_mut(),
|
||||
};
|
||||
|
||||
let context = Context::none();
|
||||
let mut vm = Vm::new(
|
||||
engine,
|
||||
context.track(),
|
||||
Scopes::new(Some(world.library())),
|
||||
Span::detached(),
|
||||
);
|
||||
|
||||
f(&mut vm)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn docs_test() {
|
||||
assert_eq!(
|
||||
"[citation](https://typst.app/docs/reference/model/cite/)",
|
||||
super::plain_docs_sentence("[citation]($cite)")
|
||||
);
|
||||
assert_eq!(
|
||||
"[citation][cite]",
|
||||
super::plain_docs_sentence("[citation][cite]")
|
||||
);
|
||||
assert_eq!(
|
||||
"[citation](https://typst.app/docs/reference/model/cite/)",
|
||||
super::plain_docs_sentence("[citation]($cite)")
|
||||
);
|
||||
assert_eq!(
|
||||
"[citation][cite][cite2]",
|
||||
super::plain_docs_sentence("[citation][cite][cite2]")
|
||||
);
|
||||
assert_eq!(
|
||||
"[citation][cite](test)[cite2]",
|
||||
super::plain_docs_sentence("[citation][cite](test)[cite2]")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use if_chain::if_chain;
|
||||
use typst::engine::Sink;
|
||||
use typst::foundations::{repr, Capturer, CastInfo, Value};
|
||||
use typst::layout::Length;
|
||||
use typst::syntax::{ast, LinkedNode, Source, SyntaxKind};
|
||||
use typst::World;
|
||||
use typst_shim::eval::CapturesVisitor;
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
use typst_shim::utils::{round_2, Numeric};
|
||||
|
||||
use super::{plain_docs_sentence, summarize_font_family, truncated_repr};
|
||||
use crate::analysis::analyze_expr;
|
||||
|
||||
/// Describe the item under the cursor.
|
||||
///
|
||||
/// Passing a `document` (from a previous compilation) is optional, but enhances
|
||||
/// the autocompletions. Label completions, for instance, are only generated
|
||||
/// when the document is available.
|
||||
pub fn tooltip_(world: &dyn World, source: &Source, cursor: usize) -> Option<Tooltip> {
|
||||
let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
if leaf.kind().is_trivia() {
|
||||
return None;
|
||||
}
|
||||
|
||||
named_param_tooltip(world, &leaf)
|
||||
.or_else(|| font_tooltip(world, &leaf))
|
||||
// todo: test that label_tooltip can be removed safely
|
||||
// .or_else(|| document.and_then(|doc| label_tooltip(doc, &leaf)))
|
||||
.or_else(|| expr_tooltip(world, &leaf))
|
||||
.or_else(|| closure_tooltip(&leaf))
|
||||
}
|
||||
|
||||
/// A hover tooltip.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Tooltip {
|
||||
/// A string of text.
|
||||
Text(EcoString),
|
||||
/// A string of Typst code.
|
||||
Code(EcoString),
|
||||
}
|
||||
|
||||
/// Tooltip for a hovered expression.
|
||||
pub fn expr_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let mut ancestor = leaf;
|
||||
while !ancestor.is::<ast::Expr>() {
|
||||
ancestor = ancestor.parent()?;
|
||||
}
|
||||
|
||||
let expr = ancestor.cast::<ast::Expr>()?;
|
||||
if !expr.hash() && !matches!(expr, ast::Expr::MathIdent(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let values = analyze_expr(world, ancestor);
|
||||
|
||||
if let [(value, _)] = values.as_slice() {
|
||||
if let Some(docs) = value.docs() {
|
||||
return Some(Tooltip::Text(plain_docs_sentence(docs)));
|
||||
}
|
||||
|
||||
if let &Value::Length(length) = value {
|
||||
if let Some(tooltip) = length_tooltip(length) {
|
||||
return Some(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if expr.is_literal() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut last = None;
|
||||
let mut pieces: Vec<EcoString> = vec![];
|
||||
let mut unique_func: Option<Value> = None;
|
||||
let mut unique = true;
|
||||
let mut iter = values.iter();
|
||||
for (value, _) in (&mut iter).take(Sink::MAX_VALUES - 1) {
|
||||
if let Some((prev, count)) = &mut last {
|
||||
if *prev == value {
|
||||
*count += 1;
|
||||
continue;
|
||||
} else if *count > 1 {
|
||||
write!(pieces.last_mut().unwrap(), " (x{count})").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(value, Value::Func(..) | Value::Type(..)) {
|
||||
match &unique_func {
|
||||
Some(unique_func) if unique => {
|
||||
unique = unique_func == value;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
unique_func = Some(value.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unique = false;
|
||||
}
|
||||
|
||||
pieces.push(truncated_repr(value));
|
||||
last = Some((value, 1));
|
||||
}
|
||||
|
||||
// Don't report the only function reference...
|
||||
// Note we usually expect the `definition` analyzer work in this case, otherwise
|
||||
// please open an issue for this.
|
||||
if unique_func.is_some() && unique {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((_, count)) = last {
|
||||
if count > 1 {
|
||||
write!(pieces.last_mut().unwrap(), " (x{count})").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
if iter.next().is_some() {
|
||||
pieces.push("...".into());
|
||||
}
|
||||
|
||||
let tooltip = repr::pretty_comma_list(&pieces, false);
|
||||
// todo: check sensible length, value highlighting
|
||||
(!tooltip.is_empty()).then(|| Tooltip::Code(tooltip.into()))
|
||||
}
|
||||
|
||||
/// Tooltip for a hovered closure.
|
||||
fn closure_tooltip(leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
// Only show this tooltip when hovering over the equals sign or arrow of
|
||||
// the closure. Showing it across the whole subtree is too noisy.
|
||||
if !matches!(leaf.kind(), SyntaxKind::Eq | SyntaxKind::Arrow) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the closure to analyze.
|
||||
let parent = leaf.parent()?;
|
||||
if parent.kind() != SyntaxKind::Closure {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Analyze the closure's captures.
|
||||
let mut visitor = CapturesVisitor::new(None, Capturer::Function);
|
||||
visitor.visit(parent);
|
||||
|
||||
let captures = visitor.finish();
|
||||
let mut names: Vec<_> = captures
|
||||
.iter()
|
||||
.map(|(name, _)| eco_format!("`{name}`"))
|
||||
.collect();
|
||||
if names.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
names.sort();
|
||||
|
||||
let tooltip = repr::separated_list(&names, "and");
|
||||
Some(Tooltip::Text(eco_format!(
|
||||
"This closure captures {tooltip}."
|
||||
)))
|
||||
}
|
||||
|
||||
/// Tooltip text for a hovered length.
|
||||
fn length_tooltip(length: Length) -> Option<Tooltip> {
|
||||
length.em.is_zero().then(|| {
|
||||
Tooltip::Code(eco_format!(
|
||||
"{}pt = {}mm = {}cm = {}in",
|
||||
round_2(length.abs.to_pt()),
|
||||
round_2(length.abs.to_mm()),
|
||||
round_2(length.abs.to_cm()),
|
||||
round_2(length.abs.to_inches())
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Tooltips for components of a named parameter.
|
||||
fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
let (func, named) = if_chain! {
|
||||
// Ensure that we are in a named pair in the arguments to a function
|
||||
// call or set rule.
|
||||
if let Some(parent) = leaf.parent();
|
||||
if let Some(named) = parent.cast::<ast::Named>();
|
||||
if let Some(grand) = parent.parent();
|
||||
if matches!(grand.kind(), SyntaxKind::Args);
|
||||
if let Some(grand_grand) = grand.parent();
|
||||
if let Some(expr) = grand_grand.cast::<ast::Expr>();
|
||||
if let Some(ast::Expr::Ident(callee)) = match expr {
|
||||
ast::Expr::FuncCall(call) => Some(call.callee()),
|
||||
ast::Expr::Set(set) => Some(set.target()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Find metadata about the function.
|
||||
if let Some(Value::Func(func)) = world.library().global.scope().get(&callee).map(|x| x.read());
|
||||
then { (func, named) }
|
||||
else { return None; }
|
||||
};
|
||||
|
||||
// Hovering over the parameter name.
|
||||
if_chain! {
|
||||
if leaf.index() == 0;
|
||||
if let Some(ident) = leaf.cast::<ast::Ident>();
|
||||
if let Some(param) = func.param(&ident);
|
||||
then {
|
||||
return Some(Tooltip::Text(plain_docs_sentence(param.docs)));
|
||||
}
|
||||
}
|
||||
|
||||
// Hovering over a string parameter value.
|
||||
if_chain! {
|
||||
if let Some(string) = leaf.cast::<ast::Str>();
|
||||
if let Some(param) = func.param(&named.name());
|
||||
if let Some(docs) = find_string_doc(¶m.input, &string.get());
|
||||
then {
|
||||
return Some(Tooltip::Text(docs.into()));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Find documentation for a castable string.
|
||||
fn find_string_doc(info: &CastInfo, string: &str) -> Option<&'static str> {
|
||||
match info {
|
||||
CastInfo::Value(Value::Str(s), docs) if s.as_str() == string => Some(docs),
|
||||
CastInfo::Union(options) => options
|
||||
.iter()
|
||||
.find_map(|option| find_string_doc(option, string)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tooltip for font.
|
||||
fn font_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option<Tooltip> {
|
||||
if_chain! {
|
||||
// Ensure that we are on top of a string.
|
||||
if let Some(string) = leaf.cast::<ast::Str>();
|
||||
let lower = string.get().to_lowercase();
|
||||
|
||||
// Ensure that we are in the arguments to the text function.
|
||||
if let Some(parent) = leaf.parent();
|
||||
if let Some(named) = parent.cast::<ast::Named>();
|
||||
if named.name().as_str() == "font";
|
||||
|
||||
// Find the font family.
|
||||
if let Some((_, iter)) = world
|
||||
.book()
|
||||
.families()
|
||||
.find(|&(family, _)| family.to_lowercase().as_str() == lower.as_str());
|
||||
|
||||
then {
|
||||
let detail = summarize_font_family(iter);
|
||||
return Some(Tooltip::Text(detail));
|
||||
}
|
||||
};
|
||||
|
||||
None
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue