arithmetic_typing/types/mod.rs
1//! Base types, such as `Type` and `DynConstraints`.
2
3use std::{borrow::Cow, fmt};
4
5use crate::{
6 arith::{CompleteConstraints, ConstraintSet, Num, ObjectSafeConstraint, WithBoolean},
7 PrimitiveType,
8};
9
10mod fn_type;
11mod object;
12mod quantifier;
13mod tuple;
14
15pub(crate) use self::{
16 fn_type::{FnParams, ParamConstraints},
17 quantifier::ParamQuantifier,
18 tuple::IndexError,
19};
20pub use self::{
21 fn_type::{FnWithConstraints, Function, FunctionBuilder},
22 object::Object,
23 tuple::{LengthVar, Slice, Tuple, TupleIndex, TupleLen, UnknownLen},
24};
25
26/// Type variable.
27///
28/// A variable represents a certain unknown type. Variables can be either *free*
29/// or *bound* to a [`Function`] (these are known as type params in Rust).
30/// Types input to a [`TypeEnvironment`] can only have bounded variables (this is
31/// verified in runtime), but types output by the inference process can contain both.
32///
33/// # Notation
34///
35/// - Bounded type variables are represented as `'T`, `'U`, `'V`, etc.
36/// The tick is inspired by lifetimes in Rust and implicit type params in [F*]. It allows
37/// to easily distinguish between vars and primitive types.
38/// - Free variables are represented as `_`.
39///
40/// [`TypeEnvironment`]: crate::TypeEnvironment
41/// [F*]: http://www.fstar-lang.org/tutorial/
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct TypeVar {
44 index: usize,
45 is_free: bool,
46}
47
48impl fmt::Display for TypeVar {
49 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50 if self.is_free {
51 formatter.write_str("_")
52 } else {
53 write!(formatter, "'{}", Self::param_str(self.index))
54 }
55 }
56}
57
58impl TypeVar {
59 fn param_str(index: usize) -> Cow<'static, str> {
60 const PARAM_NAMES: &str = "TUVXYZ";
61 PARAM_NAMES.get(index..=index).map_or_else(
62 || Cow::from(format!("T{}", index - PARAM_NAMES.len())),
63 Cow::from,
64 )
65 }
66
67 /// Creates a bounded type variable that can be used to [build functions](FunctionBuilder).
68 pub const fn param(index: usize) -> Self {
69 Self {
70 index,
71 is_free: false,
72 }
73 }
74
75 /// Returns the 0-based index of this variable.
76 pub fn index(self) -> usize {
77 self.index
78 }
79
80 /// Is this variable free (not bounded in a function declaration)?
81 pub fn is_free(self) -> bool {
82 self.is_free
83 }
84}
85
86/// Enumeration encompassing all types supported by the type system.
87///
88/// Parametric by the [`PrimitiveType`].
89///
90/// # Notation
91///
92/// - [`Self::Any`] is represented as `any`.
93/// - [`Self::Dyn`] types are represented as documented in [`DynConstraints`].
94/// - [`Prim`](Self::Prim)itive types are represented using the [`Display`](fmt::Display)
95/// implementation of the corresponding [`PrimitiveType`].
96/// - [`Var`](Self::Var)s are represented as documented in [`TypeVar`].
97/// - Notation for [functional](Function) and [tuple](Tuple) types is documented separately.
98///
99/// [`ConstraintSet`]: crate::arith::ConstraintSet
100///
101/// # Examples
102///
103/// There are conversions to construct `Type`s eloquently:
104///
105/// ```
106/// # use arithmetic_typing::{Function, UnknownLen, Type};
107/// let tuple: Type = (Type::BOOL, Type::NUM).into();
108/// assert_eq!(tuple.to_string(), "(Bool, Num)");
109/// let slice = tuple.repeat(UnknownLen::param(0));
110/// assert_eq!(slice.to_string(), "[(Bool, Num); N]");
111/// let fn_type: Type = Function::builder()
112/// .with_arg(slice)
113/// .returning(Type::NUM)
114/// .into();
115/// assert_eq!(fn_type.to_string(), "([(Bool, Num); N]) -> Num");
116/// ```
117///
118/// A `Type` can also be parsed from a string:
119///
120/// ```
121/// # use arithmetic_typing::{ast::TypeAst, Type};
122/// # use std::convert::TryFrom;
123/// # use assert_matches::assert_matches;
124/// # fn main() -> anyhow::Result<()> {
125/// let slice = <Type>::try_from(&TypeAst::try_from("[(Bool, Num)]")?)?;
126/// assert_matches!(slice, Type::Tuple(t) if t.as_slice().is_some());
127/// let fn_type = <Type>::try_from(&TypeAst::try_from("([(Bool, Num); N]) -> Num")?)?;
128/// assert_matches!(fn_type, Type::Function(_));
129/// # Ok(())
130/// # }
131/// ```
132///
133/// # `Any` type
134///
135/// [`Self::Any`], denoted as `any`, is a catch-all type similar to `any` in TypeScript.
136/// It allows to circumvent type system limitations at the cost of being exteremely imprecise.
137/// `any` type can be used in any context (destructured, called with args of any quantity
138/// and type and so on), with each application of the type evaluated independently.
139/// Thus, the same `any` variable can be treated as a function, a tuple, a primitive type, etc.
140///
141/// ```
142/// # use arithmetic_parser::grammars::{F32Grammar, Parse};
143/// # use arithmetic_typing::{Annotated, TypeEnvironment, Type};
144/// # use assert_matches::assert_matches;
145///
146/// # fn main() -> anyhow::Result<()> {
147/// let code = r#"
148/// wildcard: any = 1; // `any` can be assigned from anything
149/// wildcard == 1 && wildcard == (2, 3);
150/// (x, y, ...) = wildcard; // destructuring `any` always succeeds
151/// wildcard(1, |x| x + 1); // calling `any` as a funcion works as well
152/// "#;
153/// let ast = Annotated::<F32Grammar>::parse_statements(code)?;
154/// let mut env = TypeEnvironment::new();
155/// env.process_statements(&ast)?;
156///
157/// // Destructure outputs are certain types that can be inferred
158/// // from their usage, rather than `any`!
159/// assert_matches!(env["x"], Type::Var(_));
160/// let bogus_code = "x + 1 == 2; x(1)";
161/// let ast = Annotated::<F32Grammar>::parse_statements(bogus_code)?;
162/// let errors = env.process_statements(&ast).unwrap_err();
163/// # assert_eq!(errors.len(), 1);
164/// let err = errors.iter().next().unwrap();
165/// assert_eq!(*err.main_span().fragment(), "x(1)");
166/// # Ok(())
167/// # }
168/// ```
169#[derive(Debug, Clone)]
170#[non_exhaustive]
171pub enum Type<Prim: PrimitiveType = Num> {
172 /// Any type aka "I'll think about typing later". Similar to `any` type in TypeScript.
173 /// See [the dedicated section](#any-type) for more details.
174 Any,
175 /// Arbitrary type implementing certain constraints. Similar to `dyn _` types in Rust or use of
176 /// interfaces in type position in TypeScript.
177 ///
178 /// See [`DynConstraints`] for details.
179 Dyn(DynConstraints<Prim>),
180 /// Primitive type.
181 Prim(Prim),
182 /// Functional type.
183 Function(Box<Function<Prim>>),
184 /// Tuple type.
185 Tuple(Tuple<Prim>),
186 /// Object type.
187 Object(Object<Prim>),
188 /// Type variable.
189 Var(TypeVar),
190}
191
192impl<Prim: PrimitiveType> PartialEq for Type<Prim> {
193 fn eq(&self, other: &Self) -> bool {
194 match (self, other) {
195 (Self::Any, _) | (_, Self::Any) => true,
196 (Self::Dyn(x), Self::Dyn(y)) => x == y,
197 (Self::Prim(x), Self::Prim(y)) => x == y,
198 (Self::Var(x), Self::Var(y)) => x == y,
199 (Self::Tuple(xs), Self::Tuple(ys)) => xs == ys,
200 (Self::Object(x), Self::Object(y)) => x == y,
201 (Self::Function(x), Self::Function(y)) => x == y,
202 _ => false,
203 }
204 }
205}
206
207impl<Prim: PrimitiveType> fmt::Display for Type<Prim> {
208 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
209 match self {
210 Self::Any => formatter.write_str("any"),
211 Self::Dyn(constraints) => {
212 if constraints.inner.is_empty() {
213 formatter.write_str("dyn")
214 } else {
215 write!(formatter, "dyn {}", constraints)
216 }
217 }
218 Self::Var(var) => fmt::Display::fmt(var, formatter),
219 Self::Prim(num) => fmt::Display::fmt(num, formatter),
220 Self::Function(fn_type) => fmt::Display::fmt(fn_type, formatter),
221 Self::Tuple(tuple) => fmt::Display::fmt(tuple, formatter),
222 Self::Object(obj) => fmt::Display::fmt(obj, formatter),
223 }
224 }
225}
226
227impl<Prim: PrimitiveType> From<Function<Prim>> for Type<Prim> {
228 fn from(fn_type: Function<Prim>) -> Self {
229 Self::Function(Box::new(fn_type))
230 }
231}
232
233impl<Prim: PrimitiveType> From<Tuple<Prim>> for Type<Prim> {
234 fn from(tuple: Tuple<Prim>) -> Self {
235 Self::Tuple(tuple)
236 }
237}
238
239impl<Prim: PrimitiveType> From<Slice<Prim>> for Type<Prim> {
240 fn from(slice: Slice<Prim>) -> Self {
241 Self::Tuple(slice.into())
242 }
243}
244
245impl<Prim: PrimitiveType> From<Object<Prim>> for Type<Prim> {
246 fn from(object: Object<Prim>) -> Self {
247 Self::Object(object)
248 }
249}
250
251impl<Prim: PrimitiveType> From<DynConstraints<Prim>> for Type<Prim> {
252 fn from(constraints: DynConstraints<Prim>) -> Self {
253 Self::Dyn(constraints)
254 }
255}
256
257macro_rules! impl_from_tuple_for_type {
258 ($($var:tt : $ty:ident),*) => {
259 impl<Prim, $($ty : Into<Type<Prim>>,)*> From<($($ty,)*)> for Type<Prim>
260 where
261 Prim: PrimitiveType,
262 {
263 #[allow(unused_variables)] // `tuple` is unused for empty tuple
264 fn from(tuple: ($($ty,)*)) -> Self {
265 Self::Tuple(Tuple::from(vec![$(tuple.$var.into(),)*]))
266 }
267 }
268 };
269}
270
271impl_from_tuple_for_type!();
272impl_from_tuple_for_type!(0: T);
273impl_from_tuple_for_type!(0: T, 1: U);
274impl_from_tuple_for_type!(0: T, 1: U, 2: V);
275impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W);
276impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X);
277impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y);
278impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z);
279impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A);
280impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A, 8: B);
281impl_from_tuple_for_type!(0: T, 1: U, 2: V, 3: W, 4: X, 5: Y, 6: Z, 7: A, 8: B, 9: C);
282
283impl Type {
284 /// Numeric primitive type.
285 pub const NUM: Self = Type::Prim(Num::Num);
286}
287
288impl<Prim: WithBoolean> Type<Prim> {
289 /// Boolean primitive type.
290 pub const BOOL: Self = Type::Prim(Prim::BOOL);
291}
292
293impl<Prim: PrimitiveType> Type<Prim> {
294 /// Returns a void type (an empty tuple).
295 pub fn void() -> Self {
296 Self::Tuple(Tuple::empty())
297 }
298
299 /// Creates a bounded type variable with the specified `index`.
300 pub fn param(index: usize) -> Self {
301 Self::Var(TypeVar::param(index))
302 }
303
304 pub(crate) fn free_var(index: usize) -> Self {
305 Self::Var(TypeVar {
306 index,
307 is_free: true,
308 })
309 }
310
311 /// Creates a slice type.
312 pub fn slice(element: impl Into<Type<Prim>>, length: impl Into<TupleLen>) -> Self {
313 Self::Tuple(Slice::new(element.into(), length).into())
314 }
315
316 /// Creates a slice type by repeating this type.
317 pub fn repeat(self, length: impl Into<TupleLen>) -> Slice<Prim> {
318 Slice::new(self, length)
319 }
320
321 /// Checks if this type is void (i.e., an empty tuple).
322 pub fn is_void(&self) -> bool {
323 matches!(self, Self::Tuple(tuple) if tuple.is_empty())
324 }
325
326 /// Returns `Some(true)` if this type is known to be primitive,
327 /// `Some(false)` if it's known not to be primitive, and `None` if either case is possible.
328 pub(crate) fn is_primitive(&self) -> Option<bool> {
329 match self {
330 Self::Prim(_) => Some(true),
331 Self::Tuple(_) | Self::Object(_) | Self::Function(_) => Some(false),
332 _ => None,
333 }
334 }
335
336 /// Returns `true` iff this type does not contain type / length variables.
337 ///
338 /// See [`TypeEnvironment`](crate::TypeEnvironment) for caveats of dealing with
339 /// non-concrete types.
340 pub fn is_concrete(&self) -> bool {
341 match self {
342 Self::Var(var) => !var.is_free,
343 Self::Any | Self::Prim(_) => true,
344 Self::Dyn(constraints) => constraints.is_concrete(),
345 Self::Function(fn_type) => fn_type.is_concrete(),
346 Self::Tuple(tuple) => tuple.is_concrete(),
347 Self::Object(obj) => obj.is_concrete(),
348 }
349 }
350}
351
352/// Arbitrary type implementing certain constraints. Similar to `dyn _` types in Rust or use of
353/// interfaces in type position in TypeScript.
354///
355/// [`Constraint`]s in this type must be [object-safe](crate::arith::ObjectSafeConstraint).
356/// `DynConstraints` can also specify an [`Object`] constraint, which can be converted to it
357/// using the [`From`] trait.
358///
359/// [`Constraint`]: crate::arith::Constraint
360///
361/// # Notation
362///
363/// - If the constraints do not include an object constraint, they are [`Display`](fmt::Display)ed
364/// like a [`ConstraintSet`] with `dyn` prefix; e.g, `dyn Lin + Hash`.
365/// - If the constraints include an object constraint, it is specified before all other constraints,
366/// but after the `dyn` prefix; e.g., `dyn { x: Num } + Lin`.
367///
368/// # Examples
369///
370/// `dyn _` types can be used to express that any types satisfying certain constraints
371/// should be accepted.
372///
373/// ```
374/// # use arithmetic_parser::grammars::{F32Grammar, Parse};
375/// # use arithmetic_typing::{defs::Prelude, Annotated, TypeEnvironment, Type, Function};
376/// #
377/// # fn main() -> anyhow::Result<()> {
378/// let code = r#"
379/// sum_lengths = |...pts: dyn { x: _, y: _ }| {
380/// pts.fold(0, |acc, { x, y }| acc + sqrt(x * x + y * y))
381/// };
382/// sum_lengths(#{ x: 1, y: 2 }, #{ x: 3, y: 4, z: 5 })
383/// "#;
384/// let ast = Annotated::<F32Grammar>::parse_statements(code)?;
385///
386/// let mut env = TypeEnvironment::new();
387/// let sqrt = Function::builder().with_arg(Type::NUM).returning(Type::NUM);
388/// env.insert("fold", Prelude::Fold).insert("sqrt", sqrt);
389/// env.process_statements(&ast)?;
390///
391/// assert_eq!(
392/// env["sum_lengths"].to_string(),
393/// "(...[dyn { x: Num, y: Num }; N]) -> Num"
394/// );
395/// # Ok(())
396/// # }
397/// ```
398///
399/// One of primary use cases of `dyn _` is restricting varargs of a function:
400///
401/// ```
402/// # use arithmetic_parser::grammars::{F32Grammar, Parse};
403/// # use arithmetic_typing::{
404/// # ast::TypeAst, defs::Prelude, error::ErrorKind, Annotated, TypeEnvironment, Type,
405/// # };
406/// # use std::convert::TryFrom;
407/// # use assert_matches::assert_matches;
408/// #
409/// # fn main() -> anyhow::Result<()> {
410/// // Function that accepts any amount of linear args (not necessarily
411/// // of the same type) and returns a number.
412/// let digest_fn = Type::try_from(&TypeAst::try_from("(...[dyn Lin; N]) -> Num")?)?;
413/// let mut env = TypeEnvironment::new();
414/// env.insert("true", Prelude::True).insert("digest", digest_fn);
415///
416/// let code = r#"
417/// digest(1, 2, (3, 4), #{ x: 5, y: (6,) }) == 1;
418/// digest(3, true) == 0; // fails: `true` is not linear
419/// "#;
420/// let ast = Annotated::<F32Grammar>::parse_statements(code)?;
421/// let errors = env.process_statements(&ast).unwrap_err();
422///
423/// let err = errors.iter().next().unwrap();
424/// assert_eq!(*err.main_span().fragment(), "true");
425/// assert_matches!(err.kind(), ErrorKind::FailedConstraint { .. });
426/// # Ok(())
427/// # }
428/// ```
429#[derive(Clone, PartialEq)]
430pub struct DynConstraints<Prim: PrimitiveType> {
431 pub(crate) inner: CompleteConstraints<Prim>,
432}
433
434impl<Prim: PrimitiveType> fmt::Debug for DynConstraints<Prim> {
435 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
436 fmt::Debug::fmt(&self.inner, formatter)
437 }
438}
439
440impl<Prim: PrimitiveType> fmt::Display for DynConstraints<Prim> {
441 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
442 fmt::Display::fmt(&self.inner, formatter)
443 }
444}
445
446impl<Prim: PrimitiveType> From<Object<Prim>> for DynConstraints<Prim> {
447 fn from(object: Object<Prim>) -> Self {
448 Self {
449 inner: object.into(),
450 }
451 }
452}
453
454impl<Prim: PrimitiveType> DynConstraints<Prim> {
455 /// Creates constraints based on a single constraint.
456 pub fn just(constraint: impl ObjectSafeConstraint<Prim>) -> Self {
457 Self {
458 inner: CompleteConstraints::from(ConstraintSet::just(constraint)),
459 }
460 }
461
462 /// Checks if this constraint set is empty.
463 pub fn is_empty(&self) -> bool {
464 self.inner.is_empty()
465 }
466
467 /// Returns the enclosed object constraint, if any.
468 pub fn object(&self) -> Option<&Object<Prim>> {
469 self.inner.object.as_ref()
470 }
471
472 fn is_concrete(&self) -> bool {
473 self.inner.object.as_ref().map_or(true, Object::is_concrete)
474 }
475
476 /// Adds the specified `constraint` to these constraints.
477 pub fn insert(&mut self, constraint: impl ObjectSafeConstraint<Prim>) {
478 self.inner.simple.insert(constraint);
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485 use crate::ast::TypeAst;
486
487 use std::convert::TryFrom;
488
489 #[test]
490 fn types_are_equal_to_self() -> anyhow::Result<()> {
491 const SAMPLE_TYPES: &[&str] = &[
492 "Num",
493 "(Num, Bool)",
494 "(Num, ...[Bool; N]) -> ()",
495 "(Num) -> Num",
496 "for<'T: Lin> (['T; N]) -> 'T",
497 ];
498
499 for &sample_type in SAMPLE_TYPES {
500 let ty = <Type>::try_from(&TypeAst::try_from(sample_type)?)?;
501 assert!(ty.eq(&ty), "Type is not equal to self: {}", ty);
502 }
503 Ok(())
504 }
505
506 #[test]
507 fn equality_is_preserved_on_renaming_params() {
508 const EQUAL_FNS: &[&str] = &[
509 "for<'T: Lin> (['T; N]) -> 'T",
510 "for<'T: Lin> (['T; L]) -> 'T",
511 "for<'Ty: Lin> (['Ty; N]) -> 'Ty",
512 "for<'N: Lin> (['N; T]) -> 'N",
513 ];
514
515 let functions: Vec<Type> = EQUAL_FNS
516 .iter()
517 .map(|&s| Type::try_from(&TypeAst::try_from(s).unwrap()).unwrap())
518 .collect();
519 for (i, function) in functions.iter().enumerate() {
520 for other_function in &functions[(i + 1)..] {
521 assert_eq!(function, other_function);
522 }
523 }
524 }
525
526 #[test]
527 fn unequal_functions() {
528 const FUNCTIONS: &[&str] = &[
529 "for<'T: Lin> (['T; N]) -> 'T",
530 "for<len! N; 'T: Lin> (['T; N]) -> 'T",
531 "(['T; N]) -> 'T",
532 "for<'T: Lin> (['T; N], 'T) -> 'T",
533 "for<'T: Lin> (['T; N]) -> ('T)",
534 ];
535
536 let functions: Vec<Type> = FUNCTIONS
537 .iter()
538 .map(|&s| Type::try_from(&TypeAst::try_from(s).unwrap()).unwrap())
539 .collect();
540 for (i, function) in functions.iter().enumerate() {
541 for other_function in &functions[(i + 1)..] {
542 assert_ne!(function, other_function);
543 }
544 }
545 }
546
547 #[test]
548 fn concrete_types() {
549 let sample_types = &[
550 Type::NUM,
551 Type::BOOL,
552 Type::Any,
553 (Type::BOOL, Type::NUM).into(),
554 Type::try_from(&TypeAst::try_from("for<'T: Lin> (['T; N]) -> 'T").unwrap()).unwrap(),
555 ];
556
557 for ty in sample_types {
558 assert!(ty.is_concrete(), "{:?}", ty);
559 }
560 }
561
562 #[test]
563 fn non_concrete_types() {
564 let sample_types = &[
565 Type::free_var(2),
566 (Type::NUM, Type::free_var(0)).into(),
567 Function::builder()
568 .with_arg(Type::free_var(0))
569 .returning(Type::void())
570 .into(),
571 ];
572
573 for ty in sample_types {
574 assert!(!ty.is_concrete(), "{:?}", ty);
575 }
576 }
577}