use super::*;
#[test]
fn test_generic_result_alias_supplies_real_error_type() {
let source = r#"
pub struct ConversionError;
pub struct ConversionResult;
pub type Result<T> = std::result::Result<T, ConversionError>;
pub fn convert(html: &str) -> Result<ConversionResult> {
unimplemented!()
}
"#;
let surface = extract_from_source(source);
let convert = surface.functions.iter().find(|f| f.name == "convert").unwrap();
assert_eq!(
convert.error_type.as_deref(),
Some("ConversionError"),
"generic Result<T> alias must resolve error_type from its own definition, got: {:?}",
convert.error_type
);
}
#[test]
fn test_generic_result_alias_supplies_real_error_type_for_method() {
let source = r#"
pub struct ConversionError;
pub struct ConversionResult;
pub type Result<T> = std::result::Result<T, ConversionError>;
pub struct Converter;
impl Converter {
pub fn convert(&self, html: &str) -> Result<ConversionResult> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let converter = surface.types.iter().find(|t| t.name == "Converter").unwrap();
let convert = converter.methods.iter().find(|m| m.name == "convert").unwrap();
assert_eq!(
convert.error_type.as_deref(),
Some("ConversionError"),
"generic Result<T> alias must resolve error_type from its own definition, got: {:?}",
convert.error_type
);
}
#[test]
fn test_result_alias_resolves_when_declared_in_a_different_module() {
let source = r#"
pub struct ConversionError;
pub struct ConversionResult;
pub type Result<T> = std::result::Result<T, ConversionError>;
pub mod convert_api {
use super::{ConversionResult, Result};
pub fn convert(html: &str) -> Result<ConversionResult> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let convert = surface.functions.iter().find(|f| f.name == "convert").unwrap();
assert_eq!(
convert.error_type.as_deref(),
Some("ConversionError"),
"alias declared in a sibling module must still supply the error type, got: {:?}",
convert.error_type
);
}
const CRATE_WITH_PRIVATE_SUBSYSTEM_ALIAS: &str = r#"
pub mod error {
pub struct SampleCrateError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
}
pub mod extraction {
pub mod binary {
pub mod error {
pub struct BinaryFormatError;
pub type Result<T> = std::result::Result<T, BinaryFormatError>;
}
pub mod model {
use super::error::Result;
pub fn parse_header(bytes: &[u8]) -> Result<u32> {
unimplemented!()
}
}
}
}
pub mod plugins {
use crate::Result;
pub struct Embedding;
pub trait EmbeddingBackend {
fn embed(&self, texts: Vec<String>) -> Result<Embedding>;
}
}
pub use error::{Result, SampleCrateError};
"#;
#[test]
fn test_trait_method_uses_the_canonical_alias_not_a_module_private_one() {
let surface = extract_from_source(CRATE_WITH_PRIVATE_SUBSYSTEM_ALIAS);
let backend = surface
.types
.iter()
.find(|t| t.name == "EmbeddingBackend")
.expect("trait must be extracted");
let embed = backend.methods.iter().find(|m| m.name == "embed").unwrap();
assert_eq!(
embed.error_type.as_deref(),
Some("SampleCrateError"),
"a trait importing crate::Result must resolve to the crate's exported error type, got: {:?}",
embed.error_type
);
}
#[test]
fn test_module_private_alias_still_applies_inside_its_own_subsystem() {
let surface = extract_from_source(CRATE_WITH_PRIVATE_SUBSYSTEM_ALIAS);
let parse_header = surface
.functions
.iter()
.find(|f| f.name == "parse_header")
.expect("subsystem function must be extracted");
assert_eq!(
parse_header.error_type.as_deref(),
Some("BinaryFormatError"),
"a module importing its own subsystem alias keeps that alias's error type, got: {:?}",
parse_header.error_type
);
}
#[test]
fn test_foreign_result_alias_is_not_replaced_by_the_crate_error() {
let source = r#"
pub mod error {
pub struct SampleCrateError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
}
pub mod scripting {
use anyhow::Result;
pub struct Script;
pub fn compile(source: &str) -> Result<Script> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let compile = surface.functions.iter().find(|f| f.name == "compile").unwrap();
assert_eq!(
compile.error_type.as_deref(),
Some("anyhow::Error"),
"anyhow::Result must not be rewritten to the crate error type, got: {:?}",
compile.error_type
);
}
#[test]
fn test_crate_qualified_result_beats_a_foreign_result_import() {
let source = r#"
pub mod error {
pub struct SampleCrateError;
}
pub type Result<T> = std::result::Result<T, error::SampleCrateError>;
pub mod api {
use anyhow::Result;
pub struct Extraction;
pub fn extract(input: &str) -> crate::Result<Extraction> {
unimplemented!()
}
pub fn helper(input: &str) -> Result<u32> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let extract = surface.functions.iter().find(|f| f.name == "extract").unwrap();
assert_eq!(
extract.error_type.as_deref(),
Some("error::SampleCrateError"),
"crate::Result<T> must resolve through the crate alias, got: {:?}",
extract.error_type
);
let helper = surface.functions.iter().find(|f| f.name == "helper").unwrap();
assert_eq!(
helper.error_type.as_deref(),
Some("anyhow::Error"),
"the bare imported anyhow::Result must keep anyhow::Error, got: {:?}",
helper.error_type
);
}
#[test]
fn test_foreign_qualified_result_is_not_rewritten_to_the_crate_error() {
let source = r#"
pub struct SampleCrateError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
pub mod api {
use crate::Result;
pub struct Script;
pub fn compile(source: &str) -> anyhow::Result<Script> {
unimplemented!()
}
pub fn run(source: &str) -> Result<u32> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let compile = surface.functions.iter().find(|f| f.name == "compile").unwrap();
assert_eq!(
compile.error_type.as_deref(),
Some("anyhow::Error"),
"an inline anyhow::Result must keep anyhow::Error, got: {:?}",
compile.error_type
);
let run = surface.functions.iter().find(|f| f.name == "run").unwrap();
assert_eq!(
run.error_type.as_deref(),
Some("SampleCrateError"),
"the imported crate alias must still supply the crate error, got: {:?}",
run.error_type
);
}
#[test]
fn test_super_qualified_result_resolves_against_the_parent_module() {
let source = r#"
pub mod error {
pub struct SampleCrateError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
}
pub mod binary {
pub mod error {
pub struct BinaryFormatError;
pub type Result<T> = std::result::Result<T, BinaryFormatError>;
}
pub mod model {
pub fn parse_header(bytes: &str) -> super::error::Result<u32> {
unimplemented!()
}
}
}
pub use error::{Result, SampleCrateError};
"#;
let surface = extract_from_source(source);
let parse_header = surface.functions.iter().find(|f| f.name == "parse_header").unwrap();
assert_eq!(
parse_header.error_type.as_deref(),
Some("BinaryFormatError"),
"super::error::Result must resolve to the parent module's alias, got: {:?}",
parse_header.error_type
);
}
#[test]
fn test_unqualified_result_still_falls_back_to_the_canonical_alias() {
let source = r#"
pub mod error {
pub struct SampleCrateError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
}
pub mod api {
pub fn load(path: &str) -> Result<u32> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let load = surface.functions.iter().find(|f| f.name == "load").unwrap();
assert_eq!(
load.error_type.as_deref(),
Some("SampleCrateError"),
"an unqualified Result must still reach the canonical alias, got: {:?}",
load.error_type
);
}
#[test]
fn test_crate_alias_over_anyhow_error_stays_anyhow() {
let source = r#"
pub type Result<T> = std::result::Result<T, anyhow::Error>;
pub mod api {
pub struct Report;
pub fn build() -> crate::Result<Report> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let build = surface.functions.iter().find(|f| f.name == "build").unwrap();
assert_eq!(
build.error_type.as_deref(),
Some("anyhow::Error"),
"an alias whose error really is anyhow::Error must stay anyhow::Error, got: {:?}",
build.error_type
);
}
#[test]
fn test_inline_two_parameter_result_ignores_the_alias() {
let source = r#"
pub struct SampleCrateError;
pub struct ParseError;
pub type Result<T> = std::result::Result<T, SampleCrateError>;
pub mod api {
use crate::{ParseError, Result};
pub fn parse(input: &str) -> std::result::Result<u32, ParseError> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let parse = surface.functions.iter().find(|f| f.name == "parse").unwrap();
assert_eq!(
parse.error_type.as_deref(),
Some("ParseError"),
"an explicit two-parameter Result must keep its own error type, got: {:?}",
parse.error_type
);
}
#[test]
fn test_alias_generic_over_its_error_parameter_uses_the_default() {
let source = r#"
pub struct SampleCrateError;
pub type Result<T, E = SampleCrateError> = std::result::Result<T, E>;
pub mod api {
use crate::Result;
pub fn load(path: &str) -> Result<u32> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let load = surface.functions.iter().find(|f| f.name == "load").unwrap();
assert_eq!(
load.error_type.as_deref(),
Some("SampleCrateError"),
"the error parameter's default is the alias's real error type, got: {:?}",
load.error_type
);
}
#[test]
fn test_alias_generic_over_an_undefaulted_error_parameter_records_no_hint() {
let source = r#"
pub type Result<T, E> = std::result::Result<T, E>;
pub mod api {
use crate::Result;
pub fn load(path: &str) -> Result<u32> {
unimplemented!()
}
}
"#;
let surface = extract_from_source(source);
let load = surface.functions.iter().find(|f| f.name == "load").unwrap();
assert_eq!(
load.error_type.as_deref(),
Some("anyhow::Error"),
"a bare generic parameter must never be recorded as an error type, got: {:?}",
load.error_type
);
}
#[test]
fn test_error_type_matches_the_declared_enum_named_in_an_explicit_two_error_crate() {
let source = r#"
pub enum FirstError {
Boom,
}
pub enum SecondError {
Bang,
}
pub struct Widget;
pub fn build(input: &str) -> std::result::Result<Widget, SecondError> {
unimplemented!()
}
"#;
let surface = extract_from_source(source);
let build = surface.functions.iter().find(|f| f.name == "build").unwrap();
assert_eq!(
build.error_type.as_deref(),
Some("SecondError"),
"an explicit two-parameter Result naming the second declared enum must resolve to it \
exactly, not the first-declared enum, got: {:?}",
build.error_type
);
}
#[test]
fn test_error_type_resolves_correctly_through_a_cross_module_alias_with_two_error_enums() {
let source = r#"
pub mod primary_error {
pub enum FirstError {
Boom,
}
pub type Result<T> = std::result::Result<T, FirstError>;
}
pub mod secondary {
pub mod error {
pub enum SecondError {
Bang,
}
pub type Result<T> = std::result::Result<T, SecondError>;
}
pub mod api {
use super::error::Result;
pub struct Widget;
pub fn build(input: &str) -> Result<Widget> {
unimplemented!()
}
}
}
"#;
let surface = extract_from_source(source);
let build = surface.functions.iter().find(|f| f.name == "build").unwrap();
assert_eq!(
build.error_type.as_deref(),
Some("SecondError"),
"a Result<T> alias declared in a sibling module, in a crate with two error enums, must \
resolve to its own module's error type, got: {:?}",
build.error_type
);
}