use std::collections::HashSet;
use super::Parser;
use super::ast::{Annotation, Entry, Expr, Import, Modifier, Property};
use crate::error::Result;
use crate::lexer::TokenKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum BodyKind {
Module,
AmendingModule,
Class,
Object,
When,
For,
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum ConstKey {
String(String),
Int(i64),
Bool(bool),
}
pub(super) struct BodyScope {
pub(super) kind: BodyKind,
for_offset: Option<usize>,
properties: HashSet<String>,
local_properties: HashSet<String>,
methods: HashSet<String>,
entries: HashSet<ConstKey>,
has_generator: bool,
duplicate: Option<(usize, String)>,
type_aliases: Vec<(usize, String, Vec<String>)>,
}
impl BodyScope {
pub(super) fn new(kind: BodyKind, for_offset: Option<usize>) -> Self {
Self {
kind,
for_offset,
properties: HashSet::new(),
local_properties: HashSet::new(),
methods: HashSet::new(),
entries: HashSet::new(),
has_generator: false,
duplicate: None,
type_aliases: Vec::new(),
}
}
pub(super) fn record_type_alias(
&mut self,
offset: usize,
name: &str,
mut refs: Vec<String>,
params: &[String],
) {
refs.retain(|r| !params.contains(r));
self.type_aliases.push((offset, name.to_string(), refs));
}
fn cyclic_type_alias(&self) -> Option<usize> {
if self.type_aliases.is_empty() {
return None;
}
let index_of: std::collections::HashMap<&str, usize> = self
.type_aliases
.iter()
.enumerate()
.map(|(i, (_, name, _))| (name.as_str(), i))
.collect();
let mut state = vec![0u8; self.type_aliases.len()];
for root in 0..self.type_aliases.len() {
if state[root] != 0 {
continue;
}
let mut stack = vec![(root, 0usize)];
state[root] = 1;
while let Some((alias, next)) = stack.last_mut() {
let refs = &self.type_aliases[*alias].2;
if *next == refs.len() {
state[*alias] = 2;
stack.pop();
continue;
}
let r = &refs[*next];
*next += 1;
let Some(&target) = index_of.get(r.as_str()) else {
continue;
};
match state[target] {
1 => return Some(self.type_aliases[target].0),
0 => {
state[target] = 1;
stack.push((target, 0));
}
_ => {}
}
}
}
None
}
pub(super) fn declare_imports(&mut self, imports: &[Import]) -> Option<String> {
for import in imports {
let name = match &import.alias {
Some(alias) => alias.as_str(),
None if import.is_glob => continue,
None => inferred_import_name(&import.uri),
};
if !self.properties.insert(name.to_string()) {
return Some(name.to_string());
}
}
None
}
pub(super) fn set_has_generator(&mut self) {
self.has_generator = true;
}
}
fn modifier_keyword(modifier: &Modifier) -> &'static str {
match modifier {
Modifier::Local => "local",
Modifier::Const => "const",
Modifier::Fixed => "fixed",
Modifier::Hidden => "hidden",
Modifier::Abstract => "abstract",
Modifier::Open => "open",
Modifier::External => "external",
}
}
const MODULE_MODIFIERS: &[Modifier] = &[Modifier::Abstract, Modifier::Open];
const CLASS_MODIFIERS: &[Modifier] = &[
Modifier::Abstract,
Modifier::Open,
Modifier::Local,
Modifier::External,
];
const TYPE_ALIAS_MODIFIERS: &[Modifier] = &[Modifier::Local, Modifier::External];
const METHOD_MODIFIERS: &[Modifier] = &[
Modifier::Abstract,
Modifier::Local,
Modifier::External,
Modifier::Const,
];
const PROPERTY_MODIFIERS: &[Modifier] = &[
Modifier::Abstract,
Modifier::Local,
Modifier::Hidden,
Modifier::External,
Modifier::Fixed,
Modifier::Const,
];
const OBJECT_MEMBER_MODIFIERS: &[Modifier] = &[Modifier::Local, Modifier::Const];
pub(crate) const PKL_VERSION: &str = "0.32.1";
fn version_triple(version: &str) -> Option<(u64, u64, u64)> {
let core = version.split(['-', '+']).next()?;
let mut parts = core.split('.').map(|part| part.parse::<u64>().ok());
Some((parts.next()??, parts.next()??, parts.next()??))
}
fn constant_string(expr: &Expr) -> Option<String> {
match expr {
Expr::String(s) => Some(s.to_string()),
Expr::Binop(super::ast::BinOp::Add, left, right) => {
Some(constant_string(left)? + &constant_string(right)?)
}
Expr::StringInterpolation(parts) => parts
.iter()
.map(|part| match part {
super::ast::StringInterpPart::Literal(s) => Some(s.to_string()),
super::ast::StringInterpPart::Expr(expr) => constant_string(expr),
})
.collect(),
_ => None,
}
}
fn inferred_import_name(uri: &str) -> &str {
let last = uri.rsplit(['/', ':', '#']).next().unwrap_or(uri);
match last.rfind('.') {
Some(dot) if dot > 0 => &last[..dot],
_ => last,
}
}
impl Parser<'_> {
fn semantic_error(&self, offset: usize, message: impl Into<String>) -> crate::error::Error {
crate::error::Error::parse(self.name, self.source, offset, message.into())
}
fn check_modifiers(
&self,
offset: usize,
modifiers: &[Modifier],
valid: &[Modifier],
what: &str,
) -> Result<()> {
if let Some(invalid) = modifiers.iter().find(|m| !valid.contains(m)) {
return Err(self.semantic_error(
offset,
format!(
"Modifier `{}` is not applicable to {what}.",
modifier_keyword(invalid)
),
));
}
let has = |m: Modifier| modifiers.contains(&m);
let message = if has(Modifier::External) {
"External members can only be defined by standard library modules."
} else if has(Modifier::Local) && has(Modifier::Hidden) {
"Modifier `hidden` is redundant here; just use `local`."
} else if has(Modifier::Local) && has(Modifier::Fixed) {
"Modifier `fixed` is redundant here; just use `local`."
} else if has(Modifier::Abstract) && has(Modifier::Open) {
"Modifier `open` is redundant here; just use `abstract`."
} else {
return Ok(());
};
Err(self.semantic_error(offset, message))
}
fn check_object_member_modifiers(&self, offset: usize, modifiers: &[Modifier]) -> Result<()> {
self.check_modifiers(offset, modifiers, OBJECT_MEMBER_MODIFIERS, "object members")?;
if modifiers.contains(&Modifier::Const) && !modifiers.contains(&Modifier::Local) {
return Err(self.semantic_error(
offset,
"Modifier `const` can only be applied to object members that are also `local`.",
));
}
Ok(())
}
pub(super) fn check_min_pkl_version(
&self,
annotations: &[Annotation],
module_name: Option<&str>,
) -> Result<()> {
let Some(required) = annotations
.iter()
.filter(|annotation| annotation.name == "ModuleInfo")
.flat_map(|annotation| &annotation.body)
.find_map(|entry| match entry {
Entry::Property(prop) if prop.name == "minPklVersion" => {
prop.value.as_ref().and_then(constant_string)
}
_ => None,
})
else {
return Ok(());
};
let (Some(required_triple), Some(current)) =
(version_triple(&required), version_triple(PKL_VERSION))
else {
return Ok(());
};
if required_triple <= current {
return Ok(());
}
let name = module_name.map(str::to_string).unwrap_or_else(|| {
std::path::Path::new(self.name)
.file_stem()
.map(|stem| stem.to_string_lossy().into_owned())
.unwrap_or_else(|| self.name.to_string())
});
Err(self.semantic_error(
0,
format!(
"Module `{name}` requires Pkl version {required} or higher, but your Pkl version is {PKL_VERSION}."
),
))
}
pub(super) fn check_module_modifiers(
&self,
offset: usize,
modifiers: &[Modifier],
amends: bool,
) -> Result<()> {
self.check_modifiers(offset, modifiers, MODULE_MODIFIERS, "modules")?;
if amends {
self.check_modifiers(offset, modifiers, &[], "modules that amend another module")?;
}
Ok(())
}
pub(super) fn duplicate_error(&self, offset: usize, name: &str) -> crate::error::Error {
self.semantic_error(offset, format!("Duplicate definition of member `{name}`."))
}
fn declare(&mut self, offset: usize, name: &str, method: bool, local: bool) -> Result<()> {
let in_object = matches!(
self.body.kind,
BodyKind::Object | BodyKind::When | BodyKind::For
);
let set = if method {
&mut self.body.methods
} else if in_object && local {
&mut self.body.local_properties
} else {
&mut self.body.properties
};
if set.insert(name.to_string()) {
return Ok(());
}
if !in_object {
return Err(self.duplicate_error(offset, name));
}
if self.body.duplicate.is_none() {
self.body.duplicate = Some((offset, name.to_string()));
}
Ok(())
}
pub(super) fn finish_body(&self, scope: &BodyScope) -> Result<()> {
if let Some(offset) = scope.cyclic_type_alias() {
return Err(self.semantic_error(offset, "Type alias definitions must not be cyclic."));
}
if scope.has_generator || matches!(scope.kind, BodyKind::When | BodyKind::For) {
return Ok(());
}
match &scope.duplicate {
Some((offset, name)) => Err(self.duplicate_error(*offset, name)),
None => Ok(()),
}
}
pub(super) fn check_class(
&mut self,
offset: usize,
modifiers: &[Modifier],
name: &str,
) -> Result<()> {
self.check_modifiers(offset, modifiers, CLASS_MODIFIERS, "classes")?;
if self.body.kind == BodyKind::AmendingModule && !modifiers.contains(&Modifier::Local) {
return Err(self.semantic_error(offset, "Class needs a `local` modifier."));
}
self.declare(offset, name, false, modifiers.contains(&Modifier::Local))
}
pub(super) fn check_type_alias(
&mut self,
offset: usize,
modifiers: &[Modifier],
name: &str,
) -> Result<()> {
self.check_modifiers(offset, modifiers, TYPE_ALIAS_MODIFIERS, "type aliases")?;
if self.body.kind == BodyKind::AmendingModule && !modifiers.contains(&Modifier::Local) {
return Err(self.semantic_error(offset, "Type alias needs a `local` modifier."));
}
self.declare(offset, name, false, modifiers.contains(&Modifier::Local))
}
pub(super) fn check_union_defaults(
&self,
offset: usize,
variants: &[(super::TypeExpr, bool)],
) -> Result<()> {
let defaults = variants
.iter()
.filter(|(_, is_default)| *is_default)
.count();
if defaults == 0 {
Ok(())
} else if variants.len() == 1 {
Err(self.semantic_error(offset, "Only type unions can have a default marker (*)."))
} else if defaults > 1 {
Err(self.semantic_error(
offset,
"A type union cannot have more than one default type.",
))
} else {
Ok(())
}
}
pub(super) fn type_parameter_names(&self, start: usize) -> Vec<(String, usize)> {
let mut names = Vec::new();
let mut depth = 0;
for (i, tok) in self.tokens.iter().enumerate().skip(start) {
match &tok.kind {
TokenKind::Lt => depth += 1,
TokenKind::Gt if depth > 0 => {
depth -= 1;
if depth == 0 {
break;
}
}
TokenKind::Ident(name) if depth == 1 => {
let is_variance = name == "out"
&& matches!(
self.tokens.get(i + 1).map(|t| &t.kind),
Some(TokenKind::Ident(_))
);
if !is_variance {
names.push((name.clone(), tok.offset));
}
}
TokenKind::Eof => break,
_ => {}
}
}
names
}
pub(super) fn check_type_parameters(&self, start: usize) -> Result<()> {
let names = self.type_parameter_names(start);
for (i, (name, offset)) in names.iter().enumerate() {
if names[..i].iter().any(|(other, _)| other == name) {
return Err(
self.semantic_error(*offset, format!("Duplicate type parameter `{name}`."))
);
}
}
Ok(())
}
pub(super) fn type_parameters_error(&self) -> crate::error::Error {
self.semantic_error(
self.peek_tok().offset,
"Only standard library members can have type parameters.",
)
}
pub(super) fn check_method(
&mut self,
offset: usize,
modifiers: &[Modifier],
name: &str,
) -> Result<()> {
let local = modifiers.contains(&Modifier::Local);
match self.body.kind {
BodyKind::Module | BodyKind::Class => {
self.check_modifiers(offset, modifiers, METHOD_MODIFIERS, "methods")?;
}
BodyKind::For => {
return Err(self.semantic_error(
self.body.for_offset.unwrap_or(offset),
"A for-generator cannot generate object methods (only entries and elements).",
));
}
kind => {
self.check_modifiers(offset, modifiers, OBJECT_MEMBER_MODIFIERS, "object members")?;
if !local {
let message = if kind == BodyKind::AmendingModule {
"Method needs a `local` modifier."
} else {
"Method needs a `local` modifier because it is defined in an object, not a class."
};
return Err(self.semantic_error(offset, message));
}
}
}
self.declare(offset, name, true, local)
}
pub(super) fn check_property(&mut self, offset: usize, prop: &Property) -> Result<()> {
let local = prop.modifiers.contains(&Modifier::Local);
match self.body.kind {
BodyKind::Module | BodyKind::Class => {
self.check_modifiers(offset, &prop.modifiers, PROPERTY_MODIFIERS, "properties")?;
}
BodyKind::For => {
return Err(self.semantic_error(
self.body.for_offset.unwrap_or(offset),
"A for-generator cannot generate object properties (only entries and elements).",
));
}
_ => {
self.check_object_member_modifiers(offset, &prop.modifiers)?;
if !local && prop.type_ann.is_some() {
return Err(self.semantic_error(
offset,
"A non-local object property cannot have a type annotation.",
));
}
if local && prop.body.is_some() {
return Err(self
.semantic_error(offset, "A local property definition cannot be amended."));
}
}
}
if local && prop.value.is_none() && prop.body.is_none() {
return Err(self.semantic_error(offset, "Missing property value."));
}
self.declare(offset, &prop.name, false, local)
}
pub(super) fn check_entry_key(&mut self, offset: usize, key: &Expr) {
let key = match key {
Expr::String(s) => ConstKey::String(s.to_string()),
Expr::Int(i) => ConstKey::Int(*i),
Expr::Bool(b) => ConstKey::Bool(*b),
_ => return,
};
let shown = match &key {
ConstKey::String(s) => format!("\"{s}\""),
ConstKey::Int(i) => i.to_string(),
ConstKey::Bool(b) => b.to_string(),
};
if !self.body.entries.insert(key) && self.body.duplicate.is_none() {
self.body.duplicate = Some((offset, shown));
}
}
pub(super) fn for_scope(&self, for_offset: usize) -> BodyScope {
BodyScope::new(BodyKind::For, Some(for_offset))
}
pub(super) fn when_scope(&self) -> BodyScope {
match self.body.for_offset {
Some(offset) => BodyScope::new(BodyKind::For, Some(offset)),
None => BodyScope::new(BodyKind::When, None),
}
}
}