Skip to content

Commit

Permalink
Add class constraints for built-in classes (#2007)
Browse files Browse the repository at this point in the history
This PR exposes some relevant built-in classes via class constraints.

# Changes
- Add `TypeParameter`, `ClassConstraint`, and `ConstraintParameter` AST
nodes to represent the notion of a generic type that might have class
constraints, and constraints that may have parameters (e.g. `Exp[Int]`).
  -  update visitors accordingly
- `Ty` can now contain arbitrary constraints
- Add notion of primitive classes to the AST
- Update constraint checking algorithm to check type parameters for
primitive constraints at callable decl time
- Update constraint generation algorithm to add additional
user-specified constraints to arguments that are passed in to callables
(`fn constrained_ty`)
- Add completions for primitive classes to the language service
- Adds a sample describing class constraints

tiny changes:
- Add docstrings discretionally
- Remove some unnecessary derives
- Tried to clarify and unify jargon around classes, constraints,
parameters, and arguments in general
- Some expect_tests were updated in minor ways as their underlying types
changed (`Ty` and `TyParam`, for example)
- Improve an error message for `MissingTy` (it mentioned something about
global types not being inferred when 99% of the time it does not apply
to a global type, it applies to a callable parameter with a missing
type)
  • Loading branch information
sezna authored Nov 14, 2024
1 parent a6e4b32 commit 97385cd
Show file tree
Hide file tree
Showing 41 changed files with 2,725 additions and 381 deletions.
146 changes: 143 additions & 3 deletions compiler/qsc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ pub struct CallableDecl {
/// The name of the callable.
pub name: Box<Ident>,
/// The generic parameters to the callable.
pub generics: Box<[Box<Ident>]>,
pub generics: Box<[TypeParameter]>,
/// The input to the callable.
pub input: Box<Pat>,
/// The return type of the callable.
Expand All @@ -510,9 +510,13 @@ impl Display for CallableDecl {
if !self.generics.is_empty() {
write!(indent, "\ngenerics:")?;
indent = set_indentation(indent, 2);
let mut buf = Vec::with_capacity(self.generics.len());
for param in &self.generics {
write!(indent, "\n{param}")?;
buf.push(format!("{param}"));
}

let buf = buf.join(",\n");
write!(indent, "\n{buf}")?;
indent = set_indentation(indent, 1);
}
write!(indent, "\ninput: {}", self.input)?;
Expand Down Expand Up @@ -674,7 +678,7 @@ pub enum TyKind {
/// A named type.
Path(PathKind),
/// A type parameter.
Param(Box<Ident>),
Param(TypeParameter),
/// A tuple type.
Tuple(Box<[Ty]>),
/// An invalid type.
Expand Down Expand Up @@ -1968,3 +1972,139 @@ impl ImportOrExportItem {
}
}
}

/// A [`TypeParameter`] is a generic type variable with optional bounds (constraints).
#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
pub struct TypeParameter {
/// Class constraints specified for this type parameter -- any type variable passed in
/// as an argument to these parameters must satisfy these constraints.
pub constraints: ClassConstraints,
/// The name of the type parameter.
pub ty: Ident,
/// The span of the full type parameter, including its name and its constraints.
pub span: Span,
}

impl WithSpan for TypeParameter {
fn with_span(self, span: Span) -> Self {
Self { span, ..self }
}
}

impl TypeParameter {
/// Instantiates a new `TypeParameter` with the given type name, constraints, and span.
#[must_use]
pub fn new(ty: Ident, bounds: ClassConstraints, span: Span) -> Self {
Self {
ty,
constraints: bounds,
span,
}
}
}

impl std::fmt::Display for TypeParameter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// 'A: Eq + Ord + Clone
write!(
f,
"{}{}",
self.ty.name,
if self.constraints.0.is_empty() {
Default::default()
} else {
format!(": {}", self.constraints)
}
)
}
}

/// A list of class constraints, used when constraining a type parameter.
#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
pub struct ClassConstraints(pub Box<[ClassConstraint]>);

/// An individual class constraint, used when constraining a type parameter.
/// To understand this concept, think of parameters in a function signature -- the potential arguments that can
/// be passed to them are constrained by what type is specified. Type-level parameters are no different, and
/// the type variables that are passed to a type parameter must satisfy the constraints specified in the type parameter.
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub struct ClassConstraint {
/// The name of the constraint.
pub name: Ident,
/// Parameters for a constraint. For example, `Iterator` has a parameter `T` in `Iterator<T>` -- this
/// is the type of the item that is coming out of the iterator.
pub parameters: Box<[ConstraintParameter]>,
}

