mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-23 12:45:04 +00:00
feat: improve and prefer to use post type check (#231)
* dev: rename literal_type_check to post_type_check * feat: post check signature * feat: post check array/dict elements * feat: post check positional arguments * fix: signature of func with/where * fix: kind of items * dev: remove cross-module fixture * fix: kind of completions
This commit is contained in:
parent
27f992bd87
commit
0b566f83de
61 changed files with 1129 additions and 430 deletions
|
@ -95,12 +95,11 @@ mod literal_type_check_tests {
|
|||
use typst::syntax::LinkedNode;
|
||||
|
||||
use crate::analysis::ty;
|
||||
use crate::syntax::get_check_target;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
snapshot_testing("literal_type_check", &|ctx, path| {
|
||||
snapshot_testing("post_type_check", &|ctx, path| {
|
||||
let source = ctx.source_by_path(&path).unwrap();
|
||||
|
||||
let pos = ctx
|
||||
|
@ -108,17 +107,10 @@ mod literal_type_check_tests {
|
|||
.unwrap();
|
||||
let root = LinkedNode::new(source.root());
|
||||
let node = root.leaf_at(pos + 1).unwrap();
|
||||
let target = get_check_target(node).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to get check target at {pos:?} in {:?}",
|
||||
source.text()
|
||||
)
|
||||
});
|
||||
let text = target.node().clone().map(|n| n.get().clone().into_text());
|
||||
let text = text.unwrap_or_default();
|
||||
let text = node.get().clone().into_text();
|
||||
|
||||
let result = ty::type_check(ctx, source.clone());
|
||||
let literal_type = result.and_then(|info| ty::literal_type_check(ctx, &info, target));
|
||||
let literal_type = result.and_then(|info| ty::post_type_check(ctx, &info, node));
|
||||
|
||||
with_settings!({
|
||||
description => format!("Check on {text:?} ({pos:?})"),
|
||||
|
|
|
@ -23,10 +23,9 @@ use typst::{foundations::Value, syntax::ast, text::Font};
|
|||
use typst::{layout::Position, syntax::FileId as TypstFileId};
|
||||
|
||||
use super::{
|
||||
literal_type_check, DefUseInfo, FlowType, ImportInfo, PathPreference, Signature,
|
||||
SignatureTarget, TypeCheckInfo,
|
||||
post_type_check, DefUseInfo, FlowType, ImportInfo, PathPreference, Signature, SignatureTarget,
|
||||
TypeCheckInfo,
|
||||
};
|
||||
use crate::syntax::get_check_target;
|
||||
use crate::{
|
||||
lsp_to_typst,
|
||||
syntax::{
|
||||
|
@ -661,10 +660,7 @@ impl<'w> AnalysisContext<'w> {
|
|||
let source = self.source_by_id(id).ok()?;
|
||||
let ty_chk = self.type_check(source.clone())?;
|
||||
|
||||
let check_target = get_check_target(k.clone())?;
|
||||
|
||||
literal_type_check(self, &ty_chk, check_target.clone())
|
||||
.or_else(|| ty_chk.mapping.get(&k.span()).cloned())
|
||||
post_type_check(self, &ty_chk, k.clone()).or_else(|| ty_chk.mapping.get(&k.span()).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Top-level evaluation of a source file.
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
@ -25,8 +25,8 @@ mod def;
|
|||
pub(crate) use def::*;
|
||||
mod builtin;
|
||||
pub(crate) use builtin::*;
|
||||
mod literal_flow;
|
||||
pub(crate) use literal_flow::*;
|
||||
mod post_check;
|
||||
pub(crate) use post_check::*;
|
||||
|
||||
/// Type checking at the source unit level.
|
||||
pub(crate) fn type_check(ctx: &mut AnalysisContext, source: Source) -> Option<Arc<TypeCheckInfo>> {
|
||||
|
@ -84,6 +84,48 @@ impl TypeCheckInfo {
|
|||
|
||||
worker.simplify(ty, principal)
|
||||
}
|
||||
|
||||
pub fn var_at(
|
||||
&mut self,
|
||||
var_site: Span,
|
||||
def_id: DefId,
|
||||
f: impl FnOnce() -> FlowVar,
|
||||
) -> &mut FlowVar {
|
||||
let var = self.vars.entry(def_id).or_insert_with(f);
|
||||
Self::witness_(var_site, var.get_ref(), &mut self.mapping);
|
||||
var
|
||||
}
|
||||
|
||||
// todo: distinguish at least, at most
|
||||
pub fn witness_at_least(&mut self, site: Span, ty: FlowType) {
|
||||
Self::witness_(site, ty, &mut self.mapping);
|
||||
}
|
||||
|
||||
pub fn witness_at_most(&mut self, site: Span, ty: FlowType) {
|
||||
Self::witness_(site, ty, &mut self.mapping);
|
||||
}
|
||||
|
||||
fn witness_(site: Span, ty: FlowType, mapping: &mut HashMap<Span, FlowType>) {
|
||||
if site.is_detached() {
|
||||
return;
|
||||
}
|
||||
|
||||
// todo: intersect/union
|
||||
let site_store = mapping.entry(site);
|
||||
match site_store {
|
||||
Entry::Occupied(e) => match e.into_mut() {
|
||||
FlowType::Union(v) => {
|
||||
v.push(ty);
|
||||
}
|
||||
e => {
|
||||
*e = FlowType::from_types([e.clone(), ty].into_iter());
|
||||
}
|
||||
},
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -108,7 +150,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
let w = self.check_inner(root).unwrap_or(FlowType::Undef);
|
||||
|
||||
if let Some(s) = should_record {
|
||||
self.info.mapping.insert(s, w.clone());
|
||||
self.info.witness_at_least(s, w.clone());
|
||||
}
|
||||
|
||||
w
|
||||
|
@ -123,8 +165,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
SyntaxKind::ContentBlock => return self.check_in_mode(root, InterpretMode::Markup),
|
||||
|
||||
// todo: space effect
|
||||
SyntaxKind::Space => FlowType::None,
|
||||
SyntaxKind::Parbreak => FlowType::None,
|
||||
SyntaxKind::Space => FlowType::Space,
|
||||
SyntaxKind::Parbreak => FlowType::Space,
|
||||
|
||||
SyntaxKind::Text => FlowType::Content,
|
||||
SyntaxKind::Linebreak => FlowType::Content,
|
||||
|
@ -161,8 +203,6 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
SyntaxKind::LoopBreak => FlowType::None,
|
||||
SyntaxKind::LoopContinue => FlowType::None,
|
||||
SyntaxKind::FuncReturn => FlowType::None,
|
||||
SyntaxKind::LineComment => FlowType::None,
|
||||
SyntaxKind::BlockComment => FlowType::None,
|
||||
SyntaxKind::Error => FlowType::None,
|
||||
SyntaxKind::Eof => FlowType::None,
|
||||
|
||||
|
@ -181,7 +221,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
return self
|
||||
.ctx
|
||||
.mini_eval(root.cast()?)
|
||||
.map(|v| (FlowType::Value(Box::new((v, root.span())))))
|
||||
.map(|v| (FlowType::Value(Box::new((v, Span::detached())))))
|
||||
}
|
||||
SyntaxKind::Parenthesized => return self.check_children(root),
|
||||
SyntaxKind::Array => return self.check_array(root),
|
||||
|
@ -205,6 +245,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
SyntaxKind::DestructAssignment => return self.check_destruct_assign(root),
|
||||
|
||||
// Rest all are clauses
|
||||
SyntaxKind::LineComment => FlowType::Clause,
|
||||
SyntaxKind::BlockComment => FlowType::Clause,
|
||||
SyntaxKind::Named => FlowType::Clause,
|
||||
SyntaxKind::Keyed => FlowType::Clause,
|
||||
SyntaxKind::Spread => FlowType::Clause,
|
||||
|
@ -303,7 +345,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
|
||||
for elem in root.children() {
|
||||
let ty = self.check(elem);
|
||||
if matches!(ty, FlowType::Clause) {
|
||||
if matches!(ty, FlowType::Clause | FlowType::Space) {
|
||||
continue;
|
||||
}
|
||||
elements.push(ty);
|
||||
|
@ -343,7 +385,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
let unary: ast::Unary = root.cast()?;
|
||||
|
||||
if let Some(constant) = self.ctx.mini_eval(ast::Expr::Unary(unary)) {
|
||||
return Some(FlowType::Value(Box::new((constant, root.span()))));
|
||||
return Some(FlowType::Value(Box::new((constant, Span::detached()))));
|
||||
}
|
||||
|
||||
let op = unary.op();
|
||||
|
@ -362,7 +404,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
let binary: ast::Binary = root.cast()?;
|
||||
|
||||
if let Some(constant) = self.ctx.mini_eval(ast::Expr::Binary(binary)) {
|
||||
return Some(FlowType::Value(Box::new((constant, root.span()))));
|
||||
return Some(FlowType::Value(Box::new((constant, Span::detached()))));
|
||||
}
|
||||
|
||||
let op = binary.op();
|
||||
|
@ -427,7 +469,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
log::debug!("func_call: {callee:?} with {args:?}");
|
||||
|
||||
if let FlowType::Args(args) = args {
|
||||
self.check_apply(callee, &args, &func_call.args(), &mut candidates)?;
|
||||
self.check_apply(
|
||||
callee,
|
||||
func_call.callee().span(),
|
||||
&args,
|
||||
&func_call.args(),
|
||||
&mut candidates,
|
||||
)?;
|
||||
}
|
||||
|
||||
if candidates.len() == 1 {
|
||||
|
@ -553,7 +601,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
log::debug!("set rule: {callee:?} with {args:?}");
|
||||
|
||||
if let FlowType::Args(args) = args {
|
||||
self.check_apply(callee, &args, &set_rule.args(), &mut candidates)?;
|
||||
self.check_apply(
|
||||
callee,
|
||||
set_rule.target().span(),
|
||||
&args,
|
||||
&set_rule.args(),
|
||||
&mut candidates,
|
||||
)?;
|
||||
}
|
||||
|
||||
if candidates.len() == 1 {
|
||||
|
@ -653,7 +707,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
.get_ref(&r)
|
||||
.or_else(|| Some(self.def_use_info.get_def(s.id()?, &r)?.0))?;
|
||||
|
||||
let var = self.info.vars.entry(def_id).or_insert_with(|| {
|
||||
Some(self.info.var_at(s, def_id, || {
|
||||
// let store = FlowVarStore {
|
||||
// name: r.name.into(),
|
||||
// id: def_id,
|
||||
|
@ -670,10 +724,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
}))),
|
||||
// kind: FlowVarKind::Strong(FlowType::Any),
|
||||
}
|
||||
});
|
||||
|
||||
self.info.mapping.insert(s, var.get_ref());
|
||||
Some(var)
|
||||
}))
|
||||
}
|
||||
|
||||
fn check_pattern(
|
||||
|
@ -709,11 +760,12 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
fn check_apply(
|
||||
&mut self,
|
||||
callee: FlowType,
|
||||
callee_span: Span,
|
||||
args: &FlowArgs,
|
||||
syntax_args: &ast::Args,
|
||||
candidates: &mut Vec<FlowType>,
|
||||
) -> Option<()> {
|
||||
// log::debug!("check func callee {callee:?}");
|
||||
log::debug!("check func callee {callee:?}");
|
||||
|
||||
match &callee {
|
||||
FlowType::Var(v) => {
|
||||
|
@ -722,15 +774,29 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
FlowVarKind::Weak(w) => {
|
||||
let w = w.read();
|
||||
for lb in w.lbs.iter() {
|
||||
self.check_apply(lb.clone(), args, syntax_args, candidates)?;
|
||||
self.check_apply(
|
||||
lb.clone(),
|
||||
callee_span,
|
||||
args,
|
||||
syntax_args,
|
||||
candidates,
|
||||
)?;
|
||||
}
|
||||
for ub in w.ubs.iter() {
|
||||
self.check_apply(ub.clone(), args, syntax_args, candidates)?;
|
||||
self.check_apply(
|
||||
ub.clone(),
|
||||
callee_span,
|
||||
args,
|
||||
syntax_args,
|
||||
candidates,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowType::Func(v) => {
|
||||
self.info.witness_at_least(callee_span, callee.clone());
|
||||
|
||||
let f = v.as_ref();
|
||||
let mut pos = f.pos.iter();
|
||||
// let mut named = f.named.clone();
|
||||
|
@ -759,15 +825,16 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
FlowType::With(_e) => {}
|
||||
FlowType::Args(_e) => {}
|
||||
FlowType::Union(_e) => {}
|
||||
FlowType::Field(_e) => {}
|
||||
FlowType::Let(_) => {}
|
||||
FlowType::Value(f) => {
|
||||
if let Value::Func(f) = &f.0 {
|
||||
self.check_apply_runtime(f, args, syntax_args, candidates);
|
||||
self.check_apply_runtime(f, callee_span, args, syntax_args, candidates);
|
||||
}
|
||||
}
|
||||
FlowType::ValueDoc(f) => {
|
||||
if let Value::Func(f) = &f.0 {
|
||||
self.check_apply_runtime(f, args, syntax_args, candidates);
|
||||
self.check_apply_runtime(f, callee_span, args, syntax_args, candidates);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -778,12 +845,19 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
FlowType::None => {}
|
||||
FlowType::Infer => {}
|
||||
FlowType::FlowNone => {}
|
||||
FlowType::Space => {}
|
||||
FlowType::Auto => {}
|
||||
FlowType::Builtin(_) => {}
|
||||
FlowType::Boolean(_) => {}
|
||||
FlowType::At(e) => {
|
||||
let primary_type = self.check_primary_type(e.0 .0.clone());
|
||||
self.check_apply_method(primary_type, e.0 .1.clone(), args, candidates);
|
||||
self.check_apply_method(
|
||||
primary_type,
|
||||
callee_span,
|
||||
e.0 .1.clone(),
|
||||
args,
|
||||
candidates,
|
||||
);
|
||||
}
|
||||
FlowType::Unary(_) => {}
|
||||
FlowType::Binary(_) => {}
|
||||
|
@ -904,27 +978,23 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
log::debug!("constrain record item {key} {lhs:?} ⪯ {rhs:?}");
|
||||
self.constrain(lhs, rhs);
|
||||
if !sl.is_detached() {
|
||||
// todo: intersect/union
|
||||
self.info.mapping.entry(*sl).or_insert(rhs.clone());
|
||||
self.info.witness_at_most(*sl, rhs.clone());
|
||||
}
|
||||
if !sr.is_detached() {
|
||||
// todo: intersect/union
|
||||
self.info.mapping.entry(*sr).or_insert(lhs.clone());
|
||||
self.info.witness_at_least(*sr, lhs.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
(FlowType::Value(lhs), rhs) => {
|
||||
log::debug!("constrain value {lhs:?} ⪯ {rhs:?}");
|
||||
if !lhs.1.is_detached() {
|
||||
// todo: intersect/union
|
||||
self.info.mapping.entry(lhs.1).or_insert(rhs.clone());
|
||||
self.info.witness_at_most(lhs.1, rhs.clone());
|
||||
}
|
||||
}
|
||||
(lhs, FlowType::Value(rhs)) => {
|
||||
log::debug!("constrain value {lhs:?} ⪯ {rhs:?}");
|
||||
if !rhs.1.is_detached() {
|
||||
// todo: intersect/union
|
||||
self.info.mapping.entry(rhs.1).or_insert(lhs.clone());
|
||||
self.info.witness_at_least(rhs.1, lhs.clone());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -961,6 +1031,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
|
||||
FlowType::Tuple(..) => e,
|
||||
FlowType::Array(..) => e,
|
||||
FlowType::Field(..) => e,
|
||||
FlowType::Clause => e,
|
||||
FlowType::Undef => e,
|
||||
FlowType::Content => e,
|
||||
|
@ -968,6 +1039,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
FlowType::None => e,
|
||||
FlowType::Infer => e,
|
||||
FlowType::FlowNone => e,
|
||||
FlowType::Space => e,
|
||||
FlowType::Auto => e,
|
||||
FlowType::Builtin(_) => e,
|
||||
FlowType::At(e) => self.check_primary_type(e.0 .0.clone()),
|
||||
|
@ -982,11 +1054,14 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
fn check_apply_method(
|
||||
&mut self,
|
||||
primary_type: FlowType,
|
||||
callee_span: Span,
|
||||
method_name: EcoString,
|
||||
args: &FlowArgs,
|
||||
_candidates: &mut Vec<FlowType>,
|
||||
) -> Option<()> {
|
||||
log::debug!("check method at {method_name:?} on {primary_type:?}");
|
||||
self.info
|
||||
.witness_at_least(callee_span, primary_type.clone());
|
||||
match primary_type {
|
||||
FlowType::Func(v) => match method_name.as_str() {
|
||||
// todo: process where specially
|
||||
|
@ -1025,10 +1100,16 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
fn check_apply_runtime(
|
||||
&mut self,
|
||||
f: &Func,
|
||||
callee_span: Span,
|
||||
args: &FlowArgs,
|
||||
syntax_args: &ast::Args,
|
||||
candidates: &mut Vec<FlowType>,
|
||||
) -> Option<()> {
|
||||
// todo: hold signature
|
||||
self.info.witness_at_least(
|
||||
callee_span,
|
||||
FlowType::Value(Box::new((Value::Func(f.clone()), Span::detached()))),
|
||||
);
|
||||
let sig = analyze_dyn_signature(self.ctx, f.clone());
|
||||
|
||||
log::debug!("check runtime func {f:?} at args: {args:?}");
|
||||
|
@ -1047,7 +1128,8 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
let pos_ty = pos.next().unwrap_or(&FlowType::Any);
|
||||
self.constrain(pos_in, pos_ty);
|
||||
if let Some(syntax_pos) = syntax_pos.next() {
|
||||
self.info.mapping.insert(syntax_pos.span(), pos_ty.clone());
|
||||
self.info
|
||||
.witness_at_least(syntax_pos.span(), pos_ty.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1068,11 +1150,9 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
self.constrain(named_in, named_ty);
|
||||
if let Some(syntax_named) = syntax_named {
|
||||
self.info
|
||||
.mapping
|
||||
.insert(syntax_named.span(), named_ty.clone());
|
||||
.witness_at_least(syntax_named.span(), named_ty.clone());
|
||||
self.info
|
||||
.mapping
|
||||
.insert(syntax_named.expr().span(), named_ty.clone());
|
||||
.witness_at_least(syntax_named.expr().span(), named_ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1247,6 +1327,9 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
self.analyze(ub, pol);
|
||||
}
|
||||
}
|
||||
FlowType::Field(v) => {
|
||||
self.analyze(&v.1, pol);
|
||||
}
|
||||
FlowType::Value(_v) => {}
|
||||
FlowType::ValueDoc(_v) => {}
|
||||
FlowType::Clause => {}
|
||||
|
@ -1256,6 +1339,7 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
FlowType::None => {}
|
||||
FlowType::Infer => {}
|
||||
FlowType::FlowNone => {}
|
||||
FlowType::Space => {}
|
||||
FlowType::Auto => {}
|
||||
FlowType::Boolean(_) => {}
|
||||
FlowType::Builtin(_) => {}
|
||||
|
@ -1265,6 +1349,8 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
|
||||
fn transform(&mut self, ty: &FlowType, pol: bool) -> FlowType {
|
||||
match ty {
|
||||
// todo
|
||||
FlowType::Let(w) => self.transform_let(w, None, pol),
|
||||
FlowType::Var(v) => {
|
||||
if let Some(cano) = self.cano_local_cache.get(&(v.0, self.principal)) {
|
||||
return cano.clone();
|
||||
|
@ -1277,35 +1363,7 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
FlowVarKind::Weak(w) => {
|
||||
let w = w.read();
|
||||
|
||||
let mut lbs = Vec::with_capacity(w.lbs.len());
|
||||
let mut ubs = Vec::with_capacity(w.ubs.len());
|
||||
|
||||
log::debug!(
|
||||
"transform var [principal={}] {v:?} with {w:?}",
|
||||
self.principal
|
||||
);
|
||||
|
||||
if !self.principal || ((pol) && !self.negatives.contains(&v.0)) {
|
||||
for lb in w.lbs.iter() {
|
||||
lbs.push(self.transform(lb, pol));
|
||||
}
|
||||
}
|
||||
if !self.principal || ((!pol) && !self.positives.contains(&v.0)) {
|
||||
for ub in w.ubs.iter() {
|
||||
ubs.push(self.transform(ub, !pol));
|
||||
}
|
||||
}
|
||||
|
||||
if ubs.is_empty() {
|
||||
if lbs.len() == 1 {
|
||||
return lbs.pop().unwrap();
|
||||
}
|
||||
if lbs.is_empty() {
|
||||
return FlowType::Any;
|
||||
}
|
||||
}
|
||||
|
||||
FlowType::Let(Arc::new(FlowVarStore { lbs, ubs }))
|
||||
self.transform_let(&w, Some(&v.0), pol)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1387,13 +1445,17 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
|
||||
FlowType::Union(Box::new(v2))
|
||||
}
|
||||
FlowType::Field(f) => {
|
||||
let (x, y, z) = *f.clone();
|
||||
|
||||
FlowType::Field(Box::new((x, self.transform(&y, pol), z)))
|
||||
}
|
||||
FlowType::At(a) => {
|
||||
let a2 = a.clone();
|
||||
|
||||
FlowType::At(a2)
|
||||
}
|
||||
// todo
|
||||
FlowType::Let(_) => FlowType::Any,
|
||||
|
||||
FlowType::Value(v) => FlowType::Value(v.clone()),
|
||||
FlowType::ValueDoc(v) => FlowType::ValueDoc(v.clone()),
|
||||
FlowType::Element(v) => FlowType::Element(*v),
|
||||
|
@ -1404,11 +1466,41 @@ impl<'a, 'b> TypeSimplifier<'a, 'b> {
|
|||
FlowType::None => FlowType::None,
|
||||
FlowType::Infer => FlowType::Infer,
|
||||
FlowType::FlowNone => FlowType::FlowNone,
|
||||
FlowType::Space => FlowType::Space,
|
||||
FlowType::Auto => FlowType::Auto,
|
||||
FlowType::Boolean(b) => FlowType::Boolean(*b),
|
||||
FlowType::Builtin(b) => FlowType::Builtin(b.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_let(&mut self, w: &FlowVarStore, def_id: Option<&DefId>, pol: bool) -> FlowType {
|
||||
let mut lbs = Vec::with_capacity(w.lbs.len());
|
||||
let mut ubs = Vec::with_capacity(w.ubs.len());
|
||||
|
||||
log::debug!("transform let [principal={}] with {w:?}", self.principal);
|
||||
|
||||
if !self.principal || ((pol) && !def_id.is_some_and(|i| self.negatives.contains(i))) {
|
||||
for lb in w.lbs.iter() {
|
||||
lbs.push(self.transform(lb, pol));
|
||||
}
|
||||
}
|
||||
if !self.principal || ((!pol) && !def_id.is_some_and(|i| self.positives.contains(i))) {
|
||||
for ub in w.ubs.iter() {
|
||||
ubs.push(self.transform(ub, !pol));
|
||||
}
|
||||
}
|
||||
|
||||
if ubs.is_empty() {
|
||||
if lbs.len() == 1 {
|
||||
return lbs.pop().unwrap();
|
||||
}
|
||||
if lbs.is_empty() {
|
||||
return FlowType::Any;
|
||||
}
|
||||
}
|
||||
|
||||
FlowType::Let(Arc::new(FlowVarStore { lbs, ubs }))
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ident_ref(root: &LinkedNode, c: ast::Ident) -> Option<IdentRef> {
|
||||
|
@ -1434,7 +1526,7 @@ impl Joiner {
|
|||
// definite = definite.join(p);
|
||||
// }
|
||||
|
||||
// println!("possibles: {:?} {:?}", self.definite, self.possibles);
|
||||
// log::debug!("possibles: {:?} {:?}", self.definite, self.possibles);
|
||||
|
||||
FlowType::Any
|
||||
}
|
||||
|
@ -1447,6 +1539,7 @@ impl Joiner {
|
|||
match (child, &self.definite) {
|
||||
(FlowType::Clause, _) => {}
|
||||
(FlowType::Undef, _) => {}
|
||||
(FlowType::Space, _) => {}
|
||||
(FlowType::Any, _) | (_, FlowType::Any) => {}
|
||||
(FlowType::Infer, _) => {}
|
||||
(FlowType::None, _) => {}
|
||||
|
@ -1493,6 +1586,8 @@ impl Joiner {
|
|||
(FlowType::Union(..), _) => self.definite = FlowType::Undef,
|
||||
(FlowType::Let(w), FlowType::None) => self.definite = FlowType::Let(w),
|
||||
(FlowType::Let(..), _) => self.definite = FlowType::Undef,
|
||||
(FlowType::Field(w), FlowType::None) => self.definite = FlowType::Field(w),
|
||||
(FlowType::Field(..), _) => self.definite = FlowType::Undef,
|
||||
(FlowType::Boolean(b), FlowType::None) => self.definite = FlowType::Boolean(b),
|
||||
(FlowType::Boolean(..), _) => self.definite = FlowType::Undef,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use typst::{
|
|||
|
||||
use crate::analysis::ty::param_mapping;
|
||||
|
||||
use super::{FlowBuiltinType, TypeCheckInfo};
|
||||
use super::FlowBuiltinType;
|
||||
|
||||
struct RefDebug<'a>(&'a FlowType);
|
||||
|
||||
|
@ -31,6 +31,7 @@ pub(crate) enum FlowType {
|
|||
Undef,
|
||||
Content,
|
||||
Any,
|
||||
Space,
|
||||
None,
|
||||
Infer,
|
||||
FlowNone,
|
||||
|
@ -39,6 +40,7 @@ pub(crate) enum FlowType {
|
|||
Builtin(FlowBuiltinType),
|
||||
Value(Box<(Value, Span)>),
|
||||
ValueDoc(Box<(Value, &'static str)>),
|
||||
Field(Box<(EcoString, FlowType, Span)>),
|
||||
Element(Element),
|
||||
|
||||
Var(Box<(DefId, EcoString)>),
|
||||
|
@ -64,6 +66,7 @@ impl fmt::Debug for FlowType {
|
|||
FlowType::Undef => f.write_str("Undef"),
|
||||
FlowType::Content => f.write_str("Content"),
|
||||
FlowType::Any => f.write_str("Any"),
|
||||
FlowType::Space => f.write_str("Space"),
|
||||
FlowType::None => f.write_str("None"),
|
||||
FlowType::Infer => f.write_str("Infer"),
|
||||
FlowType::FlowNone => f.write_str("FlowNone"),
|
||||
|
@ -92,7 +95,8 @@ impl fmt::Debug for FlowType {
|
|||
}
|
||||
f.write_str(")")
|
||||
}
|
||||
FlowType::Let(v) => write!(f, "{v:?}"),
|
||||
FlowType::Let(v) => write!(f, "({v:?})"),
|
||||
FlowType::Field(ff) => write!(f, "{:?}: {:?}", ff.0, ff.1),
|
||||
FlowType::Var(v) => write!(f, "@{}", v.1),
|
||||
FlowType::Unary(u) => write!(f, "{u:?}"),
|
||||
FlowType::Binary(b) => write!(f, "{b:?}"),
|
||||
|
@ -125,11 +129,14 @@ impl FlowType {
|
|||
CastInfo::Any => FlowType::Any,
|
||||
CastInfo::Value(v, doc) => FlowType::ValueDoc(Box::new((v.clone(), *doc))),
|
||||
CastInfo::Type(ty) => FlowType::Value(Box::new((Value::Type(*ty), Span::detached()))),
|
||||
CastInfo::Union(e) => FlowType::Union(Box::new(
|
||||
e.iter()
|
||||
.flat_map(|e| Self::from_return_site(f, e))
|
||||
.collect(),
|
||||
)),
|
||||
CastInfo::Union(e) => {
|
||||
// flat union
|
||||
let e = UnionIter(vec![e.as_slice().iter()]);
|
||||
|
||||
FlowType::Union(Box::new(
|
||||
e.flat_map(|e| Self::from_return_site(f, e)).collect(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Some(ty)
|
||||
|
@ -151,11 +158,14 @@ impl FlowType {
|
|||
CastInfo::Any => FlowType::Any,
|
||||
CastInfo::Value(v, doc) => FlowType::ValueDoc(Box::new((v.clone(), *doc))),
|
||||
CastInfo::Type(ty) => FlowType::Value(Box::new((Value::Type(*ty), Span::detached()))),
|
||||
CastInfo::Union(e) => FlowType::Union(Box::new(
|
||||
e.iter()
|
||||
.flat_map(|e| Self::from_param_site(f, p, e))
|
||||
.collect(),
|
||||
)),
|
||||
CastInfo::Union(e) => {
|
||||
// flat union
|
||||
let e = UnionIter(vec![e.as_slice().iter()]);
|
||||
|
||||
FlowType::Union(Box::new(
|
||||
e.flat_map(|e| Self::from_param_site(f, p, e)).collect(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Some(ty)
|
||||
|
@ -175,28 +185,26 @@ impl FlowType {
|
|||
FlowType::Union(Box::new(e.collect()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn signatures(
|
||||
&self,
|
||||
ty_chk: &TypeCheckInfo,
|
||||
principal: bool,
|
||||
) -> Option<Vec<FlowSignature>> {
|
||||
let mut res = Vec::new();
|
||||
check_signatures(self, &mut res, ty_chk, principal);
|
||||
if res.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// todo: bad performance
|
||||
for sig in &mut res {
|
||||
for pos in &mut sig.pos {
|
||||
*pos = ty_chk.simplify(pos.clone(), principal);
|
||||
}
|
||||
for (_, ty) in &mut sig.named {
|
||||
*ty = ty_chk.simplify(ty.clone(), principal);
|
||||
struct UnionIter<'a>(Vec<std::slice::Iter<'a, CastInfo>>);
|
||||
|
||||
impl<'a> Iterator for UnionIter<'a> {
|
||||
type Item = &'a CastInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let iter = self.0.last_mut()?;
|
||||
if let Some(e) = iter.next() {
|
||||
match e {
|
||||
CastInfo::Union(e) => {
|
||||
self.0.push(e.as_slice().iter());
|
||||
}
|
||||
_ => return Some(e),
|
||||
}
|
||||
} else {
|
||||
self.0.pop();
|
||||
}
|
||||
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +249,7 @@ pub(crate) struct FlowIfType {
|
|||
|
||||
impl FlowIfType {}
|
||||
|
||||
#[derive(Clone, Hash)]
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub(crate) struct FlowVarStore {
|
||||
pub lbs: Vec<FlowType>,
|
||||
pub ubs: Vec<FlowType>,
|
||||
|
@ -373,6 +381,38 @@ pub(crate) struct FlowSignature {
|
|||
pub rest: Option<FlowType>,
|
||||
pub ret: FlowType,
|
||||
}
|
||||
impl FlowSignature {
|
||||
/// Array constructor
|
||||
pub(crate) fn array_cons(elem: FlowType, anyify: bool) -> FlowSignature {
|
||||
let ret = if anyify { FlowType::Any } else { elem.clone() };
|
||||
FlowSignature {
|
||||
pos: Vec::new(),
|
||||
named: Vec::new(),
|
||||
rest: Some(elem),
|
||||
ret,
|
||||
}
|
||||
}
|
||||
|
||||
/// Dictionary constructor
|
||||
pub(crate) fn dict_cons(named: &FlowRecord, anyify: bool) -> FlowSignature {
|
||||
let ret = if anyify {
|
||||
FlowType::Any
|
||||
} else {
|
||||
FlowType::Dict(named.clone())
|
||||
};
|
||||
FlowSignature {
|
||||
pos: Vec::new(),
|
||||
named: named
|
||||
.fields
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(name, ty, _)| (name, ty))
|
||||
.collect(),
|
||||
rest: None,
|
||||
ret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FlowSignature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -451,60 +491,3 @@ impl fmt::Debug for FlowRecord {
|
|||
f.write_str("}")
|
||||
}
|
||||
}
|
||||
|
||||
fn instantiate_signature(
|
||||
f: &FlowType,
|
||||
args: Vec<FlowArgs>,
|
||||
sigs: &mut Vec<FlowSignature>,
|
||||
ty_chk: &TypeCheckInfo,
|
||||
principal: bool,
|
||||
) {
|
||||
let sigs_checkpoint = sigs.len();
|
||||
check_signatures(f, sigs, ty_chk, principal);
|
||||
if sigs.len() == sigs_checkpoint {
|
||||
return;
|
||||
}
|
||||
for sig in &mut sigs[sigs_checkpoint..] {
|
||||
// consume the positional arguments
|
||||
sig.pos = if sig.pos.len() > args.len() {
|
||||
sig.pos.split_off(args.len())
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn check_signatures(
|
||||
ty: &FlowType,
|
||||
res: &mut Vec<FlowSignature>,
|
||||
ty_chk: &TypeCheckInfo,
|
||||
principal: bool,
|
||||
) {
|
||||
match ty {
|
||||
FlowType::Func(s) => res.push(*s.clone()),
|
||||
FlowType::With(w) => {
|
||||
instantiate_signature(&w.0, w.1.clone(), res, ty_chk, principal);
|
||||
}
|
||||
FlowType::Union(u) => {
|
||||
for ty in u.iter() {
|
||||
check_signatures(ty, res, ty_chk, principal);
|
||||
}
|
||||
}
|
||||
FlowType::Var(u) => {
|
||||
let var = ty_chk.vars.get(&u.0);
|
||||
if let Some(var) = var {
|
||||
let FlowVarKind::Weak(w) = &var.kind;
|
||||
let w = w.read();
|
||||
for lb in &w.ubs {
|
||||
check_signatures(lb, res, ty_chk, principal);
|
||||
}
|
||||
if !principal {
|
||||
for ub in &w.lbs {
|
||||
check_signatures(ub, res, ty_chk, principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
//! Infer more than the principal type of some expression.
|
||||
|
||||
use typst::syntax::{
|
||||
ast::{self, AstNode},
|
||||
LinkedNode, SyntaxKind,
|
||||
};
|
||||
|
||||
use crate::{syntax::CheckTarget, AnalysisContext};
|
||||
|
||||
use super::{FlowType, FlowVarKind, TypeCheckInfo};
|
||||
|
||||
// todo: detect recursive usage
|
||||
|
||||
pub(crate) fn literal_type_check(
|
||||
_ctx: &mut AnalysisContext,
|
||||
info: &TypeCheckInfo,
|
||||
node: CheckTarget<'_>,
|
||||
) -> Option<FlowType> {
|
||||
let node = node.node()?;
|
||||
let mut worker = LiteralTypeCheckWorker { _ctx, info };
|
||||
|
||||
worker.check(node)
|
||||
}
|
||||
|
||||
struct LiteralTypeCheckWorker<'a, 'w> {
|
||||
_ctx: &'a mut AnalysisContext<'w>,
|
||||
info: &'a TypeCheckInfo,
|
||||
}
|
||||
|
||||
impl<'a, 'w> LiteralTypeCheckWorker<'a, 'w> {
|
||||
fn check(&mut self, node: LinkedNode) -> Option<FlowType> {
|
||||
let parent = node.parent()?;
|
||||
match parent.kind() {
|
||||
SyntaxKind::LetBinding => {
|
||||
let p = parent.cast::<ast::LetBinding>()?;
|
||||
let exp = p.init()?;
|
||||
if exp.span() == node.span() {
|
||||
match p.kind() {
|
||||
ast::LetBindingKind::Closure(_c) => {
|
||||
return None;
|
||||
}
|
||||
ast::LetBindingKind::Normal(pattern) => {
|
||||
return self.destruct_let(pattern, node.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SyntaxKind::Named => {
|
||||
let p = parent.cast::<ast::Named>()?;
|
||||
let exp = p.expr();
|
||||
if exp.span() == node.span() {
|
||||
let ty = self.info.mapping.get(&p.span())?;
|
||||
return self.ubs(ty);
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn destruct_let(&self, pattern: ast::Pattern<'_>, node: LinkedNode<'_>) -> Option<FlowType> {
|
||||
match pattern {
|
||||
ast::Pattern::Placeholder(_) => None,
|
||||
ast::Pattern::Normal(n) => {
|
||||
let ast::Expr::Ident(ident) = n else {
|
||||
return None;
|
||||
};
|
||||
let ty = self.info.mapping.get(&ident.span())?;
|
||||
self.ubs(ty)
|
||||
}
|
||||
ast::Pattern::Parenthesized(p) => {
|
||||
self.destruct_let(p.expr().to_untyped().cast()?, node)
|
||||
}
|
||||
// todo: pattern matching
|
||||
ast::Pattern::Destructuring(_d) => {
|
||||
let _ = node;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ubs(&self, ty: &FlowType) -> Option<FlowType> {
|
||||
match ty {
|
||||
FlowType::Let(ty) => Some(FlowType::from_types(ty.ubs.iter().cloned())),
|
||||
FlowType::Var(ty) => {
|
||||
let v = self.info.vars.get(&ty.0)?;
|
||||
match &v.kind {
|
||||
FlowVarKind::Weak(w) => {
|
||||
let r = w.read();
|
||||
Some(FlowType::from_types(r.ubs.iter().cloned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Some(ty.clone()),
|
||||
}
|
||||
}
|
||||
}
|
505
crates/tinymist-query/src/analysis/ty/post_check.rs
Normal file
505
crates/tinymist-query/src/analysis/ty/post_check.rs
Normal file
|
@ -0,0 +1,505 @@
|
|||
//! Infer more than the principal type of some expression.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use typst::{
|
||||
foundations::Value,
|
||||
syntax::{
|
||||
ast::{self, AstNode},
|
||||
LinkedNode, Span, SyntaxKind,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
analysis::{analyze_dyn_signature, FlowVarStore, Signature},
|
||||
syntax::{get_check_target, CheckTarget, ParamTarget},
|
||||
AnalysisContext,
|
||||
};
|
||||
|
||||
use super::{
|
||||
FlowArgs, FlowBuiltinType, FlowRecord, FlowSignature, FlowType, FlowVarKind, TypeCheckInfo,
|
||||
FLOW_INSET_DICT, FLOW_MARGIN_DICT, FLOW_OUTSET_DICT, FLOW_RADIUS_DICT, FLOW_STROKE_DICT,
|
||||
};
|
||||
|
||||
/// With given type information, check the type of a literal expression again by
|
||||
/// touching the possible related nodes.
|
||||
pub(crate) fn post_type_check(
|
||||
_ctx: &mut AnalysisContext,
|
||||
info: &TypeCheckInfo,
|
||||
node: LinkedNode,
|
||||
) -> Option<FlowType> {
|
||||
let mut worker = PostTypeCheckWorker {
|
||||
ctx: _ctx,
|
||||
checked: HashMap::new(),
|
||||
info,
|
||||
};
|
||||
|
||||
worker.check(&node)
|
||||
}
|
||||
|
||||
enum Abstracted<T, V> {
|
||||
Type(T),
|
||||
Value(V),
|
||||
}
|
||||
|
||||
type AbstractedSignature<'a> = Abstracted<&'a FlowSignature, &'a Signature>;
|
||||
|
||||
struct SignatureWrapper<'a>(AbstractedSignature<'a>);
|
||||
|
||||
impl<'a> SignatureWrapper<'a> {
|
||||
fn named(&self, name: &str) -> Option<&FlowType> {
|
||||
match &self.0 {
|
||||
Abstracted::Type(sig) => sig.named.iter().find(|(k, _)| k == name).map(|(_, v)| v),
|
||||
Abstracted::Value(sig) => sig
|
||||
.primary()
|
||||
.named
|
||||
.get(name)
|
||||
.and_then(|p| p.infer_type.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
fn names(&self, mut f: impl FnMut(&str)) {
|
||||
match &self.0 {
|
||||
Abstracted::Type(sig) => {
|
||||
for (k, _) in &sig.named {
|
||||
f(k);
|
||||
}
|
||||
}
|
||||
Abstracted::Value(sig) => {
|
||||
for (k, p) in &sig.primary().named {
|
||||
if p.infer_type.is_some() {
|
||||
f(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pos(&self, pos: usize) -> Option<&FlowType> {
|
||||
match &self.0 {
|
||||
Abstracted::Type(sig) => sig.pos.get(pos),
|
||||
// todo: bindings
|
||||
Abstracted::Value(sig) => sig
|
||||
.primary()
|
||||
.pos
|
||||
.get(pos)
|
||||
.and_then(|p| p.infer_type.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
fn rest(&self) -> Option<&FlowType> {
|
||||
match &self.0 {
|
||||
Abstracted::Type(sig) => sig.rest.as_ref(),
|
||||
Abstracted::Value(sig) => sig
|
||||
.primary()
|
||||
.rest
|
||||
.as_ref()
|
||||
.and_then(|p| p.infer_type.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SignatureReceiver(FlowVarStore);
|
||||
|
||||
impl SignatureReceiver {
|
||||
fn insert(&mut self, ty: &FlowType, pol: bool) {
|
||||
if pol {
|
||||
self.0.lbs.push(ty.clone());
|
||||
} else {
|
||||
self.0.ubs.push(ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_signature<'a>(
|
||||
receiver: &'a mut SignatureReceiver,
|
||||
target: &'a ParamTarget,
|
||||
) -> impl FnMut(&mut PostTypeCheckWorker, SignatureWrapper, &[FlowArgs], bool) -> Option<()> + 'a {
|
||||
move |_worker, sig, args, pol| {
|
||||
match &target {
|
||||
ParamTarget::Named(n) => {
|
||||
let ident = n.cast::<ast::Ident>()?;
|
||||
let ty = sig.named(ident.get())?;
|
||||
receiver.insert(ty, !pol);
|
||||
|
||||
Some(())
|
||||
}
|
||||
ParamTarget::Positional {
|
||||
// todo: spreads
|
||||
spreads: _,
|
||||
positional,
|
||||
is_spread,
|
||||
} => {
|
||||
if *is_spread {
|
||||
return None;
|
||||
}
|
||||
|
||||
// truncate args
|
||||
let c = args.iter().map(|args| args.args.len()).sum::<usize>();
|
||||
let nth = sig.pos(c + positional).or_else(|| sig.rest())?;
|
||||
receiver.insert(nth, !pol);
|
||||
|
||||
// names
|
||||
sig.names(|name| {
|
||||
// todo: reduce fields
|
||||
receiver.insert(
|
||||
&FlowType::Field(Box::new((name.into(), FlowType::Any, Span::detached()))),
|
||||
!pol,
|
||||
);
|
||||
});
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PostTypeCheckWorker<'a, 'w> {
|
||||
ctx: &'a mut AnalysisContext<'w>,
|
||||
checked: HashMap<Span, Option<FlowType>>,
|
||||
info: &'a TypeCheckInfo,
|
||||
}
|
||||
|
||||
impl<'a, 'w> PostTypeCheckWorker<'a, 'w> {
|
||||
fn check(&mut self, node: &LinkedNode) -> Option<FlowType> {
|
||||
let span = node.span();
|
||||
if let Some(ty) = self.checked.get(&span) {
|
||||
return ty.clone();
|
||||
}
|
||||
// loop detection
|
||||
self.checked.insert(span, None);
|
||||
|
||||
let ty = self.check_(node);
|
||||
self.checked.insert(span, ty.clone());
|
||||
ty
|
||||
}
|
||||
|
||||
fn check_(&mut self, node: &LinkedNode) -> Option<FlowType> {
|
||||
let context = node.parent()?;
|
||||
log::debug!("post check: {:?}::{:?}", context.kind(), node.kind());
|
||||
let checked_context = self.check_context(context, node);
|
||||
let res = self.check_self(context, node, checked_context);
|
||||
log::debug!(
|
||||
"post check(res): {:?}::{:?} -> {res:?}",
|
||||
context.kind(),
|
||||
node.kind(),
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
fn check_context_or(
|
||||
&mut self,
|
||||
context: &LinkedNode,
|
||||
context_ty: Option<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
let checked_context = self.check(context);
|
||||
if checked_context.is_some() && context_ty.is_some() {
|
||||
let c = checked_context?;
|
||||
let s = context_ty?;
|
||||
|
||||
Some(FlowType::from_types([c, s].into_iter()))
|
||||
} else {
|
||||
checked_context.or(context_ty)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_target(
|
||||
&mut self,
|
||||
node: Option<CheckTarget>,
|
||||
context_ty: Option<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
let Some(node) = node else {
|
||||
return context_ty;
|
||||
};
|
||||
log::debug!("post check target: {node:?}");
|
||||
|
||||
match node {
|
||||
CheckTarget::Param {
|
||||
callee,
|
||||
target,
|
||||
is_set,
|
||||
} => {
|
||||
let callee = self.check_context_or(&callee, context_ty)?;
|
||||
log::debug!("post check call target: ({callee:?})::{target:?} is_set: {is_set}");
|
||||
|
||||
let mut resp = SignatureReceiver::default();
|
||||
|
||||
self.check_signatures(&callee, false, &mut check_signature(&mut resp, &target));
|
||||
|
||||
log::debug!("post check target iterated: {:?}", resp.0);
|
||||
Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false))
|
||||
}
|
||||
CheckTarget::Element { container, target } => {
|
||||
let container_ty = self.check_context_or(&container, context_ty)?;
|
||||
log::debug!("post check element target: {container_ty:?}::{target:?}");
|
||||
|
||||
let mut resp = SignatureReceiver::default();
|
||||
|
||||
self.check_element_of(
|
||||
&container_ty,
|
||||
false,
|
||||
&container,
|
||||
&mut check_signature(&mut resp, &target),
|
||||
);
|
||||
|
||||
log::debug!("post check target iterated: {:?}", resp.0);
|
||||
Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false))
|
||||
}
|
||||
CheckTarget::Paren {
|
||||
container,
|
||||
is_before,
|
||||
} => {
|
||||
let container_ty = self.check_context_or(&container, context_ty)?;
|
||||
log::info!("post check param target: {container_ty:?}::{is_before:?}");
|
||||
|
||||
let mut resp = SignatureReceiver::default();
|
||||
resp.0.lbs.push(container_ty.clone());
|
||||
|
||||
let target = ParamTarget::positional_from_before(true);
|
||||
self.check_element_of(
|
||||
&container_ty,
|
||||
false,
|
||||
&container,
|
||||
&mut check_signature(&mut resp, &target),
|
||||
);
|
||||
|
||||
log::debug!("post check target iterated: {:?}", resp.0);
|
||||
Some(self.info.simplify(FlowType::Let(Arc::new(resp.0)), false))
|
||||
}
|
||||
CheckTarget::Normal(target) => {
|
||||
let ty = self.check_context_or(&target, context_ty)?;
|
||||
log::debug!("post check target: {ty:?}");
|
||||
Some(ty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_context(&mut self, context: &LinkedNode, node: &LinkedNode) -> Option<FlowType> {
|
||||
match context.kind() {
|
||||
SyntaxKind::LetBinding => {
|
||||
let p = context.cast::<ast::LetBinding>()?;
|
||||
let exp = p.init()?;
|
||||
if exp.span() != node.span() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match p.kind() {
|
||||
ast::LetBindingKind::Closure(_c) => None,
|
||||
ast::LetBindingKind::Normal(pattern) => {
|
||||
self.destruct_let(pattern, node.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: constraint node
|
||||
SyntaxKind::Args | SyntaxKind::Named => {
|
||||
self.check_target(get_check_target(context.clone()), None)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_self(
|
||||
&mut self,
|
||||
context: &LinkedNode,
|
||||
node: &LinkedNode,
|
||||
context_ty: Option<FlowType>,
|
||||
) -> Option<FlowType> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Ident => {
|
||||
let ty = self.info.mapping.get(&node.span());
|
||||
log::debug!("post check ident: {node:?} -> {ty:?}");
|
||||
self.simplify(ty?)
|
||||
}
|
||||
// todo: destructuring
|
||||
SyntaxKind::FieldAccess => {
|
||||
let ty = self.info.mapping.get(&node.span());
|
||||
self.simplify(ty?)
|
||||
.or_else(|| self.check_context_or(context, context_ty))
|
||||
}
|
||||
_ => self.check_target(get_check_target(node.clone()), context_ty),
|
||||
}
|
||||
}
|
||||
|
||||
fn destruct_let(&mut self, pattern: ast::Pattern, node: LinkedNode) -> Option<FlowType> {
|
||||
match pattern {
|
||||
ast::Pattern::Placeholder(_) => None,
|
||||
ast::Pattern::Normal(n) => {
|
||||
let ast::Expr::Ident(ident) = n else {
|
||||
return None;
|
||||
};
|
||||
let ty = self.info.mapping.get(&ident.span())?;
|
||||
self.simplify(ty)
|
||||
}
|
||||
ast::Pattern::Parenthesized(p) => {
|
||||
self.destruct_let(p.expr().to_untyped().cast()?, node)
|
||||
}
|
||||
// todo: pattern matching
|
||||
ast::Pattern::Destructuring(_d) => {
|
||||
let _ = node;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_signatures(
|
||||
&mut self,
|
||||
ty: &FlowType,
|
||||
pol: bool,
|
||||
checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>,
|
||||
) {
|
||||
self.check_signatures_(ty, pol, SigParamKind::Call, &mut Vec::new(), checker);
|
||||
}
|
||||
|
||||
fn check_element_of(
|
||||
&mut self,
|
||||
ty: &FlowType,
|
||||
pol: bool,
|
||||
context: &LinkedNode,
|
||||
checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>,
|
||||
) {
|
||||
self.check_signatures_(ty, pol, sig_context_of(context), &mut Vec::new(), checker);
|
||||
}
|
||||
|
||||
fn check_signatures_(
|
||||
&mut self,
|
||||
ty: &FlowType,
|
||||
pol: bool,
|
||||
sig_kind: SigParamKind,
|
||||
args: &mut Vec<FlowArgs>,
|
||||
checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>,
|
||||
) {
|
||||
match ty {
|
||||
FlowType::Builtin(FlowBuiltinType::Stroke)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(&FLOW_STROKE_DICT, pol, checker);
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Margin)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(&FLOW_MARGIN_DICT, pol, checker);
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Inset)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(&FLOW_INSET_DICT, pol, checker);
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Outset)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(&FLOW_OUTSET_DICT, pol, checker);
|
||||
}
|
||||
FlowType::Builtin(FlowBuiltinType::Radius)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(&FLOW_RADIUS_DICT, pol, checker);
|
||||
}
|
||||
FlowType::Func(sig) if sig_kind == SigParamKind::Call => {
|
||||
checker(self, SignatureWrapper(Abstracted::Type(sig)), args, pol);
|
||||
}
|
||||
FlowType::Array(sig)
|
||||
if matches!(sig_kind, SigParamKind::Array | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
let sig = FlowSignature::array_cons(*sig.clone(), true);
|
||||
checker(self, SignatureWrapper(Abstracted::Type(&sig)), args, pol);
|
||||
}
|
||||
FlowType::Dict(sig)
|
||||
if matches!(sig_kind, SigParamKind::Dict | SigParamKind::ArrayOrDict) =>
|
||||
{
|
||||
self.check_dict_signature(sig, pol, checker);
|
||||
}
|
||||
FlowType::With(w) if sig_kind == SigParamKind::Call => {
|
||||
let c = args.len();
|
||||
args.extend(w.1.iter().cloned());
|
||||
self.check_signatures_(&w.0, pol, sig_kind, args, checker);
|
||||
args.truncate(c);
|
||||
}
|
||||
FlowType::Union(u) => {
|
||||
for ty in u.iter() {
|
||||
self.check_signatures_(ty, pol, sig_kind, args, checker);
|
||||
}
|
||||
}
|
||||
FlowType::Let(u) => {
|
||||
for lb in &u.ubs {
|
||||
self.check_signatures_(lb, pol, sig_kind, args, checker);
|
||||
}
|
||||
for ub in &u.lbs {
|
||||
self.check_signatures_(ub, !pol, sig_kind, args, checker);
|
||||
}
|
||||
}
|
||||
FlowType::Var(u) => {
|
||||
let Some(v) = self.info.vars.get(&u.0) else {
|
||||
return;
|
||||
};
|
||||
match &v.kind {
|
||||
FlowVarKind::Weak(w) => {
|
||||
let r = w.read();
|
||||
for lb in &r.ubs {
|
||||
self.check_signatures_(lb, pol, sig_kind, args, checker);
|
||||
}
|
||||
for ub in &r.lbs {
|
||||
self.check_signatures_(ub, !pol, sig_kind, args, checker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// todo: deduplicate checking early
|
||||
FlowType::Value(v) => {
|
||||
if sig_kind == SigParamKind::Call {
|
||||
if let Value::Func(f) = &v.0 {
|
||||
let sig = analyze_dyn_signature(self.ctx, f.clone());
|
||||
checker(self, SignatureWrapper(Abstracted::Value(&sig)), args, pol);
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowType::ValueDoc(v) => {
|
||||
if sig_kind == SigParamKind::Call {
|
||||
if let Value::Func(f) = &v.0 {
|
||||
let sig = analyze_dyn_signature(self.ctx, f.clone());
|
||||
checker(self, SignatureWrapper(Abstracted::Value(&sig)), args, pol);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_dict_signature(
|
||||
&mut self,
|
||||
sig: &FlowRecord,
|
||||
pol: bool,
|
||||
checker: &mut impl FnMut(&mut Self, SignatureWrapper, &[FlowArgs], bool) -> Option<()>,
|
||||
) {
|
||||
let sig = FlowSignature::dict_cons(sig, true);
|
||||
checker(self, SignatureWrapper(Abstracted::Type(&sig)), &[], pol);
|
||||
}
|
||||
|
||||
fn simplify(&mut self, ty: &FlowType) -> Option<FlowType> {
|
||||
Some(self.info.simplify(ty.clone(), false))
|
||||
}
|
||||
}
|
||||
|
||||
fn sig_context_of(context: &LinkedNode) -> SigParamKind {
|
||||
match context.kind() {
|
||||
SyntaxKind::Parenthesized => SigParamKind::ArrayOrDict,
|
||||
SyntaxKind::Array => {
|
||||
let c = context.cast::<ast::Array>();
|
||||
if c.is_some_and(|e| e.items().next().is_some()) {
|
||||
SigParamKind::ArrayOrDict
|
||||
} else {
|
||||
SigParamKind::Array
|
||||
}
|
||||
}
|
||||
SyntaxKind::Dict => SigParamKind::Dict,
|
||||
_ => SigParamKind::Array,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum SigParamKind {
|
||||
Call,
|
||||
Array,
|
||||
Dict,
|
||||
ArrayOrDict,
|
||||
}
|
|
@ -9,7 +9,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/element_where.typ
|
|||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "caption",
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
|
|
|
@ -9,7 +9,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
|
|||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "authors",
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
|
@ -27,7 +27,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "class",
|
||||
"sortText": "001",
|
||||
"textEdit": {
|
||||
|
@ -45,7 +45,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "font",
|
||||
"sortText": "002",
|
||||
"textEdit": {
|
||||
|
|
|
@ -9,7 +9,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args2.typ
|
|||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "class",
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
|
@ -27,7 +27,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args2.typ
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "font",
|
||||
"sortText": "001",
|
||||
"textEdit": {
|
||||
|
|
|
@ -9,7 +9,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
|
|||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "authors",
|
||||
"sortText": "000",
|
||||
"textEdit": {
|
||||
|
@ -27,7 +27,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "class",
|
||||
"sortText": "001",
|
||||
"textEdit": {
|
||||
|
@ -45,7 +45,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": 6,
|
||||
"kind": 5,
|
||||
"label": "font",
|
||||
"sortText": "002",
|
||||
"textEdit": {
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\\\"Test\\\",)\" (30)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/text_font2.typ
|
||||
---
|
||||
(TextFont | Array<TextFont>)
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\\\"Test\\\",)\" (33)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/text_font3.typ
|
||||
---
|
||||
(TextFont | Array<TextFont>)
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\\\"Test\\\",)\" (31)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/text_font4.typ
|
||||
---
|
||||
(TextFont | Array<TextFont>)
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\\\"Test\\\",)\" (47)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/text_font5.typ
|
||||
---
|
||||
(TextFont | Array<TextFont>)
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\\\"Test\\\",)\" (105)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/user_func.typ
|
||||
---
|
||||
<nil>
|
|
@ -1,2 +1,2 @@
|
|||
#let x = 1;
|
||||
#(/* position after */ x);
|
||||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2(/* position */)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (48)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/playground/base.typ
|
||||
---
|
||||
( ⪰ Any ⪯ Stroke)
|
|
@ -2,6 +2,6 @@
|
|||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"\\\"Test\\\"\" (30)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/literal_type_check/text_font.typ
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font.typ
|
||||
---
|
||||
(TextFont | Array<TextFont>)
|
||||
( ⪰ "Test" ⪯ (TextFont | Array<TextFont>))
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (30)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font2.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (33)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font3.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (31)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font4.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (47)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font5.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"\\\"Test\\\"\" (34)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element.typ
|
||||
---
|
||||
( ⪰ (TextFont | Array<TextFont>) | TextFont)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"\\\"Test\\\"\" (31)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element2.typ
|
||||
---
|
||||
( ⪰ ( ⪰ "Test" ⪯ (TextFont | Array<TextFont>)) | TextFont)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"\\\"Test\\\"\" (34)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element3.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"\\\"Test\\\"\" (31)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_font_element4.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \")\" (78)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke.typ
|
||||
---
|
||||
Any
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (61)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke1.typ
|
||||
---
|
||||
Any
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \")\" (69)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke2.typ
|
||||
---
|
||||
( ⪰ Any ⪯ Stroke)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (49)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke3.typ
|
||||
---
|
||||
Any
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (48)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/text_stroke4.typ
|
||||
---
|
||||
( ⪰ Any ⪯ Stroke)
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \":\" (34)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_external_ever.typ
|
||||
---
|
||||
( ⪰ ( ⪰ "article" | "article" | "letter" | "article" | "letter") | ( ⪰ "article" | "article" | "letter" | "article" | "letter"))
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \"(\" (105)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_func.typ
|
||||
---
|
||||
TextFont
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
description: "Check on \")\" (98)"
|
||||
expression: literal_type
|
||||
input_file: crates/tinymist-query/src/fixtures/post_type_check/user_named.typ
|
||||
---
|
||||
( ⪰ ( ⪰ Any | None) | "font": Any)
|
|
@ -0,0 +1 @@
|
|||
#text(font: (/* position after */ "Test"))[]
|
|
@ -0,0 +1,2 @@
|
|||
#let x = (/* position after */ "Test")
|
||||
#text(font: x)[]
|
|
@ -0,0 +1 @@
|
|||
#text(font: (/* position after */ "Test", ))[]
|
|
@ -0,0 +1,2 @@
|
|||
#let x = (/* position after */ "Test", )
|
||||
#text(font: x)[]
|
|
@ -0,0 +1,2 @@
|
|||
#let tmpl2(stroke: ()) = text(stroke: stroke)
|
||||
#tmpl2(stroke: (/* position after */))
|
|
@ -0,0 +1,2 @@
|
|||
#let tmpl2(stroke: ()) = text(stroke: stroke)
|
||||
#tmpl2(stroke: (/* position */))
|
|
@ -0,0 +1,2 @@
|
|||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2(/* position after */)
|
|
@ -0,0 +1,2 @@
|
|||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2((/* position */))
|
|
@ -0,0 +1,2 @@
|
|||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2(/* position */)
|
|
@ -0,0 +1,7 @@
|
|||
#let tmpl(content, font: none) = {
|
||||
set text(font: font)
|
||||
|
||||
content
|
||||
}
|
||||
|
||||
#tmpl(/* position after */)[]
|
|
@ -10,5 +10,7 @@ input_file: crates/tinymist-query/src/fixtures/type_check/control_flow.typ
|
|||
5..7 -> @x0
|
||||
31..33 -> @x1
|
||||
58..60 -> @x2
|
||||
74..78 -> Func(here)
|
||||
74..80 -> Type(location)
|
||||
74..85 -> Type(location)
|
||||
74..87 -> Any
|
||||
|
|
|
@ -4,15 +4,21 @@ expression: result
|
|||
input_file: crates/tinymist-query/src/fixtures/type_check/infer.typ
|
||||
---
|
||||
---
|
||||
1..6 -> Func(image)
|
||||
1..18 -> Element(image)
|
||||
7..17 -> Path(Image)
|
||||
21..25 -> Func(read)
|
||||
21..37 -> (Type(string) | Type(bytes))
|
||||
26..36 -> Path(None)
|
||||
40..44 -> Func(json)
|
||||
40..57 -> Any
|
||||
45..56 -> Path(Json)
|
||||
60..64 -> Func(yaml)
|
||||
60..77 -> Any
|
||||
65..76 -> Path(Yaml)
|
||||
80..83 -> Func(xml)
|
||||
80..95 -> Any
|
||||
84..94 -> Path(Xml)
|
||||
98..102 -> Func(toml)
|
||||
98..115 -> Any
|
||||
103..114 -> Path(Toml)
|
||||
|
|
|
@ -4,6 +4,7 @@ expression: result
|
|||
input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ
|
||||
---
|
||||
---
|
||||
1..5 -> Func(text)
|
||||
1..52 -> Element(text)
|
||||
6..15 -> TextSize
|
||||
12..15 -> TextSize
|
||||
|
@ -12,13 +13,15 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ
|
|||
27..38 -> Stroke
|
||||
35..38 -> Stroke
|
||||
40..49 -> Color
|
||||
46..49 -> Color
|
||||
46..49 -> (Color | Color)
|
||||
50..52 -> Type(content)
|
||||
54..58 -> Func(path)
|
||||
54..82 -> Element(path)
|
||||
59..68 -> Color
|
||||
65..68 -> Color
|
||||
65..68 -> (Color | Color)
|
||||
70..81 -> Stroke
|
||||
78..81 -> Stroke
|
||||
84..88 -> Func(line)
|
||||
84..127 -> Element(line)
|
||||
89..100 -> Type(angle)
|
||||
96..100 -> Type(angle)
|
||||
|
@ -26,13 +29,14 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ
|
|||
110..113 -> Type(relative length)
|
||||
115..126 -> Stroke
|
||||
123..126 -> Stroke
|
||||
129..133 -> Func(rect)
|
||||
129..220 -> Element(rect)
|
||||
134..144 -> (Type(relative length) | Type(auto))
|
||||
141..144 -> (Type(relative length) | Type(auto))
|
||||
146..157 -> (Type(relative length) | Type(auto))
|
||||
154..157 -> (Type(relative length) | Type(auto))
|
||||
159..168 -> Color
|
||||
165..168 -> Color
|
||||
165..168 -> (Color | Color)
|
||||
170..181 -> Stroke
|
||||
178..181 -> Stroke
|
||||
183..194 -> Radius
|
||||
|
@ -41,33 +45,41 @@ input_file: crates/tinymist-query/src/fixtures/type_check/infer2.typ
|
|||
203..206 -> Inset
|
||||
208..219 -> Outset
|
||||
216..219 -> Outset
|
||||
222..229 -> Func(ellipse)
|
||||
222..253 -> Element(ellipse)
|
||||
230..239 -> Color
|
||||
236..239 -> Color
|
||||
236..239 -> (Color | Color)
|
||||
241..252 -> Stroke
|
||||
249..252 -> Stroke
|
||||
255..261 -> Func(circle)
|
||||
255..285 -> Element(circle)
|
||||
262..271 -> Color
|
||||
268..271 -> Color
|
||||
268..271 -> (Color | Color)
|
||||
273..284 -> Stroke
|
||||
281..284 -> Stroke
|
||||
287..290 -> Func(box)
|
||||
287..314 -> Element(box)
|
||||
291..300 -> Color
|
||||
297..300 -> Color
|
||||
297..300 -> (Color | Color)
|
||||
302..313 -> Stroke
|
||||
310..313 -> Stroke
|
||||
316..321 -> Func(block)
|
||||
316..345 -> Element(block)
|
||||
322..331 -> Color
|
||||
328..331 -> Color
|
||||
328..331 -> (Color | Color)
|
||||
333..344 -> Stroke
|
||||
341..344 -> Stroke
|
||||
347..352 -> Func(table)
|
||||
347..439 -> Element(table)
|
||||
356..365 -> Color
|
||||
362..365 -> Color
|
||||
362..365 -> (Color | Color)
|
||||
369..380 -> Stroke
|
||||
377..380 -> Stroke
|
||||
384..408 -> Any
|
||||
412..436 -> Any
|
||||
384..395 -> Func(table)
|
||||
384..408 -> (Any | Any)
|
||||
412..423 -> Func(table)
|
||||
412..436 -> (Any | Any)
|
||||
441..445 -> Func(text)
|
||||
441..457 -> Element(text)
|
||||
446..456 -> Stroke
|
||||
454..456 -> Stroke
|
||||
|
|
|
@ -4,11 +4,11 @@ expression: result
|
|||
input_file: crates/tinymist-query/src/fixtures/type_check/infer_stroke_dict.typ
|
||||
---
|
||||
---
|
||||
1..5 -> Func(text)
|
||||
1..54 -> Element(text)
|
||||
6..51 -> Stroke
|
||||
14..51 -> Stroke
|
||||
18..30 -> Color
|
||||
25..30 -> Color
|
||||
34..48 -> Length
|
||||
45..48 -> Length
|
||||
52..54 -> Type(content)
|
||||
|
|
|
@ -6,5 +6,6 @@ input_file: crates/tinymist-query/src/fixtures/type_check/set_font.typ
|
|||
"font" = "Times New Roman"
|
||||
---
|
||||
5..9 -> @font
|
||||
36..40 -> Func(text)
|
||||
41..51 -> (TextFont | Array<TextFont>)
|
||||
47..51 -> (TextFont | Array<TextFont>)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,24 +6,29 @@ input_file: crates/tinymist-query/src/fixtures/type_check/text_font.typ
|
|||
"x" = "Test"
|
||||
"y" = ("Test", )
|
||||
---
|
||||
1..5 -> Func(text)
|
||||
1..21 -> Element(text)
|
||||
6..18 -> (TextFont | Array<TextFont>)
|
||||
12..18 -> (TextFont | Array<TextFont>)
|
||||
19..21 -> Type(content)
|
||||
23..27 -> Func(text)
|
||||
23..39 -> Element(text)
|
||||
28..36 -> (TextFont | Array<TextFont>)
|
||||
34..36 -> (TextFont | Array<TextFont>)
|
||||
37..39 -> Type(content)
|
||||
41..45 -> Func(text)
|
||||
41..64 -> Element(text)
|
||||
46..61 -> (TextFont | Array<TextFont>)
|
||||
52..61 -> (TextFont | Array<TextFont>)
|
||||
62..64 -> Type(content)
|
||||
70..71 -> @x
|
||||
82..86 -> Func(text)
|
||||
82..97 -> Element(text)
|
||||
87..94 -> (TextFont | Array<TextFont>)
|
||||
93..94 -> (TextFont | Array<TextFont>)
|
||||
95..97 -> Type(content)
|
||||
103..104 -> @y
|
||||
118..122 -> Func(text)
|
||||
118..133 -> Element(text)
|
||||
123..130 -> (TextFont | Array<TextFont>)
|
||||
129..130 -> (TextFont | Array<TextFont>)
|
||||
|
|
|
@ -5,9 +5,10 @@ input_file: crates/tinymist-query/src/fixtures/type_check/with.typ
|
|||
---
|
||||
"f" = (Any) -> Any
|
||||
"g" = ((Any) -> Any).with(..[&(1)])
|
||||
"x" = ⪰ Any | 1
|
||||
"x" = ( ⪰ Any | 1)
|
||||
---
|
||||
5..6 -> @f
|
||||
7..8 -> @x
|
||||
20..21 -> @g
|
||||
24..30 -> (@x) -> @x
|
||||
24..33 -> ((@x) -> @x).with(..[&(1)])
|
||||
|
|
|
@ -80,7 +80,7 @@ impl<'a> DerefTarget<'a> {
|
|||
|
||||
pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option<DerefTarget<'_>> {
|
||||
/// Skips trivia nodes that are on the same line as the cursor.
|
||||
fn skippable_trivia(node: &LinkedNode, cursor: usize) -> bool {
|
||||
fn can_skip_trivia(node: &LinkedNode, cursor: usize) -> bool {
|
||||
// A non-trivia node is our target so we stop at it.
|
||||
if !node.kind().is_trivia() {
|
||||
return false;
|
||||
|
@ -101,7 +101,7 @@ pub fn get_deref_target(node: LinkedNode, cursor: usize) -> Option<DerefTarget<'
|
|||
|
||||
// Move to the first non-trivia node before the cursor.
|
||||
let mut node = node;
|
||||
if skippable_trivia(&node, cursor) || {
|
||||
if can_skip_trivia(&node, cursor) || {
|
||||
is_mark(node.kind())
|
||||
&& (!matches!(node.kind(), SyntaxKind::LeftParen)
|
||||
|| !matches!(
|
||||
|
@ -239,13 +239,31 @@ pub enum ParamTarget<'a> {
|
|||
},
|
||||
Named(LinkedNode<'a>),
|
||||
}
|
||||
impl<'a> ParamTarget<'a> {
|
||||
pub(crate) fn positional_from_before(before: bool) -> Self {
|
||||
ParamTarget::Positional {
|
||||
spreads: EcoVec::new(),
|
||||
positional: if before { 0 } else { 1 },
|
||||
is_spread: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CheckTarget<'a> {
|
||||
Param {
|
||||
callee: LinkedNode<'a>,
|
||||
target: ParamTarget<'a>,
|
||||
is_set: bool,
|
||||
},
|
||||
Element {
|
||||
container: LinkedNode<'a>,
|
||||
target: ParamTarget<'a>,
|
||||
},
|
||||
Paren {
|
||||
container: LinkedNode<'a>,
|
||||
is_before: bool,
|
||||
},
|
||||
Normal(LinkedNode<'a>),
|
||||
}
|
||||
|
||||
|
@ -256,11 +274,22 @@ impl<'a> CheckTarget<'a> {
|
|||
ParamTarget::Positional { .. } => return None,
|
||||
ParamTarget::Named(node) => node.clone(),
|
||||
},
|
||||
CheckTarget::Element { target, .. } => match target {
|
||||
ParamTarget::Positional { .. } => return None,
|
||||
ParamTarget::Named(node) => node.clone(),
|
||||
},
|
||||
CheckTarget::Paren { container, .. } => container.clone(),
|
||||
CheckTarget::Normal(node) => node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum ParamKind {
|
||||
Call,
|
||||
Array,
|
||||
Dict,
|
||||
}
|
||||
|
||||
pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
|
||||
let mut node = node;
|
||||
while node.kind().is_trivia() {
|
||||
|
@ -269,7 +298,7 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
|
|||
|
||||
let deref_target = get_deref_target(node.clone(), node.offset())?;
|
||||
|
||||
match deref_target {
|
||||
let deref_node = match deref_target {
|
||||
DerefTarget::Callee(callee) => {
|
||||
let parent = callee.parent()?;
|
||||
let args = match parent.cast::<ast::Expr>() {
|
||||
|
@ -277,29 +306,68 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
|
|||
Some(ast::Expr::Set(set)) => set.args(),
|
||||
_ => return None,
|
||||
};
|
||||
let args_node = node.find(args.span())?;
|
||||
let args_node = parent.find(args.span())?;
|
||||
|
||||
let param_target = get_param_target(args_node, node)?;
|
||||
Some(CheckTarget::Param {
|
||||
target: param_target,
|
||||
is_set: parent.kind() == SyntaxKind::Set,
|
||||
let is_set = parent.kind() == SyntaxKind::Set;
|
||||
let target = get_param_target(args_node, node, ParamKind::Call)?;
|
||||
return Some(CheckTarget::Param {
|
||||
callee,
|
||||
target,
|
||||
is_set,
|
||||
});
|
||||
}
|
||||
DerefTarget::ImportPath(node) | DerefTarget::IncludePath(node) => {
|
||||
return Some(CheckTarget::Normal(node));
|
||||
}
|
||||
deref_target => deref_target.node().clone(),
|
||||
};
|
||||
|
||||
let Some(node_parent) = node.parent() else {
|
||||
return Some(CheckTarget::Normal(node));
|
||||
};
|
||||
match node_parent.kind() {
|
||||
SyntaxKind::Array | SyntaxKind::Dict => {
|
||||
let target = get_param_target(
|
||||
node_parent.clone(),
|
||||
node.clone(),
|
||||
match node_parent.kind() {
|
||||
SyntaxKind::Array => ParamKind::Array,
|
||||
SyntaxKind::Dict => ParamKind::Dict,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
)?;
|
||||
Some(CheckTarget::Element {
|
||||
container: node_parent.clone(),
|
||||
target,
|
||||
})
|
||||
}
|
||||
deref_target => Some(CheckTarget::Normal(deref_target.node().clone())),
|
||||
SyntaxKind::Parenthesized => {
|
||||
let is_before = node.offset() <= node_parent.offset() + 1;
|
||||
Some(CheckTarget::Paren {
|
||||
container: node_parent.clone(),
|
||||
is_before,
|
||||
})
|
||||
}
|
||||
_ => Some(CheckTarget::Normal(deref_node)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_param_target<'a>(
|
||||
args_node: LinkedNode<'a>,
|
||||
node: LinkedNode<'a>,
|
||||
param_kind: ParamKind,
|
||||
) -> Option<ParamTarget<'a>> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Named => {
|
||||
let param_ident = node.cast::<ast::Named>()?.name();
|
||||
Some(ParamTarget::Named(args_node.find(param_ident.span())?))
|
||||
}
|
||||
SyntaxKind::Colon => {
|
||||
let prev = node.prev_leaf()?;
|
||||
let param_ident = prev.cast::<ast::Ident>()?;
|
||||
Some(ParamTarget::Named(args_node.find(param_ident.span())?))
|
||||
}
|
||||
SyntaxKind::Spread | SyntaxKind::Comma | SyntaxKind::LeftParen => {
|
||||
_ => {
|
||||
let mut spreads = EcoVec::new();
|
||||
let mut positional = 0;
|
||||
let is_spread = node.kind() == SyntaxKind::Spread;
|
||||
|
@ -307,15 +375,39 @@ fn get_param_target<'a>(
|
|||
let args_before = args_node
|
||||
.children()
|
||||
.take_while(|arg| arg.range().end <= node.offset());
|
||||
for ch in args_before {
|
||||
match ch.cast::<ast::Arg>() {
|
||||
Some(ast::Arg::Pos(..)) => {
|
||||
positional += 1;
|
||||
match param_kind {
|
||||
ParamKind::Call => {
|
||||
for ch in args_before {
|
||||
match ch.cast::<ast::Arg>() {
|
||||
Some(ast::Arg::Pos(..)) => {
|
||||
positional += 1;
|
||||
}
|
||||
Some(ast::Arg::Spread(..)) => {
|
||||
spreads.push(ch);
|
||||
}
|
||||
Some(ast::Arg::Named(..)) | None => {}
|
||||
}
|
||||
}
|
||||
Some(ast::Arg::Spread(..)) => {
|
||||
spreads.push(ch);
|
||||
}
|
||||
ParamKind::Array => {
|
||||
for ch in args_before {
|
||||
match ch.cast::<ast::ArrayItem>() {
|
||||
Some(ast::ArrayItem::Pos(..)) => {
|
||||
positional += 1;
|
||||
}
|
||||
Some(ast::ArrayItem::Spread(..)) => {
|
||||
spreads.push(ch);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
ParamKind::Dict => {
|
||||
for ch in args_before {
|
||||
if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
|
||||
spreads.push(ch);
|
||||
}
|
||||
}
|
||||
Some(ast::Arg::Named(..)) | None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,7 +417,6 @@ fn get_param_target<'a>(
|
|||
is_spread,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -216,7 +216,7 @@ pub fn find_test_position(s: &Source) -> LspPosition {
|
|||
};
|
||||
|
||||
'match_loop: loop {
|
||||
if n.kind().is_trivia() {
|
||||
if n.kind().is_trivia() || n.kind().is_error() {
|
||||
let m = if match_prev {
|
||||
n.prev_sibling()
|
||||
} else {
|
||||
|
@ -225,6 +225,10 @@ pub fn find_test_position(s: &Source) -> LspPosition {
|
|||
n = m.or_else(|| n.parent().cloned()).unwrap();
|
||||
continue;
|
||||
}
|
||||
if matches!(n.kind(), SyntaxKind::Named) {
|
||||
n = n.children().last().unwrap();
|
||||
continue;
|
||||
}
|
||||
if match_ident {
|
||||
match n.kind() {
|
||||
SyntaxKind::Closure => {
|
||||
|
@ -251,8 +255,6 @@ pub fn find_test_position(s: &Source) -> LspPosition {
|
|||
break;
|
||||
}
|
||||
|
||||
// eprintln!("position: {:?} -> {:?}", n.offset(), n);
|
||||
|
||||
typst_to_lsp::offset_to_position(n.offset(), PositionEncoding::Utf16, s)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::analysis::{
|
|||
FlowType, PathPreference, FLOW_INSET_DICT, FLOW_MARGIN_DICT, FLOW_OUTSET_DICT,
|
||||
FLOW_RADIUS_DICT, FLOW_STROKE_DICT,
|
||||
};
|
||||
use crate::syntax::{get_non_strict_def_target, param_index_at_leaf, DefTarget};
|
||||
use crate::syntax::param_index_at_leaf;
|
||||
use crate::upstream::complete::complete_code;
|
||||
use crate::upstream::plain_docs_sentence;
|
||||
|
||||
|
@ -35,6 +35,12 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
self.scope_completions_(parens, |v| v.map_or(false, &filter));
|
||||
}
|
||||
|
||||
fn seen_field(&mut self, field: EcoString) -> bool {
|
||||
!self
|
||||
.seen_casts
|
||||
.insert(typst::util::hash128(&FieldName(field)))
|
||||
}
|
||||
|
||||
/// Add completions for definitions that are available at the cursor.
|
||||
///
|
||||
/// Filters the global/math scope with the given filter.
|
||||
|
@ -200,7 +206,7 @@ fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
|
|||
complete_code(ctx);
|
||||
ctx.explicit = explict;
|
||||
|
||||
log::info!(
|
||||
log::debug!(
|
||||
"sort_and_explicit_code_completion: {:#?} {:#?}",
|
||||
completions,
|
||||
ctx.completions
|
||||
|
@ -234,7 +240,7 @@ fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) {
|
|||
compl.sort_text = Some(eco_format!("{i:03}", i = i + sort_base));
|
||||
}
|
||||
|
||||
log::info!(
|
||||
log::debug!(
|
||||
"sort_and_explicit_code_completion after: {:#?} {:#?}",
|
||||
completions,
|
||||
ctx.completions
|
||||
|
@ -276,45 +282,24 @@ pub fn param_completions<'a>(
|
|||
|
||||
let signature = analyze_dyn_signature(ctx.ctx, func.clone());
|
||||
|
||||
let def = func.span();
|
||||
let type_sig = def.id().and_then(|id| {
|
||||
let source = ctx.ctx.source_by_id(id).ok()?;
|
||||
let def = get_non_strict_def_target(source.find(def)?)?;
|
||||
let DefTarget::Let(l) = def else {
|
||||
return None;
|
||||
};
|
||||
let leaf_type = ctx.ctx.literal_type_of_node(ctx.leaf.clone());
|
||||
log::info!("pos_param_completion_by_type: {:?}", leaf_type);
|
||||
|
||||
let lb = l.cast::<ast::LetBinding>()?;
|
||||
let ast::LetBindingKind::Closure(c) = lb.kind() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let fn_ty = ctx.ctx.type_of_span(c.span());
|
||||
let info = ctx.ctx.type_check(source)?;
|
||||
|
||||
log::info!("function sig by type checking: {:?}", fn_ty);
|
||||
fn_ty.and_then(|ty| ty.signatures(&info, false))
|
||||
});
|
||||
|
||||
// Exclude named arguments which are already present.
|
||||
let exclude: Vec<_> = args
|
||||
.items()
|
||||
.filter_map(|arg| match arg {
|
||||
ast::Arg::Named(named) => Some(named.name()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
for arg in args.items() {
|
||||
if let ast::Arg::Named(named) = arg {
|
||||
ctx.seen_field(named.name().get().clone());
|
||||
}
|
||||
}
|
||||
|
||||
let primary_sig = signature.primary();
|
||||
|
||||
log::debug!("pos_param_completion: {:?}", pos_index);
|
||||
|
||||
let mut doc = None;
|
||||
if let Some(pos_index) = pos_index {
|
||||
let pos = primary_sig.pos.get(pos_index);
|
||||
log::debug!("pos_param_completion_to: {:?}", pos);
|
||||
|
||||
let mut doc = None;
|
||||
|
||||
if let Some(pos) = pos {
|
||||
if set && !pos.settable {
|
||||
return;
|
||||
|
@ -329,17 +314,14 @@ pub fn param_completions<'a>(
|
|||
ctx.cast_completions(&pos.input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for sig in type_sig.iter().flatten() {
|
||||
if let Some(pos) = sig.pos.get(pos_index) {
|
||||
log::info!("pos_param_completion by type: {:?}", pos);
|
||||
type_completion(ctx, Some(pos), doc.as_deref());
|
||||
}
|
||||
}
|
||||
if let Some(leaf_type) = leaf_type {
|
||||
type_completion(ctx, Some(&leaf_type), doc.as_deref());
|
||||
}
|
||||
|
||||
for (name, param) in &primary_sig.named {
|
||||
if exclude.iter().any(|ident| ident.as_str() == name) {
|
||||
if ctx.seen_field(name.as_ref().into()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -425,6 +407,7 @@ fn type_completion(
|
|||
match infer_type? {
|
||||
FlowType::Clause => return None,
|
||||
FlowType::Undef => return None,
|
||||
FlowType::Space => return None,
|
||||
FlowType::Content => return None,
|
||||
FlowType::Any => return None,
|
||||
FlowType::Tuple(..) | FlowType::Array(..) => {
|
||||
|
@ -443,6 +426,20 @@ fn type_completion(
|
|||
ctx.snippet_completion("false", "false", "No / Disabled.");
|
||||
ctx.snippet_completion("true", "true", "Yes / Enabled.");
|
||||
}
|
||||
FlowType::Field(f) => {
|
||||
let f = f.0.clone();
|
||||
if ctx.seen_field(f.clone()) {
|
||||
return Some(());
|
||||
}
|
||||
|
||||
ctx.completions.push(Completion {
|
||||
kind: CompletionKind::Field,
|
||||
label: f.clone(),
|
||||
apply: Some(eco_format!("{}: ${{}}", f)),
|
||||
detail: docs.map(Into::into),
|
||||
..Completion::default()
|
||||
});
|
||||
}
|
||||
FlowType::Builtin(v) => match v {
|
||||
FlowBuiltinType::Path(p) => {
|
||||
let source = ctx.ctx.source_by_id(ctx.root.span().id()?).ok()?;
|
||||
|
@ -646,6 +643,9 @@ fn type_completion(
|
|||
Some(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
struct FieldName(EcoString);
|
||||
|
||||
/// Add completions for the values of a named function parameter.
|
||||
pub fn named_param_value_completions<'a>(
|
||||
ctx: &mut CompletionContext<'a, '_>,
|
||||
|
@ -668,25 +668,12 @@ pub fn named_param_value_completions<'a>(
|
|||
// todo: regards call convention
|
||||
let func = cc.callee();
|
||||
|
||||
let def = func.span();
|
||||
let type_sig = def.id().and_then(|id| {
|
||||
let source = ctx.ctx.source_by_id(id).ok()?;
|
||||
let def = get_non_strict_def_target(source.find(def)?)?;
|
||||
let DefTarget::Let(l) = def else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let lb = l.cast::<ast::LetBinding>()?;
|
||||
let ast::LetBindingKind::Closure(c) = lb.kind() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let fn_ty = ctx.ctx.type_of_span(c.span());
|
||||
let info = ctx.ctx.type_check(source)?;
|
||||
|
||||
log::info!("function sig by type checking: {:?}", fn_ty);
|
||||
fn_ty.and_then(|ty| ty.signatures(&info, false))
|
||||
});
|
||||
let leaf_type = ctx.ctx.literal_type_of_node(ctx.leaf.clone());
|
||||
log::debug!(
|
||||
"named_param_completion_by_type: {:?} -> {:?}",
|
||||
ctx.leaf.kind(),
|
||||
leaf_type
|
||||
);
|
||||
|
||||
use typst::foundations::func::Repr;
|
||||
let mut func = func;
|
||||
|
@ -715,13 +702,10 @@ pub fn named_param_value_completions<'a>(
|
|||
}
|
||||
|
||||
let mut completed = false;
|
||||
for sig in type_sig.iter().flatten() {
|
||||
let named = sig.named.iter().find(|(n, _)| n.as_str() == name);
|
||||
if let Some((_, param)) = named {
|
||||
log::info!("named_param_completion by type: {:?}", param);
|
||||
type_completion(ctx, Some(param), doc.as_deref());
|
||||
completed = true;
|
||||
}
|
||||
if let Some(type_sig) = leaf_type {
|
||||
log::debug!("named_param_completion by type: {:?}", param);
|
||||
type_completion(ctx, Some(&type_sig), doc.as_deref());
|
||||
completed = true;
|
||||
}
|
||||
|
||||
if !completed {
|
||||
|
@ -754,7 +738,7 @@ pub fn named_param_value_completions<'a>(
|
|||
|
||||
pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
|
||||
let parent = ctx.leaf.clone();
|
||||
log::debug!("check complete_literal: {:?}", ctx.leaf);
|
||||
log::info!("check complete_literal: {:?}", ctx.leaf);
|
||||
let parent = if parent.kind().is_trivia() {
|
||||
parent.prev_sibling()?
|
||||
} else {
|
||||
|
@ -766,10 +750,10 @@ pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
|
|||
SyntaxKind::Colon => parent.parent()?,
|
||||
_ => parent,
|
||||
};
|
||||
let (named, parent) = match parent.kind() {
|
||||
SyntaxKind::Named => (parent.cast::<ast::Named>(), parent.parent()?),
|
||||
SyntaxKind::LeftParen | SyntaxKind::Comma => (None, parent.parent()?),
|
||||
_ => (None, parent),
|
||||
let parent = match parent.kind() {
|
||||
SyntaxKind::Named => parent.parent()?,
|
||||
SyntaxKind::LeftParen | SyntaxKind::Comma => parent.parent()?,
|
||||
_ => parent,
|
||||
};
|
||||
log::debug!("check complete_literal 3: {:?}", parent);
|
||||
|
||||
|
@ -791,8 +775,10 @@ pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
|
|||
};
|
||||
|
||||
// query type of the dict
|
||||
let named_span = named.map(|n| n.span()).unwrap_or_else(Span::detached);
|
||||
let named_ty = ctx.ctx.literal_type_of_span(named_span);
|
||||
let named_ty = ctx
|
||||
.ctx
|
||||
.literal_type_of_node(ctx.leaf.clone())
|
||||
.filter(|ty| !matches!(ty, FlowType::Any));
|
||||
let lit_ty = ctx.ctx.literal_type_of_span(lit_span);
|
||||
log::info!("complete_literal: {lit_ty:?} {named_ty:?}");
|
||||
|
||||
|
@ -882,6 +868,14 @@ pub fn complete_literal(ctx: &mut CompletionContext) -> Option<()> {
|
|||
self.on_lit_ty(info);
|
||||
}
|
||||
}
|
||||
FlowType::Let(u) => {
|
||||
for ut in u.ubs.iter() {
|
||||
self.on_lit_ty(ut);
|
||||
}
|
||||
for lt in u.lbs.iter() {
|
||||
self.on_lit_ty(lt);
|
||||
}
|
||||
}
|
||||
// todo: var, let, etc.
|
||||
_ => {}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue