dev: deduplicate type bounds early (#913)

* case: package subpar:0.2.0
This commit is contained in:
Myriad-Dreamin 2024-11-29 20:04:35 +08:00 committed by GitHub
parent ed79045588
commit 692e53880b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 381 additions and 43 deletions

View file

@ -31,7 +31,7 @@ pub fn bind_ty_ctx(input: TokenStream) -> TokenStream {
quote! {
impl #impl_generics TyCtx for #name #ty_generics #where_clause {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<TypeBounds> {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
self.#bind_name.global_bounds(var, pol)
}
fn local_bind_of(&self, var: &Interned<TypeVar>) -> Option<Ty> {

View file

@ -3,7 +3,7 @@
use hashbrown::HashSet;
use tinymist_derive::BindTyCtx;
use super::{prelude::*, ParamAttrs, ParamTy, SharedContext};
use super::{prelude::*, DynTypeBounds, ParamAttrs, ParamTy, SharedContext};
use super::{
ArgsTy, Sig, SigChecker, SigShape, SigSurfaceKind, SigTy, Ty, TyCtx, TyCtxMut, TypeBounds,
TypeScheme, TypeVar,
@ -106,7 +106,7 @@ pub(crate) struct PostTypeChecker<'a> {
}
impl TyCtx for PostTypeChecker<'_> {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<TypeBounds> {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
self.info.global_bounds(var, pol)
}

View file

@ -11,8 +11,8 @@ use super::{
use crate::analysis::PostTypeChecker;
use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs};
use crate::syntax::get_non_strict_def_target;
use crate::ty::{DynTypeBounds, ParamAttrs};
use crate::ty::{InsTy, TyCtx};
use crate::ty::{ParamAttrs, TypeBounds};
use crate::upstream::truncated_repr;
/// Describes a function signature.

View file

@ -6,8 +6,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
use tinymist_derive::BindTyCtx;
use super::{
prelude::*, BuiltinTy, FlowVarKind, SharedContext, TyCtxMut, TypeBounds, TypeScheme, TypeVar,
TypeVarBounds,
prelude::*, BuiltinTy, DynTypeBounds, FlowVarKind, SharedContext, TyCtxMut, TypeScheme,
TypeVar, TypeVarBounds,
};
use crate::{
syntax::{Decl, DeclExpr, Expr, ExprInfo, UnaryOp},
@ -95,7 +95,7 @@ pub(crate) struct TypeChecker<'a> {
}
impl TyCtx for TypeChecker<'_> {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<TypeBounds> {
fn global_bounds(&self, var: &Interned<TypeVar>, pol: bool) -> Option<DynTypeBounds> {
self.info.global_bounds(var, pol)
}
@ -285,7 +285,7 @@ impl TypeChecker<'_> {
match &w.bounds {
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
let mut w = w.write();
w.ubs.push(bound);
w.ubs.insert_mut(bound);
}
}
}
@ -296,7 +296,7 @@ impl TypeChecker<'_> {
match &w.bounds {
FlowVarKind::Strong(v) | FlowVarKind::Weak(v) => {
let mut v = v.write();
v.lbs.push(bound);
v.lbs.insert_mut(bound);
}
}
}

View file

@ -385,8 +385,8 @@ impl TypeChecker<'_> {
let res_ty = if let Some(annotated) = &docstring.res_ty {
self.constrain(&body, annotated);
Ty::Let(Interned::new(TypeBounds {
lbs: eco_vec![body],
ubs: eco_vec![annotated.clone()],
lbs: vec![body],
ubs: vec![annotated.clone()],
}))
} else {
body

View file

@ -0,0 +1,296 @@
// NOTE: we avoid a possible syntax error in the future when custom elements are added and self may become a keyword by appending an underscore
#import "@preview/t4t:0.3.2"
#let _resolve(elem, it, field) = {
if it.has(field) {
it.at(field)
} else {
// we can't func.at(field) to resolve the field
// eval(repr(elem) + "." + field)
elem.at(field)
}
}
/// The default figure show rule. The active set rules will be used.
///
/// This function is contextual.
///
/// - self_ (content): The figure to show using the defualt show rule.
/// -> content
#let show-figure(self_) = {
// NOTE: this is written to be close to the rust impl to make changes easier to compare
let realized = self_.body
let caption = _resolve(figure, self_, "caption")
if caption != none {
let v = v(self_.gap, weak: true)
let position = _resolve(figure.caption, caption, "position")
realized = if position == top {
caption + v + realized
} else {
realized + v + caption
}
}
realized = {
set align(center)
block(realized)
}
let placement = _resolve(figure, self_, "placement")
if placement != none {
realized = place(placement, float: true)
}
realized
}
#let _numbering = numbering
#let apply-for-all(
values,
rule,
) = (
outer => {
show: inner => {
values.map(rule).fold(inner, (acc, f) => f(acc))
}
outer
}
)
#let gather-kinds(body) = {
if t4t.is.elem(figure, body) {
if body.at("kind", default: auto) != auto {
return (figure.kind,)
}
} else if body.has("children") {
return body.children.map(gather-kinds).flatten().dedup()
}
(image, raw, table)
}
#let i18n-kind(kind) = {
let map = toml("/assets/i18n.toml")
if kind not in map.en {
panic("Unknown kind: `" + kind + "`")
}
let lang-map = map.at(text.lang, default: (:))
let region-map = if text.region != none {
lang-map.at(text.region, default: (:))
} else {
(:)
}
let term = region-map.at(kind, default: none)
if term == none {
term = lang-map.at(kind, default: none)
}
if term == none {
term = map.en.at(kind)
}
term
}
#let stitch-pairs(args) = {
if args.len() == 0 {
return ()
}
assert.ne(type(args.first()), label, message: "First item must not be a label")
let pairs = ()
while args.len() != 0 {
let item = args.remove(0)
if type(item) == label {
let last = pairs.pop()
assert.ne(type(last), label, message: "Cannot have two consecutive labels")
last.at(1) = item
pairs.push(last)
} else {
pairs.push((item, none))
}
}
pairs
}
#let sparse-numbering(numbering) = if type(numbering) == str {
let symbols = ("1", "a", "A", "i", "I", "い", "イ", "א", "가", "ㄱ", "\\*")
let c = numbering.matches(regex(symbols.join("|"))).len()
if c == 1 {
// if we have only one symbol we drop the super number
(_, num) => _numbering(numbering, num)
} else {
(..nums) => _numbering(numbering, ..nums)
}
} else {
numbering
}
#let _numbering = numbering
#let _label = label
#let _grid = grid
/// The counter used for sub figures.
#let sub-figure-counter = counter("__subpar:sub-figure-counter")
/// Creates a figure which may contain other figures, a #emph[super]figure. For
/// the meaning of parameters take a look at the regular figure documentation.
///
/// See @@grid() for a function which places its sub figures in a grid.
///
/// - kind (str, function): The image kind which should be used, this is mainly
/// relevant for introspection and defaults to `image`. This cannot be
/// automatically resovled like for normal figures and must be set.
/// - numbering (str, function): This is the numbering used for this super
/// figure.
/// - numbering-sub (str, function): This is the numbering used for the sub
/// figures.
/// - numbering-sub-ref (str, function): This is the numbering used for
/// _references_ to the sub figures. If this is a function, it receives both
/// the super and sub figure numbering respectively.
/// - supplement (content, function, auto, none): The supplement used for this
/// super figure _and_ the sub figures when referenced.
/// - propagate-supplement (bool): Whether the super figure's supplement should
/// propagate down to its sub figures.
/// - caption (content): The caption of this super figure.
/// - placement (alignment, auto, none): The float placement of this super
/// figure.
/// - scope (str): Relative to which containing scope the figure is placed. Set
/// this to `"parent"` to create a full-width figure in a two-column document.
/// Has no effect if placement is `none`. Can be set to `"parent"` or
/// `"column"`.
/// - gap (length): The gap between this super figure's caption and body.
/// - outlined (bool): Whether this super figure should appear in an outline of
/// figures.
/// - outlined-sub (bool): Whether the sub figures should appear in an outline
/// of figures.
/// - label (label, none): The label to attach to this super figure.
/// - show-sub (function, auto): A show rule override for sub figures. Recevies
/// the sub figure.
/// - show-sub-caption (function, auto): A show rule override for sub figure's
/// captions. Receives the realized numbering and caption element.
/// -> content
#let super(
kind: image,
numbering: "1",
numbering-sub: "(a)",
numbering-sub-ref: "1a",
supplement: auto,
propagate-supplement: true,
caption: none,
placement: none,
scope: "column",
gap: 0.65em,
outlined: true,
outlined-sub: false,
label: none,
show-sub: auto,
show-sub-caption: auto,
body,
) = {
t4t.assert.any-type(str, function, kind)
let assert-numbering = t4t.assert.any-type.with(str, function)
assert-numbering(numbering)
assert-numbering(numbering-sub)
assert-numbering(numbering-sub-ref)
// adjust numberings to receive either both or the sub number
numbering-sub = sparse-numbering(numbering-sub)
numbering-sub-ref = sparse-numbering(numbering-sub-ref)
t4t.assert.any-type(str, content, function, type(auto), type(none), supplement)
t4t.assert.any-type(bool, propagate-supplement)
t4t.assert.any-type(str, content, type(none), caption)
t4t.assert.any(top, bottom, auto, none, placement)
t4t.assert.any-type(length, gap)
t4t.assert.any-type(bool, outlined)
t4t.assert.any-type(bool, outlined-sub)
t4t.assert.any-type(_label, type(none), label)
t4t.assert.any-type(function, type(auto), show-sub)
t4t.assert.any-type(function, type(auto), show-sub-caption)
let function-kinds = (
image: "figure",
table: "table",
raw: "raw",
)
// NOTE: if we use no propagation, then we can fallback to the normal auto behavior, fixing #4.
if propagate-supplement and supplement == auto {
if repr(kind) in function-kinds {
supplement = context i18n-kind(function-kinds.at(repr(kind)))
} else {
panic("Cannot infer `supplement`, must be set.")
}
}
show-sub = t4t.def.if-auto(it => it, show-sub)
show-sub-caption = t4t.def.if-auto((num, it) => it, show-sub-caption)
context {
let n-super = counter(figure.where(kind: kind)).get().first() + 1
[#figure(
kind: kind,
numbering: n => _numbering(numbering, n),
supplement: supplement,
caption: caption,
placement: placement,
scope: scope,
gap: gap,
outlined: outlined,
{
// TODO: simply setting it for all doesn't seem to work
show: apply-for-all(
gather-kinds(body),
kind => (
inner => {
show figure.where(kind: kind): set figure(numbering: _ => _numbering(
numbering-sub-ref,
n-super,
sub-figure-counter.get().first() + 1,
))
inner
}
),
)
set figure(supplement: supplement) if propagate-supplement
set figure(outlined: outlined-sub, placement: none)
show figure: show-sub
show figure: it => {
let n-sub = sub-figure-counter.get().first() + 1
let num = _numbering(numbering-sub, n-super, n-sub)
show figure.caption: it => {
num
[ ]
it.body
}
show figure.caption: show-sub-caption.with(num)
sub-figure-counter.step()
it
counter(figure.where(kind: it.kind)).update(n => n - 1)
}
sub-figure-counter.update(0)
body
},
)#label]
}
}

View file

@ -14,7 +14,7 @@ use crate::{
prelude::*,
syntax::{Decl, DefKind},
ty::{
BuiltinTy, InsTy, Interned, PackageId, SigTy, StrRef, Ty, TypeBounds, TypeVar,
BuiltinTy, DynTypeBounds, InsTy, Interned, PackageId, SigTy, StrRef, Ty, TypeVar,
TypeVarBounds,
},
};
@ -197,7 +197,7 @@ impl DocsChecker<'_> {
name,
def: encoded.clone(),
};
let bounds = TypeVarBounds::new(var, TypeBounds::default());
let bounds = TypeVarBounds::new(var, DynTypeBounds::default());
let var = bounds.as_type();
self.vars.insert(encoded, bounds);
var

View file

@ -8,7 +8,7 @@ use std::{
sync::Arc,
};
use ecow::{EcoString, EcoVec};
use ecow::EcoString;
use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use reflexo_typst::TypstFileId;
@ -367,15 +367,45 @@ impl NameBone {
}
}
/// The state of a type variable (bounds of some type in program)
#[derive(Clone, Default)]
pub struct DynTypeBounds {
/// The lower bounds
pub lbs: rpds::HashTrieSetSync<Ty>,
/// The upper bounds
pub ubs: rpds::HashTrieSetSync<Ty>,
}
impl From<TypeBounds> for DynTypeBounds {
fn from(bounds: TypeBounds) -> Self {
Self {
lbs: bounds.lbs.into_iter().collect(),
ubs: bounds.ubs.into_iter().collect(),
}
}
}
impl DynTypeBounds {
/// Get frozen bounds
pub fn freeze(&self) -> TypeBounds {
// sorted
let mut lbs: Vec<_> = self.lbs.iter().cloned().collect();
lbs.sort();
let mut ubs: Vec<_> = self.ubs.iter().cloned().collect();
ubs.sort();
TypeBounds { lbs, ubs }
}
}
/// A frozen type variable (bounds of some type in program)
/// `t :> t1 | ... | tn <: f1 & ... & fn`
/// ` lbs------------- ubs-------------`
#[derive(Hash, Clone, PartialEq, Eq, Default, PartialOrd, Ord)]
pub struct TypeBounds {
/// The lower bounds
pub lbs: EcoVec<Ty>,
pub lbs: Vec<Ty>,
/// The upper bounds
pub ubs: EcoVec<Ty>,
pub ubs: Vec<Ty>,
}
impl fmt::Debug for TypeBounds {
@ -1153,7 +1183,7 @@ pub struct TypeScheme {
}
impl TyCtx for TypeScheme {
fn global_bounds(&self, var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
fn global_bounds(&self, var: &Interned<TypeVar>, _pol: bool) -> Option<DynTypeBounds> {
let v = self.vars.get(&var.def)?;
Some(v.bounds.bounds().read().clone())
}
@ -1198,25 +1228,33 @@ impl TypeScheme {
}
/// Converts a type to a type with bounds
pub fn to_bounds(&self, def: Ty) -> TypeBounds {
let mut store = TypeBounds::default();
pub fn to_bounds(&self, def: Ty) -> DynTypeBounds {
let mut store = DynTypeBounds::default();
match def {
Ty::Var(v) => {
let w = self.vars.get(&v.def).unwrap();
match &w.bounds {
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
let w = w.read();
store.lbs.extend(w.lbs.iter().cloned());
store.ubs.extend(w.ubs.iter().cloned());
for l in w.lbs.iter() {
store.lbs.insert_mut(l.clone());
}
for l in w.ubs.iter() {
store.ubs.insert_mut(l.clone());
}
}
}
}
Ty::Let(v) => {
store.lbs.extend(v.lbs.iter().cloned());
store.ubs.extend(v.ubs.iter().cloned());
for l in v.lbs.iter() {
store.lbs.insert_mut(l.clone());
}
for l in v.ubs.iter() {
store.ubs.insert_mut(l.clone());
}
}
_ => {
store.ubs.push(def);
store.ubs.insert_mut(def);
}
}
@ -1269,10 +1307,10 @@ impl fmt::Debug for TypeVarBounds {
impl TypeVarBounds {
/// Create a type variable bounds
pub fn new(var: TypeVar, init: TypeBounds) -> Self {
pub fn new(var: TypeVar, init: DynTypeBounds) -> Self {
Self {
var: Interned::new(var),
bounds: FlowVarKind::Strong(Arc::new(RwLock::new(init))),
bounds: FlowVarKind::Strong(Arc::new(RwLock::new(init.clone()))),
}
}
@ -1301,15 +1339,15 @@ impl TypeVarBounds {
#[derive(Clone)]
pub enum FlowVarKind {
/// A type variable that receives both types and values (type instances)
Strong(Arc<RwLock<TypeBounds>>),
Strong(Arc<RwLock<DynTypeBounds>>),
/// A type variable that receives only types
/// The received values will be lifted to types
Weak(Arc<RwLock<TypeBounds>>),
Weak(Arc<RwLock<DynTypeBounds>>),
}
impl FlowVarKind {
/// Get the bounds of the type variable
pub fn bounds(&self) -> &RwLock<TypeBounds> {
pub fn bounds(&self) -> &RwLock<DynTypeBounds> {
match self {
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => w,
}

View file

@ -29,7 +29,7 @@ pub trait TyCtx {
/// Get local binding of a variable.
fn local_bind_of(&self, _var: &Interned<TypeVar>) -> Option<Ty>;
/// Get the type of a variable.
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds>;
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<DynTypeBounds>;
}
impl TyCtx for () {
@ -37,7 +37,7 @@ impl TyCtx for () {
None
}
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<TypeBounds> {
fn global_bounds(&self, _var: &Interned<TypeVar>, _pol: bool) -> Option<DynTypeBounds> {
None
}
}

View file

@ -164,7 +164,7 @@ impl TypeSimplifier<'_, '_> {
fn transform(&mut self, ty: &Ty, pol: bool) -> Ty {
match ty {
Ty::Let(w) => self.transform_let(w, None, pol),
Ty::Let(w) => self.transform_let(w.lbs.iter(), w.ubs.iter(), None, pol),
Ty::Var(v) => {
if let Some(cano) = self.cano_local_cache.get(&(v.def.clone(), self.principal)) {
return cano.clone();
@ -177,7 +177,7 @@ impl TypeSimplifier<'_, '_> {
FlowVarKind::Strong(w) | FlowVarKind::Weak(w) => {
let w = w.read();
self.transform_let(&w, Some(&v.def), pol)
self.transform_let(w.lbs.iter(), w.ubs.iter(), Some(&v.def), pol)
}
};
@ -251,19 +251,25 @@ impl TypeSimplifier<'_, '_> {
}
#[allow(clippy::mutable_key_type)]
fn transform_let(&mut self, w: &TypeBounds, def_id: Option<&DeclExpr>, pol: bool) -> Ty {
let mut lbs = HashSet::with_capacity(w.lbs.len());
let mut ubs = HashSet::with_capacity(w.ubs.len());
fn transform_let<'a>(
&mut self,
lbs_iter: impl ExactSizeIterator<Item = &'a Ty>,
ubs_iter: impl ExactSizeIterator<Item = &'a Ty>,
def_id: Option<&DeclExpr>,
pol: bool,
) -> Ty {
let mut lbs = HashSet::with_capacity(lbs_iter.len());
let mut ubs = HashSet::with_capacity(ubs_iter.len());
crate::log_debug_ct!("transform let [principal={}] with {w:?}", self.principal);
crate::log_debug_ct!("transform let [principal={}]", self.principal);
if !self.principal || ((pol) && !def_id.is_some_and(|i| self.negatives.contains(i))) {
for lb in w.lbs.iter() {
for lb in lbs_iter {
lbs.insert(self.transform(lb, pol));
}
}
if !self.principal || ((!pol) && !def_id.is_some_and(|i| self.positives.contains(i))) {
for ub in w.ubs.iter() {
for ub in ubs_iter {
ubs.insert(self.transform(ub, !pol));
}
}
@ -285,8 +291,6 @@ impl TypeSimplifier<'_, '_> {
let mut ubs: Vec<_> = ubs.into_iter().collect();
ubs.sort();
let mut lbs = lbs.into_iter().collect();
let mut ubs = ubs.into_iter().collect();
Ty::Let(TypeBounds { lbs, ubs }.into())
}

View file

@ -58,7 +58,7 @@ mod tests {
use insta::{assert_debug_snapshot, assert_snapshot};
use tinymist_derive::BindTyCtx;
use super::{Interned, Ty, TyCtx, TypeBounds, TypeScheme, TypeVar};
use super::{DynTypeBounds, Interned, Ty, TyCtx, TypeScheme, TypeVar};
use crate::ty::tests::*;
use crate::ty::ApplyChecker;
#[test]

View file

@ -21,7 +21,7 @@ use crate::analysis::{func_signature, BuiltinTy, PathPreference, Ty};
use crate::syntax::{
descending_decls, interpret_mode_at, is_ident_like, CheckTarget, DescentDecl, InterpretMode,
};
use crate::ty::{Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeBounds, TypeScheme, TypeVar};
use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeScheme, TypeVar};
use crate::upstream::complete::complete_code;
use crate::{completion_kind, prelude::*, LspCompletion};