avm2: Convert `has_class_in_chain` to use a `GcCell<Class>` (#11494)
This commit is contained in:
parent
b68008426a
commit
61c50e636a
|
@ -433,7 +433,7 @@ impl<'gc> Avm2<'gc> {
|
|||
if let Some(object) = object.and_then(|obj| obj.upgrade(context.gc_context)) {
|
||||
let mut activation = Activation::from_nothing(context.reborrow());
|
||||
|
||||
if object.is_of_type(on_type, &mut activation.context) {
|
||||
if object.is_of_type(on_type.inner_class_definition(), &mut activation.context) {
|
||||
if let Err(err) = events::dispatch_event(&mut activation, object, event) {
|
||||
tracing::error!(
|
||||
"Encountered AVM2 error when broadcasting `{}` event: {:?}",
|
||||
|
|
|
@ -909,7 +909,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
matches = true;
|
||||
} else if let Ok(err_object) = err_object {
|
||||
let type_name = self.pool_multiname_static(method, e.type_name)?;
|
||||
let ty_class = self.resolve_class(&type_name)?;
|
||||
let ty_class = self.resolve_class(&type_name)?.inner_class_definition();
|
||||
|
||||
matches = err_object.is_of_type(ty_class, &mut self.context);
|
||||
}
|
||||
|
@ -2105,8 +2105,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
}
|
||||
|
||||
fn op_check_filter(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let xml = self.avm2().classes().xml;
|
||||
let xml_list = self.avm2().classes().xml_list;
|
||||
let xml = self.avm2().classes().xml.inner_class_definition();
|
||||
let xml_list = self.avm2().classes().xml_list.inner_class_definition();
|
||||
let value = self.pop_stack().coerce_to_object_or_typeerror(self, None)?;
|
||||
|
||||
if value.is_of_type(xml, &mut self.context) || value.is_of_type(xml_list, &mut self.context)
|
||||
|
@ -2791,7 +2791,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let value = self.pop_stack();
|
||||
|
||||
let multiname = self.pool_multiname_static(method, type_name_index)?;
|
||||
let type_object = self.resolve_class(&multiname)?;
|
||||
let type_object = self.resolve_class(&multiname)?.inner_class_definition();
|
||||
|
||||
let is_instance_of = value.is_of_type(self, type_object);
|
||||
self.push_stack(is_instance_of);
|
||||
|
@ -2804,7 +2804,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
.pop_stack()
|
||||
.as_object()
|
||||
.and_then(|o| o.as_class_object())
|
||||
.ok_or("Cannot check if value is of a type that is null, undefined, or not a class")?;
|
||||
.ok_or("Cannot check if value is of a type that is null, undefined, or not a class")?
|
||||
.inner_class_definition();
|
||||
let value = self.pop_stack();
|
||||
|
||||
let is_instance_of = value.is_of_type(self, type_object);
|
||||
|
@ -2821,7 +2822,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
let value = self.pop_stack();
|
||||
|
||||
let multiname = self.pool_multiname_static(method, type_name_index)?;
|
||||
let class = self.resolve_class(&multiname)?;
|
||||
let class = self.resolve_class(&multiname)?.inner_class_definition();
|
||||
|
||||
if value.is_of_type(self, class) {
|
||||
self.push_stack(value);
|
||||
|
@ -2847,7 +2848,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
|
|||
)?))?;
|
||||
let value = self.pop_stack();
|
||||
|
||||
if value.is_of_type(self, class) {
|
||||
if value.is_of_type(self, class.inner_class_definition()) {
|
||||
self.push_stack(value);
|
||||
} else {
|
||||
self.push_stack(Value::Null);
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::avm2::Multiname;
|
|||
use crate::avm2::Namespace;
|
||||
use crate::avm2::QName;
|
||||
use bitflags::bitflags;
|
||||
use fnv::FnvHashMap;
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -152,6 +153,11 @@ pub struct Class<'gc> {
|
|||
/// Whether or not this `Class` has loaded its traits or not.
|
||||
traits_loaded: bool,
|
||||
|
||||
/// Maps a type parameter to the application of this class with that parameter.
|
||||
///
|
||||
/// Only applicable if this class is generic.
|
||||
applications: FnvHashMap<ClassKey<'gc>, GcCell<'gc, Class<'gc>>>,
|
||||
|
||||
/// Whether or not this is a system-defined class.
|
||||
///
|
||||
/// System defined classes are allowed to have illegal trait configurations
|
||||
|
@ -159,6 +165,26 @@ pub struct Class<'gc> {
|
|||
is_system: bool,
|
||||
}
|
||||
|
||||
/// Allows using a `GcCell<'gc, Class<'gc>>` as a HashMap key,
|
||||
/// using the pointer address for hashing/equality.
|
||||
#[derive(Collect, Copy, Clone)]
|
||||
#[collect(no_drop)]
|
||||
struct ClassKey<'gc>(GcCell<'gc, Class<'gc>>);
|
||||
|
||||
impl PartialEq for ClassKey<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
GcCell::ptr_eq(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ClassKey<'_> {}
|
||||
|
||||
impl Hash for ClassKey<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.as_ptr().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Class<'gc> {
|
||||
/// Create a new class.
|
||||
///
|
||||
|
@ -201,6 +227,7 @@ impl<'gc> Class<'gc> {
|
|||
),
|
||||
traits_loaded: true,
|
||||
is_system: true,
|
||||
applications: FnvHashMap::default(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -210,11 +237,18 @@ impl<'gc> Class<'gc> {
|
|||
/// This is used to parameterize a generic type. The returned class will no
|
||||
/// longer be generic.
|
||||
pub fn with_type_param(
|
||||
&self,
|
||||
this: GcCell<'gc, Class<'gc>>,
|
||||
param: GcCell<'gc, Class<'gc>>,
|
||||
mc: MutationContext<'gc, '_>,
|
||||
) -> GcCell<'gc, Class<'gc>> {
|
||||
let mut new_class = self.clone();
|
||||
let read = this.read();
|
||||
let key = ClassKey(param);
|
||||
|
||||
if let Some(application) = read.applications.get(&key) {
|
||||
return *application;
|
||||
}
|
||||
|
||||
let mut new_class = (*read).clone();
|
||||
|
||||
new_class.param = Some(param);
|
||||
new_class.attributes.remove(ClassAttributes::GENERIC);
|
||||
|
@ -234,7 +268,11 @@ impl<'gc> Class<'gc> {
|
|||
AvmString::new_utf8(mc, name_with_params),
|
||||
);
|
||||
|
||||
GcCell::allocate(mc, new_class)
|
||||
let new_class = GcCell::allocate(mc, new_class);
|
||||
drop(read);
|
||||
|
||||
this.write(mc).applications.insert(key, new_class);
|
||||
new_class
|
||||
}
|
||||
|
||||
/// Set the attributes of the class (sealed/final/interface status).
|
||||
|
@ -364,6 +402,7 @@ impl<'gc> Class<'gc> {
|
|||
),
|
||||
traits_loaded: false,
|
||||
is_system: false,
|
||||
applications: Default::default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -473,7 +512,7 @@ impl<'gc> Class<'gc> {
|
|||
}
|
||||
|
||||
if instance_trait.is_override() && !did_override {
|
||||
return Err(format!("VerifyError: Trait {} in class {} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name().local_name()).into());
|
||||
return Err(format!("VerifyError: Trait {} in class {:?} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name()).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -535,6 +574,7 @@ impl<'gc> Class<'gc> {
|
|||
class_traits: Vec::new(),
|
||||
traits_loaded: true,
|
||||
is_system: false,
|
||||
applications: Default::default(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ impl<'gc> Domain<'gc> {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_class(
|
||||
fn get_class_inner(
|
||||
self,
|
||||
multiname: &Multiname<'gc>,
|
||||
) -> Result<Option<GcCell<'gc, Class<'gc>>>, Error<'gc>> {
|
||||
|
@ -159,12 +159,32 @@ impl<'gc> Domain<'gc> {
|
|||
}
|
||||
|
||||
if let Some(parent) = read.parent {
|
||||
return parent.get_class(multiname);
|
||||
return parent.get_class_inner(multiname);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_class(
|
||||
self,
|
||||
multiname: &Multiname<'gc>,
|
||||
mc: MutationContext<'gc, '_>,
|
||||
) -> Result<Option<GcCell<'gc, Class<'gc>>>, Error<'gc>> {
|
||||
let class = self.get_class_inner(multiname)?;
|
||||
|
||||
if let Some(class) = class {
|
||||
if let Some(param) = multiname.param() {
|
||||
if !param.is_any_name() {
|
||||
if let Some(resolved_param) = self.get_class(¶m, mc)? {
|
||||
return Ok(Some(Class::with_type_param(class, resolved_param, mc)));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(class)
|
||||
}
|
||||
|
||||
/// Resolve a Multiname and return the script that provided it.
|
||||
///
|
||||
/// If a name does not exist or cannot be resolved, an error will be thrown.
|
||||
|
|
|
@ -51,56 +51,96 @@ impl FilterAvm2Ext for Filter {
|
|||
activation: &mut Activation<'_, 'gc>,
|
||||
object: Object<'gc>,
|
||||
) -> Result<Filter, Error<'gc>> {
|
||||
let bevel_filter = activation.avm2().classes().bevelfilter;
|
||||
let bevel_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.bevelfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(bevel_filter, &mut activation.context) {
|
||||
return avm2_to_bevel_filter(activation, object);
|
||||
}
|
||||
|
||||
let blur_filter = activation.avm2().classes().blurfilter;
|
||||
let blur_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.blurfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(blur_filter, &mut activation.context) {
|
||||
return avm2_to_blur_filter(activation, object);
|
||||
}
|
||||
|
||||
let color_matrix_filter = activation.avm2().classes().colormatrixfilter;
|
||||
let color_matrix_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.colormatrixfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(color_matrix_filter, &mut activation.context) {
|
||||
return avm2_to_color_matrix_filter(activation, object);
|
||||
}
|
||||
|
||||
let convolution_filter = activation.avm2().classes().convolutionfilter;
|
||||
let convolution_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.convolutionfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(convolution_filter, &mut activation.context) {
|
||||
return avm2_to_convolution_filter(activation, object);
|
||||
}
|
||||
|
||||
let displacement_map_filter = activation.avm2().classes().displacementmapfilter;
|
||||
let displacement_map_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.displacementmapfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(displacement_map_filter, &mut activation.context) {
|
||||
return avm2_to_displacement_map_filter(activation, object);
|
||||
}
|
||||
|
||||
let drop_shadow_filter = activation.avm2().classes().dropshadowfilter;
|
||||
let drop_shadow_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.dropshadowfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(drop_shadow_filter, &mut activation.context) {
|
||||
return avm2_to_drop_shadow_filter(activation, object);
|
||||
}
|
||||
|
||||
let glow_filter = activation.avm2().classes().glowfilter;
|
||||
let glow_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.glowfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(glow_filter, &mut activation.context) {
|
||||
return avm2_to_glow_filter(activation, object);
|
||||
}
|
||||
|
||||
let gradient_bevel_filter = activation.avm2().classes().gradientbevelfilter;
|
||||
let gradient_bevel_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.gradientbevelfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(gradient_bevel_filter, &mut activation.context) {
|
||||
return Ok(Filter::GradientBevelFilter(avm2_to_gradient_filter(
|
||||
activation, object,
|
||||
)?));
|
||||
}
|
||||
|
||||
let gradient_glow_filter = activation.avm2().classes().gradientglowfilter;
|
||||
let gradient_glow_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.gradientglowfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(gradient_glow_filter, &mut activation.context) {
|
||||
return Ok(Filter::GradientGlowFilter(avm2_to_gradient_filter(
|
||||
activation, object,
|
||||
)?));
|
||||
}
|
||||
|
||||
let shader_filter = activation.avm2().classes().shaderfilter;
|
||||
let shader_filter = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.shaderfilter
|
||||
.inner_class_definition();
|
||||
if object.is_of_type(shader_filter, &mut activation.context) {
|
||||
return Ok(Filter::ShaderFilter(avm2_to_shader_filter(
|
||||
activation, object,
|
||||
|
|
|
@ -700,8 +700,12 @@ pub fn hit_test<'gc>(
|
|||
);
|
||||
let source_threshold = args.get_u32(activation, 1)?.clamp(0, u8::MAX.into()) as u8;
|
||||
let compare_object = args.get_object(activation, 2, "secondObject")?;
|
||||
let point_class = activation.avm2().classes().point;
|
||||
let rectangle_class = activation.avm2().classes().rectangle;
|
||||
let point_class = activation.avm2().classes().point.inner_class_definition();
|
||||
let rectangle_class = activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.rectangle
|
||||
.inner_class_definition();
|
||||
|
||||
if compare_object.is_of_type(point_class, &mut activation.context) {
|
||||
let test_point = (
|
||||
|
|
|
@ -264,7 +264,9 @@ pub fn set_filters<'gc>(
|
|||
Namespace::package("flash.filters", &mut activation.borrow_gc());
|
||||
let filter_class = Multiname::new(filters_namespace, "BitmapFilter");
|
||||
|
||||
let filter_class_object = activation.resolve_class(&filter_class)?;
|
||||
let filter_class_object = activation
|
||||
.resolve_class(&filter_class)?
|
||||
.inner_class_definition();
|
||||
let mut filter_vec = Vec::with_capacity(filters_storage.length());
|
||||
|
||||
for filter in filters_storage.iter().flatten() {
|
||||
|
|
|
@ -176,7 +176,7 @@ pub fn add_child<'gc>(
|
|||
if let Some(ctr) = parent.as_container() {
|
||||
let child = args.get_object(activation, 0, "child")?;
|
||||
if child.as_display_object().is_none() {
|
||||
let sprite = activation.avm2().classes().sprite;
|
||||
let sprite = activation.avm2().classes().sprite.inner_class_definition();
|
||||
if child.is_of_type(sprite, &mut activation.context) {
|
||||
// [NA] Hack to make Haxe work - they call addChild before super()
|
||||
// This will create an empty sprite the same way sprite's constructor will.
|
||||
|
|
|
@ -504,7 +504,11 @@ pub fn set_texture_at<'gc>(
|
|||
} else {
|
||||
let obj = args[1].coerce_to_object(activation)?;
|
||||
cube = obj.is_of_type(
|
||||
activation.avm2().classes().cubetexture,
|
||||
activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.cubetexture
|
||||
.inner_class_definition(),
|
||||
&mut activation.context,
|
||||
);
|
||||
Some(obj.as_texture().unwrap().handle())
|
||||
|
|
|
@ -235,13 +235,15 @@ impl<'gc> AvmSerializer<'gc> {
|
|||
)?));
|
||||
}
|
||||
self.obj_stack.push(obj);
|
||||
let value =
|
||||
if obj.is_of_type(activation.avm2().classes().array, &mut activation.context) {
|
||||
// TODO: Vectors
|
||||
self.serialize_iterable(activation, obj)?
|
||||
} else {
|
||||
self.serialize_object(activation, obj)?
|
||||
};
|
||||
let value = if obj.is_of_type(
|
||||
activation.avm2().classes().array.inner_class_definition(),
|
||||
&mut activation.context,
|
||||
) {
|
||||
// TODO: Vectors
|
||||
self.serialize_iterable(activation, obj)?
|
||||
} else {
|
||||
self.serialize_object(activation, obj)?
|
||||
};
|
||||
self.obj_stack
|
||||
.pop()
|
||||
.expect("Stack underflow during JSON serialization");
|
||||
|
|
|
@ -291,7 +291,7 @@ fn match_s<'gc>(
|
|||
let this = Value::from(this).coerce_to_string(activation)?;
|
||||
|
||||
let regexp_class = activation.avm2().classes().regexp;
|
||||
let pattern = if !pattern.is_of_type(activation, regexp_class) {
|
||||
let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) {
|
||||
let string = pattern.coerce_to_string(activation)?;
|
||||
regexp_class.construct(activation, &[Value::String(string)])?
|
||||
} else {
|
||||
|
@ -401,7 +401,7 @@ fn search<'gc>(
|
|||
let this = Value::from(this).coerce_to_string(activation)?;
|
||||
|
||||
let regexp_class = activation.avm2().classes().regexp;
|
||||
let pattern = if !pattern.is_of_type(activation, regexp_class) {
|
||||
let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) {
|
||||
let string = pattern.coerce_to_string(activation)?;
|
||||
regexp_class.construct(activation, &[Value::String(string)])?
|
||||
} else {
|
||||
|
|
|
@ -77,7 +77,7 @@ fn class_call<'gc>(
|
|||
|
||||
while let Some(r) = iter.next(activation) {
|
||||
let (_, item) = r?;
|
||||
let coerced_item = item.coerce_to_type(activation, value_type)?;
|
||||
let coerced_item = item.coerce_to_type(activation, value_type.inner_class_definition())?;
|
||||
new_storage.push(coerced_item, activation)?;
|
||||
}
|
||||
|
||||
|
@ -310,7 +310,7 @@ pub fn concat<'gc>(
|
|||
let my_class = this
|
||||
.instance_of()
|
||||
.ok_or("TypeError: Tried to concat into a bare object")?;
|
||||
let val_class = new_vector_storage.value_type();
|
||||
let val_class = new_vector_storage.value_type().inner_class_definition();
|
||||
|
||||
for arg in args {
|
||||
let arg_obj = arg
|
||||
|
@ -319,7 +319,7 @@ pub fn concat<'gc>(
|
|||
let arg_class = arg_obj
|
||||
.instance_of_class_definition()
|
||||
.ok_or("TypeError: Tried to concat from a bare object")?;
|
||||
if !arg.is_of_type(activation, my_class) {
|
||||
if !arg.is_of_type(activation, my_class.inner_class_definition()) {
|
||||
return Err(format!(
|
||||
"TypeError: Cannot coerce argument of type {:?} to argument of type {:?}",
|
||||
arg_class.read().name(),
|
||||
|
@ -344,7 +344,7 @@ pub fn concat<'gc>(
|
|||
return Err(format!(
|
||||
"TypeError: Cannot coerce Vector value of type {:?} to type {:?}",
|
||||
other_val_class.read().name(),
|
||||
val_class.inner_class_definition().read().name()
|
||||
val_class.read().name()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
@ -641,7 +641,8 @@ pub fn map<'gc>(
|
|||
let (i, item) = r?;
|
||||
|
||||
let new_item = callback.call(receiver, &[item, i.into(), this.into()], activation)?;
|
||||
let coerced_item = new_item.coerce_to_type(activation, value_type)?;
|
||||
let coerced_item =
|
||||
new_item.coerce_to_type(activation, value_type.inner_class_definition())?;
|
||||
|
||||
new_storage.push(coerced_item, activation)?;
|
||||
}
|
||||
|
@ -669,7 +670,7 @@ pub fn push<'gc>(
|
|||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
|
||||
let value_type = vs.value_type();
|
||||
let value_type = vs.value_type().inner_class_definition();
|
||||
|
||||
for arg in args {
|
||||
let coerced_arg = arg.coerce_to_type(activation, value_type)?;
|
||||
|
@ -703,7 +704,7 @@ pub fn unshift<'gc>(
|
|||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
|
||||
let value_type = vs.value_type();
|
||||
let value_type = vs.value_type().inner_class_definition();
|
||||
|
||||
for arg in args.iter().rev() {
|
||||
let coerced_arg = arg.coerce_to_type(activation, value_type)?;
|
||||
|
@ -729,7 +730,7 @@ pub fn insert_at<'gc>(
|
|||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_i32(activation)?;
|
||||
let value_type = vs.value_type();
|
||||
let value_type = vs.value_type().inner_class_definition();
|
||||
let value = args
|
||||
.get(1)
|
||||
.cloned()
|
||||
|
@ -930,7 +931,7 @@ pub fn splice<'gc>(
|
|||
let mut to_coerce = Vec::new();
|
||||
|
||||
for value in args[2..].iter() {
|
||||
to_coerce.push(value.coerce_to_type(activation, value_type)?);
|
||||
to_coerce.push(value.coerce_to_type(activation, value_type.inner_class_definition())?);
|
||||
}
|
||||
|
||||
let new_vs =
|
||||
|
|
|
@ -1062,14 +1062,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
|
|||
/// checking against this object.
|
||||
fn is_of_type(
|
||||
&self,
|
||||
test_class: ClassObject<'gc>,
|
||||
test_class: GcCell<'gc, Class<'gc>>,
|
||||
context: &mut UpdateContext<'_, 'gc>,
|
||||
) -> bool {
|
||||
let my_class = self.instance_of();
|
||||
|
||||
// ES3 objects are not class instances but are still treated as
|
||||
// instances of Object, which is an ES4 class.
|
||||
if my_class.is_none() && Object::ptr_eq(test_class, context.avm2.classes().object) {
|
||||
if my_class.is_none()
|
||||
&& GcCell::ptr_eq(
|
||||
test_class,
|
||||
context.avm2.classes().object.inner_class_definition(),
|
||||
)
|
||||
{
|
||||
true
|
||||
} else if let Some(my_class) = my_class {
|
||||
my_class.has_class_in_chain(test_class)
|
||||
|
|
|
@ -329,7 +329,11 @@ impl<'gc> ClassObject<'gc> {
|
|||
let mut queue = vec![class];
|
||||
while let Some(cls) = queue.pop() {
|
||||
for interface_name in cls.read().direct_interfaces() {
|
||||
let interface = self.early_resolve_class(scope.domain(), interface_name)?;
|
||||
let interface = self.early_resolve_class(
|
||||
scope.domain(),
|
||||
interface_name,
|
||||
activation.context.gc_context,
|
||||
)?;
|
||||
|
||||
if !interface.read().is_interface() {
|
||||
return Err(format!(
|
||||
|
@ -346,7 +350,11 @@ impl<'gc> ClassObject<'gc> {
|
|||
}
|
||||
|
||||
if let Some(superclass_name) = cls.read().super_class_name() {
|
||||
queue.push(self.early_resolve_class(scope.domain(), superclass_name)?);
|
||||
queue.push(self.early_resolve_class(
|
||||
scope.domain(),
|
||||
superclass_name,
|
||||
activation.context.gc_context,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
write.interfaces = interfaces;
|
||||
|
@ -385,9 +393,10 @@ impl<'gc> ClassObject<'gc> {
|
|||
&self,
|
||||
domain: Domain<'gc>,
|
||||
class_name: &Multiname<'gc>,
|
||||
mc: MutationContext<'gc, '_>,
|
||||
) -> Result<GcCell<'gc, Class<'gc>>, Error<'gc>> {
|
||||
domain
|
||||
.get_class(class_name)?
|
||||
.get_class(class_name, mc)?
|
||||
.ok_or_else(|| format!("Could not resolve class {class_name:?}").into())
|
||||
}
|
||||
|
||||
|
@ -448,24 +457,19 @@ impl<'gc> ClassObject<'gc> {
|
|||
/// interface we are checking against this class.
|
||||
///
|
||||
/// To test if a class *instance* is of a given type, see is_of_type.
|
||||
pub fn has_class_in_chain(self, test_class: ClassObject<'gc>) -> bool {
|
||||
pub fn has_class_in_chain(self, test_class: GcCell<'gc, Class<'gc>>) -> bool {
|
||||
let mut my_class = Some(self);
|
||||
|
||||
while let Some(class) = my_class {
|
||||
if Object::ptr_eq(class, test_class) {
|
||||
if GcCell::ptr_eq(class.inner_class_definition(), test_class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let (Some(my_param), Some(test_param)) =
|
||||
(class.as_class_params(), test_class.as_class_params())
|
||||
let test_class_read = test_class.read();
|
||||
if let (Some(Some(my_param)), Some(other_single_param)) =
|
||||
(class.as_class_params(), test_class_read.param())
|
||||
{
|
||||
let are_all_params_coercible = match (my_param, test_param) {
|
||||
(Some(my_param), Some(test_param)) => my_param.has_class_in_chain(test_param),
|
||||
(None, Some(_)) => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
if are_all_params_coercible {
|
||||
if my_param.has_class_in_chain(*other_single_param) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -478,9 +482,9 @@ impl<'gc> ClassObject<'gc> {
|
|||
// Therefore, we only need to check interfaces once, and we can skip
|
||||
// checking them when we processing superclasses in the `while`
|
||||
// further down in this method.
|
||||
if test_class.inner_class_definition().read().is_interface() {
|
||||
if test_class.read().is_interface() {
|
||||
for interface in self.interfaces() {
|
||||
if GcCell::ptr_eq(interface, test_class.inner_class_definition()) {
|
||||
if GcCell::ptr_eq(interface, test_class) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -851,7 +855,7 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
|
|||
.get(0)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Undefined)
|
||||
.coerce_to_type(activation, self)
|
||||
.coerce_to_type(activation, self.inner_class_definition())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -933,9 +937,8 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
|
|||
.unwrap_or(activation.avm2().classes().object)
|
||||
.inner_class_definition();
|
||||
|
||||
let parameterized_class = self_class
|
||||
.read()
|
||||
.with_type_param(class_param, activation.context.gc_context);
|
||||
let parameterized_class: GcCell<'_, Class<'_>> =
|
||||
Class::with_type_param(self_class, class_param, activation.context.gc_context);
|
||||
|
||||
let class_scope = self.0.read().class_scope;
|
||||
let instance_scope = self.0.read().instance_scope;
|
||||
|
|
|
@ -133,7 +133,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
|
|||
if name.contains_public_namespace() {
|
||||
if let Some(name) = name.local_name() {
|
||||
if let Ok(index) = name.parse::<usize>() {
|
||||
let type_of = self.0.read().vector.value_type();
|
||||
let type_of = self.0.read().vector.value_type().inner_class_definition();
|
||||
let value = match value.coerce_to_type(activation, type_of)? {
|
||||
Value::Undefined => self.0.read().vector.default(activation),
|
||||
Value::Null => self.0.read().vector.default(activation),
|
||||
|
@ -164,7 +164,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
|
|||
if name.contains_public_namespace() {
|
||||
if let Some(name) = name.local_name() {
|
||||
if let Ok(index) = name.parse::<usize>() {
|
||||
let type_of = self.0.read().vector.value_type();
|
||||
let type_of = self.0.read().vector.value_type().inner_class_definition();
|
||||
let value = match value.coerce_to_type(activation, type_of)? {
|
||||
Value::Undefined => self.0.read().vector.default(activation),
|
||||
Value::Null => self.0.read().vector.default(activation),
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
//! Property data structures
|
||||
|
||||
use crate::avm2::object::TObject;
|
||||
use crate::avm2::Activation;
|
||||
use crate::avm2::ClassObject;
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::TranslationUnit;
|
||||
use crate::avm2::Value;
|
||||
use gc_arena::GcCell;
|
||||
use gc_arena::MutationContext;
|
||||
use gc_arena::{Collect, Gc};
|
||||
|
||||
use super::class::Class;
|
||||
|
||||
#[derive(Debug, Collect, Clone, Copy)]
|
||||
#[collect(no_drop)]
|
||||
pub enum Property {
|
||||
|
@ -39,7 +40,7 @@ pub enum PropertyClass<'gc> {
|
|||
/// `Value::Undefined`, so it needs to be distinguished
|
||||
/// from the `Object` class
|
||||
Any,
|
||||
Class(ClassObject<'gc>),
|
||||
Class(GcCell<'gc, Class<'gc>>),
|
||||
Name(Gc<'gc, (Multiname<'gc>, Option<TranslationUnit<'gc>>)>),
|
||||
}
|
||||
|
||||
|
@ -64,56 +65,28 @@ impl<'gc> PropertyClass<'gc> {
|
|||
PropertyClass::Class(class) => (Some(*class), false),
|
||||
PropertyClass::Name(gc) => {
|
||||
let (name, unit) = &**gc;
|
||||
let outcome = resolve_class_private(name, *unit, activation)?;
|
||||
let class = match outcome {
|
||||
ResolveOutcome::Class(class) => {
|
||||
if name.is_any_name() {
|
||||
*self = PropertyClass::Any;
|
||||
(None, true)
|
||||
} else {
|
||||
// Note - we look up the class in the domain by name, which allows us to look up private classes.
|
||||
// This also has the advantage of letting us coerce to a class while the `ClassObject`
|
||||
// is still being constructed (since the `Class` will already exist in the domain).
|
||||
|
||||
// We should only be missing a translation unit when performing a lookup from playerglobals,
|
||||
// so use that domain if we don't have a translation unit.
|
||||
let domain =
|
||||
unit.map_or(activation.avm2().playerglobals_domain, |u| u.domain());
|
||||
if let Some(class) = domain.get_class(name, activation.context.gc_context)? {
|
||||
*self = PropertyClass::Class(class);
|
||||
(Some(class), true)
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Could not resolve class {name:?} for property coercion"
|
||||
)
|
||||
.into());
|
||||
}
|
||||
ResolveOutcome::Any => {
|
||||
*self = PropertyClass::Any;
|
||||
(None, true)
|
||||
}
|
||||
ResolveOutcome::NotFound => {
|
||||
// FP allows a class to reference its own type in a static initializer:
|
||||
// `class Foo { static var INSTANCE: Foo = new Foo(); }`
|
||||
// When this happens, the `ClassObject` for `Foo` will not yet
|
||||
// be available when we perform the coercion, since we're still
|
||||
// in the process of constructing it.
|
||||
//
|
||||
// Fortunately, a coercion to a non-primitive class either
|
||||
// succeeds with the value unchanged, or fails (if the object
|
||||
// is not an instance of the class). Therefore, we can just check
|
||||
// if the class name and domain match our property name, without
|
||||
// actually needing to perform resolution. This does not handle subclasses,
|
||||
// but that's fine - a superclass cannot reference a subclass from a class
|
||||
// initializer.
|
||||
//
|
||||
// We should eventually be able to remove this when we refactor
|
||||
// `Class`/`ClassObject` to be closer to what avmplus does.
|
||||
if let Some(object) = value.as_object() {
|
||||
if let Some(class) = object.instance_of() {
|
||||
if name.contains_name(&class.inner_class_definition().read().name())
|
||||
&& unit.map(|u| u.domain())
|
||||
== Some(class.class_scope().domain())
|
||||
{
|
||||
// Even though resolution succeeded, we haven't modified
|
||||
// this `PropertyClass`, so return `false`
|
||||
return Ok((value, false));
|
||||
}
|
||||
}
|
||||
} else if matches!(value, Value::Null) || matches!(value, Value::Undefined)
|
||||
{
|
||||
//AVM2 properties are nullable, so null is always an instance of the class
|
||||
return Ok((Value::Null, false));
|
||||
}
|
||||
|
||||
return Err(Error::from(format!(
|
||||
"Attempted to perform (private) resolution of nonexistent type {name:?}",
|
||||
)));
|
||||
}
|
||||
};
|
||||
class
|
||||
}
|
||||
}
|
||||
PropertyClass::Any => (None, false),
|
||||
};
|
||||
|
@ -129,58 +102,13 @@ impl<'gc> PropertyClass<'gc> {
|
|||
|
||||
pub fn get_name(&self, mc: MutationContext<'gc, '_>) -> Multiname<'gc> {
|
||||
match self {
|
||||
PropertyClass::Class(class) => class.inner_class_definition().read().name().into(),
|
||||
PropertyClass::Class(class) => class.read().name().into(),
|
||||
PropertyClass::Name(gc) => gc.0.clone(),
|
||||
PropertyClass::Any => Multiname::any(mc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ResolveOutcome<'gc> {
|
||||
Class(ClassObject<'gc>),
|
||||
Any,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
/// Resolves a class definition referenced by the type of a property.
|
||||
/// This supports private (`Namespace::Private`) classes,
|
||||
/// and does not use the `ScopeStack`/`ScopeChain`.
|
||||
///
|
||||
/// This is an internal operation used to resolve property type names.
|
||||
/// It does not correspond to any opcode or native method.
|
||||
fn resolve_class_private<'gc>(
|
||||
name: &Multiname<'gc>,
|
||||
unit: Option<TranslationUnit<'gc>>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<ResolveOutcome<'gc>, Error<'gc>> {
|
||||
// A Property may have a type of '*' (which corresponds to 'Multiname::any()')
|
||||
// We don't want to perform any coercions in this case - in particular,
|
||||
// this means that the property can have a value of `Undefined`.
|
||||
// If the type is `Object`, then a value of `Undefind` gets coerced
|
||||
// to `Null`
|
||||
if name.is_any_name() {
|
||||
Ok(ResolveOutcome::Any)
|
||||
} else {
|
||||
// First, check the domain for an exported (non-private) class.
|
||||
// If the property we're resolving for lacks a `TranslationUnit`,
|
||||
// use the stage domain
|
||||
let domain = unit.map_or(activation.avm2().stage_domain, |u| u.domain());
|
||||
let globals = if let Some((_, mut script)) = domain.get_defining_script(name)? {
|
||||
script.globals(&mut activation.context)?
|
||||
} else if unit.is_some() {
|
||||
return Err(format!("Could not find script for class trait {name:?}").into());
|
||||
} else {
|
||||
return Err(format!("Missing script and translation unit for class {name:?}").into());
|
||||
};
|
||||
|
||||
Ok(globals
|
||||
.get_property(name, activation)?
|
||||
.as_object()
|
||||
.and_then(|o| o.as_class_object())
|
||||
.map_or(ResolveOutcome::NotFound, ResolveOutcome::Class))
|
||||
}
|
||||
}
|
||||
|
||||
impl Property {
|
||||
pub fn new_method(disp_id: u32) -> Self {
|
||||
Property::Method { disp_id }
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use crate::avm2::activation::Activation;
|
||||
use crate::avm2::error;
|
||||
use crate::avm2::error::type_error;
|
||||
use crate::avm2::object::{ClassObject, NamespaceObject, Object, PrimitiveObject, TObject};
|
||||
use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject};
|
||||
use crate::avm2::script::TranslationUnit;
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
|
@ -16,6 +16,7 @@ use std::cell::Ref;
|
|||
use std::mem::size_of;
|
||||
use swf::avm2::types::{DefaultValue as AbcDefaultValue, Index};
|
||||
|
||||
use super::class::Class;
|
||||
use super::e4x::E4XNode;
|
||||
|
||||
/// Indicate what kind of primitive coercion would be preferred when coercing
|
||||
|
@ -957,44 +958,19 @@ impl<'gc> Value<'gc> {
|
|||
activation: &mut Activation<'_, 'gc>,
|
||||
type_name: &Multiname<'gc>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
let param_type = match activation.resolve_type(type_name) {
|
||||
Ok(param_type) => param_type,
|
||||
Err(e) => {
|
||||
// While running a class initializer, we might need to resolve the class
|
||||
// itself. For example, a static/const can run a static method, which does
|
||||
// `var foo:ClassBeingInitialized = new ClassBeingInitialized();`
|
||||
//
|
||||
// Since the class initializer is running, we won't be able to resolve the
|
||||
// ClassObject yet. If the normal `resolve_type` lookup fails, then
|
||||
// try resolving the class through `domain().get_class`, and check if the
|
||||
// object's class matches directly (not considering superclasses or interfaces).
|
||||
// Any superclasses or superinterfaces will already have been initialized,
|
||||
// so the `resolve_type` lookup will succeed for them.
|
||||
|
||||
if let Ok(Some(resolved_class)) = activation.domain().get_class(type_name) {
|
||||
// Note that we do this check *after* successfully resolving the class. This ensures
|
||||
// that we still produce errors when trying to coerce null/undefined to a completely
|
||||
// non-existent class.
|
||||
if matches!(self, Value::Undefined) || matches!(self, Value::Null) {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
if let Some(obj) = self.as_object() {
|
||||
if let Some(obj_class) = obj.instance_of_class_definition() {
|
||||
if GcCell::ptr_eq(resolved_class, obj_class) {
|
||||
return Ok(*self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(param_type) = param_type {
|
||||
self.coerce_to_type(activation, param_type)
|
||||
} else {
|
||||
Ok(*self)
|
||||
if type_name.is_any_name() {
|
||||
return Ok(*self);
|
||||
}
|
||||
let param_type = activation
|
||||
.domain()
|
||||
.get_class(type_name, activation.context.gc_context)?
|
||||
.ok_or_else(|| {
|
||||
Error::RustError(
|
||||
format!("Failed to lookup class {:?} during coercion", type_name).into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
self.coerce_to_type(activation, param_type)
|
||||
}
|
||||
|
||||
/// Coerce the value to another value by type name.
|
||||
|
@ -1008,21 +984,33 @@ impl<'gc> Value<'gc> {
|
|||
pub fn coerce_to_type(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
class: ClassObject<'gc>,
|
||||
class: GcCell<'gc, Class<'gc>>,
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
if Object::ptr_eq(class, activation.avm2().classes().int) {
|
||||
if GcCell::ptr_eq(
|
||||
class,
|
||||
activation.avm2().classes().int.inner_class_definition(),
|
||||
) {
|
||||
return Ok(self.coerce_to_i32(activation)?.into());
|
||||
}
|
||||
|
||||
if Object::ptr_eq(class, activation.avm2().classes().uint) {
|
||||
if GcCell::ptr_eq(
|
||||
class,
|
||||
activation.avm2().classes().uint.inner_class_definition(),
|
||||
) {
|
||||
return Ok(self.coerce_to_u32(activation)?.into());
|
||||
}
|
||||
|
||||
if Object::ptr_eq(class, activation.avm2().classes().number) {
|
||||
if GcCell::ptr_eq(
|
||||
class,
|
||||
activation.avm2().classes().number.inner_class_definition(),
|
||||
) {
|
||||
return Ok(self.coerce_to_number(activation)?.into());
|
||||
}
|
||||
|
||||
if Object::ptr_eq(class, activation.avm2().classes().boolean) {
|
||||
if GcCell::ptr_eq(
|
||||
class,
|
||||
activation.avm2().classes().boolean.inner_class_definition(),
|
||||
) {
|
||||
return Ok(self.coerce_to_boolean().into());
|
||||
}
|
||||
|
||||
|
@ -1030,7 +1018,10 @@ impl<'gc> Value<'gc> {
|
|||
return Ok(Value::Null);
|
||||
}
|
||||
|
||||
if Object::ptr_eq(class, activation.avm2().classes().string) {
|
||||
if GcCell::ptr_eq(
|
||||
class,
|
||||
activation.avm2().classes().string.inner_class_definition(),
|
||||
) {
|
||||
return Ok(self.coerce_to_string(activation)?.into());
|
||||
}
|
||||
|
||||
|
@ -1040,7 +1031,7 @@ impl<'gc> Value<'gc> {
|
|||
}
|
||||
|
||||
if let Some(vector) = object.as_vector_storage() {
|
||||
let name = class.inner_class_definition().read().name();
|
||||
let name = class.read().name();
|
||||
let vector_public_namespace = activation.avm2().vector_public_namespace;
|
||||
let vector_internal_namespace = activation.avm2().vector_internal_namespace;
|
||||
if name == QName::new(vector_public_namespace, "Vector")
|
||||
|
@ -1059,7 +1050,6 @@ impl<'gc> Value<'gc> {
|
|||
}
|
||||
|
||||
let name = class
|
||||
.inner_class_definition()
|
||||
.read()
|
||||
.name()
|
||||
.to_qualified_name_err_message(activation.context.gc_context);
|
||||
|
@ -1128,15 +1118,24 @@ impl<'gc> Value<'gc> {
|
|||
pub fn is_of_type(
|
||||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
type_object: ClassObject<'gc>,
|
||||
type_object: GcCell<'gc, Class<'gc>>,
|
||||
) -> bool {
|
||||
if Object::ptr_eq(type_object, activation.avm2().classes().number) {
|
||||
if GcCell::ptr_eq(
|
||||
type_object,
|
||||
activation.avm2().classes().number.inner_class_definition(),
|
||||
) {
|
||||
return self.is_number();
|
||||
}
|
||||
if Object::ptr_eq(type_object, activation.avm2().classes().uint) {
|
||||
if GcCell::ptr_eq(
|
||||
type_object,
|
||||
activation.avm2().classes().uint.inner_class_definition(),
|
||||
) {
|
||||
return self.is_u32();
|
||||
}
|
||||
if Object::ptr_eq(type_object, activation.avm2().classes().int) {
|
||||
if GcCell::ptr_eq(
|
||||
type_object,
|
||||
activation.avm2().classes().int.inner_class_definition(),
|
||||
) {
|
||||
return self.is_i32();
|
||||
}
|
||||
|
||||
|
|
|
@ -407,7 +407,13 @@ impl<'gc> VTable<'gc> {
|
|||
),
|
||||
TraitKind::Function { .. } => (
|
||||
Property::new_slot(new_slot_id),
|
||||
PropertyClass::Class(activation.avm2().classes().function),
|
||||
PropertyClass::Class(
|
||||
activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.function
|
||||
.inner_class_definition(),
|
||||
),
|
||||
),
|
||||
TraitKind::Const {
|
||||
type_name, unit, ..
|
||||
|
@ -421,7 +427,9 @@ impl<'gc> VTable<'gc> {
|
|||
),
|
||||
TraitKind::Class { .. } => (
|
||||
Property::new_const_slot(new_slot_id),
|
||||
PropertyClass::Class(activation.avm2().classes().class),
|
||||
PropertyClass::Class(
|
||||
activation.avm2().classes().class.inner_class_definition(),
|
||||
),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
@ -486,7 +494,9 @@ impl<'gc> VTable<'gc> {
|
|||
write
|
||||
.resolved_traits
|
||||
.insert(name, Property::new_slot(new_slot_id));
|
||||
write.slot_classes.push(PropertyClass::Class(class));
|
||||
write
|
||||
.slot_classes
|
||||
.push(PropertyClass::Class(class.inner_class_definition()));
|
||||
|
||||
new_slot_id
|
||||
}
|
||||
|
|
|
@ -1708,7 +1708,7 @@ pub trait TDisplayObject<'gc>:
|
|||
.object2()
|
||||
.as_object()
|
||||
.expect("MovieClip object should have been constructed");
|
||||
let movieclip_class = context.avm2.classes().movieclip;
|
||||
let movieclip_class = context.avm2.classes().movieclip.inner_class_definition();
|
||||
// It's possible to have a DefineSprite tag with multiple frames, but have
|
||||
// the corresponding `SymbolClass` *not* extend `MovieClip` (e.g. extending `Sprite` directly.)
|
||||
// When this occurs, Flash Player will run the first frame, and immediately stop.
|
||||
|
|
|
@ -219,9 +219,13 @@ impl<'gc> Bitmap<'gc> {
|
|||
context: &mut UpdateContext<'_, 'gc>,
|
||||
class: Avm2ClassObject<'gc>,
|
||||
) {
|
||||
let bitmap_class = if class.has_class_in_chain(context.avm2.classes().bitmap) {
|
||||
let bitmap_class = if class
|
||||
.has_class_in_chain(context.avm2.classes().bitmap.inner_class_definition())
|
||||
{
|
||||
BitmapClass::Bitmap(class)
|
||||
} else if class.has_class_in_chain(context.avm2.classes().bitmapdata) {
|
||||
} else if class
|
||||
.has_class_in_chain(context.avm2.classes().bitmapdata.inner_class_definition())
|
||||
{
|
||||
BitmapClass::BitmapData(class)
|
||||
} else {
|
||||
return tracing::error!("Associated class {:?} for symbol {} must extend flash.display.Bitmap or BitmapData, does neither", class.inner_class_definition().read().name(), self.id());
|
||||
|
|
|
@ -171,4 +171,16 @@ trace("/// c_vector[1] = b_vector;");
|
|||
c_vector[1] = b_vector;
|
||||
|
||||
trace("/// (contents of c_vector...)");
|
||||
trace_vector(c_vector);
|
||||
trace_vector(c_vector);
|
||||
|
||||
class MyObject {}
|
||||
|
||||
|
||||
var myobj_vec: Vector.<MyObject> = new <MyObject>[];
|
||||
try {
|
||||
var cast: Vector.<int> = myobj_vec;
|
||||
} catch (e) {
|
||||
// Replace the non-deterministic address value with a placeholder string.
|
||||
var normalized = e.toString().replace(/@[0-9A-Fa-f]+/, "@ADDRESS")
|
||||
trace("Caught error: " + normalized);
|
||||
}
|
||||
|
|
|
@ -63,3 +63,4 @@ true
|
|||
/// (contents of c_vector...)
|
||||
1,2
|
||||
5,16
|
||||
Caught error: TypeError: Error #1034: Type Coercion failed: cannot convert __AS3__.vec::Vector.<Test.as$38::MyObject>@ADDRESS to __AS3__.vec.Vector.<int>.
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue