feat: elementary tuple method checker (#669)

* feat: elementary tuple method checker

* dev: remove println!
This commit is contained in:
Myriad-Dreamin 2024-10-14 11:34:03 +08:00 committed by GitHub
parent 191c943936
commit 5898c60de1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 262 additions and 31 deletions

View file

@ -12,7 +12,7 @@ use crate::adt::interner::Interned;
pub struct ApplyTypeChecker<'a, 'b, 'w> {
pub(super) base: &'a mut TypeChecker<'b, 'w>,
pub call_site: Span,
pub args: ast::Args<'a>,
pub args: Option<ast::Args<'a>>,
pub resultant: Vec<Ty>,
}
@ -44,23 +44,114 @@ impl<'a, 'b, 'w> ApplyChecker for ApplyTypeChecker<'a, 'b, 'w> {
}
// todo: remove this after we implemented dependent types
if let Sig::TypeCons { val, .. } = sig {
if *val == typst::foundations::Type::of::<typst::foundations::Type>() {
if let Some(p0) = args.pos(0) {
self.resultant
.push(Ty::Unary(TypeUnary::new(UnaryOp::TypeOf, p0.into())));
match sig {
Sig::TypeCons { val, .. } => {
if *val == typst::foundations::Type::of::<typst::foundations::Type>() {
if let Some(p0) = args.pos(0) {
self.resultant
.push(Ty::Unary(TypeUnary::new(UnaryOp::TypeOf, p0.into())));
}
}
}
Sig::Builtin(BuiltinSig::TupleMap(this)) => {
if let Some(p0) = args.pos(0) {
let mut resultants = vec![];
log::debug!("syntax check tuple map {this:?} {p0:?}");
let mut mapper = |base: &mut TypeChecker, sig: Sig<'_>, _pol| {
resultants.push(Ty::Any);
match sig {
Sig::TupleCons(cons) => {
let res = cons
.iter()
.map(|elem| {
log::debug!(
"tuple map check on tuple elem: {elem:?} {p0:?}"
);
let args = ArgsTy::unary(elem.clone(), Ty::Any);
let mut mapper = ApplyTypeChecker {
base,
call_site: Span::detached(),
args: None,
resultant: vec![],
};
p0.call(&args, true, &mut mapper);
Ty::from_types(mapper.resultant.into_iter())
})
.collect::<Vec<_>>();
self.resultant.push(Ty::Tuple(res.into()));
}
Sig::ArrayCons(elem) => {
log::debug!("array map check on array: {elem:?} {p0:?}");
let args = ArgsTy::unary(elem.as_ref().clone(), Ty::Any);
let mut mapper = ApplyTypeChecker {
base,
call_site: Span::detached(),
args: None,
resultant: vec![],
};
p0.call(&args, true, &mut mapper);
let res = Ty::from_types(mapper.resultant.into_iter());
self.resultant.push(Ty::Array(res.into()));
}
_ => {}
}
};
let mut worker = TupleChecker {
base: self.base,
driver: &mut mapper,
};
this.tuple_element_of(pol, &mut worker);
log::debug!("resultant: {resultants:?}");
}
}
Sig::Builtin(BuiltinSig::TupleAt(this)) => {
if let Some(p0) = args.pos(0) {
let mut resultants = vec![];
log::debug!("syntax check tuple at {this:?} {p0:?}");
// todo: caster
let selector = match p0 {
Ty::Value(v) => match v.val {
Value::Int(i) => Ok(i as usize),
Value::Float(i) => Ok(i as usize),
_ => Err(p0),
},
ty => Err(ty),
};
let mut mapper = |_base: &mut TypeChecker, sig: Sig<'_>, _pol| {
resultants.push(Ty::Any);
match sig {
Sig::TupleCons(cons) => {
log::debug!("tuple at check on tuple elem: {cons:?} {p0:?}");
let sel = match selector {
Ok(i) => cons.get(i).cloned(),
Err(_) => None,
};
let res =
sel.unwrap_or_else(|| Ty::from_types(cons.iter().cloned()));
self.resultant.push(res);
}
Sig::ArrayCons(elem) => {
log::debug!("array at check on array: {elem:?} {p0:?}");
self.resultant.push(elem.as_ref().clone());
}
_ => {}
}
};
let mut worker = TupleChecker {
base: self.base,
driver: &mut mapper,
};
this.tuple_element_of(pol, &mut worker);
log::debug!("resultant: {resultants:?}");
}
}
_ => {}
}
// let v = val.inner();
// use typst::foundations::func::Repr;
// if let Repr::Native(v) = v {
// match v.name {
// "assert" => {}
// "panic" => {}
// _ => {}
// }
// }
let callee = sig.ty();
@ -108,3 +199,36 @@ impl<'a, 'b, 'w> ApplyChecker for ApplyTypeChecker<'a, 'b, 'w> {
// );
}
}
trait TupleCheckDriver {
fn check(&mut self, base: &mut TypeChecker, sig: Sig, pol: bool);
}
impl<T: FnMut(&mut TypeChecker, Sig, bool)> TupleCheckDriver for T {
fn check(&mut self, base: &mut TypeChecker, sig: Sig, pol: bool) {
self(base, sig, pol);
}
}
pub struct TupleChecker<'a, 'b, 'w> {
pub(super) base: &'a mut TypeChecker<'b, 'w>,
driver: &'a mut dyn TupleCheckDriver,
}
impl<'a, 'b, 'w> ApplyChecker for TupleChecker<'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 apply(&mut self, sig: Sig, _args: &Interned<ArgsTy>, pol: bool) {
self.driver.check(self.base, sig, pol);
}
}

View file

@ -333,7 +333,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
let mut worker = ApplyTypeChecker {
base: self,
call_site: func_call.callee().span(),
args: func_call.args(),
args: Some(func_call.args()),
resultant: vec![],
};
callee.call(&args, true, &mut worker);
@ -467,7 +467,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
let mut worker = ApplyTypeChecker {
base: self,
call_site: set_rule.target().span(),
args: set_rule.args(),
args: Some(set_rule.args()),
resultant: vec![],
};
callee.call(&args, true, &mut worker);

View file

@ -0,0 +1,12 @@
---
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/tuple_at.typ
---
"a" = (1, 2, 3, )
"b" = 2
---
5..6 -> @a
25..26 -> @b
29..30 -> @a
29..36 -> 2

View file

@ -0,0 +1,20 @@
---
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/tuple_map.typ
---
"a" = (1, )
"b" = (Type(string), )
"f" = (( ⪯ (Type(bytes) | Type(float) | Type(integer) | Type(label) | Type(string) | Type(type) | Type(version)))) => Type(string)
"x" = Any
---
5..6 -> @a
20..21 -> @f
24..25 -> @x
29..32 -> Type(string)
29..35 -> Type(string)
33..34 -> @x
42..43 -> @b
46..47 -> @a
46..54 -> (Type(string), )
52..53 -> @f

View file

@ -0,0 +1,2 @@
#let a = (1, 2, 3);
#let b = a.at(1);

View file

@ -0,0 +1,3 @@
#let a = (1,);
#let f = x => str(x);
#let b = a.map(f);

View file

