use super::{
Checker, Env, Expr, FLOAT, INT, Language, Node, Result, SArrayItem, SExpr, Span,
TranslationError, Type, array, type_error, unsupported,
};
impl Checker {
pub(super) fn array_literal(
&mut self,
items: &[SArrayItem],
inferred: Option<&Type>,
span: Option<Span>,
env: &Env,
path: &[String],
expected: Option<&Type>,
) -> Result<Expr> {
let mut element = expected.and_then(Type::element).cloned();
if element.is_none()
&& let Some(inferred) = inferred
{
element = Some(self.resolve_type(Some(inferred), path, span)?);
}
let mut parts: Vec<Expr> = Vec::new();
let mut run: Option<usize> = None;
for item in items {
let want = element.as_ref().map(|element| {
if item.spread {
array(element.clone())
} else {
element.clone()
}
});
let value = self.expr(&item.value, env, path, want.as_ref(), false)?;
let place = item.value.span.or(span);
let mut ty = if item.spread {
value.ty.element().cloned().ok_or_else(|| {
unsupported(
&format!("spread of {}", value.ty.key()),
"only arrays are spread into an array literal",
place,
)
})?
} else {
value.ty.clone()
};
if element.is_none() && self.language == Language::JavaScript && ty == Type::Nat {
ty = INT;
}
let element = element.get_or_insert(ty).clone();
let target = if item.spread {
array(element.clone())
} else {
element.clone()
};
let value = self.coerce(value, &target, place)?;
if item.spread {
parts.push(value);
run = None;
} else if let Some(Node::Array { items }) = run
.and_then(|index| parts.get_mut(index))
.map(|part| &mut part.node)
{
items.push(value);
} else {
run = Some(parts.len());
parts.push(Expr::new(
Node::Array { items: vec![value] },
array(element),
));
}
}
let Some(element) = element else {
return Err(unsupported(
"empty array of an unknown type",
"nothing fixes the type of the elements of this empty array; declare it with JSDoc",
span,
));
};
let ty = array(element);
let mut parts = parts.into_iter();
let Some(first) = parts.next() else {
return Ok(Expr::new(Node::Array { items: Vec::new() }, ty));
};
Ok(parts.fold(first, |left, right| {
Expr::new(
Node::Append {
left: Box::new(left),
right: Box::new(right),
},
ty.clone(),
)
}))
}
pub(super) fn index(
&mut self,
object: &SExpr,
index: &SExpr,
span: Option<Span>,
env: &Env,
path: &[String],
) -> Result<Expr> {
let array = self.expr(object, env, path, None, false)?;
let Some(element) = array.ty.element().cloned() else {
return Err(unsupported(
&format!("indexing of {}", array.ty.key()),
"only arrays are indexed in the portable core",
span,
));
};
let place = index.span.or(span);
let index = self.expr(index, env, path, None, false)?;
if !index.ty.is_float() && !matches!(index.ty, Type::Int | Type::Nat) {
return Err(type_error(
format!(
"array index of type {}; an index is a Number or a BigInt",
index.ty.key()
),
place,
));
}
Ok(Expr::new(
Node::Index {
array: Box::new(array),
index: Box::new(index),
},
element,
))
}
pub(super) fn length(
&mut self,
object: &SExpr,
integer: bool,
span: Option<Span>,
env: &Env,
path: &[String],
) -> Result<Expr> {
let array = self.expr(object, env, path, None, false)?;
if array.ty.element().is_none() {
return Err(type_error(format!("length of {}", array.ty.key()), span));
}
let ty = if integer { INT } else { FLOAT };
Ok(Expr::new(
Node::Length {
array: Box::new(array),
},
ty,
))
}
pub(super) fn length_field(
&mut self,
object: &SExpr,
span: Option<Span>,
env: &Env,
path: &[String],
) -> Result<Option<Expr>> {
let array = self.expr(object, env, path, None, false)?;
if array.ty.element().is_some() {
return Ok(Some(Expr::new(
Node::Length {
array: Box::new(array),
},
FLOAT,
)));
}
if array.ty == Type::String {
return Err(unsupported(
"length of a string",
"String.prototype.length counts UTF-16 code units, which the portable string types do not keep",
span,
));
}
Ok(None)
}
}
pub(super) fn array_text(span: Option<Span>) -> TranslationError {
unsupported(
"text of an array",
"console.log lays an array out with util.inspect and String joins its elements with commas, which the translation does not reproduce yet; print the elements one by one",
span,
)
}