mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: elementary select checker (#668)
* feat: elementary select checker * dev: remove println!
This commit is contained in:
parent
cc29728fc9
commit
191c943936
12 changed files with 336 additions and 5 deletions
|
@ -22,10 +22,12 @@ use super::{
|
|||
|
||||
mod apply;
|
||||
mod post_check;
|
||||
mod select;
|
||||
mod syntax;
|
||||
|
||||
pub(crate) use apply::*;
|
||||
pub(crate) use post_check::*;
|
||||
pub(crate) use select::*;
|
||||
|
||||
/// Type checking at the source unit level.
|
||||
pub(crate) fn type_check(ctx: &mut AnalysisContext, source: Source) -> Option<Arc<TypeScheme>> {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Type checking on source file
|
||||
//! Type checking at apply site
|
||||
|
||||
use typst::syntax::{ast, Span};
|
||||
|
||||
|
|
49
crates/tinymist-query/src/analysis/ty/select.rs
Normal file
49
crates/tinymist-query/src/analysis/ty/select.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
//! Type checking at select site
|
||||
|
||||
use typst::syntax::Span;
|
||||
|
||||
use crate::analysis::SelectChecker;
|
||||
use crate::analysis::Ty;
|
||||
|
||||
use super::*;
|
||||
use crate::adt::interner::Interned;
|
||||
|
||||
pub struct SelectFieldChecker<'a, 'b, 'w> {
|
||||
pub(super) base: &'a mut TypeChecker<'b, 'w>,
|
||||
pub select_site: Span,
|
||||
pub key: &'a Interned<str>,
|
||||
pub resultant: Vec<Ty>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'w> SelectChecker for SelectFieldChecker<'a, 'b, 'w> {
|
||||
fn bound_of_var(
|
||||
&mut self,
|
||||
var: &Interned<super::TypeVar>,
|
||||
_pol: bool,
|
||||
) -> Option<super::TypeBounds> {
|
||||
self.base
|
||||
.info
|
||||
.vars
|
||||
.get(&var.def)
|
||||
.map(|v| v.bounds.bounds().read().clone())
|
||||
}
|
||||
|
||||
fn select(&mut self, iface: Iface, key: &Interned<str>, pol: bool) {
|
||||
log::debug!("selecting field: {iface:?} {key:?}");
|
||||
let _ = pol;
|
||||
|
||||
let ins = iface.ty();
|
||||
if let Some(ins) = ins {
|
||||
self.base.info.witness_at_least(self.select_site, ins);
|
||||
}
|
||||
|
||||
let Some(IfaceShape { iface }) = iface.shape(Some(self.base.ctx)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let res = iface.field_by_name(self.key);
|
||||
if let Some(res) = res {
|
||||
self.resultant.push(res.clone());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -305,10 +305,20 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
fn check_field_access(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||
let field_access: ast::FieldAccess = root.cast()?;
|
||||
|
||||
let ty = self.check_expr_in(field_access.target().span(), root.clone());
|
||||
let field = field_access.field().get().clone();
|
||||
let select_site = field_access.target().span();
|
||||
let ty = self.check_expr_in(select_site, root.clone());
|
||||
let field = Interned::new_str(field_access.field().get());
|
||||
|
||||
Some(Ty::Select(SelectTy::new(ty.into(), field.into())))
|
||||
// todo: move this to base
|
||||
let base = Ty::Select(SelectTy::new(ty.clone().into(), field.clone()));
|
||||
let mut worker = SelectFieldChecker {
|
||||
base: self,
|
||||
select_site,
|
||||
key: &field,
|
||||
resultant: vec![base],
|
||||
};
|
||||
ty.select(&field, true, &mut worker);
|
||||
Some(Ty::from_types(worker.resultant.into_iter()))
|
||||
}
|
||||
|
||||
fn check_func_call(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#let a = (
|
||||
a: "1",
|
||||
);
|
||||
#let b = a.a;
|
|
@ -21,4 +21,5 @@ input_file: crates/tinymist-query/src/fixtures/type_check/confusing-name.typ
|
|||
114..118 -> @info
|
||||
121..122 -> @x
|
||||
121..140 -> Any
|
||||
123..131 -> Type(datetime)
|
||||
123..139 -> Any
|
||||
|
|
|
@ -11,5 +11,5 @@ input_file: crates/tinymist-query/src/fixtures/type_check/control_flow.typ
|
|||
31..33 -> @x1
|
||||
58..60 -> @x2
|
||||
74..78 -> Func(here)
|
||||
74..80 -> Type(location)
|
||||
74..80 -> (Type(location) | Type(location))
|
||||
74..87 -> Any
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/analysis.rs
|
||||
expression: result
|
||||
input_file: crates/tinymist-query/src/fixtures/type_check/dict_infer.typ
|
||||
---
|
||||
"a" = {"a": "1"}
|
||||
"b" = ("1" | {"a": "1"}.a)
|
||||
---
|
||||
5..6 -> @a
|
||||
29..30 -> @b
|
||||
33..34 -> (@a | {"a": "1"})
|
|
@ -313,6 +313,10 @@ pub trait TypeInterface {
|
|||
fn interface(&self) -> impl Iterator<Item = (&StrRef, &Ty)>;
|
||||
/// Get the field by bone offset.
|
||||
fn field_by_bone_offset(&self, i: usize) -> Option<&Ty>;
|
||||
/// Get the field by name.
|
||||
fn field_by_name(&self, name: &StrRef) -> Option<&Ty> {
|
||||
self.field_by_bone_offset(self.bone().find(name)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension common methods for [`TypeInterface`].
|
||||
|
|
205
crates/tinymist-query/src/ty/iface.rs
Normal file
205
crates/tinymist-query/src/ty/iface.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use typst::foundations::{Dict, Value};
|
||||
|
||||
use crate::{adt::interner::Interned, analysis::*, ty::def::*};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Iface<'a> {
|
||||
Dict(&'a Interned<RecordTy>),
|
||||
Element {
|
||||
val: &'a typst::foundations::Element,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Type {
|
||||
val: &'a typst::foundations::Type,
|
||||
at: &'a Ty,
|
||||
},
|
||||
Value {
|
||||
val: &'a Dict,
|
||||
at: &'a Ty,
|
||||
},
|
||||
ArrayCons(&'a TyRef),
|
||||
Partialize(&'a Iface<'a>),
|
||||
}
|
||||
|
||||
pub struct IfaceShape {
|
||||
pub iface: Interned<RecordTy>,
|
||||
}
|
||||
|
||||
impl<'a> Iface<'a> {
|
||||
pub fn ty(self) -> Option<Ty> {
|
||||
Some(match self {
|
||||
Iface::ArrayCons(t) => Ty::Array(t.clone()),
|
||||
Iface::Dict(t) => Ty::Dict(t.clone()),
|
||||
Iface::Type { val, .. } => Ty::Builtin(BuiltinTy::Type(*val)),
|
||||
Iface::Element { val, .. } => Ty::Builtin(BuiltinTy::Element(*val)),
|
||||
Iface::Value { at, .. } => at.clone(),
|
||||
Iface::Partialize(..) => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shape(self, _ctx: Option<&mut AnalysisContext>) -> Option<IfaceShape> {
|
||||
log::debug!("iface shape: {self:?}");
|
||||
|
||||
let record_ins = match self {
|
||||
// Iface::ArrayCons(a) => SigTy::array_cons(a.as_ref().clone(), false),
|
||||
Iface::ArrayCons(..) => return None,
|
||||
Iface::Dict(d) => d.clone(),
|
||||
// Iface::Type { val, .. } => ctx?.type_of_func(&val.constructor().ok()?)?,
|
||||
// Iface::Value { val, .. } => ctx?.type_of_func(val)?, // todo
|
||||
Iface::Partialize(..) => return None,
|
||||
Iface::Element { .. } => return None,
|
||||
Iface::Type { .. } => return None,
|
||||
Iface::Value { .. } => return None,
|
||||
};
|
||||
|
||||
Some(IfaceShape { iface: record_ins })
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IfaceChecker {
|
||||
fn check(&mut self, sig: Iface, args: &mut IfaceCheckContext, pol: bool) -> Option<()>;
|
||||
fn check_var(&mut self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IfaceChecker for T
|
||||
where
|
||||
T: FnMut(Iface, &mut IfaceCheckContext, bool) -> Option<()>,
|
||||
{
|
||||
fn check(&mut self, sig: Iface, args: &mut IfaceCheckContext, pol: bool) -> Option<()> {
|
||||
self(sig, args, pol)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Iterate over the signatures of the given type.
|
||||
pub fn iface_surface(
|
||||
&self,
|
||||
pol: bool,
|
||||
// iface_kind: IfaceSurfaceKind,
|
||||
checker: &mut impl IfaceChecker,
|
||||
) {
|
||||
let context = IfaceCheckContext {
|
||||
args: Vec::new(),
|
||||
at: TyRef::new(Ty::Any),
|
||||
};
|
||||
let mut worker = IfaceCheckDriver {
|
||||
ctx: context,
|
||||
checker,
|
||||
};
|
||||
|
||||
worker.ty(self, pol);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IfaceCheckContext {
|
||||
pub args: Vec<Interned<SigTy>>,
|
||||
pub at: TyRef,
|
||||
}
|
||||
|
||||
pub struct IfaceCheckDriver<'a> {
|
||||
ctx: IfaceCheckContext,
|
||||
checker: &'a mut dyn IfaceChecker,
|
||||
}
|
||||
|
||||
impl BoundChecker for IfaceCheckDriver<'_> {
|
||||
fn collect(&mut self, ty: &Ty, pol: bool) {
|
||||
self.ty(ty, pol);
|
||||
}
|
||||
|
||||
fn bound_of_var(&mut self, var: &Interned<TypeVar>, pol: bool) -> Option<TypeBounds> {
|
||||
self.checker.check_var(var, pol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IfaceCheckDriver<'a> {
|
||||
fn dict_as_iface(&self) -> bool {
|
||||
// matches!(
|
||||
// self.ctx.sig_kind,
|
||||
// SigSurfaceKind::DictIface | SigSurfaceKind::ArrayOrDict
|
||||
// )
|
||||
true
|
||||
}
|
||||
|
||||
fn value_as_iface(&self) -> bool {
|
||||
// matches!(self.ctx.sig_kind, SigSurfaceKind::Func)
|
||||
true
|
||||
}
|
||||
|
||||
fn ty(&mut self, ty: &Ty, pol: bool) {
|
||||
log::debug!("check iface ty: {ty:?}");
|
||||
|
||||
match ty {
|
||||
Ty::Builtin(BuiltinTy::Stroke) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_STROKE_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Margin) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_MARGIN_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Inset) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_INSET_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Outset) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_OUTSET_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Radius) if self.dict_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Dict(&FLOW_RADIUS_DICT), &mut self.ctx, pol);
|
||||
}
|
||||
// // todo: deduplicate checking early
|
||||
Ty::Value(v) => {
|
||||
if self.value_as_iface() {
|
||||
match &v.val {
|
||||
// Value::Func(f) => {
|
||||
// self.checker
|
||||
// .check(Iface::Value { val: f, at: ty }, &mut self.ctx, pol);
|
||||
// }
|
||||
Value::Dict(d) => {
|
||||
self.checker
|
||||
.check(Iface::Value { val: d, at: ty }, &mut self.ctx, pol);
|
||||
}
|
||||
Value::Type(t) => {
|
||||
self.checker
|
||||
.check(Iface::Type { val: t, at: ty }, &mut self.ctx, pol);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Type(e)) if self.value_as_iface() => {
|
||||
// todo: distinguish between element and function
|
||||
self.checker
|
||||
.check(Iface::Type { val: e, at: ty }, &mut self.ctx, pol);
|
||||
}
|
||||
Ty::Builtin(BuiltinTy::Element(e)) if self.value_as_iface() => {
|
||||
self.checker
|
||||
.check(Iface::Element { val: e, at: ty }, &mut self.ctx, pol);
|
||||
}
|
||||
// Ty::Func(sig) if self.value_as_iface() => {
|
||||
// self.checker.check(Iface::Type(sig), &mut self.ctx, pol);
|
||||
// }
|
||||
// Ty::Array(sig) if self.array_as_sig() => {
|
||||
// // let sig = FlowSignature::array_cons(*sig.clone(), true);
|
||||
// self.checker.check(Iface::ArrayCons(sig), &mut self.ctx, pol);
|
||||
// }
|
||||
// // todo: tuple
|
||||
// Ty::Tuple(_) => {}
|
||||
Ty::Dict(sig) if self.dict_as_iface() => {
|
||||
// self.check_dict_signature(sig, pol, self.checker);
|
||||
self.checker.check(Iface::Dict(sig), &mut self.ctx, pol);
|
||||
}
|
||||
_ if ty.has_bounds() => ty.bounds(pol, self),
|
||||
_ => {}
|
||||
}
|
||||
// Ty::Select(sel) => sel.ty.bounds(pol, &mut MethodDriver(self,
|
||||
// &sel.select)), // todo: calculate these operators
|
||||
// Ty::Unary(_) => {}
|
||||
// Ty::Binary(_) => {}
|
||||
// Ty::If(_) => {}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,9 @@ mod bound;
|
|||
mod builtin;
|
||||
mod def;
|
||||
mod describe;
|
||||
mod iface;
|
||||
mod mutate;
|
||||
mod select;
|
||||
mod sig;
|
||||
mod simplify;
|
||||
mod subst;
|
||||
|
@ -14,7 +16,9 @@ pub(crate) use apply::*;
|
|||
pub(crate) use bound::*;
|
||||
pub(crate) use builtin::*;
|
||||
pub use def::*;
|
||||
pub(crate) use iface::*;
|
||||
pub(crate) use mutate::*;
|
||||
pub(crate) use select::*;
|
||||
pub(crate) use sig::*;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1 +1,42 @@
|
|||
use crate::{adt::interner::Interned, ty::def::*};
|
||||
|
||||
use super::{Iface, IfaceChecker};
|
||||
|
||||
pub trait SelectChecker {
|
||||
fn select(&mut self, sig: Iface, key: &Interned<str>, pol: bool);
|
||||
|
||||
fn bound_of_var(&mut self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
/// Select the given type with the given key.
|
||||
pub fn select(&self, key: &Interned<str>, pol: bool, checker: &mut impl SelectChecker) {
|
||||
SelectKeyChecker(checker, key).ty(self, pol);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SelectKeyChecker<'a, T>(&'a mut T, &'a Interned<str>);
|
||||
|
||||
impl<'a, T: SelectChecker> SelectKeyChecker<'a, T> {
|
||||
fn ty(&mut self, ty: &Ty, pol: bool) {
|
||||
ty.iface_surface(pol, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: SelectChecker> IfaceChecker for SelectKeyChecker<'a, T> {
|
||||
fn check(
|
||||
&mut self,
|
||||
iface: Iface,
|
||||
_ctx: &mut super::IfaceCheckContext,
|
||||
pol: bool,
|
||||
) -> Option<()> {
|
||||
self.0.select(iface, self.1, pol);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn check_var(&mut self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
|
||||
self.0.bound_of_var(_var, _pol)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue