Skip to content

Commit d67ca70

Browse files
committed
enhance errors with line numbers
1 parent 8721a8a commit d67ca70

File tree

9 files changed

+82
-19
lines changed

9 files changed

+82
-19
lines changed

assets/tests/push_children/adding_empty_list_does_nothing.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ local entity = world.spawn()
22

33
world.push_children(entity, {})
44

5-
assert(#world.get_children(entity) == 0)
5+
assert(#world.get_children(entity) == 0)

crates/bevy_mod_scripting_asset/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ categories.workspace = true
1212
readme.workspace = true
1313

1414
[dependencies]
15+
bevy_mod_scripting_display = { workspace = true }
16+
bevy_mod_scripting_derive = { workspace = true }
1517
bevy_reflect = { workspace = true }
1618
bevy_asset = { workspace = true }
1719
bevy_log = { workspace = true }

crates/bevy_mod_scripting_asset/src/language.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
//! Defines supported scripting languages and their file extensions.
22
3+
use bevy_mod_scripting_derive::DebugWithTypeInfo;
34
use serde::{Deserialize, Serialize};
45
use std::borrow::Cow;
56

67
/// Represents a scripting language. Languages which compile into another language should use the target language as their language.
7-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
8+
#[derive(
9+
Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, DebugWithTypeInfo,
10+
)]
11+
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
812
pub enum Language {
913
/// The Rhai scripting language
1014
Rhai,

crates/bevy_mod_scripting_bindings/src/error.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Error types for the bindings
22
use crate::{
3-
Namespace, ReflectBaseType, ReflectReference, access_map::ReflectAccessId,
3+
FunctionCallContext, Namespace, ReflectBaseType, ReflectReference, access_map::ReflectAccessId,
44
script_value::ScriptValue,
55
};
66
use bevy_ecs::entity::Entity;
7+
use bevy_mod_scripting_asset::Language;
78
use bevy_mod_scripting_derive::DebugWithTypeInfo;
89
use bevy_mod_scripting_display::{
910
DebugWithTypeInfo, DisplayWithTypeInfo, GetTypeInfo, OrFakeId, PrintReflectAsDebug,
@@ -148,6 +149,8 @@ pub enum InteropError {
148149
on: Box<Namespace>,
149150
/// The error that occurred
150151
error: Box<InteropError>,
152+
/// The context at the time of the call
153+
context: Box<Option<FunctionCallContext>>,
151154
},
152155
/// An error occurred when converting a function argument
153156
FunctionArgConversionError {
@@ -353,11 +356,13 @@ impl InteropError {
353356
function_name: impl Display,
354357
on: Namespace,
355358
error: InteropError,
359+
context: Option<FunctionCallContext>,
356360
) -> Self {
357361
Self::FunctionInteropError {
358362
function_name: Box::new(function_name.to_string()),
359363
on: Box::new(on),
360364
error: Box::new(error),
365+
context: Box::new(context),
361366
}
362367
}
363368

@@ -423,11 +428,16 @@ impl InteropError {
423428
}
424429

425430
/// Creates a new missing function error.
426-
pub fn missing_function(function_name: impl Display, on: Namespace) -> Self {
431+
pub fn missing_function(
432+
function_name: impl Display,
433+
on: Namespace,
434+
context: Option<FunctionCallContext>,
435+
) -> Self {
427436
Self::FunctionInteropError {
428437
function_name: Box::new(function_name.to_string()),
429438
on: Box::new(on),
430439
error: Box::new(InteropError::str("Function not found")),
440+
context: Box::new(context),
431441
}
432442
}
433443
}
@@ -563,10 +573,14 @@ impl DisplayWithTypeInfo for InteropError {
563573
function_name,
564574
on,
565575
error,
576+
context,
566577
} => {
567578
write!(
568579
f,
569-
"Error in function {} on {}: {}",
580+
"Error {} in function {} on {}: {}",
581+
context
582+
.clone()
583+
.unwrap_or(FunctionCallContext::new(Language::Unknown)),
570584
function_name,
571585
WithTypeInfo::new_with_opt_info(on, type_info_provider),
572586
error

crates/bevy_mod_scripting_bindings/src/function/script_function.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,30 @@ pub trait ScriptFunctionMut<'env, Marker> {
4040

4141
/// The caller context when calling a script function.
4242
/// Functions can choose to react to caller preferences such as converting 1-indexed numbers to 0-indexed numbers
43-
#[derive(Clone, Debug, Reflect)]
43+
#[derive(Clone, Reflect, DebugWithTypeInfo)]
44+
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
4445
#[reflect(opaque)]
4546
pub struct FunctionCallContext {
4647
language: Language,
4748
location_context: Option<LocationContext>,
4849
}
4950

50-
#[derive(Clone, Debug, Reflect)]
51+
impl std::fmt::Display for FunctionCallContext {
52+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53+
f.write_str("in language: ")?;
54+
self.language.fmt(f)?;
55+
if let Some(context) = &self.location_context {
56+
f.write_str(", at line: ")?;
57+
context.line.fmt(f)?;
58+
f.write_str(", at column: ")?;
59+
context.col.fmt(f)?;
60+
}
61+
Ok(())
62+
}
63+
}
64+
65+
#[derive(Clone, Reflect, DebugWithTypeInfo)]
66+
#[debug_with_type_info(bms_display_path = "bevy_mod_scripting_display")]
5167
/// Describes a location within a script
5268
pub struct LocationContext {
5369
/// The line number
@@ -187,12 +203,13 @@ impl DynamicScriptFunction {
187203
profiling::scope!("Dynamic Call ", self.name().deref());
188204
let args = args.into_iter().collect::<VecDeque<_>>();
189205
// should we be inlining call errors into the return value?
190-
let return_val = (self.func)(context, args);
206+
let return_val = (self.func)(context.clone(), args);
191207
match return_val {
192208
ScriptValue::Error(e) => Err(InteropError::function_interop_error(
193209
self.name(),
194210
self.info.namespace,
195211
e,
212+
Some(context),
196213
)),
197214
v => Ok(v),
198215
}
@@ -224,12 +241,13 @@ impl DynamicScriptFunctionMut {
224241
let args = args.into_iter().collect::<VecDeque<_>>();
225242
// should we be inlining call errors into the return value?
226243
let mut write = self.func.write();
227-
let return_val = (write)(context, args);
244+
let return_val = (write)(context.clone(), args);
228245
match return_val {
229246
ScriptValue::Error(e) => Err(InteropError::function_interop_error(
230247
self.name(),
231248
self.info.namespace,
232249
e,
250+
Some(context),
233251
)),
234252
v => Ok(v),
235253
}
@@ -766,6 +784,7 @@ mod test {
766784
function_name,
767785
on,
768786
error,
787+
..
769788
} = gotten
770789
{
771790
assert_eq!(*function_name, expected_function_name);

crates/bevy_mod_scripting_bindings/src/world.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ impl<'w> WorldAccessGuard<'w> {
619619
return Err(InteropError::missing_function(
620620
name.to_string(),
621621
Namespace::OnType(type_id),
622+
Some(context.clone()),
622623
));
623624
}
624625
};
@@ -660,6 +661,7 @@ impl WorldAccessGuard<'_> {
660661
"field missing and no default provided: '{}'",
661662
descriptor.into()
662663
)),
664+
None,
663665
)
664666
})?;
665667
return Ok(default_data.default().into_partial_reflect());
@@ -820,6 +822,7 @@ impl WorldAccessGuard<'_> {
820822
"construct",
821823
Namespace::OnType(TypeId::of::<World>()),
822824
InteropError::str("missing 'variant' field in enum constructor payload"),
825+
None,
823826
)
824827
})?;
825828

