Skip to content

Commit

Permalink
UDT field completions
Browse files Browse the repository at this point in the history
  • Loading branch information
minestarks committed Oct 9, 2024
1 parent 93c3360 commit 04b054b
Show file tree
Hide file tree
Showing 21 changed files with 927 additions and 136 deletions.
19 changes: 16 additions & 3 deletions compiler/qsc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,16 @@ impl WithSpan for Expr {
}
}

/// The identifier in a field access expression.
#[derive(Clone, Debug, Default, PartialEq)]
pub enum FieldAccess {
/// The field name.
Ok(Box<Ident>),
/// The field access was missing a field name.
#[default]
Err,
}

/// An expression kind.
#[derive(Clone, Debug, Default, PartialEq)]
pub enum ExprKind {
Expand Down Expand Up @@ -862,7 +872,7 @@ pub enum ExprKind {
/// A failure: `fail "message"`.
Fail(Box<Expr>),
/// A field accessor: `a::F` or `a.F`.
Field(Box<Expr>, Box<Ident>),
Field(Box<Expr>, FieldAccess),
/// A for loop: `for a in b { ... }`.
For(Box<Pat>, Box<Expr>, Box<Block>),
/// An unspecified expression, _, which may indicate partial application or a typed hole.
Expand Down Expand Up @@ -1030,11 +1040,14 @@ fn display_conjugate(
Ok(())
}

fn display_field(mut indent: Indented<Formatter>, expr: &Expr, id: &Ident) -> fmt::Result {
fn display_field(mut indent: Indented<Formatter>, expr: &Expr, field: &FieldAccess) -> fmt::Result {
write!(indent, "Field:")?;
indent = set_indentation(indent, 1);
write!(indent, "\n{expr}")?;
write!(indent, "\n{id}")?;
match field {
FieldAccess::Ok(i) => write!(indent, "\n{i}")?,
FieldAccess::Err => write!(indent, "\nErr")?,
}
Ok(())
}

Expand Down
12 changes: 7 additions & 5 deletions compiler/qsc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Licensed under the MIT License.

use crate::ast::{
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, 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,
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,
};
use qsc_data_structures::span::Span;

Expand Down Expand Up @@ -288,7 +288,9 @@ pub fn walk_expr(vis: &mut impl MutVisitor, expr: &mut Expr) {
ExprKind::Fail(msg) => vis.visit_expr(msg),
ExprKind::Field(record, name) => {
vis.visit_expr(record);
vis.visit_ident(name);
if let FieldAccess::Ok(name) = name {
vis.visit_ident(name);
}
}
ExprKind::For(pat, iter, block) => {
vis.visit_pat(pat);
Expand Down
12 changes: 7 additions & 5 deletions compiler/qsc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Licensed under the MIT License.

use crate::ast::{
Attr, Block, CallableBody, CallableDecl, Expr, ExprKind, 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,
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,
};

pub trait Visitor<'a>: Sized {
Expand Down Expand Up @@ -257,7 +257,9 @@ pub fn walk_expr<'a>(vis: &mut impl Visitor<'a>, expr: &'a Expr) {
ExprKind::Fail(msg) => vis.visit_expr(msg),
ExprKind::Field(record, name) => {
vis.visit_expr(record);
vis.visit_ident(name);
if let FieldAccess::Ok(name) = name {
vis.visit_ident(name);
}
}
ExprKind::For(pat, iter, block) => {
vis.visit_pat(pat);
Expand Down
15 changes: 8 additions & 7 deletions compiler/qsc_codegen/src/qsharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use std::io::Write;
use std::vec;

use qsc_ast::ast::{
self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Functor,
FunctorExpr, FunctorExprKind, Ident, Idents, ImportOrExportItem, Item, ItemKind, Lit,
Mutability, Pat, PatKind, Path, PathKind, Pauli, QubitInit, QubitInitKind, QubitSource, SetOp,
SpecBody, SpecDecl, SpecGen, Stmt, StmtKind, StringComponent, TernOp, TopLevelNode, Ty, TyDef,
TyDefKind, TyKind, UnOp,
self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind,
FieldAccess, Functor, FunctorExpr, FunctorExprKind, Ident, Idents, ImportOrExportItem, Item,
ItemKind, Lit, Mutability, Pat, PatKind, Path, PathKind, Pauli, QubitInit, QubitInitKind,
QubitSource, SetOp, SpecBody, SpecDecl, SpecGen, Stmt, StmtKind, StringComponent, TernOp,
TopLevelNode, Ty, TyDef, TyDefKind, TyKind, UnOp,
};
use qsc_ast::ast::{Namespace, Package};
use qsc_ast::visit::Visitor;
Expand Down Expand Up @@ -483,7 +483,7 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
self.write("fail ");
self.visit_expr(msg);
}
ExprKind::Field(record, name) => {
ExprKind::Field(record, ast::FieldAccess::Ok(name)) => {
self.visit_expr(record);
self.write(".");
self.visit_ident(name);
Expand Down Expand Up @@ -716,7 +716,8 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
}
ExprKind::Err
| ExprKind::Path(PathKind::Err(_))
| ExprKind::Struct(PathKind::Err(_), ..) => {
| ExprKind::Struct(PathKind::Err(_), ..)
| ExprKind::Field(_, FieldAccess::Err) => {
unreachable!();
}
}
Expand Down
7 changes: 4 additions & 3 deletions compiler/qsc_frontend/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
typeck::{self, convert},
};
use miette::Diagnostic;
use qsc_ast::ast::{self, Ident, Idents, PathKind};
use qsc_ast::ast::{self, FieldAccess, Ident, Idents, PathKind};
use qsc_data_structures::{index_map::IndexMap, span::Span, target::TargetCapabilityFlags};
use qsc_hir::{
assigner::Assigner,
Expand Down Expand Up @@ -576,7 +576,7 @@ impl With<'_> {
hir::ExprKind::Conjugate(self.lower_block(within), self.lower_block(apply))
}
ast::ExprKind::Fail(message) => hir::ExprKind::Fail(Box::new(self.lower_expr(message))),
ast::ExprKind::Field(container, name) => {
ast::ExprKind::Field(container, FieldAccess::Ok(name)) => {
let container = self.lower_expr(container);
let field = self.lower_field(&container.ty, &name.name);
hir::ExprKind::Field(Box::new(container), field)
Expand Down Expand Up @@ -682,7 +682,8 @@ impl With<'_> {
}
ast::ExprKind::Err
| &ast::ExprKind::Path(ast::PathKind::Err(_))
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => hir::ExprKind::Err,
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..)
| ast::ExprKind::Field(_, FieldAccess::Err) => hir::ExprKind::Err,
};

hir::Expr {
Expand Down
48 changes: 39 additions & 9 deletions compiler/qsc_frontend/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,12 @@ pub(super) type Names = IndexMap<NodeId, Res>;
#[must_use]
pub fn path_as_field_accessor<'a>(
names: &Names,
path: &'a ast::Path,
path: &'a impl Idents,
) -> Option<(NodeId, Vec<&'a ast::Ident>)> {
if path.segments.is_some() {
let parts: Vec<&Ident> = path.iter().collect();
let first = parts.first().expect("path should have at least one part");
if let Some(&Res::Local(node_id)) = names.get(first.id) {
return Some((node_id, parts));
}
let parts: Vec<&Ident> = path.iter().collect();
let first = parts.first().expect("path should have at least one part");
if let Some(&Res::Local(node_id)) = names.get(first.id) {
return Some((node_id, parts));
}
// If any of the above conditions are not met, return None.
None
Expand Down Expand Up @@ -653,6 +651,38 @@ impl Resolver {
}
}

fn resolve_path_kind(&mut self, kind: NameKind, path: &ast::PathKind) -> Result<(), Error> {
match path {
PathKind::Ok(path) => self.resolve_path(kind, path).map(|_| ()),
PathKind::Err(incomplete_path) => {
// First we check if the the path can be resolved as a field accessor.
// We do this by checking if the first part of the path is a local variable.
if let (NameKind::Term, Some(incomplet_path)) = (kind, incomplete_path) {
let first = incomplet_path
.segments
.first()
.expect("path `segments` should have at least one element");
match resolve(
NameKind::Term,
&self.globals,
self.locals.get_scopes(&self.curr_scope_chain),
first,
&None,
) {
Ok(res) if matches!(res, Res::Local(_)) => {
// The path is a field accessor.
self.names.insert(first.id, res.clone());
return Ok(());
}
Err(err) if !matches!(err, Error::NotFound(_, _)) => return Err(err), // Local was found but has issues.
_ => return Ok(()), // The path is assumed to not be a field accessor, so move on.
}
}
Ok(())
}
}
}

fn resolve_path(&mut self, kind: NameKind, path: &ast::Path) -> Result<Res, Error> {
let name = &path.name;
let segments = &path.segments;
Expand Down Expand Up @@ -1428,8 +1458,8 @@ impl AstVisitor<'_> for With<'_> {
visitor.visit_expr(output);
});
}
ast::ExprKind::Path(PathKind::Ok(path)) => {
if let Err(e) = self.resolver.resolve_path(NameKind::Term, path) {
ast::ExprKind::Path(path) => {
if let Err(e) = self.resolver.resolve_path_kind(NameKind::Term, path) {
self.resolver.errors.push(e);
};
}
Expand Down
93 changes: 60 additions & 33 deletions compiler/qsc_frontend/src/typeck/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use super::{
};
use crate::resolve::{self, Names, Res};
use qsc_ast::ast::{
self, BinOp, Block, Expr, ExprKind, Functor, Ident, Lit, NodeId, Pat, PatKind, Path, PathKind,
QubitInit, QubitInitKind, Spec, Stmt, StmtKind, StringComponent, TernOp, TyKind, UnOp,
self, BinOp, Block, Expr, ExprKind, FieldAccess, Functor, Ident, Idents, Lit, NodeId, Pat,
PatKind, Path, PathKind, QubitInit, QubitInitKind, Spec, Stmt, StmtKind, StringComponent,
TernOp, TyKind, UnOp,
};
use qsc_data_structures::span::Span;
use qsc_hir::{
Expand Down Expand Up @@ -268,16 +269,20 @@ impl<'a> Context<'a> {
}
ExprKind::Field(record, name) => {
let record = self.infer_expr(record);
let item_ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span));
self.inferrer.class(
expr.span,
Class::HasField {
record: record.ty,
name: name.name.to_string(),
item: item_ty.clone(),
},
);
self.diverge_if(record.diverges, converge(item_ty))
if let FieldAccess::Ok(name) = name {
let item_ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span));
self.inferrer.class(
expr.span,
Class::HasField {
record: record.ty,
name: name.name.to_string(),
item: item_ty.clone(),
},
);
self.diverge_if(record.diverges, converge(item_ty))
} else {
converge(Ty::Err)
}
}
ExprKind::For(item, container, body) => {
let item_ty = self.infer_pat(item);
Expand Down Expand Up @@ -394,7 +399,7 @@ impl<'a> Context<'a> {
Lit::String(_) => converge(Ty::Prim(Prim::String)),
},
ExprKind::Paren(expr) => self.infer_expr(expr),
ExprKind::Path(PathKind::Ok(path)) => self.infer_path(expr, path),
ExprKind::Path(path) => self.infer_path_kind(expr, path),
ExprKind::Range(start, step, end) => {
let mut diverges = false;
for expr in start.iter().chain(step).chain(end) {
Expand Down Expand Up @@ -532,9 +537,7 @@ impl<'a> Context<'a> {
self.typed_holes.push((expr.id, expr.span));
converge(self.inferrer.fresh_ty(TySource::not_divergent(expr.span)))
}
ExprKind::Err
| ast::ExprKind::Path(ast::PathKind::Err(_))
| ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => converge(Ty::Err),
ExprKind::Err | ast::ExprKind::Struct(ast::PathKind::Err(_), ..) => converge(Ty::Err),
};

self.record(expr.id, ty.ty.clone());
Expand Down Expand Up @@ -570,24 +573,23 @@ impl<'a> Context<'a> {
record
}

fn infer_path(&mut self, expr: &Expr, path: &Path) -> Partial<Ty> {
match resolve::path_as_field_accessor(self.names, path) {
// If the path is a field accessor, we infer the type of first segment
// as an expr, and the rest as subsequent fields.
Some((first_id, parts)) => {
let record = converge(
self.table
.terms
.get(first_id)
.expect("local should have type")
.clone(),
);
let (first, rest) = parts
.split_first()
.expect("path should have at least one part");
self.record(first.id, record.ty.clone());
self.infer_path_parts(record, rest, expr.span.lo)
fn infer_path_kind(&mut self, expr: &Expr, path: &PathKind) -> Partial<Ty> {
match path {
PathKind::Ok(path) => self.infer_path(expr, path),
PathKind::Err(incomplete_path) => {
if let Some(incomplete_path) = incomplete_path {
// If this is a field access, infer the fields,
// but leave the whole expression as `Err`.
let _ = self.infer_path_as_field_access(&incomplete_path.segments, expr);
}
converge(Ty::Err)
}
}
}

fn infer_path(&mut self, expr: &Expr, path: &Path) -> Partial<Ty> {
match self.infer_path_as_field_access(path, expr) {
Some(record) => record,
// Otherwise we infer the path as a namespace path.
None => match self.names.get(path.id) {
None => converge(Ty::Err),
Expand Down Expand Up @@ -620,6 +622,31 @@ impl<'a> Context<'a> {
}
}

fn infer_path_as_field_access(
&mut self,
path: &impl Idents,
expr: &Expr,
) -> Option<Partial<Ty>> {
// If the path is a field accessor, we infer the type of first segment
// as an expr, and the rest as subsequent fields.
if let Some((first_id, parts)) = resolve::path_as_field_accessor(self.names, path) {
let record = converge(
self.table
.terms
.get(first_id)
.expect("local should have type")
.clone(),
);
let (first, rest) = parts
.split_first()
.expect("path should have at least one part");
self.record(first.id, record.ty.clone());
Some(self.infer_path_parts(record, rest, expr.span.lo))
} else {
None
}
}

fn infer_hole_tuple<T>(
&mut self,
hole: fn(Ty) -> T,
Expand Down
Loading

0 comments on commit 04b054b

Please sign in to comment.