impl std::fmt::Display for ClassConstraint {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// Iterator<T>
write!(
f,
"{}{}",
self.name.name,
if self.parameters.is_empty() {
String::new()
} else {
format!(
"[{}]",
self.parameters
.iter()
.map(|x| x.ty.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
)
}
}

/// An individual constraint parameter is a type that is passed to a constraint, such as `T` in `Iterator<T>`.
/// #[derive(Default, `PartialEq`, Eq, Clone, Hash, Debug)]
#[derive(Default, PartialEq, Eq, Clone, Hash, Debug)]
pub struct ConstraintParameter {
/// The type variable being passed as a constraint parameter.
pub ty: Ty,
}

impl WithSpan for ConstraintParameter {
fn with_span(self, span: Span) -> Self {
Self {
ty: self.ty.with_span(span),
}
}
}

impl ClassConstraint {
/// Getter for the `span` field of the `name` field (the name of the class constraint).
#[must_use]
pub fn span(&self) -> Span {
self.name.span
}
}

impl ClassConstraints {
/// The conjoined span of all of the bounds
#[must_use]
pub fn span(&self) -> Span {
Span {
lo: self.0.first().map(|i| i.span().lo).unwrap_or_default(),
hi: self.0.last().map(|i| i.span().hi).unwrap_or_default(),
}
}
}

impl std::fmt::Display for ClassConstraints {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// A + B + C + D
write!(
f,
"{}",
self.0
.iter()
.map(|x| format!("{}", x.name.name,))
.collect::<Vec<_>>()
.join(" + "),
)
}
}
30 changes: 27 additions & 3 deletions compiler/qsc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ast::{
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, TypeParameter,
};
use qsc_data_structures::span::Span;

Expand Down Expand Up @@ -164,7 +164,17 @@ pub fn walk_ty_def(vis: &mut impl MutVisitor, def: &mut TyDef) {
pub fn walk_callable_decl(vis: &mut impl MutVisitor, decl: &mut CallableDecl) {
vis.visit_span(&mut decl.span);
vis.visit_ident(&mut decl.name);
decl.generics.iter_mut().for_each(|p| vis.visit_ident(p));
decl.generics.iter_mut().for_each(|p| {
vis.visit_ident(&mut p.ty);
p.constraints.0.iter_mut().for_each(|b| {
vis.visit_ident(&mut b.name);
b.parameters
.iter_mut()
.for_each(|crate::ast::ConstraintParameter { ty, .. }| {
vis.visit_ty(ty);
});
});
});
vis.visit_pat(&mut decl.input);
vis.visit_ty(&mut decl.output);
decl.functors
Expand Down Expand Up @@ -226,7 +236,21 @@ pub fn walk_ty(vis: &mut impl MutVisitor, ty: &mut Ty) {
}
TyKind::Hole | TyKind::Err => {}
TyKind::Paren(ty) => vis.visit_ty(ty),
TyKind::Param(name) => vis.visit_ident(name),
TyKind::Param(TypeParameter {
ty,
constraints: bounds,
..
}) => {
for bound in &mut bounds.0 {
vis.visit_ident(&mut bound.name);
bound.parameters.iter_mut().for_each(
|crate::ast::ConstraintParameter { ref mut ty, .. }| {
vis.visit_ty(ty);
},
);
}
vis.visit_ident(ty);
}
TyKind::Path(path) => vis.visit_path_kind(path),
TyKind::Tuple(tys) => tys.iter_mut().for_each(|t| vis.visit_ty(t)),
}
Expand Down
31 changes: 28 additions & 3 deletions compiler/qsc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ast::{
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, TypeParameter,
};

pub trait Visitor<'a>: Sized {
Expand Down Expand Up @@ -149,7 +149,17 @@ pub fn walk_ty_def<'a>(vis: &mut impl Visitor<'a>, def: &'a TyDef) {

pub fn walk_callable_decl<'a>(vis: &mut impl Visitor<'a>, decl: &'a CallableDecl) {
vis.visit_ident(&decl.name);
decl.generics.iter().for_each(|p| vis.visit_ident(p));
decl.generics.iter().for_each(|p| {
vis.visit_ident(&p.ty);
p.constraints.0.iter().for_each(|b| {
vis.visit_ident(&b.name);
b.parameters
.iter()
.for_each(|crate::ast::ConstraintParameter { ty, .. }| {
vis.visit_ty(ty);
});
});
});
vis.visit_pat(&decl.input);
vis.visit_ty(&decl.output);
decl.functors.iter().for_each(|f| vis.visit_functor_expr(f));
Expand Down Expand Up @@ -201,7 +211,22 @@ pub fn walk_ty<'a>(vis: &mut impl Visitor<'a>, ty: &'a Ty) {
TyKind::Hole | TyKind::Err => {}
TyKind::Paren(ty) => vis.visit_ty(ty),
TyKind::Path(path) => vis.visit_path_kind(path),
TyKind::Param(name) => vis.visit_ident(name),
TyKind::Param(TypeParameter {
ty,
constraints: bounds,
..
}) => {
for bound in &bounds.0 {
vis.visit_ident(&bound.name);

bound.parameters.iter().for_each(
|crate::ast::ConstraintParameter { ty, .. }| {
vis.visit_ty(ty);
},
);
}
vis.visit_ident(ty);
}
TyKind::Tuple(tys) => tys.iter().for_each(|t| vis.visit_ty(t)),
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/qsc_codegen/src/qsharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,10 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
self.write("<");
if let Some((last, most)) = decl.generics.split_last() {
for i in most {
self.visit_ident(i);
self.visit_ident(&i.ty);
self.write(", ");
}
self.visit_ident(last);
self.visit_ident(&last.ty);
}

self.write(">");
Expand Down Expand Up @@ -349,7 +349,7 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
self.write(")");
}
TyKind::Path(path) => self.visit_path_kind(path),
TyKind::Param(name) => self.visit_ident(name),
TyKind::Param(name) => self.visit_ident(&name.ty),
TyKind::Tuple(tys) => {
if tys.is_empty() {
self.write("()");
Expand Down
60 changes: 53 additions & 7 deletions compiler/qsc_doc_gen/src/display.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use qsc_ast::ast::{self, Idents};
use qsc_ast::ast::{self, Idents, TypeParameter as AstTypeParameter};
use qsc_frontend::resolve;
use qsc_hir::{
hir::{self, PackageId},
ty::{self, GenericParam},
ty::{self, TypeParameter as HirTypeParameter},
};
use regex_lite::Regex;
use std::{
Expand Down Expand Up @@ -218,7 +218,45 @@ impl<'a> Display for AstCallableDecl<'a> {
.decl
.generics
.iter()
.map(|p| p.name.clone())
.map(
|AstTypeParameter {
ty, constraints, ..
}| {
format!(
"{}{}",
ty.name,
if constraints.0.is_empty() {
Default::default()
} else {
format!(
": {}",
constraints
.0
.iter()
.map(|bound| {
let constraint_parameters = bound
.parameters
.iter()
.map(|x| format!("{}", AstTy { ty: &x.ty }))
.collect::<Vec<_>>()
.join(", ");
format!(
"{}{}",
bound.name.name,
if constraint_parameters.is_empty() {
Default::default()
} else {
format!("[{constraint_parameters}]")
}
)
})
.collect::<Vec<_>>()
.join(" + ")
)
}
)
},
)
.collect::<Vec<_>>()
.join(", ");
write!(f, "<{type_params}>")?;
Expand Down Expand Up @@ -517,7 +555,7 @@ impl<'a> Display for AstTy<'a> {
ast::TyKind::Hole => write!(f, "_"),
ast::TyKind::Paren(ty) => write!(f, "{}", AstTy { ty }),
ast::TyKind::Path(path) => write!(f, "{}", AstPathKind { path }),
ast::TyKind::Param(id) => write!(f, "{}", id.name),
ast::TyKind::Param(AstTypeParameter { ty, .. }) => write!(f, "{}", ty.name),
ast::TyKind::Tuple(tys) => fmt_tuple(f, tys, |ty| AstTy { ty }),
ast::TyKind::Err => write!(f, "?"),
}
Expand Down Expand Up @@ -615,12 +653,20 @@ where
write!(formatter, "}}")
}

fn display_type_params(generics: &[GenericParam]) -> String {
fn display_type_params(generics: &[HirTypeParameter]) -> String {
let type_params = generics
.iter()
.filter_map(|generic| match generic {
GenericParam::Ty(name) => Some(name.name.clone()),
GenericParam::Functor(_) => None,
HirTypeParameter::Ty { name, bounds } => Some(format!(
"{}{}",
name,
if bounds.is_empty() {
Default::default()
} else {
format!(": {bounds}")
}
)),
HirTypeParameter::Functor(_) => None,
})
.collect::<Vec<_>>()
.join(", ");
Expand Down
Loading

0 comments on commit 97385cd

Please sign in to comment.