Skip to content

Commit 97385cd

Browse files
authored
Add class constraints for built-in classes (#2007)
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)
1 parent a6e4b32 commit 97385cd

File tree

41 files changed

+2725
-381
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2725
-381
lines changed

compiler/qsc_ast/src/ast.rs

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ pub struct CallableDecl {
486486
/// The name of the callable.
487487
pub name: Box<Ident>,
488488
/// The generic parameters to the callable.
489-
pub generics: Box<[Box<Ident>]>,
489+
pub generics: Box<[TypeParameter]>,
490490
/// The input to the callable.
491491
pub input: Box<Pat>,
492492
/// The return type of the callable.
@@ -510,9 +510,13 @@ impl Display for CallableDecl {
510510
if !self.generics.is_empty() {
511511
write!(indent, "\ngenerics:")?;
512512
indent = set_indentation(indent, 2);
513+
let mut buf = Vec::with_capacity(self.generics.len());
513514
for param in &self.generics {
514-
write!(indent, "\n{param}")?;
515+
buf.push(format!("{param}"));
515516
}
517+
518+
let buf = buf.join(",\n");
519+
write!(indent, "\n{buf}")?;
516520
indent = set_indentation(indent, 1);
517521
}
518522
write!(indent, "\ninput: {}", self.input)?;
@@ -674,7 +678,7 @@ pub enum TyKind {
674678
/// A named type.
675679
Path(PathKind),
676680
/// A type parameter.
677-
Param(Box<Ident>),
681+
Param(TypeParameter),
678682
/// A tuple type.
679683
Tuple(Box<[Ty]>),
680684
/// An invalid type.
@@ -1968,3 +1972,139 @@ impl ImportOrExportItem {
19681972
}
19691973
}
19701974
}
1975+
1976+
/// A [`TypeParameter`] is a generic type variable with optional bounds (constraints).
1977+
#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
1978+
pub struct TypeParameter {
1979+
/// Class constraints specified for this type parameter -- any type variable passed in
1980+
/// as an argument to these parameters must satisfy these constraints.
1981+
pub constraints: ClassConstraints,
1982+
/// The name of the type parameter.
1983+
pub ty: Ident,
1984+
/// The span of the full type parameter, including its name and its constraints.
1985+
pub span: Span,
1986+
}
1987+
1988+
impl WithSpan for TypeParameter {
1989+
fn with_span(self, span: Span) -> Self {
1990+
Self { span, ..self }
1991+
}
1992+
}
1993+
1994+
impl TypeParameter {
1995+
/// Instantiates a new `TypeParameter` with the given type name, constraints, and span.
1996+
#[must_use]
1997+
pub fn new(ty: Ident, bounds: ClassConstraints, span: Span) -> Self {
1998+
Self {
1999+
ty,
2000+
constraints: bounds,
2001+
span,
2002+
}
2003+
}
2004+
}
2005+
2006+
impl std::fmt::Display for TypeParameter {
2007+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2008+
// 'A: Eq + Ord + Clone
2009+
write!(
2010+
f,
2011+
"{}{}",
2012+
self.ty.name,
2013+
if self.constraints.0.is_empty() {
2014+
Default::default()
2015+
} else {
2016+
format!(": {}", self.constraints)
2017+
}
2018+
)
2019+
}
2020+
}
2021+
2022+
/// A list of class constraints, used when constraining a type parameter.
2023+
#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
2024+
pub struct ClassConstraints(pub Box<[ClassConstraint]>);
2025+
2026+
/// An individual class constraint, used when constraining a type parameter.
2027+
/// To understand this concept, think of parameters in a function signature -- the potential arguments that can
2028+
/// be passed to them are constrained by what type is specified. Type-level parameters are no different, and
2029+
/// the type variables that are passed to a type parameter must satisfy the constraints specified in the type parameter.
2030+
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
2031+
pub struct ClassConstraint {
2032+
/// The name of the constraint.
2033+
pub name: Ident,
2034+
/// Parameters for a constraint. For example, `Iterator` has a parameter `T` in `Iterator<T>` -- this
2035+
/// is the type of the item that is coming out of the iterator.
2036+
pub parameters: Box<[ConstraintParameter]>,
2037+
}
2038+
2039+
impl std::fmt::Display for ClassConstraint {
2040+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2041+
// Iterator<T>
2042+
write!(
2043+
f,
2044+
"{}{}",
2045+
self.name.name,
2046+
if self.parameters.is_empty() {
2047+
String::new()
2048+
} else {
2049+
format!(
2050+
"[{}]",
2051+
self.parameters
2052+
.iter()
2053+
.map(|x| x.ty.to_string())
2054+
.collect::<Vec<_>>()
2055+
.join(", ")
2056+
)
2057+
}
2058+
)
2059+
}
2060+
}
2061+
2062+
/// An individual constraint parameter is a type that is passed to a constraint, such as `T` in `Iterator<T>`.
2063+
/// #[derive(Default, `PartialEq`, Eq, Clone, Hash, Debug)]
2064+
#[derive(Default, PartialEq, Eq, Clone, Hash, Debug)]
2065+
pub struct ConstraintParameter {
2066+
/// The type variable being passed as a constraint parameter.
2067+
pub ty: Ty,
2068+
}
2069+
2070+
impl WithSpan for ConstraintParameter {
2071+
fn with_span(self, span: Span) -> Self {
2072+
Self {
2073+
ty: self.ty.with_span(span),
2074+
}
2075+
}
2076+
}
2077+
2078+
impl ClassConstraint {
2079+
/// Getter for the `span` field of the `name` field (the name of the class constraint).
2080+
#[must_use]
2081+
pub fn span(&self) -> Span {
2082+
self.name.span
2083+
}
2084+
}
2085+
2086+
impl ClassConstraints {
2087+
/// The conjoined span of all of the bounds
2088+
#[must_use]
2089+
pub fn span(&self) -> Span {
2090+
Span {
2091+
lo: self.0.first().map(|i| i.span().lo).unwrap_or_default(),
2092+
hi: self.0.last().map(|i| i.span().hi).unwrap_or_default(),
2093+
}
2094+
}
2095+
}
2096+
2097+
impl std::fmt::Display for ClassConstraints {
2098+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2099+
// A + B + C + D
2100+
write!(
2101+
f,
2102+
"{}",
2103+
self.0
2104+
.iter()
2105+
.map(|x| format!("{}", x.name.name,))
2106+
.collect::<Vec<_>>()
2107+
.join(" + "),
2108+
)
2109+
}
2110+
}

compiler/qsc_ast/src/mut_visit.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::ast::{
55
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
66
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
77
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
8-
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
8+
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, TypeParameter,
99
};
1010
use qsc_data_structures::span::Span;
1111

@@ -164,7 +164,17 @@ pub fn walk_ty_def(vis: &mut impl MutVisitor, def: &mut TyDef) {
164164
pub fn walk_callable_decl(vis: &mut impl MutVisitor, decl: &mut CallableDecl) {
165165
vis.visit_span(&mut decl.span);
166166
vis.visit_ident(&mut decl.name);
167-
decl.generics.iter_mut().for_each(|p| vis.visit_ident(p));
167+
decl.generics.iter_mut().for_each(|p| {
168+
vis.visit_ident(&mut p.ty);
169+
p.constraints.0.iter_mut().for_each(|b| {
170+
vis.visit_ident(&mut b.name);
171+
b.parameters
172+
.iter_mut()
173+
.for_each(|crate::ast::ConstraintParameter { ty, .. }| {
174+
vis.visit_ty(ty);
175+
});
176+
});
177+
});
168178
vis.visit_pat(&mut decl.input);
169179
vis.visit_ty(&mut decl.output);
170180
decl.functors
@@ -226,7 +236,21 @@ pub fn walk_ty(vis: &mut impl MutVisitor, ty: &mut Ty) {
226236
}
227237
TyKind::Hole | TyKind::Err => {}
228238
TyKind::Paren(ty) => vis.visit_ty(ty),
229-
TyKind::Param(name) => vis.visit_ident(name),
239+
TyKind::Param(TypeParameter {
240+
ty,
241+
constraints: bounds,
242+
..
243+
}) => {
244+
for bound in &mut bounds.0 {
245+
vis.visit_ident(&mut bound.name);
246+
bound.parameters.iter_mut().for_each(
247+
|crate::ast::ConstraintParameter { ref mut ty, .. }| {
248+
vis.visit_ty(ty);
249+
},
250+
);
251+
}
252+
vis.visit_ident(ty);
253+
}
230254
TyKind::Path(path) => vis.visit_path_kind(path),
231255
TyKind::Tuple(tys) => tys.iter_mut().for_each(|t| vis.visit_ty(t)),
232256
}

compiler/qsc_ast/src/visit.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::ast::{
55
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, FieldAccess, FieldAssign, FieldDef,
66
FunctorExpr, FunctorExprKind, Ident, Item, ItemKind, Namespace, Package, Pat, PatKind, Path,
77
PathKind, QubitInit, QubitInitKind, SpecBody, SpecDecl, Stmt, StmtKind, StringComponent,
8-
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind,
8+
StructDecl, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, TypeParameter,
99
};
1010

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

150150
pub fn walk_callable_decl<'a>(vis: &mut impl Visitor<'a>, decl: &'a CallableDecl) {
151151
vis.visit_ident(&decl.name);
152-
decl.generics.iter().for_each(|p| vis.visit_ident(p));
152+
decl.generics.iter().for_each(|p| {
153+
vis.visit_ident(&p.ty);
154+
p.constraints.0.iter().for_each(|b| {
155+
vis.visit_ident(&b.name);
156+
b.parameters
157+
.iter()
158+
.for_each(|crate::ast::ConstraintParameter { ty, .. }| {
159+
vis.visit_ty(ty);
160+
});
161+
});
162+
});
153163
vis.visit_pat(&decl.input);
154164
vis.visit_ty(&decl.output);
155165
decl.functors.iter().for_each(|f| vis.visit_functor_expr(f));
@@ -201,7 +211,22 @@ pub fn walk_ty<'a>(vis: &mut impl Visitor<'a>, ty: &'a Ty) {
201211
TyKind::Hole | TyKind::Err => {}
202212
TyKind::Paren(ty) => vis.visit_ty(ty),
203213
TyKind::Path(path) => vis.visit_path_kind(path),
204-
TyKind::Param(name) => vis.visit_ident(name),
214+
TyKind::Param(TypeParameter {
215+
ty,
216+
constraints: bounds,
217+
..
218+
}) => {
219+
for bound in &bounds.0 {
220+
vis.visit_ident(&bound.name);
221+
222+
bound.parameters.iter().for_each(
223+
|crate::ast::ConstraintParameter { ty, .. }| {
224+
vis.visit_ty(ty);
225+
},
226+
);
227+
}
228+
vis.visit_ident(ty);
229+
}
205230
TyKind::Tuple(tys) => tys.iter().for_each(|t| vis.visit_ty(t)),
206231
}
207232
}

