瀏覽代碼

Add type parameters to the type 'AST', check missing Dim constraints

David Peter 1 年之前
父節點
當前提交
b445d5501b

+ 1 - 1
examples/generics.nbt

@@ -1,4 +1,4 @@
-fn my_sqrt<D>(x: D^2) -> D = x^(1/2)
+fn my_sqrt<D: Dim>(x: D^2) -> D = x^(1/2)
 
 let x: Length = my_sqrt(4 meter²)
 let y: Scalar = my_sqrt(100)

+ 1 - 1
examples/numbat_syntax.nbt

@@ -64,7 +64,7 @@ let q3: Length / Time = 2 m/s      # more complex type annotation
 
 fn foo(z: Scalar) -> Scalar = 2 * z + 3                   # A simple function
 fn speed(len: Length, dur: Time) -> Velocity = len / dur  # Two parameters
-fn my_sqrt<T>(q: T^2) -> T = q^(1/2)                      # A generic function
+fn my_sqrt<T: Dim>(q: T^2) -> T = q^(1/2)                 # A generic function
 fn is_non_negative(x: Scalar) -> Bool = x ≥ 0             # Returns a bool
 
 # 6. Dimension definitions

+ 1 - 1
examples/numerical_diff.nbt

@@ -1,6 +1,6 @@
 let eps = 1e-10
 
-fn diff<X, Y>(f: Fn[(X) -> Y], x: X) -> Y / X =
+fn diff<X: Dim, Y: Dim>(f: Fn[(X) -> Y], x: X) -> Y / X =
   (f(x + eps · unit_of(x)) - f(x)) / (eps · unit_of(x))
 
 assert_eq(diff(log, 2.0), 0.5, 1e-5)

+ 1 - 1
examples/recipe.nbt

@@ -6,7 +6,7 @@ unit serving
 let original_recipe_servings = 2 servings
 let desired_servings = 3 servings
 
-fn scale<D>(quantity: D) -> D =
+fn scale(quantity) =
     quantity × desired_servings / original_recipe_servings
 
 print("Milk:          {scale(500 ml)}")

+ 5 - 5
numbat/modules/core/functions.nbt

@@ -1,20 +1,20 @@
 @name("Absolute value")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.abs")
-fn abs<T>(x: T) -> T
+fn abs<T: Dim>(x: T) -> T
 
 @name("Round")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.round")
 @description("Round to the nearest integer.")
-fn round<T>(x: T) -> T
+fn round<T: Dim>(x: T) -> T
 
 @name("Floor function")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.floor")
-fn floor<T>(x: T) -> T
+fn floor<T: Dim>(x: T) -> T
 
 @name("Ceil function")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.ceil")
-fn ceil<T>(x: T) -> T
+fn ceil<T: Dim>(x: T) -> T
 
 @name("Modulo")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid")
-fn mod<T>(a: T, b: T) -> T
+fn mod<T: Dim>(a: T, b: T) -> T

+ 1 - 1
numbat/modules/core/lists.nbt

@@ -69,4 +69,4 @@ fn intersperse<A>(sep: A, xs: List<A>) -> List<A> =
 
 fn _add(x, y) = x + y # TODO: replace this with a local function once we support them
 @description("Sum all elements of a list")
-fn sum<A>(xs: List<A>) -> A = foldl(_add, 0, xs)
+fn sum<D: Dim>(xs: List<D>) -> D = foldl(_add, 0, xs)

+ 4 - 4
numbat/modules/core/quantities.nbt

@@ -1,7 +1,7 @@
 use core::scalar
 
-fn unit_of<T>(x: T) -> T
-fn value_of<T>(x: T) -> Scalar = x / unit_of(x)
+fn unit_of<T: Dim>(x: T) -> T
+fn value_of<T: Dim>(x: T) -> Scalar = x / unit_of(x)
 
-fn is_nan<T>(n: T) -> Bool
-fn is_infinite<T>(n: T) -> Bool
+fn is_nan<T: Dim>(n: T) -> Bool
+fn is_infinite<T: Dim>(n: T) -> Bool

+ 2 - 2
numbat/modules/extra/algebra.nbt

@@ -1,9 +1,9 @@
 use math::functions
 
-fn _qe_solution<A, B>(a: A, b: B, c: B²/A, sign: Scalar) -> B/A =
+fn _qe_solution<A: Dim, B: Dim>(a: A, b: B, c: B²/A, sign: Scalar) -> B/A =
   (-b + sign × sqrt(b² - 4 a c)) / 2 a
 
-fn quadratic_equation<A2, B2>(a: A2, b: B2, c: B2²/A2) -> String =
+fn quadratic_equation<A: Dim, B: Dim>(a: A, b: B, c: B²/A) -> String =
   if a == 0
     then if b == 0 then if c == 0 then "infinitely many solutions" else "no solution" else "x = {-c / b}"
     else if b² < 4 a c then "no real-valued solution" else if b² == 4 a c then "x = {-b / 2 a}" else "x₁ = {_qe_solution(a, b, c, 1)}; x₂ = {_qe_solution(a, b, c, -1)}"

+ 9 - 9
numbat/modules/math/functions.nbt

@@ -5,10 +5,10 @@ use math::constants
 
 @name("Square root")
 @url("https://en.wikipedia.org/wiki/Square_root")
-fn sqrt<D>(x: D^2) -> D = x^(1/2)
+fn sqrt<D: Dim>(x: D^2) -> D = x^(1/2)
 
 @name("Square function")
-fn sqr<D>(x: D) -> D^2 = x^2
+fn sqr<D: Dim>(x: D) -> D^2 = x^2
 
 ## Exponential and logarithm
 
@@ -59,7 +59,7 @@ fn acos(x: Scalar) -> Scalar
 fn atan(x: Scalar) -> Scalar
 
 @url("https://en.wikipedia.org/wiki/Atan2")
-fn atan2<T>(y: T, x: T) -> Scalar
+fn atan2<T: Dim>(y: T, x: T) -> Scalar
 
 @name("Hyperbolic sine")
 @url("https://en.wikipedia.org/wiki/Hyperbolic_functions")
@@ -104,14 +104,14 @@ fn gamma(x: Scalar) -> Scalar
 
 ### Geometry
 
-fn hypot2<T>(x: T, y: T) -> T = sqrt(x^2 + y^2)
-fn hypot3<T>(x: T, y: T, z: T) -> T = sqrt(x^2 + y^2 + z^2)
+fn hypot2<T: Dim>(x: T, y: T) -> T = sqrt(x^2 + y^2)
+fn hypot3<T: Dim>(x: T, y: T, z: T) -> T = sqrt(x^2 + y^2 + z^2)
 
 # The following functions use a generic dimension instead of
 # 'Length' in order to allow for computations in pixels, for
 # example
 
-fn circle_area<L>(radius: L) -> L^2 = π × radius^2
-fn circle_circumference<L>(radius: L) -> L = 2 π × radius
-fn sphere_area<L>(radius: L) -> L^2 = 4 π × radius^2
-fn sphere_volume<L>(radius: L) -> L^3 = 4/3 × π × radius^3
+fn circle_area<L: Dim>(radius: L) -> L^2 = π × radius^2
+fn circle_circumference<L: Dim>(radius: L) -> L = 2 π × radius
+fn sphere_area<L: Dim>(radius: L) -> L^2 = 4 π × radius^2
+fn sphere_volume<L: Dim>(radius: L) -> L^3 = 4/3 × π × radius^3

+ 4 - 4
numbat/modules/math/statistics.nbt

@@ -8,7 +8,7 @@ use math::functions
 @name("Continuous uniform distribution sampling")
 @url("https://en.wikipedia.org/wiki/Continuous_uniform_distribution")
 @description("Uniformly samples the interval [a,b) if a<=b or [b,a) if b<a using inversion sampling.")
-fn rand_uniform<T>(a: T, b: T) -> T =
+fn rand_uniform<T: Dim>(a: T, b: T) -> T =
     if a <= b
     then random() * (b - a) + a
     else random() * (a - b) + b
@@ -46,7 +46,7 @@ fn rand_binom(n: Scalar, p: Scalar) -> Scalar =
 @name("Normal distribution sampling")
 @url("https://en.wikipedia.org/wiki/Normal_distribution")
 @description("Samples a normal distribution with mean μ and standard deviation σ using the Box-Muller transform.")
-fn rand_norm<T>(μ: T, σ: T) -> T =
+fn rand_norm<T: Dim>(μ: T, σ: T) -> T =
     μ + sqrt(-2 σ² × ln(random())) × sin(2π × random())
 
 @name("Geometric distribution sampling")
@@ -78,7 +78,7 @@ fn rand_poisson(λ: Scalar) -> Scalar =
 @url("https://en.wikipedia.org/wiki/Exponential_distribution")
 @description("Sampling an exponential distribution (the distribution of the distance between events in a Poisson process with rate λ) using inversion sampling.
               The rate parameter λ must be positive.")
-fn rand_expon<T>(λ: T) -> 1/T =
+fn rand_expon<T: Dim>(λ: T) -> 1/T =
     if value_of(λ) > 0
     then - ln(1-random()) / λ
     else error("Argument λ must be positive.")
@@ -93,7 +93,7 @@ fn rand_lognorm(μ: Scalar, σ: Scalar) -> Scalar =
 @url("https://en.wikipedia.org/wiki/Pareto_distribution")
 @description("Sampling a Pareto distribution with minimum value min and shape parameter α using inversion sampling.
               Both parameters α and min must be positive.")
-fn rand_pareto<T>(α: Scalar, min: T) -> T =
+fn rand_pareto<T: Dim>(α: Scalar, min: T) -> T =
     if value_of(min) > 0 && α > 0
     then min / ((1-random())^(1/α))
     else error("Both arguments α and min must be positive.")

+ 7 - 0
numbat/src/diagnostic.rs

@@ -460,6 +460,13 @@ impl ErrorDiagnostic for TypeCheckError {
             TypeCheckError::ConstraintSolverError(_) | TypeCheckError::SubstitutionError(_) => {
                 d.with_message(inner_error) // TODO
             }
+            TypeCheckError::MissingDimBound(span) => d
+                .with_labels(vec![span
+                    .diagnostic_label(LabelStyle::Primary)
+                    .with_message(inner_error)])
+                .with_notes(vec![
+                    "Consider adding `: Dim` after the type parameter".to_owned()
+                ]),
         };
         vec![d]
     }

+ 8 - 3
numbat/src/dimension.rs

@@ -1,12 +1,13 @@
 use crate::arithmetic::{Exponent, Power};
-use crate::ast::TypeExpression;
+use crate::ast::{TypeExpression, TypeParameterBound};
 use crate::registry::{BaseRepresentation, Registry, Result};
+use crate::span::Span;
 use crate::BaseRepresentationFactor;
 
 #[derive(Default, Clone)]
 pub struct DimensionRegistry {
     registry: Registry<()>,
-    pub introduced_type_parameters: Vec<String>,
+    pub introduced_type_parameters: Vec<(Span, String, Option<TypeParameterBound>)>,
 }
 
 impl DimensionRegistry {
@@ -17,7 +18,11 @@ impl DimensionRegistry {
         match expression {
             TypeExpression::Unity(_) => Ok(BaseRepresentation::unity()),
             TypeExpression::TypeIdentifier(_, name) => {
-                if self.introduced_type_parameters.contains(name) {
+                if self
+                    .introduced_type_parameters
+                    .iter()
+                    .any(|(_, n, _)| n == name)
+                {
                     Ok(BaseRepresentation::from_factor(BaseRepresentationFactor(
                         name.clone(),
                         Exponent::from_integer(1),

+ 4 - 16
numbat/src/typechecker/constraints.rs

@@ -216,9 +216,7 @@ impl Constraint {
                 Type::Dimension(_) => TrivialResultion::Satisfied,
                 _ => TrivialResultion::Violated,
             },
-            Constraint::IsDType(Type::Dimension(_)) => TrivialResultion::Satisfied,
-            Constraint::IsDType(Type::TVar(_)) => TrivialResultion::Unknown,
-            Constraint::IsDType(_) => TrivialResultion::Violated,
+            Constraint::IsDType(_) => TrivialResultion::Unknown,
             Constraint::EqualScalar(d) if d.is_scalar() => TrivialResultion::Satisfied,
             Constraint::EqualScalar(d) if d.type_variables().is_empty() => {
                 TrivialResultion::Violated
@@ -234,7 +232,9 @@ impl Constraint {
                 debug!("  (1) SOLVING: {} ~ {} trivially ", t1, t2);
                 Some(Satisfied::trivially())
             }
-            Constraint::Equal(Type::TVar(x), t) if !t.contains(x) => {
+            Constraint::Equal(Type::TVar(x), t) | Constraint::Equal(t, Type::TVar(x))
+                if !t.contains(x) =>
+            {
                 debug!(
                     "  (2) SOLVING: {x} ~ {t} with substitution {x} := {t}",
                     x = x.unsafe_name(),
@@ -245,18 +245,6 @@ impl Constraint {
                     t.clone(),
                 )))
             }
-            Constraint::Equal(s, Type::TVar(x)) if !s.contains(x) => {
-                // TODO: merge with branch above
-                debug!(
-                    "  (3) SOLVING: {s} ~ {x} with substitution {x} := {s}",
-                    s = s,
-                    x = x.unsafe_name()
-                );
-                Some(Satisfied::with_substitution(Substitution::single(
-                    x.clone(),
-                    s.clone(),
-                )))
-            }
             Constraint::Equal(t @ Type::Fn(params1, return1), s @ Type::Fn(params2, return2)) => {
                 debug!(
                     "  (4) SOLVING: {t} ~ {s} with new constraints for all parameters and return types",

+ 3 - 0
numbat/src/typechecker/error.rs

@@ -140,6 +140,9 @@ pub enum TypeCheckError {
 
     #[error(transparent)]
     SubstitutionError(#[from] SubstitutionError),
+
+    #[error("Missing dimension bound for type parameter")]
+    MissingDimBound(Span),
 }
 
 pub type Result<T> = std::result::Result<T, TypeCheckError>;

+ 38 - 20
numbat/src/typechecker/mod.rs

@@ -15,7 +15,10 @@ use std::collections::HashMap;
 use std::ops::Deref;
 
 use crate::arithmetic::Exponent;
-use crate::ast::{self, BinaryOperator, ProcedureKind, StringPart, TypeAnnotation, TypeExpression};
+use crate::ast::{
+    self, BinaryOperator, ProcedureKind, StringPart, TypeAnnotation, TypeExpression,
+    TypeParameterBound,
+};
 use crate::dimension::DimensionRegistry;
 use crate::name_resolution::Namespace;
 use crate::name_resolution::LAST_RESULT_IDENTIFIERS;
@@ -108,9 +111,13 @@ impl TypeChecker {
                 for (factor, _) in dtype.factors.iter_mut() {
                     *factor = match factor {
                         DTypeFactor::BaseDimension(ref n)
-                            if self.registry.introduced_type_parameters.contains(n) =>
+                            if self
+                                .registry
+                                .introduced_type_parameters
+                                .iter()
+                                .any(|(_, name, _)| name == n) =>
                         {
-                            DTypeFactor::TVar(TypeVariable::new(n))
+                            DTypeFactor::TPar(n.clone())
                         }
                         ref f => f.deref().clone(),
                     }
@@ -1269,7 +1276,7 @@ impl TypeChecker {
                 let mut typechecker_fn = self.clone(); // TODO: is this even needed?
                 let is_ffi_function = body.is_none();
 
-                for (span, type_parameter, _bound) in type_parameters {
+                for (span, type_parameter, bound) in type_parameters {
                     if typechecker_fn.type_namespace.has_identifier(type_parameter) {
                         return Err(TypeCheckError::TypeParameterNameClash(
                             *span,
@@ -1282,10 +1289,11 @@ impl TypeChecker {
                         .add_identifier(type_parameter.clone(), *span, "type parameter".to_owned())
                         .ok(); // TODO: is this call even correct?
 
-                    typechecker_fn
-                        .registry
-                        .introduced_type_parameters
-                        .push(type_parameter.clone());
+                    typechecker_fn.registry.introduced_type_parameters.push((
+                        *span,
+                        type_parameter.clone(),
+                        bound.clone(),
+                    ));
                 }
 
                 let mut typed_parameters = vec![];
@@ -1296,17 +1304,7 @@ impl TypeChecker {
                         .transpose()?;
 
                     let parameter_type = match &annotated_type {
-                        Some(annotated_type) if annotated_type.is_closed() => {
-                            annotated_type.clone()
-                        }
-                        Some(annotated_type) => {
-                            // TODO: is this the right way to handle type annotations with generics?
-                            let parameter_type = typechecker_fn.fresh_type_variable();
-                            typechecker_fn
-                                .add_equal_constraint(&parameter_type, &annotated_type)
-                                .ok();
-                            parameter_type
-                        }
+                        Some(annotated_type) => annotated_type.clone(),
                         None => typechecker_fn.fresh_type_variable(),
                     };
 
@@ -1355,7 +1353,6 @@ impl TypeChecker {
                     .iter()
                     .map(|(_, _, type_)| type_.clone())
                     .collect();
-                // let return_type = typechecker_fn.fresh_type_variable();
 
                 let fn_type =
                     TypeScheme::Concrete(Type::Fn(parameter_types, Box::new(return_type.clone())));
@@ -1459,6 +1456,7 @@ impl TypeChecker {
                 self.constraints = typechecker_fn.constraints;
                 self.name_generator = typechecker_fn.name_generator;
                 self.env = typechecker_fn.env;
+                self.registry = typechecker_fn.registry;
                 // self.type_namespace = typechecker_fn.type_namespace;
                 // self.value_namespace = typechecker_fn.value_namespace;
 
@@ -1653,6 +1651,7 @@ impl TypeChecker {
 
     fn check_statement(&mut self, statement: &ast::Statement) -> Result<typed_ast::Statement> {
         self.constraints.clear();
+        self.registry.introduced_type_parameters.clear();
 
         // Elaborate the program/statement: turn the AST into a typed AST, possibly
         // with "holes" inside, i.e. type variables that will only later be filled
@@ -1680,6 +1679,25 @@ impl TypeChecker {
 
         self.env.apply(&substitution)?;
 
+        // Make sure that the user-specified type parameter bounds are properly reflected:
+        for (span, type_parameter, bound) in &self.registry.introduced_type_parameters {
+            match bound {
+                Some(TypeParameterBound::Dim) => {
+                    // The type parameter might be over-constrained, but that's okay
+                }
+                None => {
+                    // Make sure that the type parameter is not part of dtype_variables.
+                    // Otherwise, a `Dim` bound is missing.
+                    if dtype_variables.iter().any(|tv| match tv {
+                        TypeVariable::Named(name) => name == type_parameter,
+                        _ => false,
+                    }) {
+                        return Err(TypeCheckError::MissingDimBound(*span));
+                    }
+                }
+            }
+        }
+
         // For all dimension type variables that are still free, check all of their occurences
         // within type_, and then multiply the corresponding exponents with the least common
         // multiple of the denominators of the exponents. For example, this will turn

+ 26 - 1
numbat/src/typechecker/substitutions.rs

@@ -55,6 +55,12 @@ impl ApplySubstitution for Type {
                 }
                 Ok(())
             }
+            Type::TPar(n) => {
+                if let Some(type_) = s.lookup(&TypeVariable::new(n)) {
+                    *self = type_.clone();
+                }
+                Ok(())
+            }
             Type::Dimension(dtype) => dtype.apply(s),
             Type::Boolean => Ok(()),
             Type::String => Ok(()),
@@ -98,7 +104,26 @@ impl ApplySubstitution for DType {
                         new_dtype = new_dtype.multiply(&dtype.power(*power));
                     }
                 }
-                _ => {}
+                DTypeFactor::TPar(name) => {
+                    let tv = TypeVariable::new(name);
+
+                    if let Some(type_) = substitution.lookup(&tv) {
+                        let dtype = match type_ {
+                            Type::Dimension(dt) => dt.clone(),
+                            Type::TVar(tv) => DType::from_type_variable(tv.clone()),
+                            t => {
+                                return Err(SubstitutionError::SubstitutedNonDTypeWithinDType(
+                                    t.clone(),
+                                ));
+                            }
+                        };
+
+                        new_dtype = new_dtype
+                            .divide(&DType::from_type_parameter(name.clone()).power(*power));
+                        new_dtype = new_dtype.multiply(&dtype.power(*power));
+                    }
+                }
+                DTypeFactor::BaseDimension(_) => {}
             }
         }
 

+ 3 - 3
numbat/src/typechecker/tests.rs

@@ -250,21 +250,21 @@ fn generics_basic() {
     );
     assert_successful_typecheck(
         "
-            fn f<D>(x: D) -> D^2 = x*x
+            fn f<D: Dim>(x: D) -> D^2 = x*x
             f(2)
             f(2 a)
             ",
     );
     assert_successful_typecheck(
         "
-            fn f<D0, D1>(x: D0, y: D1) -> D0/D1^2 = x/y^2
+            fn f<D0: Dim, D1: Dim>(x: D0, y: D1) -> D0/D1^2 = x/y^2
             f(2, 3)
             f(2 a, 2 b)
             ",
     );
     assert_successful_typecheck(
         "
-        fn f3<T>(y: T, x: T) = atan2(y, x)
+        fn f3<T: Dim>(y: T, x: T) = atan2(y, x)
         ",
     );
 

+ 29 - 2
numbat/src/typed_ast.rs

@@ -18,6 +18,7 @@ use crate::{markup as m, BaseRepresentation, BaseRepresentationFactor};
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum DTypeFactor {
     TVar(TypeVariable),
+    TPar(String),
     BaseDimension(String),
 }
 
@@ -26,6 +27,7 @@ impl DTypeFactor {
         match self {
             DTypeFactor::TVar(TypeVariable::Named(name)) => name,
             DTypeFactor::TVar(TypeVariable::Quantified(_)) => unreachable!(),
+            DTypeFactor::TPar(name) => name,
             DTypeFactor::BaseDimension(name) => name,
         }
     }
@@ -88,6 +90,10 @@ impl DType {
         DType::from_factors(&[(DTypeFactor::TVar(v), Exponent::from_integer(1))])
     }
 
+    pub fn from_type_parameter(name: String) -> DType {
+        DType::from_factors(&[(DTypeFactor::TPar(name), Exponent::from_integer(1))])
+    }
+
     pub fn from_tgen(i: usize) -> DType {
         DType::from_factors(&[(
             DTypeFactor::TVar(TypeVariable::Quantified(i)),
@@ -133,6 +139,13 @@ impl DType {
                         format!("$tgen{}^{}", i, n)
                     }
                 }
+                DTypeFactor::TPar(name) => {
+                    if *n == Exponent::from_integer(1) {
+                        name.to_owned()
+                    } else {
+                        format!("{}^{}", name, n)
+                    }
+                }
             })
             .collect::<Vec<String>>()
             .join(" × ");
@@ -144,8 +157,13 @@ impl DType {
         self.factors.sort_by(|(f1, _), (f2, _)| match (f1, f2) {
             (DTypeFactor::TVar(v1), DTypeFactor::TVar(v2)) => v1.cmp(v2),
             (DTypeFactor::TVar(_), _) => std::cmp::Ordering::Less,
+
             (DTypeFactor::BaseDimension(d1), DTypeFactor::BaseDimension(d2)) => d1.cmp(d2),
-            (DTypeFactor::BaseDimension(_), _) => std::cmp::Ordering::Greater,
+            (DTypeFactor::BaseDimension(_), DTypeFactor::TVar(_)) => std::cmp::Ordering::Greater,
+            (DTypeFactor::BaseDimension(_), DTypeFactor::TPar(_)) => std::cmp::Ordering::Less,
+
+            (DTypeFactor::TPar(p1), DTypeFactor::TPar(p2)) => p1.cmp(p2),
+            (DTypeFactor::TPar(_), _) => std::cmp::Ordering::Greater,
         });
 
         // Merge powers of equal factors:
@@ -195,7 +213,8 @@ impl DType {
             .iter()
             .filter_map(|(f, _)| match f {
                 DTypeFactor::TVar(v) => Some(v.clone()),
-                _ => None,
+                DTypeFactor::TPar(v) => Some(TypeVariable::new(v)),
+                DTypeFactor::BaseDimension(_) => None,
             })
             .collect();
         vars.sort();
@@ -242,6 +261,9 @@ impl DType {
                 DTypeFactor::TVar(TypeVariable::Quantified(_)) => {
                     unreachable!("Unexpected quantified type")
                 }
+                DTypeFactor::TPar(name) => {
+                    factors.push(BaseRepresentationFactor(name.clone(), n.clone()));
+                }
             }
         }
         BaseRepresentation::from_factors(factors)
@@ -280,6 +302,7 @@ pub struct StructInfo {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum Type {
     TVar(TypeVariable),
+    TPar(String),
     Dimension(DType),
     Boolean,
     String,
@@ -296,6 +319,7 @@ impl std::fmt::Display for Type {
             Type::TVar(TypeVariable::Quantified(_)) => {
                 unreachable!("Quantified types should not be printed")
             }
+            Type::TPar(name) => write!(f, "{}", name),
             Type::Dimension(d) => d.fmt(f),
             Type::Boolean => write!(f, "Bool"),
             Type::String => write!(f, "String"),
@@ -329,6 +353,7 @@ impl PrettyPrint for Type {
             Type::TVar(TypeVariable::Quantified(_)) => {
                 unreachable!("Quantified types should not be printed")
             }
+            Type::TPar(name) => m::type_identifier(name),
             Type::Dimension(d) => d.pretty_print(),
             Type::Boolean => m::type_identifier("Bool"),
             Type::String => m::type_identifier("String"),
@@ -382,6 +407,7 @@ impl Type {
     pub(crate) fn type_variables(&self) -> Vec<TypeVariable> {
         match self {
             Type::TVar(v) => vec![v.clone()],
+            Type::TPar(n) => vec![TypeVariable::new(n)],
             Type::Dimension(d) => d.type_variables(),
             Type::Boolean | Type::String | Type::DateTime => vec![],
             Type::Fn(param_types, return_type) => {
@@ -417,6 +443,7 @@ impl Type {
         match self {
             Type::TVar(TypeVariable::Quantified(i)) => Type::TVar(type_variables[*i].clone()),
             Type::TVar(v) => Type::TVar(v.clone()),
+            Type::TPar(n) => Type::TPar(n.clone()),
             Type::Dimension(d) => Type::Dimension(d.instantiate(type_variables)),
             Type::Boolean | Type::String | Type::DateTime => self.clone(),
             Type::Fn(param_types, return_type) => Type::Fn(