dev: introduce TypeType and Module type (#843)

* dev: introduce `TypeType` and `Module` type

g

* test: update snapshot

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-11-18 12:52:27 +08:00 committed by GitHub
parent c551950a85
commit a9800bc802
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 150 additions and 96 deletions

View file

@ -720,7 +720,7 @@ impl SharedContext {
return cached;
}
let res = crate::analysis::term_value(self, val);
let res = crate::analysis::term_value(val);
self.analysis
.caches

View file

@ -464,6 +464,12 @@ fn analyze_dyn_signature(
SignatureTarget::Convert(func) | SignatureTarget::Runtime(func) => func.clone(),
};
Some(func_signature(func))
}
/// Gets the signature of a function.
#[comemo::memoize]
pub fn func_signature(func: Func) -> Signature {
use typst::foundations::func::Repr;
let mut with_stack = eco_vec![];
let mut func = func;
@ -482,19 +488,6 @@ fn analyze_dyn_signature(
func = f.0.clone();
}
let signature = analyze_dyn_signature_inner(func);
log::trace!("got signature {signature:?}");
if with_stack.is_empty() {
return Some(Signature::Primary(signature));
}
Some(Signature::Partial(Arc::new(PartialSignature {
signature,
with_stack,
})))
}
fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
let mut pos_tys = vec![];
let mut named_tys = Vec::new();
let mut rest_ty = None;
@ -530,7 +523,6 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
}
};
use typst::foundations::func::Repr;
let ret_ty = match func.inner() {
Repr::With(..) => unreachable!(),
Repr::Closure(c) => {
@ -561,13 +553,24 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
param_specs.push(doc);
}
Arc::new(PrimarySignature {
let signature = Arc::new(PrimarySignature {
docs: func.docs().map(From::from),
param_specs,
has_fill_or_size_or_stroke,
sig_ty: sig_ty.into(),
_broken: broken,
})
});
log::trace!("got signature {signature:?}");
if with_stack.is_empty() {
return Signature::Primary(signature);
}
Signature::Partial(Arc::new(PartialSignature {
signature,
with_stack,
}))
}
fn analyze_closure_signature(

View file

@ -31,6 +31,7 @@ pub(crate) fn type_check(
route: &mut TypeEnv,
) -> Arc<TypeScheme> {
let mut info = TypeScheme::default();
info.valid = true;
info.revision = ei.revision;
route.insert(ei.fid, Arc::new(TypeScheme::default()));
@ -48,6 +49,16 @@ pub(crate) fn type_check(
let type_check_start = std::time::Instant::now();
checker.check(&root);
let exports = checker
.ei
.exports
.clone()
.into_iter()
.map(|(k, v)| (k.clone(), checker.check(v)))
.collect();
checker.info.exports = exports;
let elapsed = type_check_start.elapsed();
log::debug!("Type checking on {:?} took {elapsed:?}", checker.ei.fid);
@ -99,14 +110,7 @@ impl<'a> TyCtxMut for TypeChecker<'a> {
fn check_module_item(&mut self, fid: TypstFileId, k: &StrRef) -> Option<Ty> {
let ei = self.ctx.expr_stage_by_id(fid)?;
let item = ei.exports.get(k)?;
match item {
Expr::Decl(decl) => Some(self.check_decl(decl)),
Expr::Ref(r) => r.root.clone().map(|r| self.check(&r)),
_ => {
panic!("unexpected module item: {item:?}");
}
}
Some(self.check(ei.exports.get(k)?))
}
}

View file

@ -1,6 +1,39 @@
use crate::analysis::func_signature;
use super::*;
pub fn term_value(ctx: &Arc<SharedContext>, value: &Value) -> Ty {
pub fn is_plain_value(value: &Value) -> bool {
matches!(
value,
Value::Label(..)
| Value::None
| Value::Auto
| Value::Bool(..)
| Value::Int(..)
| Value::Float(..)
| Value::Decimal(..)
| Value::Length(..)
| Value::Angle(..)
| Value::Ratio(..)
| Value::Relative(..)
| Value::Fraction(..)
| Value::Color(..)
| Value::Gradient(..)
| Value::Pattern(..)
| Value::Symbol(..)
| Value::Version(..)
| Value::Str(..)
| Value::Bytes(..)
| Value::Datetime(..)
| Value::Duration(..)
| Value::Content(..)
| Value::Styles(..)
)
}
/// Gets the type of a value.
#[comemo::memoize]
pub fn term_value(value: &Value) -> Ty {
match value {
Value::Array(a) => {
let values = a
@ -34,38 +67,17 @@ pub fn term_value(ctx: &Arc<SharedContext>, value: &Value) -> Ty {
.collect();
Ty::Dict(RecordTy::new(values))
}
Value::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)),
Value::Type(ty) => Ty::Builtin(BuiltinTy::TypeType(*ty)),
Value::Dyn(v) => Ty::Builtin(BuiltinTy::Type(v.ty())),
Value::Func(func) => Ty::Func(ctx.type_of_func(func.clone()).type_sig()),
Value::Label(..)
| Value::None
| Value::Auto
| Value::Bool(..)
| Value::Int(..)
| Value::Float(..)
| Value::Decimal(..)
| Value::Length(..)
| Value::Angle(..)
| Value::Ratio(..)
| Value::Relative(..)
| Value::Fraction(..)
| Value::Color(..)
| Value::Gradient(..)
| Value::Pattern(..)
| Value::Symbol(..)
| Value::Version(..)
| Value::Str(..)
| Value::Bytes(..)
| Value::Datetime(..)
| Value::Duration(..)
| Value::Content(..)
| Value::Styles(..) => Ty::Value(InsTy::new(value.clone())),
Value::Func(func) => Ty::Func(func_signature(func.clone()).type_sig()),
_ if is_plain_value(value) => Ty::Value(InsTy::new(value.clone())),
_ => Ty::Any,
}
}
pub fn term_value_rec(value: &Value, s: Span) -> Ty {
match value {
Value::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)),
Value::Type(ty) => Ty::Builtin(BuiltinTy::TypeType(*ty)),
Value::Dyn(v) => Ty::Builtin(BuiltinTy::Type(v.ty())),
Value::None
| Value::Auto

View file

@ -484,7 +484,8 @@ impl<'a> TypeChecker<'a> {
Ty::Builtin(BuiltinTy::Content)
}
fn check_import(&mut self, _import: &Interned<ImportExpr>) -> Ty {
fn check_import(&mut self, import: &Interned<ImportExpr>) -> Ty {
self.check_ref(&import.decl);
Ty::Builtin(BuiltinTy::None)
}
@ -527,8 +528,21 @@ impl<'a> TypeChecker<'a> {
pub(crate) fn check_decl(&mut self, decl: &Interned<Decl>) -> Ty {
let v = Ty::Var(self.get_var(decl));
if let Decl::Label(..) = decl.as_ref() {
self.constrain(&v, &Ty::Builtin(BuiltinTy::Label));
match decl.kind() {
DefKind::Reference => {
self.constrain(&v, &Ty::Builtin(BuiltinTy::Label));
}
DefKind::Module => {
let ty = if decl.is_def() {
Some(Ty::Builtin(BuiltinTy::Module(decl.clone())))
} else {
self.ei.get_def(decl).map(|e| self.check(&e))
};
if let Some(ty) = ty {
self.constrain(&v, &ty);
}
}
_ => {}
}
v

View file

@ -1,6 +1,6 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (84..86)
description: Completion on c( (85..87)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self3.typ
---
@ -12,7 +12,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self3.typ
"kind": 9,
"label": "baz",
"labelDetails": {
"description": "base.typ"
"description": "module(base)"
},
"textEdit": {
"newText": "baz",

View file

@ -1,6 +1,6 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (87..89)
description: Completion on c( (88..90)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self4.typ
---
@ -12,7 +12,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self4.typ
"kind": 9,
"label": "baz",
"labelDetails": {
"description": "base.typ"
"description": "module(base)"
},
"textEdit": {
"newText": "baz",

File diff suppressed because one or more lines are too long

View file

@ -2,9 +2,8 @@
source: crates/tinymist-query/src/hover.rs
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/value_repr.typ
snapshot_kind: text
---
{
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: function = box,\n w06: any = list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dictionary = (:),\n w13: dictionary = (a: 1),\n w14: dictionary = (a: box),\n w15: dictionary = (a: list.item),\n) = int;\n```\n\n---\n\n\n# Positional Parameters\n\n## x\n\n```typc\ntype: \n```\n\n\n\n## y (positional)\n\n```typc\ntype: \n```\n\n\n\n## z (positional)\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## w01\n\n```typc\ntype: 1\n```\n\n\n\n## w02 (named)\n\n```typc\ntype: \"test\"\n```\n\n\n\n## w03 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w04 (named)\n\n```typc\ntype: \n```\n\n\n\n## w05 (named)\n\n```typc\ntype: box\n```\n\n\n\n## w06 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w07 (named)\n\n```typc\ntype: content\n```\n\n\n\n## w08 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w09 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w10 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w11 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w12 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w13 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w14 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w15 (named)\n\n```typc\ntype: dictionary\n```\n\n",
"contents": "```typc\nlet f(\n x: any,\n y: any,\n z: any,\n w01: int = 1,\n w02: str = \"test\",\n w03: any = 1 + 2,\n w04: any = Label(test),\n w05: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box = (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box,\n w06: any = (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item,\n w07: content = Expr(..),\n w08: any = Expr(..),\n w09: any = 1 + 2,\n w10: array = (\n 1,\n 2,\n ),\n w11: array = (),\n w12: dictionary = (:),\n w13: dictionary = (a: 1),\n w14: dictionary = (a: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box),\n w15: dictionary = (a: (body-indent: length, indent: length, marker: array | content | function, spacing: auto | length, tight: bool, ..: content) => list.item),\n) = int;\n```\n\n---\n\n\n# Positional Parameters\n\n## x\n\n```typc\ntype: \n```\n\n\n\n## y (positional)\n\n```typc\ntype: \n```\n\n\n\n## z (positional)\n\n```typc\ntype: \n```\n\n\n\n# Named Parameters\n\n## w01\n\n```typc\ntype: 1\n```\n\n\n\n## w02 (named)\n\n```typc\ntype: \"test\"\n```\n\n\n\n## w03 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w04 (named)\n\n```typc\ntype: \n```\n\n\n\n## w05 (named)\n\n```typc\ntype: (content | none, baseline: relative, clip: bool, fill: color, height: auto | relative, inset: inset, outset: outset, radius: radius, stroke: stroke, width: auto | fraction | relative) => box\n```\n\n\n\n## w06 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w07 (named)\n\n```typc\ntype: content\n```\n\n\n\n## w08 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w09 (named)\n\n```typc\ntype: any\n```\n\n\n\n## w10 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w11 (named)\n\n```typc\ntype: array\n```\n\n\n\n## w12 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w13 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w14 (named)\n\n```typc\ntype: dictionary\n```\n\n\n\n## w15 (named)\n\n```typc\ntype: dictionary\n```\n\n",
"range": "23:20:23:21"
}

View file

@ -2,13 +2,14 @@
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/external.typ
snapshot_kind: text
---
"base" = Any
"bad-instantiate" = Any
"prefix" = (("title": ( ⪯ Any)) => TypeBinary { operands: (TypeBinary { operands: (TypeBinary { operands: (Any, None), op: Add }, None), op: Add }, None), op: Add }).with(..("title": None) => any)
"title" = None
---
0..0 -> @bad-instantiate
1..21 -> @base
27..33 -> @prefix
34..39 -> @title
53..68 -> @bad-instantiate

View file

@ -2,12 +2,13 @@
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/recursive.typ
snapshot_kind: text
---
"base" = Any
"a" = Any
"f" = () => Any
---
0..0 -> @a
1..21 -> @base
27..28 -> @f
33..34 -> @a
33..36 -> Any

View file

@ -2,12 +2,13 @@
source: crates/tinymist-query/src/analysis.rs
expression: result
input_file: crates/tinymist-query/src/fixtures/type_check/recursive_use.typ
snapshot_kind: text
---
"base" = Any
"a" = Any
"f" = () => Any
---
0..0 -> @a
1..21 -> @base
27..28 -> @f
33..34 -> @a
33..37 -> Any

View file

@ -737,7 +737,7 @@ pub struct SetExpr {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImportExpr {
pub decl: DeclExpr,
pub decl: Interned<RefExpr>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

View file

@ -161,6 +161,14 @@ impl std::hash::Hash for ExprInfo {
}
impl ExprInfo {
pub fn get_def(&self, decl: &Interned<Decl>) -> Option<Expr> {
if decl.is_def() {
return Some(Expr::Decl(decl.clone()));
}
let resolved = self.resolves.get(&decl.span())?;
Some(Expr::Ref(resolved.clone()))
}
pub fn get_refs(
&self,
decl: Interned<Decl>,
@ -612,7 +620,7 @@ impl<'a> ExprWorker<'a> {
.insert_mut(decl.name().clone(), Expr::Ref(mod_ref.clone()));
}
self.resolve_as(mod_ref);
self.resolve_as(mod_ref.clone());
let fid = mod_expr.as_ref().and_then(|mod_expr| match mod_expr {
Expr::Type(Ty::Value(v)) => match &v.val {
@ -678,7 +686,7 @@ impl<'a> ExprWorker<'a> {
}
};
Expr::Import(ImportExpr { decl }.into())
Expr::Import(ImportExpr { decl: mod_ref }.into())
}
fn check_import(&mut self, source: ast::Expr, is_import: bool) -> Option<Expr> {

View file

@ -274,7 +274,7 @@ impl<'a, T: fmt::Write> ExprPrinter<'a, T> {
fn write_import(&mut self, i: &Interned<ImportExpr>) -> fmt::Result {
self.f.write_str("import(")?;
self.write_decl(&i.decl)?;
self.write_decl(&i.decl.decl)?;
self.f.write_str(")")
}
@ -589,7 +589,7 @@ impl<'a, T: fmt::Write> ExprDescriber<'a, T> {
fn write_import(&mut self, i: &Interned<ImportExpr>) -> fmt::Result {
self.f.write_str("import(")?;
self.write_decl(&i.decl)?;
self.write_decl(&i.decl.decl)?;
self.f.write_str(")")
}

View file

@ -10,6 +10,7 @@ use typst::{
layout::Length,
};
use crate::syntax::Decl;
use crate::ty::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
@ -228,7 +229,9 @@ pub enum BuiltinTy {
Tag(Box<(StrRef, Option<Interned<PackageId>>)>),
Type(typst::foundations::Type),
TypeType(typst::foundations::Type),
Element(typst::foundations::Element),
Module(Interned<Decl>),
Path(PathPreference),
}
@ -263,6 +266,7 @@ impl fmt::Debug for BuiltinTy {
BuiltinTy::Inset => write!(f, "Inset"),
BuiltinTy::Outset => write!(f, "Outset"),
BuiltinTy::Radius => write!(f, "Radius"),
BuiltinTy::TypeType(ty) => write!(f, "TypeType({})", ty.short_name()),
BuiltinTy::Type(ty) => write!(f, "Type({})", ty.short_name()),
BuiltinTy::Element(e) => e.fmt(f),
BuiltinTy::Tag(tag) => {
@ -273,6 +277,7 @@ impl fmt::Debug for BuiltinTy {
write!(f, "Tag({name:?})")
}
}
BuiltinTy::Module(m) => write!(f, "{m:?}"),
BuiltinTy::Path(p) => write!(f, "Path({p:?})"),
}
}
@ -343,6 +348,7 @@ impl BuiltinTy {
BuiltinTy::Inset => "inset",
BuiltinTy::Outset => "outset",
BuiltinTy::Radius => "radius",
BuiltinTy::TypeType(..) => "type",
BuiltinTy::Type(ty) => ty.short_name(),
BuiltinTy::Element(ty) => ty.name(),
BuiltinTy::Tag(tag) => {
@ -353,6 +359,7 @@ impl BuiltinTy {
eco_format!("tag {name}")
};
}
BuiltinTy::Module(m) => return eco_format!("module({})", m.name()),
BuiltinTy::Path(s) => match s {
PathPreference::None => "[any]",
PathPreference::Special => "[any]",

View file

@ -182,6 +182,7 @@ impl Ty {
pub fn name(&self) -> Interned<str> {
match self {
Ty::Var(v) => v.name.clone(),
Ty::Builtin(BuiltinTy::Module(m)) => m.name().clone(),
ty => ty
.value()
.and_then(|v| Some(Interned::new_str(v.name()?)))
@ -1085,8 +1086,12 @@ impl IfTy {
/// A type scheme on a group of syntax structures (typing)
#[derive(Default)]
pub struct TypeScheme {
/// Whether the typing is valid
pub valid: bool,
/// The revision used
pub revision: usize,
/// The exported types
pub exports: FxHashMap<StrRef, Ty>,
/// The typing on definitions
pub vars: FxHashMap<DeclExpr, TypeVarBounds>,
/// The checked documentation of definitions

View file

@ -2,15 +2,11 @@ use ecow::{eco_format, EcoString};
use reflexo::hash::hash128;
use typst::foundations::Repr;
use crate::{ty::prelude::*, upstream::truncated_repr_};
impl TypeScheme {
/// Describe the given type with the given type scheme.
pub fn describe(&self, ty: &Ty) -> Option<EcoString> {
let mut worker: TypeDescriber = TypeDescriber::default();
worker.describe_root(ty)
}
}
use crate::{
analysis::{is_plain_value, term_value},
ty::prelude::*,
upstream::truncated_repr_,
};
impl Ty {
/// Describe the given type.
@ -196,6 +192,13 @@ impl TypeDescriber {
Ty::Builtin(b) => {
return b.describe();
}
Ty::Value(v) if matches!(v.val, Value::Module(..)) => {
let Value::Module(m) = &v.val else {
return "module".into();
};
return eco_format!("module({})", m.name());
}
Ty::Value(v) if !is_plain_value(&v.val) => return self.describe(&term_value(&v.val)),
Ty::Value(v) if self.value => return truncated_repr_::<181>(&v.val),
Ty::Value(v) if self.repr => return v.val.ty().short_name().into(),
Ty::Value(v) => return v.val.repr(),

View file

@ -60,10 +60,7 @@ impl Ty {
// iface_kind: IfaceSurfaceKind,
checker: &mut impl IfaceChecker,
) {
let context = IfaceCheckContext {
args: Vec::new(),
at: TyRef::new(Ty::Any),
};
let context = IfaceCheckContext { args: Vec::new() };
let mut worker = IfaceCheckDriver {
ctx: context,
checker,
@ -75,7 +72,6 @@ impl Ty {
pub struct IfaceCheckContext {
pub args: Vec<Interned<SigTy>>,
pub at: TyRef,
}
#[derive(BindTyCtx)]
@ -161,6 +157,12 @@ impl<'a> IfaceCheckDriver<'a> {
self.checker
.check(Iface::Element { val: e, at: ty }, &mut self.ctx, pol);
}
Ty::Builtin(BuiltinTy::Module(e)) => {
if let Decl::Module(m) = e.as_ref() {
self.checker
.check(Iface::Module { val: m.fid, at: ty }, &mut self.ctx, pol);
}
}
// Ty::Func(sig) if self.value_as_iface() => {
// self.checker.check(Iface::Type(sig), &mut self.ctx, pol);
// }
@ -174,13 +176,7 @@ impl<'a> IfaceCheckDriver<'a> {
// self.check_dict_signature(sig, pol, self.checker);
self.checker.check(Iface::Dict(sig), &mut self.ctx, pol);
}
Ty::Var(v) => match v.def.as_ref() {
Decl::Module(m) => {
self.checker
.check(Iface::Module { val: m.fid, at: ty }, &mut self.ctx, pol);
}
_ => ty.bounds(pol, self),
},
Ty::Var(..) => ty.bounds(pol, self),
_ if ty.has_bounds() => ty.bounds(pol, self),
_ => {}
}

View file

@ -287,7 +287,7 @@ impl<'a> CompletionContext<'a> {
.and_then(|types| {
let ty = types.type_of_span(span)?;
let ty = types.simplify(ty, false);
types.describe(&ty).map(From::from)
ty.describe().map(From::from)
})
.or_else(|| {
if let DefKind::Instance(_, v) = &def_kind {
@ -767,6 +767,7 @@ fn type_completion(
BuiltinTy::Infer => return None,
BuiltinTy::FlowNone => return None,
BuiltinTy::Tag(..) => return None,
BuiltinTy::Module(..) => return None,
BuiltinTy::Path(p) => {
let source = ctx.ctx.source_by_id(ctx.root.span().id()?).ok()?;
@ -896,7 +897,7 @@ fn type_completion(
BuiltinTy::RefLabel => {
ctx.ref_completions();
}
BuiltinTy::Type(ty) => {
BuiltinTy::TypeType(ty) | BuiltinTy::Type(ty) => {
if *ty == Type::of::<NoneValue>() {
let docs = docs.or(Some("Nothing."));
type_completion(ctx, &Ty::Builtin(BuiltinTy::None), docs);