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:
Myriad-Dreamin 2024-05-04 20:56:40 +08:00 committed by GitHub
parent 27f992bd87
commit 0b566f83de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 1129 additions and 430 deletions

View file

@ -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:?})"),

View file

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

View file

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

View file

@ -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);
}
}
}
}
_ => {}
}
}

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,2 +1,2 @@
#let x = 1;
#(/* position after */ x);
#let tmpl2(stroke) = text(stroke: stroke)
#tmpl2(/* position */)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
#text(font: (/* position after */ "Test"))[]

View file

@ -0,0 +1,2 @@
#let x = (/* position after */ "Test")
#text(font: x)[]

View file

@ -0,0 +1 @@
#text(font: (/* position after */ "Test", ))[]

View file

@ -0,0 +1,2 @@
#let x = (/* position after */ "Test", )
#text(font: x)[]

View file

@ -0,0 +1,2 @@
#let tmpl2(stroke: ()) = text(stroke: stroke)
#tmpl2(stroke: (/* position after */))

View file

@ -0,0 +1,2 @@
#let tmpl2(stroke: ()) = text(stroke: stroke)
#tmpl2(stroke: (/* position */))

View file

@ -0,0 +1,2 @@
#let tmpl2(stroke) = text(stroke: stroke)
#tmpl2(/* position after */)

View file

@ -0,0 +1,2 @@
#let tmpl2(stroke) = text(stroke: stroke)
#tmpl2((/* position */))

View file

@ -0,0 +1,2 @@
#let tmpl2(stroke) = text(stroke: stroke)
#tmpl2(/* position */)

View file

@ -0,0 +1,7 @@
#let tmpl(content, font: none) = {
set text(font: font)
content
}
#tmpl(/* position after */)[]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
_ => {}
}