feat: elementary select checker (#668)

* feat: elementary select checker

* dev: remove println!
This commit is contained in:
Myriad-Dreamin 2024-10-13 15:14:24 +08:00 committed by GitHub
parent cc29728fc9
commit 191c943936
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 336 additions and 5 deletions

View file

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

View file

@ -1,4 +1,4 @@
//! Type checking on source file
//! Type checking at apply site
use typst::syntax::{ast, Span};

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

View file

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

View file

@ -0,0 +1,4 @@
#let a = (
a: "1",
);
#let b = a.a;

View file

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

View file

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

View file

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

View file

@ -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`].

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

View file

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

View file

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