use super::{error::InvalidRecordTypeError, *};
use error::ParseError;
use indexmap::{IndexMap, map::Entry};
use crate::{
ast::{
self,
record::{FieldDef, FieldMetadata, FieldPathElem, Include},
typ::{EnumRow, EnumRows, RecordRow, RecordRows, Type},
*,
},
environment::Environment,
identifier::Ident,
position::{RawSpan, TermPos},
typ::{DictTypeFlavour, EnumRowsF, RecordRowsF, TypeF, VarKind},
};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
};
pub enum UniTermNode<'ast> {
Var(LocIdent),
Record(UniRecord<'ast>),
Term(Ast<'ast>),
Type(Type<'ast>),
}
pub struct UniTerm<'ast> {
node: UniTermNode<'ast>,
pos: TermPos,
}
impl<'ast> From<UniTermNode<'ast>> for UniTerm<'ast> {
fn from(node: UniTermNode<'ast>) -> Self {
UniTerm {
node,
pos: TermPos::None,
}
}
}
impl UniTerm<'_> {
pub fn with_pos(mut self, pos: TermPos) -> Self {
self.pos = pos;
self
}
}
impl<'ast> TryConvert<'ast, UniTerm<'ast>> for Type<'ast> {
type Error = ParseError;
fn try_convert(alloc: &'ast AstAlloc, ut: UniTerm<'ast>) -> Result<Self, ParseError> {
let pos = ut.pos;
let typ = match ut.node {
UniTermNode::Var(id) => TypeF::Var(id.ident()),
UniTermNode::Record(r) => Type::try_convert(alloc, r)?.typ,
UniTermNode::Type(ty) => ty.typ,
UniTermNode::Term(ast) => {
if matches!(
ast.node,
Node::Null
| Node::Bool(_)
| Node::Number(_)
| Node::String(_)
| Node::Array(_)
| Node::EnumVariant { .. }
| Node::StringChunks(_)
) {
return Err(ParseError::InvalidContract(ut.pos.unwrap()));
}
TypeF::Contract(alloc.alloc(Ast {
node: ast.node,
pos,
}))
}
};
Ok(Type { typ, pos })
}
}
impl<'ast> TryConvert<'ast, UniTerm<'ast>> for Ast<'ast> {
type Error = ParseError;
fn try_convert(alloc: &'ast AstAlloc, ut: UniTerm<'ast>) -> Result<Self, ParseError> {
let UniTerm { node, pos } = ut;
let node = match node {
UniTermNode::Var(id) => Node::Var(id),
UniTermNode::Record(r) => Ast::try_convert(alloc, r)?.node,
UniTermNode::Type(typ) => {
let typ = typ.fix_type_vars(alloc, pos.unwrap())?;
if let TypeF::Contract(ctr) = typ.typ {
ctr.node.clone()
} else {
alloc.typ(typ)
}
}
UniTermNode::Term(ast) => ast.node,
};
Ok(Ast { node, pos })
}
}
impl<'ast> From<Ast<'ast>> for UniTerm<'ast> {
fn from(ast: Ast<'ast>) -> Self {
let pos = ast.pos;
UniTerm {
node: UniTermNode::Term(ast),
pos,
}
}
}
impl<'ast> From<Node<'ast>> for UniTerm<'ast> {
fn from(node: Node<'ast>) -> Self {
UniTerm {
node: UniTermNode::Term(node.into()),
pos: TermPos::None,
}
}
}
impl<'ast> From<Type<'ast>> for UniTerm<'ast> {
fn from(ty: Type<'ast>) -> Self {
let pos = ty.pos;
UniTerm {
node: UniTermNode::Type(ty),
pos,
}
}
}
impl<'ast> From<UniRecord<'ast>> for UniTerm<'ast> {
fn from(ur: UniRecord<'ast>) -> Self {
let pos = ur.pos;
UniTerm {
node: UniTermNode::Record(ur),
pos,
}
}
}
impl<T, U> TryConvert<'_, T> for U
where
U: TryFrom<T>,
{
type Error = U::Error;
fn try_convert(_: &AstAlloc, from: T) -> Result<Self, Self::Error> {
U::try_from(from)
}
}
#[derive(Clone)]
pub struct UniRecord<'ast> {
pub fields: Vec<FieldDef<'ast>>,
pub tail: Option<(RecordRows<'ast>, TermPos)>,
pub includes: Vec<Include<'ast>>,
pub open: bool,
pub pos: TermPos,
pub pos_ellipsis: TermPos,
}
impl<'ast> UniRecord<'ast> {
pub fn check_typed_field_without_def(&self) -> Result<(), ParseError> {
enum FieldState {
Candidate((RawSpan, RawSpan)),
Defined,
}
let mut candidate_fields = IndexMap::new();
let first_without_def = self.fields.iter().find_map(|field_def| {
let path_as_ident = field_def.path_as_ident();
match &field_def {
FieldDef {
path: _,
value: None,
metadata:
FieldMetadata {
annotation: Annotation { typ: Some(typ), .. },
..
},
..
} => {
if let Some(ident) = path_as_ident {
match candidate_fields.entry(ident.ident()) {
Entry::Occupied(_) => None,
Entry::Vacant(vacant_entry) => {
vacant_entry.insert(FieldState::Candidate((
ident.pos.unwrap(),
typ.pos.unwrap(),
)));
None
}
}
}
else {
Some((field_def.pos.unwrap(), typ.pos.unwrap()))
}
}
field_def => {
if let (Some(ident), Some(_)) = (path_as_ident, &field_def.value) {
candidate_fields.insert(ident.ident(), FieldState::Defined);
}
None
}
}
});
let first_without_def =
first_without_def.or(candidate_fields.into_iter().find_map(|(_, field_state)| {
if let FieldState::Candidate(spans) = field_state {
Some(spans)
} else {
None
}
}));
if let Some((ident_span, annot_span)) = first_without_def {
Err(ParseError::TypedFieldWithoutDefinition {
field_span: ident_span,
annot_span,
})
} else {
Ok(())
}
}
pub fn is_record_type(&self) -> bool {
self.fields.iter().all(|field_def| {
field_def.path.len() == 1
&& matches!(&field_def,
FieldDef {
value: None,
metadata:
FieldMetadata {
doc: None,
annotation:
Annotation {
typ: Some(_),
contracts,
},
opt: false,
not_exported: false,
priority: MergePriority::Neutral,
},
..
} if contracts.is_empty())
}) && self.includes.is_empty()
}
pub fn into_type_strict(
self,
alloc: &'ast AstAlloc,
) -> Result<Type<'ast>, InvalidRecordTypeError> {
fn term_to_record_rows<'ast>(
alloc: &'ast AstAlloc,
id: LocIdent,
field_def: FieldDef<'ast>,
tail: RecordRows<'ast>,
) -> Result<RecordRows<'ast>, InvalidRecordTypeError> {
match field_def {
FieldDef {
path: _,
value: None,
metadata:
FieldMetadata {
doc: None,
annotation:
Annotation {
typ: Some(typ),
contracts: [],
},
opt: false,
not_exported: false,
priority: MergePriority::Neutral,
},
pos: _,
} => Ok(RecordRows(RecordRowsF::Extend {
row: RecordRow {
id,
typ: alloc.type_data(typ.typ, typ.pos),
},
tail: alloc.record_rows(tail.0),
})),
_ => {
Err(InvalidRecordTypeError::InvalidField(
id.pos.fuse(field_def.pos).unwrap(),
))
}
}
}
debug_assert!((self.pos_ellipsis == TermPos::None) != self.open);
if let Some(raw_span) = self.pos_ellipsis.into_opt() {
return Err(InvalidRecordTypeError::IsOpen(raw_span));
}
if let Some(raw_span) = self.includes.first().map(|incl| incl.ident.pos.unwrap()) {
return Err(InvalidRecordTypeError::InterpolatedField(raw_span));
}
let mut fields_seen = HashMap::new();
let rrows = self
.fields
.into_iter()
.rev()
.try_fold(
self.tail
.map(|(tail, _)| tail)
.unwrap_or(RecordRows(RecordRowsF::Empty)),
|acc: RecordRows, field_def| {
if field_def.path.len() > 1 {
let span = field_def
.path
.iter()
.map(|path_elem| path_elem.pos().unwrap())
.reduce(|acc, span| acc.fuse(span).unwrap_or(acc))
.unwrap();
Err(InvalidRecordTypeError::InvalidField(span))
} else {
let elem = field_def.path.last().unwrap();
let id = match elem {
FieldPathElem::Ident(id) => *id,
FieldPathElem::Expr(_) => {
return Err(InvalidRecordTypeError::InterpolatedField(
field_def.pos.unwrap(),
));
}
};
if let Some(prev_id) = fields_seen.insert(id.ident(), id) {
return Err(InvalidRecordTypeError::RepeatedField {
orig: id.pos.unwrap(),
dup: prev_id.pos.unwrap(),
});
}
term_to_record_rows(alloc, id, field_def, acc)
}
},
)?;
Ok(Type {
typ: TypeF::Record(rrows),
pos: self.pos,
})
}
pub fn with_pos(mut self, pos: TermPos) -> Self {
self.pos = pos;
self
}
}
impl<'ast> TryConvert<'ast, UniRecord<'ast>> for Ast<'ast> {
type Error = ParseError;
fn try_convert(alloc: &'ast AstAlloc, ur: UniRecord<'ast>) -> Result<Self, ParseError> {
let pos = ur.pos;
if ur.tail.is_some() || (ur.is_record_type() && !ur.fields.is_empty()) {
let tail_span = ur.tail.as_ref().and_then(|t| t.1.into_opt());
let typ =
ur.into_type_strict(alloc)
.map_err(|cause| ParseError::InvalidRecordType {
tail_span,
record_span: pos.unwrap(),
cause,
})?;
let typ = typ.fix_type_vars(alloc, pos.unwrap())?;
Ok(alloc.typ(typ).spanned(pos))
} else {
ur.check_typed_field_without_def()?;
let UniRecord { fields, open, .. } = ur;
let field_defs_fixed = fields
.into_iter()
.map(|field_def| {
Ok(FieldDef {
metadata: fix_field_types(
alloc,
field_def.metadata,
field_def.pos.unwrap(),
)?,
..field_def
})
})
.collect::<Result<Vec<_>, ParseError>>()?;
Ok(alloc
.record(ast::record::Record {
field_defs: alloc.alloc_many(field_defs_fixed),
includes: alloc.alloc_many(ur.includes),
open,
})
.spanned(pos))
}
}
}
impl<'ast> TryConvert<'ast, UniRecord<'ast>> for Type<'ast> {
type Error = ParseError;
fn try_convert(alloc: &'ast AstAlloc, ur: UniRecord<'ast>) -> Result<Self, ParseError> {
let pos = ur.pos;
if let Some((_, tail_pos)) = ur.tail {
ur.into_type_strict(alloc)
.map_err(|cause| ParseError::InvalidRecordType {
tail_span: tail_pos.into_opt(),
record_span: pos.unwrap(),
cause,
})
} else {
let pos = ur.pos;
ur.clone().into_type_strict(alloc).or_else(|_| {
Ast::try_convert(alloc, ur).map(|ast| Type {
typ: TypeF::Contract(alloc.alloc(ast)),
pos,
})
})
}
}
}
#[derive(PartialEq, Eq)]
pub(super) struct VarKindCell(RefCell<Option<VarKind>>);
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(super) struct VarKindMismatch;
pub(super) type BoundVarEnv = Environment<Ident, VarKindCell>;
impl VarKindCell {
pub(super) fn new() -> Self {
VarKindCell(RefCell::new(None))
}
pub(super) fn try_set(&self, var_kind: VarKind) -> Result<(), VarKindMismatch> {
match (&mut *self.0.borrow_mut(), var_kind) {
(s @ None, var_kind) => {
*s = Some(var_kind);
Ok(())
}
(Some(data), var_kind) if data == &var_kind => Ok(()),
(
Some(VarKind::RecordRows { excluded: ex1 }),
VarKind::RecordRows { excluded: ex2 },
) => {
ex1.extend(ex2);
Ok(())
}
(Some(VarKind::EnumRows { excluded: ex1 }), VarKind::EnumRows { excluded: ex2 }) => {
ex1.extend(ex2);
Ok(())
}
_ => Err(VarKindMismatch),
}
}
#[allow(dead_code)]
pub fn var_kind(&self) -> Option<VarKind> {
self.0.borrow().clone()
}
pub fn take_var_kind(&self) -> Option<VarKind> {
self.0.borrow_mut().take()
}
}
pub(super) trait FixTypeVars<'ast>
where
Self: Sized,
{
fn fix_type_vars(self, alloc: &'ast AstAlloc, span: RawSpan) -> Result<Self, ParseError> {
Ok(self
.fix_type_vars_env(alloc, BoundVarEnv::new(), span)?
.unwrap_or(self))
}
fn fix_type_vars_ref(
&self,
alloc: &'ast AstAlloc,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
self.fix_type_vars_env(alloc, BoundVarEnv::new(), span)
}
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError>;
}
impl<'ast> FixTypeVars<'ast> for Type<'ast> {
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
mut bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
use crate::ast::typ::TypeUnr;
let pos = self.pos;
let build_fixed = |new_type: TypeUnr<'ast>| -> Self { Type { typ: new_type, pos } };
match self.typ {
TypeF::Dyn
| TypeF::Number
| TypeF::Bool
| TypeF::String
| TypeF::ForeignId
| TypeF::Symbol
| TypeF::Contract(_)
| TypeF::Dict { flavour: DictTypeFlavour::Contract, ..}
| TypeF::Wildcard(_) => Ok(None),
TypeF::Arrow(src, tgt) => {
let src_result = src.fix_type_vars_env(alloc, bound_vars.clone(), span)?;
let tgt_result = tgt.fix_type_vars_env(alloc, bound_vars, span)?;
if src_result.is_some() || tgt_result.is_some() {
let src = src_result.map(|ty| alloc.alloc(ty)).unwrap_or(src);
let tgt = tgt_result.map(|ty| alloc.alloc(ty)).unwrap_or(tgt);
Ok(Some(build_fixed(TypeF::Arrow(src, tgt))))
}
else {
Ok(None)
}
}
TypeF::Var(sym) => {
if let Some(cell) = bound_vars.get(&sym) {
cell.try_set(VarKind::Type)
.map_err(|_| ParseError::TypeVariableKindMismatch {
ty_var: LocIdent::from(sym).with_pos(self.pos),
span
})?;
Ok(None)
} else {
let id = LocIdent::from(sym).with_pos(self.pos);
Ok(Some(build_fixed(TypeF::Contract(alloc.alloc(Ast {
node: Node::Var(id),
pos: id.pos,
})))))
}
}
TypeF::Forall {
var,
var_kind: ref prev_var_kind,
body,
} => {
bound_vars.insert(var.ident(), VarKindCell::new());
let body_fixed = body.fix_type_vars_env(alloc, bound_vars.clone(), span)?;
let var_kind = bound_vars
.get(&var.ident())
.unwrap()
.take_var_kind()
.unwrap_or_default();
if body_fixed.is_some() || !matches!((&var_kind, &prev_var_kind), (&VarKind::Type, &VarKind::Type)) {
let body = body_fixed.map(|body| alloc.alloc(body)).unwrap_or(body);
Ok(Some(build_fixed(TypeF::Forall {
var,
var_kind,
body,
})))
} else {
Ok(None)
}
}
TypeF::Dict {
type_fields,
flavour: flavour @ DictTypeFlavour::Type
} => {
Ok(type_fields.fix_type_vars_env(alloc, bound_vars, span)?.map(|ty| {
build_fixed(TypeF::Dict {
type_fields: alloc.alloc(ty),
flavour,
})
}))
}
TypeF::Array(ty) => {
Ok(ty.fix_type_vars_env(alloc, bound_vars, span)?.map(|ty|
build_fixed(TypeF::Array(alloc.alloc(ty)))))
}
TypeF::Enum(ref erows) => {
Ok(erows.fix_type_vars_env(alloc, bound_vars, span)?.map(|erows|
build_fixed(TypeF::Enum(erows))
))
}
TypeF::Record(ref rrows) => {
Ok(rrows.fix_type_vars_env(alloc, bound_vars, span)?.map(|rrows|
build_fixed(TypeF::Record(rrows))
))
}
}
}
}
impl<'ast> FixTypeVars<'ast> for RecordRows<'ast> {
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
fn do_fix<'ast>(
rrows: &RecordRows<'ast>,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
mut maybe_excluded: HashSet<Ident>,
) -> Result<Option<RecordRows<'ast>>, ParseError> {
match rrows.0 {
RecordRowsF::Empty | RecordRowsF::TailDyn => Ok(None),
RecordRowsF::TailVar(id) => {
if let Some(cell) = bound_vars.get(&id.ident()) {
cell.try_set(VarKind::RecordRows {
excluded: maybe_excluded,
})
.map_err(|_| ParseError::TypeVariableKindMismatch { ty_var: id, span })?;
}
Ok(None)
}
RecordRowsF::Extend { ref row, tail } => {
maybe_excluded.insert(row.id.ident());
let row_fixed = row.fix_type_vars_env(alloc, bound_vars.clone(), span)?;
let tail_fixed = do_fix(tail, alloc, bound_vars, span, maybe_excluded)?;
if row_fixed.is_some() || tail_fixed.is_some() {
let row = row_fixed.unwrap_or_else(|| row.clone());
let tail = tail_fixed
.map(|tail_fixed| alloc.alloc(tail_fixed))
.unwrap_or(tail);
Ok(Some(RecordRows(RecordRowsF::Extend { row, tail })))
} else {
Ok(None)
}
}
}
}
do_fix(self, alloc, bound_vars, span, HashSet::new())
}
}
impl<'ast> FixTypeVars<'ast> for RecordRow<'ast> {
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
Ok(self
.typ
.fix_type_vars_env(alloc, bound_vars, span)?
.map(|typ| RecordRow {
id: self.id,
typ: alloc.alloc(typ),
}))
}
}
impl<'ast> FixTypeVars<'ast> for EnumRows<'ast> {
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
fn do_fix<'ast>(
erows: &EnumRows<'ast>,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
mut maybe_excluded: HashSet<Ident>,
) -> Result<Option<EnumRows<'ast>>, ParseError> {
match erows.0 {
EnumRowsF::Empty => Ok(None),
EnumRowsF::TailVar(id) => {
if let Some(cell) = bound_vars.get(&id.ident()) {
cell.try_set(VarKind::EnumRows {
excluded: maybe_excluded,
})
.map_err(|_| ParseError::TypeVariableKindMismatch { ty_var: id, span })?;
}
Ok(None)
}
EnumRowsF::Extend { ref row, tail } => {
if row.typ.is_some() {
maybe_excluded.insert(row.id.ident());
}
let row_fixed = row.fix_type_vars_env(alloc, bound_vars.clone(), span)?;
let tail_fixed = do_fix(tail, alloc, bound_vars, span, maybe_excluded)?;
if row_fixed.is_some() || tail_fixed.is_some() {
let row = row_fixed.unwrap_or_else(|| row.clone());
let tail = tail_fixed
.map(|tail_fixed| alloc.alloc(tail_fixed))
.unwrap_or(tail);
Ok(Some(EnumRows(EnumRowsF::Extend { row, tail })))
} else {
Ok(None)
}
}
}
}
do_fix(self, alloc, bound_vars, span, HashSet::new())
}
}
impl<'ast> FixTypeVars<'ast> for EnumRow<'ast> {
fn fix_type_vars_env(
&self,
alloc: &'ast AstAlloc,
bound_vars: BoundVarEnv,
span: RawSpan,
) -> Result<Option<Self>, ParseError> {
let maybe_fixed = self
.typ
.as_ref()
.map(|ty| {
ty.fix_type_vars_env(alloc, bound_vars.clone(), span)
})
.transpose()?
.flatten();
Ok(maybe_fixed.map(|typ| EnumRow {
id: self.id,
typ: Some(alloc.alloc(typ)),
}))
}
}
pub fn fix_field_types<'ast>(
alloc: &'ast AstAlloc,
metadata: FieldMetadata<'ast>,
span: RawSpan,
) -> Result<FieldMetadata<'ast>, ParseError> {
use std::borrow::Cow;
let typ = metadata
.annotation
.typ
.map(|typ| typ.fix_type_vars(alloc, span))
.transpose()?;
let contracts: Result<Vec<Cow<'ast, _>>, ParseError> = metadata
.annotation
.contracts
.iter()
.map(|ctr| {
Ok(ctr
.fix_type_vars_ref(alloc, span)?
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(ctr)))
})
.collect();
let contracts = contracts?;
let contracts = if contracts.iter().all(|cow| matches!(cow, Cow::Borrowed(_))) {
metadata.annotation.contracts
} else {
alloc.alloc_many(contracts.into_iter().map(|cow| cow.into_owned()))
};
Ok(FieldMetadata {
annotation: Annotation { typ, contracts },
..metadata
})
}