Resolve TODO around handling non-plain strings

This commit is contained in:
Joshua Warner 2025-01-11 19:49:47 -08:00
parent e0ef01fa82
commit 61bc0b3464
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
7 changed files with 168 additions and 28 deletions

View file

@ -2453,38 +2453,33 @@ fn canonicalize_pending_value_def<'a>(
}
}
IngestedFile(loc_pattern, opt_loc_ann, path_literal) => {
let relative_path =
if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value {
ingested_path
} else {
todo!(
"Only plain strings are supported. Other cases should be made impossible here"
);
};
let expr = if let Some(relative_path) = extract_str_literal(env, path_literal) {
let mut file_path: PathBuf = env.module_path.into();
// Remove the header file name and push the new path.
file_path.pop();
file_path.push(relative_path);
let mut file_path: PathBuf = env.module_path.into();
// Remove the header file name and push the new path.
file_path.pop();
file_path.push(relative_path);
let mut bytes = vec![];
let mut bytes = vec![];
match fs::File::open(&file_path).and_then(|mut file| file.read_to_end(&mut bytes)) {
Ok(_) => {
Expr::IngestedFile(file_path.into(), Arc::new(bytes), var_store.fresh())
}
Err(e) => {
env.problems.push(Problem::FileProblem {
filename: file_path.to_path_buf(),
error: e.kind(),
});
let expr = match fs::File::open(&file_path)
.and_then(|mut file| file.read_to_end(&mut bytes))
{
Ok(_) => Expr::IngestedFile(file_path.into(), Arc::new(bytes), var_store.fresh()),
Err(e) => {
env.problems.push(Problem::FileProblem {
filename: file_path.to_path_buf(),
error: e.kind(),
});
Expr::RuntimeError(RuntimeError::ReadIngestedFileError {
filename: file_path.to_path_buf(),
error: e.kind(),
region: path_literal.region,
})
Expr::RuntimeError(RuntimeError::ReadIngestedFileError {
filename: file_path.to_path_buf(),
error: e.kind(),
region: path_literal.region,
})
}
}
} else {
Expr::RuntimeError(RuntimeError::IngestedFilePathError(path_literal.region))
};
let loc_expr = Loc::at(path_literal.region, expr);
@ -2539,6 +2534,68 @@ fn canonicalize_pending_value_def<'a>(
output
}
fn extract_str_literal<'a>(
env: &mut Env<'a>,
literal: Loc<ast::StrLiteral<'a>>,
) -> Option<&'a str> {
let relative_path = match literal.value {
ast::StrLiteral::PlainLine(ingested_path) => ingested_path,
ast::StrLiteral::Line(text) => {
let mut result_text = bumpalo::collections::String::new_in(env.arena);
if !extract_str_segments(env, text, &mut result_text) {
return None;
}
result_text.into_bump_str()
}
ast::StrLiteral::Block(lines) => {
let mut result_text = bumpalo::collections::String::new_in(env.arena);
for text in lines {
if !extract_str_segments(env, text, &mut result_text) {
return None;
}
}
result_text.into_bump_str()
}
};
Some(relative_path)
}
fn extract_str_segments<'a>(
env: &mut Env<'a>,
segments: &[ast::StrSegment<'a>],
result_text: &mut bumpalo::collections::String<'a>,
) -> bool {
for segment in segments.iter() {
match segment {
ast::StrSegment::Plaintext(t) => {
result_text.push_str(t);
}
ast::StrSegment::Unicode(t) => {
let hex_code: &str = t.value;
if let Some(c) = u32::from_str_radix(hex_code, 16)
.ok()
.and_then(char::from_u32)
{
result_text.push(c);
} else {
env.problem(Problem::InvalidUnicodeCodePt(t.region));
return false;
}
}
ast::StrSegment::EscapedChar(c) => {
result_text.push(c.unescape());
}
ast::StrSegment::Interpolated(e) => {
// TODO: maybe in the future we do want to allow building up the path with local bindings?
// This would require an interpreter tho; so for now we just disallow it.
env.problem(Problem::InterpolatedStringNotAllowed(e.region));
return false;
}
}
}
true
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)]

View file

