[DEV-8000] ir::asg: Error types for unresolved identifiers during sorting

This checks explicitly for unresolved objects while sorting and provides an
explicit error for them.  For example, this will catch externs that have no
concrete resolution.

This previously fell all the way through to the unreachable! block.  The old
POC implementation was catching unresolved objects, albeit with a debug
error.
master
Mike Gerwitz 2020-07-01 15:40:21 -04:00
parent a2415c8c6f
commit 96ffd5f6e5
6 changed files with 292 additions and 7 deletions

View File

@ -14,6 +14,15 @@ commits that introduce the changes. To make a new release, run
`tools/mkrelease`, which will handle updating the heading for you. `tools/mkrelease`, which will handle updating the heading for you.
NEXT
====
Linker
------
- Provide useful error for unresolved identifiers.
- This was previously falling through to an `unreachable!` block,
producing a very opaque and useless internal error message.
v17.4.2 (2020-05-13) v17.4.2 (2020-05-13)
==================== ====================
This release adds GraphML output for linked objects to allow us to This release adds GraphML output for linked objects to allow us to

View File

@ -295,7 +295,7 @@ where
} }
while let Some(index) = dfs.next(&self.graph) { while let Some(index) = dfs.next(&self.graph) {
let ident = self.get(index).expect("missing node"); let ident = self.get(index).expect("missing node").resolved()?;
match ident.kind() { match ident.kind() {
Some(kind) => match kind { Some(kind) => match kind {
@ -386,7 +386,9 @@ where
mod test { mod test {
use super::super::graph::AsgError; use super::super::graph::AsgError;
use super::*; use super::*;
use crate::ir::asg::{Dim, IdentObject, TransitionError, TransitionResult}; use crate::ir::asg::{
Dim, IdentObject, TransitionError, TransitionResult, UnresolvedError,
};
use crate::ir::legacyir::SymDtype; use crate::ir::legacyir::SymDtype;
use crate::sym::SymbolIndex; use crate::sym::SymbolIndex;
use std::cell::RefCell; use std::cell::RefCell;
@ -400,6 +402,7 @@ mod test {
fail_redeclare: RefCell<Option<TransitionError>>, fail_redeclare: RefCell<Option<TransitionError>>,
fail_extern: RefCell<Option<TransitionError>>, fail_extern: RefCell<Option<TransitionError>>,
fail_set_fragment: RefCell<Option<TransitionError>>, fail_set_fragment: RefCell<Option<TransitionError>>,
fail_resolved: RefCell<Option<UnresolvedError>>,
} }
impl<'i> IdentObjectData<'i> for StubIdentObject<'i> { impl<'i> IdentObjectData<'i> for StubIdentObject<'i> {
@ -446,6 +449,14 @@ mod test {
Ok(self) Ok(self)
} }
fn resolved(&self) -> Result<&StubIdentObject<'i>, UnresolvedError> {
if self.fail_resolved.borrow().is_some() {
return Err(self.fail_resolved.replace(None).unwrap());
}
Ok(self)
}
fn extern_( fn extern_(
mut self, mut self,
kind: IdentKind, kind: IdentKind,
@ -1514,4 +1525,36 @@ mod test {
Ok(()) Ok(())
} }
/// A graph containing unresolved objects cannot be sorted.
#[test]
fn graph_sort_fail_unresolved() -> SortableAsgResult<(), u8> {
let mut sut = Sut::new();
let sym = symbol_dummy!(1, "unresolved");
let node = sut
.declare(&sym, IdentKind::Meta, Default::default())
.unwrap();
let ident = sut.get(node).unwrap();
let expected = UnresolvedError::Missing {
name: sym.to_string(),
};
// Cause resolved() to fail.
ident.fail_resolved.replace(Some(expected.clone()));
let result = sut
.sort(&vec![node])
.expect_err("expected sort failure due to unresolved identifier");
match result {
SortableAsgError::UnresolvedObject(err) => {
assert_eq!(expected, err);
}
_ => panic!("expected SortableAsgError::Unresolved: {:?}", result),
}
Ok(())
}
} }

View File

@ -22,6 +22,7 @@
use super::ident::IdentKind; use super::ident::IdentKind;
use super::object::{ use super::object::{
FragmentText, IdentObjectData, IdentObjectState, Source, TransitionError, FragmentText, IdentObjectData, IdentObjectState, Source, TransitionError,
UnresolvedError,
}; };
use super::Sections; use super::Sections;
use crate::sym::Symbol; use crate::sym::Symbol;
@ -186,6 +187,11 @@ where
O: IdentObjectData<'i>, O: IdentObjectData<'i>,
Ix: IndexType, Ix: IndexType,
{ {
/// Sort graph into [`Sections`].
///
/// Sorting will fail if the graph contains unresolved objects,
/// or identifiers whose kind cannot be determined
/// (see [`UnresolvedError`]).
fn sort( fn sort(
&'i self, &'i self,
roots: &[ObjectRef<Ix>], roots: &[ObjectRef<Ix>],
@ -292,6 +298,16 @@ impl From<TransitionError> for AsgError {
/// sorting process. /// sorting process.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum SortableAsgError<Ix: IndexType> { pub enum SortableAsgError<Ix: IndexType> {
/// An unresolved object was encountered during sorting.
///
/// An unresolved object means that the graph has an incomplete picture
/// of the program,
/// and so sorting cannot be reliably performed.
/// Since all objects are supposed to be resolved prior to sorting,
/// this represents either a problem with the program being compiled
/// or a bug in the compiler itself.
UnresolvedObject(UnresolvedError),
/// The kind of an object encountered during sorting could not be /// The kind of an object encountered during sorting could not be
/// determined. /// determined.
/// ///
@ -305,9 +321,16 @@ pub enum SortableAsgError<Ix: IndexType> {
Cycles(Vec<Vec<ObjectRef<Ix>>>), Cycles(Vec<Vec<ObjectRef<Ix>>>),
} }
impl<Ix: IndexType> From<UnresolvedError> for SortableAsgError<Ix> {
fn from(err: UnresolvedError) -> Self {
Self::UnresolvedObject(err)
}
}
impl<Ix: IndexType> std::fmt::Display for SortableAsgError<Ix> { impl<Ix: IndexType> std::fmt::Display for SortableAsgError<Ix> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Self::UnresolvedObject(err) => std::fmt::Display::fmt(err, fmt),
Self::MissingObjectKind(name) => write!( Self::MissingObjectKind(name) => write!(
fmt, fmt,
"internal error: missing object kind for object `{}` (this may be a compiler bug!)", "internal error: missing object kind for object `{}` (this may be a compiler bug!)",
@ -320,7 +343,10 @@ impl<Ix: IndexType> std::fmt::Display for SortableAsgError<Ix> {
impl<Ix: IndexType> std::error::Error for SortableAsgError<Ix> { impl<Ix: IndexType> std::error::Error for SortableAsgError<Ix> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None match self {
Self::UnresolvedObject(err) => Some(err),
_ => None,
}
} }
} }

View File

@ -203,7 +203,7 @@ pub use graph::{
pub use ident::{DataType, Dim, IdentKind, IdentKindError}; pub use ident::{DataType, Dim, IdentKind, IdentKindError};
pub use object::{ pub use object::{
FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source, FragmentText, IdentObject, IdentObjectData, IdentObjectState, Source,
TransitionError, TransitionResult, TransitionError, TransitionResult, UnresolvedError,
}; };
pub use section::{Section, SectionIterator, Sections}; pub use section::{Section, SectionIterator, Sections};

View File

@ -212,6 +212,23 @@ where
/// since rules may vary between implementations. /// since rules may vary between implementations.
fn resolve(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>; fn resolve(self, kind: IdentKind, src: Source<'i>) -> TransitionResult<T>;
/// Assertion to return self if identifier is resolved,
/// otherwise failing with [`UnresolvedError`].
///
/// This simplifies working with identifiers without having to match on
/// specific variants,
/// and will continue to work if new variants are added in the
/// future that are considered to be unresolved.
///
/// Since this does not cause a state transition and is useful in
/// contexts where ownership over the identifier is not possible,
/// this accepts and returns a reference to the identifier.
///
/// At present,
/// both [`IdentObject::Missing`] and [`IdentObject::Extern`] are
/// considered to be unresolved.
fn resolved(&self) -> Result<&T, UnresolvedError>;
/// Resolve identifier against an extern declaration or produce an /// Resolve identifier against an extern declaration or produce an
/// extern. /// extern.
/// ///
@ -373,6 +390,25 @@ impl<'i> IdentObjectState<'i, IdentObject<'i>> for IdentObject<'i> {
} }
} }
fn resolved(&self) -> Result<&IdentObject<'i>, UnresolvedError> {
match self {
IdentObject::Missing(name) => Err(UnresolvedError::Missing {
name: name.to_string(),
}),
IdentObject::Extern(name, ref kind, ref src) => {
Err(UnresolvedError::Extern {
name: name.to_string(),
kind: kind.clone(),
pkg_name: src.pkg_name.map(|s| s.to_string()),
})
}
IdentObject::Ident(_, _, _)
| IdentObject::IdentFragment(_, _, _, _) => Ok(self),
}
}
fn extern_( fn extern_(
self, self,
kind: IdentKind, kind: IdentKind,
@ -524,6 +560,52 @@ impl std::error::Error for TransitionError {
} }
} }
/// Resolved identifier was expected.
#[derive(Clone, Debug, PartialEq)]
pub enum UnresolvedError {
/// Expected identifier is missing and nothing about it is known.
Missing { name: String },
/// Expected identifier has not yet been resolved with a concrete
/// definition.
Extern {
/// Identifier name.
name: String,
/// Expected identifier type.
kind: IdentKind,
/// Name of package where the extern was defined.
pkg_name: Option<String>,
},
}
impl std::fmt::Display for UnresolvedError {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
UnresolvedError::Missing { name } => {
write!(fmt, "missing expected identifier `{}`", name,)
}
UnresolvedError::Extern {
name,
kind,
pkg_name,
} => write!(
fmt,
"unresolved extern `{}` of type `{}`, declared in `{}`",
name,
kind,
pkg_name.as_ref().unwrap_or(&"<unknown>".into()),
),
}
}
}
impl std::error::Error for UnresolvedError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
/// Compiled fragment for identifier. /// Compiled fragment for identifier.
/// ///
/// This represents the text associated with an identifier. /// This represents the text associated with an identifier.
@ -782,8 +864,24 @@ mod test {
} }
#[test] #[test]
fn ident_object_ident() { fn resolved_on_missing() {
let sym = symbol_dummy!(1, "missing"); let sym = symbol_dummy!(1, "missing");
let result = IdentObject::declare(&sym)
.resolved()
.expect_err("expected error asserting resolved() on missing");
match result {
UnresolvedError::Missing { name: e_name } => {
assert_eq!(sym.to_string(), e_name);
}
_ => panic!("expected UnresolvedError {:?}", result),
}
}
#[test]
fn ident_object_ident() {
let sym = symbol_dummy!(1, "ident");
let kind = IdentKind::Meta; let kind = IdentKind::Meta;
let src = Source { let src = Source {
desc: Some("ident ctor".into()), desc: Some("ident ctor".into()),
@ -798,6 +896,25 @@ mod test {
); );
} }
#[test]
fn resolved_on_ident() {
let sym = symbol_dummy!(1, "ident resolve");
let kind = IdentKind::Meta;
let src = Source {
desc: Some("ident ctor".into()),
..Default::default()
};
assert_eq!(
&IdentObject::Ident(&sym, kind.clone(), src.clone()),
IdentObject::declare(&sym)
.resolve(kind.clone(), src.clone())
.unwrap()
.resolved()
.unwrap(),
);
}
// Note that we don't care about similar sources. It's expected // Note that we don't care about similar sources. It's expected
// that the system populating the ASG will only resolve local // that the system populating the ASG will only resolve local
// symbols, and so redeclarations should represent that multiple // symbols, and so redeclarations should represent that multiple
@ -834,7 +951,7 @@ mod test {
#[test] #[test]
fn ident_object() { fn ident_object() {
let sym = symbol_dummy!(1, "missing"); let sym = symbol_dummy!(1, "extern");
let kind = IdentKind::Class(Dim::from_u8(1)); let kind = IdentKind::Class(Dim::from_u8(1));
let src = Source { let src = Source {
desc: Some("extern".into()), desc: Some("extern".into()),
@ -847,6 +964,72 @@ mod test {
); );
} }
#[test]
fn resolved_on_extern() {
let sym = symbol_dummy!(1, "extern resolved");
let kind = IdentKind::Class(Dim::from_u8(1));
let pkg_name = symbol_dummy!(2, "pkg/name");
let src = Source {
pkg_name: Some(&pkg_name),
desc: Some("extern".into()),
..Default::default()
};
let result =
IdentObject::Extern(&sym, kind.clone(), src.clone())
.resolved()
.expect_err(
"expected error asserting resolved() on extern",
);
match result {
UnresolvedError::Extern {
name: e_name,
kind: e_kind,
pkg_name: e_pkg_name,
} => {
assert_eq!(sym.to_string(), e_name);
assert_eq!(kind, e_kind);
assert_eq!(Some(pkg_name.to_string()), e_pkg_name);
}
_ => panic!("expected UnresolvedError: {:?}", result),
}
}
#[test]
fn resolved_on_extern_error_fmt_without_pkg() {
let meta = IdentKind::Meta;
let err = UnresolvedError::Extern {
name: "foo".into(),
kind: IdentKind::Meta,
pkg_name: None,
};
let msg = format!("{}", err);
assert!(msg.contains("`foo`"));
assert!(msg.contains("in `<unknown>`"));
assert!(msg.contains(&format!("`{}`", meta)));
}
#[test]
fn resolved_on_extern_error_fmt_with_pkg() {
let meta = IdentKind::Meta;
let pkg = "pkg".to_string();
let err = UnresolvedError::Extern {
name: "foo".into(),
kind: IdentKind::Meta,
pkg_name: Some(pkg.clone()),
};
let msg = format!("{}", err);
assert!(msg.contains("`foo`"));
assert!(msg.contains(&format!("in `{}`", pkg)));
assert!(msg.contains(&format!("`{}`", meta)));
}
// Extern first, then identifier // Extern first, then identifier
#[test] #[test]
fn redeclare_compatible_resolves() { fn redeclare_compatible_resolves() {
@ -1020,6 +1203,27 @@ mod test {
); );
} }
#[test]
fn resolved_on_fragment() {
let sym = symbol_dummy!(1, "tofrag resolved");
let src = Source {
generated: true,
..Default::default()
};
let kind = IdentKind::Meta;
let ident = IdentObject::declare(&sym)
.resolve(kind.clone(), src.clone())
.unwrap();
let text = FragmentText::from("a fragment for resolved()");
let ident_with_frag = ident.set_fragment(text.clone());
assert_eq!(
Ok(&IdentObject::IdentFragment(&sym, kind, src, text)),
ident_with_frag.unwrap().resolved(),
);
}
#[test] #[test]
fn add_fragment_to_fragment_fails() { fn add_fragment_to_fragment_fails() {
let sym = symbol_dummy!(1, "badsym"); let sym = symbol_dummy!(1, "badsym");

View File

@ -294,7 +294,10 @@ impl<W: Write> XmleWriter<W> {
self.writer.write_event(Event::Empty(sym))?; self.writer.write_event(Event::Empty(sym))?;
} }
_ => unreachable!("filtered out during sorting"), _ => unreachable!(
"identifier should have been filtered out during sorting: {:?}",
ident,
),
} }
} }