compiler/qsc_codegen/src/qsharp.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
231231
self.write("<");
232232
if let Some((last, most)) = decl.generics.split_last() {
233233
for i in most {
234-
self.visit_ident(i);
234+
self.visit_ident(&i.ty);
235235
self.write(", ");
236236
}
237-
self.visit_ident(last);
237+
self.visit_ident(&last.ty);
238238
}
239239

240240
self.write(">");
@@ -349,7 +349,7 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
349349
self.write(")");
350350
}
351351
TyKind::Path(path) => self.visit_path_kind(path),
352-
TyKind::Param(name) => self.visit_ident(name),
352+
TyKind::Param(name) => self.visit_ident(&name.ty),
353353
TyKind::Tuple(tys) => {
354354
if tys.is_empty() {
355355
self.write("()");

compiler/qsc_doc_gen/src/display.rs

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use qsc_ast::ast::{self, Idents};
4+
use qsc_ast::ast::{self, Idents, TypeParameter as AstTypeParameter};
55
use qsc_frontend::resolve;
66
use qsc_hir::{
77
hir::{self, PackageId},
8-
ty::{self, GenericParam},
8+
ty::{self, TypeParameter as HirTypeParameter},
99
};
1010
use regex_lite::Regex;
1111
use std::{
@@ -218,7 +218,45 @@ impl<'a> Display for AstCallableDecl<'a> {
218218
.decl
219219
.generics
220220
.iter()
221-
.map(|p| p.name.clone())
221+
.map(
222+
|AstTypeParameter {
223+
ty, constraints, ..
224+
}| {
225+
format!(
226+
"{}{}",
227+
ty.name,
228+
if constraints.0.is_empty() {
229+
Default::default()
230+
} else {
231+
format!(
232+
": {}",
233+
constraints
234+
.0
235+
.iter()
236+
.map(|bound| {
237+
let constraint_parameters = bound
238+
.parameters
239+
.iter()
240+
.map(|x| format!("{}", AstTy { ty: &x.ty }))
241+
.collect::<Vec<_>>()
242+
.join(", ");
243+
format!(
244+
"{}{}",
245+
bound.name.name,
246+
if constraint_parameters.is_empty() {
247+
Default::default()
248+
} else {
249+
format!("[{constraint_parameters}]")
250+
}
251+
)
252+
})
253+
.collect::<Vec<_>>()
254+
.join(" + ")
255+
)
256+
}
257+
)
258+
},
259+
)
222260
.collect::<Vec<_>>()
223261
.join(", ");
224262
write!(f, "<{type_params}>")?;
@@ -517,7 +555,7 @@ impl<'a> Display for AstTy<'a> {
517555
ast::TyKind::Hole => write!(f, "_"),
518556
ast::TyKind::Paren(ty) => write!(f, "{}", AstTy { ty }),
519557
ast::TyKind::Path(path) => write!(f, "{}", AstPathKind { path }),
520-
ast::TyKind::Param(id) => write!(f, "{}", id.name),
558+
ast::TyKind::Param(AstTypeParameter { ty, .. }) => write!(f, "{}", ty.name),
521559
ast::TyKind::Tuple(tys) => fmt_tuple(f, tys, |ty| AstTy { ty }),
522560
ast::TyKind::Err => write!(f, "?"),
523561
}
@@ -615,12 +653,20 @@ where
615653
write!(formatter, "}}")
616654
}
617655

618-
fn display_type_params(generics: &[GenericParam]) -> String {
656+
fn display_type_params(generics: &[HirTypeParameter]) -> String {
619657
let type_params = generics
620658
.iter()
621659
.filter_map(|generic| match generic {
622-
GenericParam::Ty(name) => Some(name.name.clone()),
623-
GenericParam::Functor(_) => None,
660+
HirTypeParameter::Ty { name, bounds } => Some(format!(
661+
"{}{}",
662+
name,
663+
if bounds.is_empty() {
664+
Default::default()
665+
} else {
666+
format!(": {bounds}")
667+
}
668+
)),
669+
HirTypeParameter::Functor(_) => None,
624670
})
625671
.collect::<Vec<_>>()
626672
.join(", ");

0 commit comments

Comments
 (0)