@ -12,16 +12,21 @@ pub trait ApplyChecker {
}
}
static EMPTY_ARGS: Lazy<Interned<ArgsTy>> = Lazy::new(|| ArgsTy::default().into());
impl Ty {
/// Call the given type with the given arguments.
pub fn call(&self, args: &Interned<ArgsTy>, pol: bool, checker: &mut impl ApplyChecker) {
self.apply(SigSurfaceKind::Call, args, pol, checker)
}
/// Get the tuple element type of the given type.
pub fn tuple_element_of(&self, pol: bool, checker: &mut impl ApplyChecker) {
self.apply(SigSurfaceKind::Array, &EMPTY_ARGS, pol, checker)
}
/// Get the element type of the given type.
pub fn element_of(&self, pol: bool, checker: &mut impl ApplyChecker) {
static EMPTY_ARGS: Lazy<Interned<ArgsTy>> = Lazy::new(|| ArgsTy::default().into());
self.apply(SigSurfaceKind::ArrayOrDict, &EMPTY_ARGS, pol, checker)
}

View file

@ -156,6 +156,15 @@ impl<'a> Iterator for UnionIter<'a> {
}
}
// todo: we can write some proto files for builtin sigs
#[derive(Debug, Clone, Copy)]
pub enum BuiltinSig<'a> {
/// Map a function over a tuple.
TupleMap(&'a Ty),
/// Get element of a tuple.
TupleAt(&'a Ty),
}
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltinTy {
Clause,

View file

@ -558,6 +558,37 @@ impl SigTy {
})
}
/// Unary constructor
#[comemo::memoize]
pub fn unary(inp: Ty, ret: Ty) -> Interned<SigTy> {
Interned::new(Self {
inputs: Interned::new(vec![inp]),
body: Some(ret),
names: NameBone::empty(),
name_started: 0,
spread_left: false,
spread_right: false,
})
}
/// Tuple constructor
#[comemo::memoize]
pub fn tuple_cons(elems: Interned<Vec<Ty>>, anyify: bool) -> Interned<SigTy> {
let ret = if anyify {
Ty::Any
} else {
Ty::Tuple(elems.clone())
};
Interned::new(Self {
inputs: elems,
body: Some(ret),
names: NameBone::empty(),
name_started: 0,
spread_left: false,
spread_right: false,
})
}
/// Dictionary constructor
#[comemo::memoize]
pub fn dict_cons(named: &Interned<RecordTy>, anyify: bool) -> Interned<SigTy> {

View file

@ -4,12 +4,14 @@ use crate::{adt::interner::Interned, analysis::*, ty::def::*};
#[derive(Debug, Clone, Copy)]
pub enum Sig<'a> {
Builtin(BuiltinSig<'a>),
Type(&'a Interned<SigTy>),
TypeCons {
val: &'a typst::foundations::Type,
at: &'a Ty,
},
ArrayCons(&'a TyRef),
TupleCons(&'a Interned<Vec<Ty>>),
DictCons(&'a Interned<RecordTy>),
Value {
val: &'a Func,
@ -31,8 +33,10 @@ pub struct SigShape<'a> {
impl<'a> Sig<'a> {
pub fn ty(self) -> Option<Ty> {
Some(match self {
Sig::Builtin(_) => return None,
Sig::Type(t) => Ty::Func(t.clone()),
Sig::ArrayCons(t) => Ty::Array(t.clone()),
Sig::TupleCons(t) => Ty::Tuple(t.clone()),
Sig::DictCons(t) => Ty::Dict(t.clone()),
Sig::TypeCons { val, .. } => Ty::Builtin(BuiltinTy::Type(*val)),
Sig::Value { at, .. } => at.clone(),
@ -48,7 +52,9 @@ impl<'a> Sig<'a> {
};
let sig_ins = match cano_sig {
Sig::Builtin(_) => return None,
Sig::ArrayCons(a) => SigTy::array_cons(a.as_ref().clone(), false),
Sig::TupleCons(t) => SigTy::tuple_cons(t.clone(), false),
Sig::DictCons(d) => SigTy::dict_cons(d, false),
Sig::TypeCons { val, .. } => ctx?.type_of_func(&val.constructor().ok()?)?,
Sig::Value { val, .. } => ctx?.type_of_func(val)?,
@ -160,6 +166,7 @@ impl<'a> SigCheckDriver<'a> {
}
fn ty(&mut self, ty: &Ty, pol: bool) {
log::debug!("check sig: {ty:?}");
match ty {
Ty::Builtin(BuiltinTy::Stroke) if self.dict_as_sig() => {
self.checker
@ -215,11 +222,11 @@ impl<'a> SigCheckDriver<'a> {
self.checker.check(Sig::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(Sig::ArrayCons(sig), &mut self.ctx, pol);
}
// todo: tuple
Ty::Tuple(_) => {}
Ty::Tuple(tup) if self.array_as_sig() => {
self.checker.check(Sig::TupleCons(tup), &mut self.ctx, pol);
}
Ty::Dict(sig) if self.dict_as_sig() => {
// self.check_dict_signature(sig, pol, self.checker);
self.checker.check(Sig::DictCons(sig), &mut self.ctx, pol);
@ -242,6 +249,7 @@ impl<'a> SigCheckDriver<'a> {
impl BoundChecker for SigCheckDriver<'_> {
fn collect(&mut self, ty: &Ty, pol: bool) {
log::debug!("sig bounds: {ty:?}");
self.ty(ty, pol);
}
@ -256,6 +264,17 @@ impl<'a, 'b> MethodDriver<'a, 'b> {
fn is_binder(&self) -> bool {
matches!(self.1.as_ref(), "with" | "where")
}
fn array_method(&mut self, ty: &Ty, pol: bool) {
let method = match self.1.as_ref() {
"map" => BuiltinSig::TupleMap(ty),
"at" => BuiltinSig::TupleAt(ty),
_ => return,
};
self.0
.checker
.check(Sig::Builtin(method), &mut self.0.ctx, pol);
}
}
impl<'a, 'b> BoundChecker for MethodDriver<'a, 'b> {
@ -264,16 +283,20 @@ impl<'a, 'b> BoundChecker for MethodDriver<'a, 'b> {
match ty {
// todo: deduplicate checking early
Ty::Value(v) => {
if let Value::Func(f) = &v.val {
if self.is_binder() {
self.0.checker.check(
Sig::Partialize(&Sig::Value { val: f, at: ty }),
&mut self.0.ctx,
pol,
);
} else {
// todo: general select operator
match &v.val {
Value::Func(f) => {
if self.is_binder() {
self.0.checker.check(
Sig::Partialize(&Sig::Value { val: f, at: ty }),
&mut self.0.ctx,
pol,
);
} else {
// todo: general select operator
}
}
Value::Array(..) => self.array_method(ty, pol),
_ => {}
}
}
Ty::Builtin(BuiltinTy::Element(e)) => {
@ -298,6 +321,8 @@ impl<'a, 'b> BoundChecker for MethodDriver<'a, 'b> {
// todo: general select operator
}
}
Ty::Tuple(..) => self.array_method(ty, pol),
Ty::Array(..) => self.array_method(ty, pol),
// todo: general select operator
_ => {}
}