Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 18 additions & 44 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,9 +1196,11 @@ impl Scope {
/// An enum is a type alias for a nominal enum type, so its name resolves as a type
/// and its identity travels wherever the alias is imported.
///
/// Enums may only be declared at the top level of the program's own files
/// (the parser rejects declarations inside `mod` blocks, the driver rejects them in dependency files),
/// so the bare name is unique program-wide and identifies the enum in the ABI.
/// Identity is the declaration site, not the written name: `file_id` is the
/// source file the declaration came from, and it participates in the type's
/// equality. Two files may therefore each declare an `Action` without the
/// two becoming one type. `name` stays a display string, which is what
/// diagnostics print and what witness and argument files write.
///
/// ## Errors
///
Expand All @@ -1208,10 +1210,16 @@ impl Scope {
name: AliasName,
visibility: Visibility,
variants: Arc<[EnumVariantInfo]>,
span: Span,
) -> Result<(), Error> {
self.check_alias_free(&name)?;

let info = EnumInfo::new(Arc::clone(name.as_inner()), variants);
let info = EnumInfo::new(
Arc::clone(name.as_inner()),
variants,
span,
Arc::from(self.module_path.clone()),
);
let resolved = ResolvedType::enumeration(info);

self.current_module_mut()
Expand Down Expand Up @@ -1458,7 +1466,12 @@ impl AbstractSyntaxTree for Item {
})
.collect::<Result<Arc<[EnumVariantInfo]>, Diagnostic>>()?;
scope
.insert_enum(decl.name().clone(), decl.visibility().clone(), variants)
.insert_enum(
decl.name().clone(),
decl.visibility().clone(),
variants,
*decl.span(),
)
.with_span(decl)?;

Ok(Self::EnumDeclaration)
Expand Down Expand Up @@ -3270,45 +3283,6 @@ mod enum_tests {
assert!(result.is_err(), "redefined enum name should error");
}

#[test]
fn enum_declaration_inside_module_errors() {
// FIXME: Enums may only be declared at the top level of a file.
let result = analyze(
"mod m {
pub enum Choice { X, Y, }
}
fn main() {}",
);
let err = result.expect_err("enum inside `mod` must be rejected");
assert!(
err.contains("top level"),
"error should say enums are top-level only: {err}"
);
}

#[test]
fn enum_declaration_in_dependency_errors() {
use crate::ast::scope_resolution_tests::analyze_multifile;

// FIXME: An enum's declared name is its identity in the ABI, so enums may only be declared in the program's own files.
let result = analyze_multifile(vec![
(
"main.simf",
"use lib::A::helper;
fn main() { helper(); }",
),
(
"libs/lib/A.simf",
"pub enum Status { On, Off, } pub fn helper() {}",
),
]);
let err = result.expect_err("enums in dependency files must be rejected");
assert!(
err.contains("dependency"),
"error should say enums cannot live in dependency files: {err}"
);
}

#[test]
fn enum_payload_match_binds_payload() {
let result = analyze(
Expand Down
58 changes: 0 additions & 58 deletions src/driver/resolve_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,6 @@ use crate::error::{Diagnostic, DiagnosticManager, Error, Span};
use crate::parse::{self, Visibility};
use crate::str::{Identifier, ModuleName};

/// All enum declarations among `items`, recursing into `mod` blocks.
fn enum_declarations(items: &[parse::Item]) -> Vec<&parse::EnumDeclaration> {
let mut found = Vec::new();
for item in items {
match item {
parse::Item::EnumDeclaration(decl) => found.push(decl),
parse::Item::Module(module) => found.extend(enum_declarations(module.items())),
_ => {}
}
}
found
}

// TODO: allow enums in deps when mentioned problems are resolved
/// Enums by design are nominative, therefore to reason about same named enums in different modules
/// we have to have a stable ABI with the suport of "qualified name".
/// Currently, there is no support of "qualified name" concpet, therefore at the time of creating
/// enums, it is forbidden to decler them in dependencies.
///
/// If we used current ABI we would face following problems:
/// 1. Adding or removing an unrelated dependency renumbers the files, so the same enum's ABI
/// name changes between builds even though no source changed.
/// The whole point of "identity is the qualified name" is that serialized forms can identify an enum across builds.
/// 2. Unwritable witness files. A user filling in a witness would have
/// to write `unit_2::Action::Cold` (a name that appears nowhere in their source and that they can't predict).
/// 3. Meaningless nominal distinctness. `a::Action` vs `b::Action` being distinct types only makes
/// sense if a and b are the user's module names, not compiler-generated counters.
fn forbid_enum_dec_in_deps(
source_id: usize,
local_items: &[parse::Item],
diagnostics: &mut DiagnosticManager,
) {
if source_id == MAIN_MODULE {
return;
}

for decl in enum_declarations(local_items) {
diagnostics.push(Diagnostic::new(
Error::Grammar {
msg: format!(
"enum `{}` is declared in a dependency file; \
enums may only be declared in the program's own files",
decl.name()
),
},
*decl.as_ref(),
));
}
}

/// This is a core component of the [`DependencyGraph`].
impl DependencyGraph {
/// Resolves the dependency graph and constructs the final AST program.
Expand Down Expand Up @@ -113,14 +63,6 @@ impl DependencyGraph {
}
}

forbid_enum_dec_in_deps(source_id, &local_items, diagnostics);

// TODO(enums): the flattened output wraps every file — the
// entry file included — in a generated module, but enum
// declarations are only valid at the top level of a file, so
// flattening an enum program produces source that no longer
// re-parses (`TemplateAst::flatten`). Splice the entry
// file's items at the root instead of wrapping them.
let name = ModuleName::from_ident(&Self::get_module_name(source_id));
items.push(parse::Item::Module(parse::Module::new(
source_id,
Expand Down
Loading
Loading