@@ -834,6 +837,7 @@ impl WorldAccessGuard<'_> {
834837
variant_name,
835838
enum_info.type_path()
836839
)),
840+
None,
837841
)
838842
})?;
839843

crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,11 @@ impl UserData for LuaReflectReference {
325325
let iter_func = world
326326
.lookup_function([TypeId::of::<ReflectReference>()], "iter")
327327
.map_err(|f| {
328-
InteropError::missing_function(f, TypeId::of::<ReflectReference>().into())
328+
InteropError::missing_function(
329+
f,
330+
TypeId::of::<ReflectReference>().into(),
331+
Some(LUA_CALLER_CONTEXT),
332+
)
329333
})
330334
.map_err(IntoMluaError::to_lua_error)?;
331335

@@ -347,7 +351,11 @@ impl UserData for LuaReflectReference {
347351
let func = world
348352
.lookup_function([TypeId::of::<ReflectReference>()], "display")
349353
.map_err(|f| {
350-
InteropError::missing_function(f, TypeId::of::<ReflectReference>().into())
354+
InteropError::missing_function(
355+
f,
356+
TypeId::of::<ReflectReference>().into(),
357+
Some(LUA_CALLER_CONTEXT),
358+
)
351359
})
352360
.map_err(IntoMluaError::to_lua_error)?;
353361
let out = func
@@ -389,10 +397,12 @@ impl UserData for LuaStaticReflectReference {
389397
},
390398
Err(key) => key,
391399
};
392-
Err(
393-
InteropError::missing_function(format!("{key:#?}"), type_id.into())
394-
.into_lua_err(),
400+
Err(InteropError::missing_function(
401+
format!("{key:#?}"),
402+
type_id.into(),
403+
Some(LUA_CALLER_CONTEXT),
395404
)
405+
.into_lua_err())
396406
},
397407
);
398408
}

crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ impl IntoLua for LuaScriptValue {
142142
ScriptValue::Error(script_error) => return Err(mlua::Error::external(script_error)),
143143
ScriptValue::Function(function) => lua
144144
.create_function(move |lua, args: Variadic<LuaScriptValue>| {
145-
let loc = lua.inspect_stack(0).map(|debug| LocationContext {
146-
line: debug.curr_line() as u32,
145+
let loc = lua.inspect_stack(1).map(|debug| LocationContext {
146+
line: debug.curr_line().try_into().unwrap_or_default(),
147147
col: 0,
148148
});
149149
let out = function

crates/languages/bevy_mod_scripting_rhai/src/bindings/reference.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,11 @@ impl IntoIterator for RhaiReflectReference {
261261
let iter_func = world
262262
.lookup_function([TypeId::of::<ReflectReference>()], "iter")
263263
.map_err(|f| {
264-
InteropError::missing_function(f, TypeId::of::<ReflectReference>().into())
264+
InteropError::missing_function(
265+
f,
266+
TypeId::of::<ReflectReference>().into(),
267+
Some(RHAI_CALLER_CONTEXT),
268+
)
265269
})?;
266270

267271
iter_func.call(
@@ -562,6 +566,7 @@ impl CustomType for RhaiReflectReference {
562566
InteropError::missing_function(
563567
f,
564568
TypeId::of::<ReflectReference>().into(),
569+
Some(RHAI_CALLER_CONTEXT),
565570
)
566571
})?;
567572

@@ -592,6 +597,7 @@ impl CustomType for RhaiReflectReference {
592597
InteropError::missing_function(
593598
f,
594599
TypeId::of::<ReflectReference>().into(),
600+
Some(RHAI_CALLER_CONTEXT),
595601
)
596602
})?;
597603

@@ -639,8 +645,12 @@ impl CustomType for RhaiStaticReflectReference {
639645
};
640646

641647
Err::<_, Box<EvalAltResult>>(
642-
InteropError::missing_function(format!("{key:#?}"), type_id.into())
643-
.into_rhai_error(),
648+
InteropError::missing_function(
649+
format!("{key:#?}"),
650+
type_id.into(),
651+
Some(RHAI_CALLER_CONTEXT),
652+
)
653+
.into_rhai_error(),
644654
)
645655
});
646656
}

0 commit comments

Comments
 (0)