@ -263,6 +263,7 @@ pub enum Problem {
field_region: Region,
record_region: Region,
},
InterpolatedStringNotAllowed(Region),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -280,6 +281,7 @@ impl Problem {
Problem::UnusedDef(_, _) => Warning,
Problem::UnusedImport(_, _) => Warning,
Problem::UnusedModuleImport(_, _) => Warning,
Problem::InterpolatedStringNotAllowed(_) => RuntimeError,
Problem::ImportNameConflict { .. } => RuntimeError,
Problem::ExplicitBuiltinImport(_, _) => Warning,
Problem::ExplicitBuiltinTypeImport(_, _) => Warning,
@ -376,6 +378,7 @@ impl Problem {
..
}
| Problem::ExplicitBuiltinImport(_, region)
| Problem::InterpolatedStringNotAllowed(region)
| Problem::ExplicitBuiltinTypeImport(_, region)
| Problem::ImportShadowsSymbol { region, .. }
| Problem::UnusedArgument(_, _, _, region)
@ -687,6 +690,7 @@ pub enum RuntimeError {
NonFunctionHostedAnnotation(Region),
InvalidTupleIndex(Region),
IngestedFilePathError(Region),
}
impl RuntimeError {
@ -725,6 +729,7 @@ impl RuntimeError {
| RuntimeError::InvalidInt(_, _, region, _)
| RuntimeError::EmptySingleQuote(region)
| RuntimeError::InvalidTupleIndex(region)
| RuntimeError::IngestedFilePathError(region)
| RuntimeError::MultipleCharsInSingleQuote(region)
| RuntimeError::DegenerateBranch(region)
| RuntimeError::InvalidInterpolation(region)

View file

@ -0,0 +1,2 @@
import "\\" as m
e

View file

@ -0,0 +1,55 @@
@0-16 SpaceAfter(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@0-14,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
IngestedFileImport(
IngestedFileImport {
before_path: [],
path: @6-10 Line(
[
EscapedChar(
Backslash,
),
],
),
name: KeywordItem {
keyword: Spaces {
before: [],
item: ImportAsKeyword,
after: [],
},
item: @13-14 "m",
},
annotation: None,
},
),
],
},
@15-16 SpaceBefore(
Var {
module_name: "",
ident: "e",
},
[
Newline,
],
),
),
[
Newline,
],
)

View file

@ -0,0 +1,2 @@
import"\\"as m
e

View file

@ -473,6 +473,7 @@ mod test_snapshots {
pass/implements_not_keyword.expr,
pass/implements_record_destructure.expr,
pass/import.moduledefs,
pass/import_backslash_as_m.expr,
pass/import_from_package.moduledefs,
pass/import_in_closure_with_curlies_after.expr,
pass/import_with_alias.moduledefs,

View file

@ -67,6 +67,7 @@ const MISSING_EXCLAMATION: &str = "MISSING EXCLAMATION";
const UNNECESSARY_EXCLAMATION: &str = "UNNECESSARY EXCLAMATION";
const EMPTY_TUPLE_TYPE: &str = "EMPTY TUPLE TYPE";
const UNBOUND_TYPE_VARS_IN_AS: &str = "UNBOUND TYPE VARIABLES IN AS";
const INTERPOLATED_STRING_NOT_ALLOWED: &str = "INTERPOLATED STRING NOT ALLOWED";
pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>,
@ -1478,6 +1479,15 @@ pub fn can_problem<'b>(
title = UNBOUND_TYPE_VARS_IN_AS.to_string();
}
Problem::InterpolatedStringNotAllowed(region) => {
doc = alloc.stack([
alloc.reflow("Interpolated strings are not allowed here:"),
alloc.region(lines.convert_region(region), severity),
alloc.reflow(r#"Only plain strings like "foo" or "foo\n" are allowed."#),
]);
title = INTERPOLATED_STRING_NOT_ALLOWED.to_string();
}
};
Report {
@ -2249,6 +2259,14 @@ fn pretty_runtime_error<'b>(
doc = report.doc;
title = INGESTED_FILE_ERROR;
}
RuntimeError::IngestedFilePathError(region) => {
doc = alloc.stack([
alloc.reflow(r"I tried to read this file, but something about the path is wrong:"),
alloc.region(lines.convert_region(region), severity),
]);
title = INGESTED_FILE_ERROR;
}
RuntimeError::InvalidPrecedence(_, _) => {
// do nothing, reported with PrecedenceProblem
unreachable!();