use llvmkit_asmparser::ll_parser::Parser;
use llvmkit_asmparser::parse_error::ParseError;
use llvmkit_ir::Module;
fn parse_and_render(src: &str) -> String {
Module::with_new("forward_refs", |module| {
Parser::new(src.as_bytes(), &module)
.expect("lexer primes")
.parse_module()
.expect("parser succeeds");
format!("{module}")
})
}
fn parse_err(src: &str) -> ParseError {
Module::with_new("forward_refs", |module| {
Parser::new(src.as_bytes(), &module)
.expect("lexer primes")
.parse_module()
.expect_err("parser rejects malformed input")
})
}
fn parse_ok(src: &str) {
Module::with_new("forward_refs", |module| {
Parser::new(src.as_bytes(), &module)
.expect("lexer primes")
.parse_module()
.expect("parser succeeds");
});
}
#[test]
fn skip_value_numbers_invalid_is_rejected() {
let err = parse_err(
"define i32 @f() {\nentry:\n %0 = add i32 1, 2\n %0 = add i32 3, 4\n ret i32 %0\n}\n",
);
assert!(matches!(err, ParseError::InvalidSlotId { .. }));
}
#[test]
fn skip_ahead_value_number_is_rejected() {
let err = parse_err("define i32 @f() {\nentry:\n %2 = add i32 1, 2\n ret i32 %2\n}\n");
assert!(matches!(err, ParseError::InvalidSlotId { .. }));
}
#[test]
fn skip_ahead_numbered_parameter_is_rejected() {
let err = parse_err("define i32 @f(i32 %2) {\nentry:\n ret i32 %2\n}\n");
assert!(matches!(err, ParseError::InvalidSlotId { .. }));
}
#[test]
fn invalid_numbered_var_is_rejected() {
let err = parse_err("define i32 @f() {\nentry:\n ret i32 %1\n}\n");
assert!(matches!(err, ParseError::UndefinedSymbol { .. }));
}
#[test]
fn unnamed_forward_ref_is_rejected() {
let err = parse_err(
"define i32 @f() {\nentry:\n %0 = add i32 %1, 1\n %1 = add i32 2, 3\n ret i32 %0\n}\n",
);
assert!(matches!(err, ParseError::UndefinedSymbol { .. }));
}
#[test]
fn unresolved_global_reference_is_rejected() {
let err = parse_err("define i32 @f() {\nentry:\n %0 = call i32 @missing()\n ret i32 %0\n}\n");
assert!(matches!(err, ParseError::UndefinedSymbol { .. }));
}
#[test]
fn upstream_forward_global_reference_fixture_parses() {
parse_ok(include_str!(
"fixtures/upstream/Assembler/2002-05-02-InvalidForwardRef.ll"
));
}
#[test]
fn forward_global_reference_signature_mismatch_is_rejected() {
let err = parse_err(
"define void @test() {\nentry:\n %a = call i32 @foo()\n %b = call i64 @foo()\n ret void\n}\ndeclare i32 @foo()\n",
);
match err {
ParseError::Expected { expected, .. } => {
assert_eq!(expected, "function callee signature mismatch");
}
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn forward_function_definition_applies_parameter_names() {
let text = parse_and_render(
"define i32 @caller() {\n\
entry:\n \
%r = call i32 @callee(i32 7)\n \
ret i32 %r\n\
}\n\
define i32 @callee(i32 %x) {\n\
entry:\n \
ret i32 %x\n\
}\n",
);
assert!(text.contains("define i32 @callee(i32 %x)"), "{text}");
assert!(text.contains("ret i32 %x"), "{text}");
}
#[test]
fn undefined_block_label_is_rejected() {
let err = parse_err("define void @f() {\nentry:\n br label %missing\n}\n");
assert!(matches!(err, ParseError::UndefinedSymbol { .. }));
}
#[test]
fn numbered_declare_records_slot_mapping() {
Module::with_new("numbered_declare", |module| {
let parsed = Parser::new(b"declare void @0()\n", &module)
.expect("lexer primes")
.parse_module()
.expect("parser succeeds");
assert!(parsed.slot_mapping.global_values.get(0).is_some());
});
}