avm2: Implement describeType
This includes all of the XML elements described in 'describeType' docs. Unfortunately, the order of elements produced by Flash depends on the iteration order of internal hashtables. As a result, the test manually stringifies an XML object, sorting the stringified children, to produce consistent output between Flash and Ruffle.
This commit is contained in:
parent
5944bae33b
commit
a07ff36726
|
@ -57,6 +57,15 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
|
||||||
|
|
||||||
let mut write = class_class.write(gc_context);
|
let mut write = class_class.write(gc_context);
|
||||||
|
|
||||||
|
// 'length' is a weird undocumented constant in Class.
|
||||||
|
// We need to define it, since it shows up in 'describeType'
|
||||||
|
const CLASS_CONSTANTS: &[(&str, i32)] = &[("length", 1)];
|
||||||
|
write.define_constant_int_class_traits(
|
||||||
|
activation.avm2().public_namespace,
|
||||||
|
CLASS_CONSTANTS,
|
||||||
|
activation,
|
||||||
|
);
|
||||||
|
|
||||||
const PUBLIC_INSTANCE_PROPERTIES: &[(
|
const PUBLIC_INSTANCE_PROPERTIES: &[(
|
||||||
&str,
|
&str,
|
||||||
Option<NativeMethodImpl>,
|
Option<NativeMethodImpl>,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
//! `flash.utils` namespace
|
//! `flash.utils` namespace
|
||||||
|
|
||||||
|
use crate::avm2::method::Method;
|
||||||
use crate::avm2::object::TObject;
|
use crate::avm2::object::TObject;
|
||||||
use crate::avm2::QName;
|
use crate::avm2::property::Property;
|
||||||
use crate::avm2::{Activation, Error, Object, Value};
|
use crate::avm2::{Activation, Error, Object, Value};
|
||||||
use crate::avm2_stub_method;
|
use crate::avm2::{ClassObject, QName};
|
||||||
use crate::string::AvmString;
|
use crate::string::AvmString;
|
||||||
use crate::string::WString;
|
use crate::string::WString;
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
|
@ -268,15 +269,54 @@ pub fn describe_type<'gc>(
|
||||||
_this: Option<Object<'gc>>,
|
_this: Option<Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
// This method is very incomplete, and should be fully implemented
|
let value = args[0].coerce_to_object(activation)?;
|
||||||
// once we have a better way of constructing XML from the Rust side
|
let class_obj = value.as_class_object().or_else(|| value.instance_of());
|
||||||
avm2_stub_method!(activation, "flash.utils", "describeType");
|
let Some(class_obj) = class_obj else {
|
||||||
|
return Ok(activation
|
||||||
|
.avm2()
|
||||||
|
.classes()
|
||||||
|
.xml
|
||||||
|
.construct(activation, &[])?
|
||||||
|
.into())
|
||||||
|
};
|
||||||
let mut xml_string = String::new();
|
let mut xml_string = String::new();
|
||||||
let qualified_name =
|
|
||||||
get_qualified_class_name(activation, None, &[args[0]])?.coerce_to_string(activation)?;
|
|
||||||
|
|
||||||
xml_string += &format!("<type name=\"{qualified_name}\"></type>");
|
let is_static = value.as_class_object().is_some();
|
||||||
|
|
||||||
|
let class = class_obj.inner_class_definition();
|
||||||
|
let class = class.read();
|
||||||
|
|
||||||
|
let qualified_name = class
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
|
||||||
|
// If we're describing a Class object, then the "superclass" the the Class class
|
||||||
|
let superclass = if is_static {
|
||||||
|
Some(activation.avm2().classes().class)
|
||||||
|
} else {
|
||||||
|
class_obj.superclass_object()
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_attr = if let Some(superclass) = superclass {
|
||||||
|
format!(
|
||||||
|
" base=\"{}\"",
|
||||||
|
superclass
|
||||||
|
.inner_class_definition()
|
||||||
|
.read()
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_dynamic = is_static || !class.is_sealed();
|
||||||
|
let is_final = is_static || class.is_final();
|
||||||
|
|
||||||
|
write!(xml_string, "<type name=\"{qualified_name}\"{base_attr} isDynamic=\"{is_dynamic}\" isFinal=\"{is_final}\" isStatic=\"{is_static}\">").unwrap();
|
||||||
|
xml_string += &describe_internal_body(activation, class_obj, is_static)?;
|
||||||
|
xml_string += "</type>";
|
||||||
|
|
||||||
let xml_avm_string = AvmString::new_utf8(activation.context.gc_context, xml_string);
|
let xml_avm_string = AvmString::new_utf8(activation.context.gc_context, xml_string);
|
||||||
|
|
||||||
Ok(activation
|
Ok(activation
|
||||||
|
@ -286,3 +326,174 @@ pub fn describe_type<'gc>(
|
||||||
.construct(activation, &[xml_avm_string.into()])?
|
.construct(activation, &[xml_avm_string.into()])?
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe_internal_body<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
class_obj: ClassObject<'gc>,
|
||||||
|
is_static: bool,
|
||||||
|
) -> Result<String, Error<'gc>> {
|
||||||
|
let mut xml_string = String::new();
|
||||||
|
|
||||||
|
let class = class_obj.inner_class_definition();
|
||||||
|
let class = class.read();
|
||||||
|
|
||||||
|
let qualified_name = class
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
|
||||||
|
// If we're describing a Class object, then the "superclass" the the Class class
|
||||||
|
let superclass = if is_static {
|
||||||
|
Some(activation.avm2().classes().class)
|
||||||
|
} else {
|
||||||
|
class_obj.superclass_object()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_super_obj = superclass;
|
||||||
|
while let Some(super_obj) = current_super_obj {
|
||||||
|
let super_name = super_obj
|
||||||
|
.inner_class_definition()
|
||||||
|
.read()
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
write!(xml_string, "<extendsClass type=\"{super_name}\"/>").unwrap();
|
||||||
|
current_super_obj = super_obj.superclass_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we're describing a Class object, we use the class vtable (which hides instance properties)
|
||||||
|
let vtable = if is_static {
|
||||||
|
class_obj.class_vtable()
|
||||||
|
} else {
|
||||||
|
class_obj.instance_vtable()
|
||||||
|
};
|
||||||
|
|
||||||
|
for interface in class_obj.interfaces() {
|
||||||
|
let interface_name = interface
|
||||||
|
.read()
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
write!(
|
||||||
|
xml_string,
|
||||||
|
"<implementsInterface type=\"{interface_name}\"/>"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME - avmplus iterates over their own hashtable, so the order in the final XML
|
||||||
|
// is different
|
||||||
|
for (prop_name, ns, prop) in vtable.resolved_traits().iter() {
|
||||||
|
// All non-public properties (including properties in the AS3 namespace) are hidden
|
||||||
|
if !ns.is_public() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match prop {
|
||||||
|
Property::ConstSlot { slot_id } | Property::Slot { slot_id } => {
|
||||||
|
let prop_class_name = vtable
|
||||||
|
.slot_class_name(*slot_id, activation.context.gc_context)?
|
||||||
|
.to_qualified_name_or_star(activation.context.gc_context);
|
||||||
|
|
||||||
|
let elem_name = match prop {
|
||||||
|
Property::ConstSlot { .. } => "constant",
|
||||||
|
Property::Slot { .. } => "variable",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(
|
||||||
|
xml_string,
|
||||||
|
"<{elem_name} name=\"{prop_name}\" type=\"{prop_class_name}\"/>"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Property::Method { disp_id } => {
|
||||||
|
let method = vtable
|
||||||
|
.get_full_method(*disp_id)
|
||||||
|
.unwrap_or_else(|| panic!("Missing method for id {disp_id:?}"));
|
||||||
|
let return_type_name = method
|
||||||
|
.method
|
||||||
|
.return_type()
|
||||||
|
.to_qualified_name_or_star(activation.context.gc_context);
|
||||||
|
let declared_by = method
|
||||||
|
.class
|
||||||
|
.inner_class_definition()
|
||||||
|
.read()
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
|
||||||
|
write!(xml_string, "<method name=\"{prop_name}\" declaredBy=\"{declared_by}\" returnType=\"{return_type_name}\">").unwrap();
|
||||||
|
write_params(&mut xml_string, &method.method, activation);
|
||||||
|
xml_string += "</method>";
|
||||||
|
}
|
||||||
|
Property::Virtual { get, set } => {
|
||||||
|
let access = match (get, set) {
|
||||||
|
(Some(_), Some(_)) => "readwrite",
|
||||||
|
(Some(_), None) => "readonly",
|
||||||
|
(None, Some(_)) => "writeonly",
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// For getters, obtain the type by looking at the getter return type.
|
||||||
|
// For setters, obtain the type by looking at the setter's first parameter.
|
||||||
|
let (method_type, defining_class) = if let Some(get) = get {
|
||||||
|
let getter = vtable
|
||||||
|
.get_full_method(*get)
|
||||||
|
.unwrap_or_else(|| panic!("Missing 'get' method for id {get:?}"));
|
||||||
|
(getter.method.return_type(), getter.class)
|
||||||
|
} else if let Some(set) = set {
|
||||||
|
let setter = vtable
|
||||||
|
.get_full_method(*set)
|
||||||
|
.unwrap_or_else(|| panic!("Missing 'set' method for id {set:?}"));
|
||||||
|
(
|
||||||
|
setter.method.signature()[0].param_type_name.clone(),
|
||||||
|
setter.class,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let accessor_type =
|
||||||
|
method_type.to_qualified_name_or_star(activation.context.gc_context);
|
||||||
|
let declared_by = defining_class
|
||||||
|
.inner_class_definition()
|
||||||
|
.read()
|
||||||
|
.name()
|
||||||
|
.to_qualified_name(activation.context.gc_context);
|
||||||
|
|
||||||
|
write!(xml_string, "<accessor name=\"{prop_name}\" access=\"{access}\" type=\"{accessor_type}\" declaredBy=\"{declared_by}\"/>").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let constructor = class_obj.constructor();
|
||||||
|
// Flash only shows a <constructor> element if it has at least one parameter
|
||||||
|
if !is_static && !constructor.signature().is_empty() {
|
||||||
|
xml_string += "<constructor>";
|
||||||
|
write_params(&mut xml_string, &constructor, activation);
|
||||||
|
xml_string += "</constructor>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're describing a Class object, add a <factory> element describing the instance.
|
||||||
|
if is_static {
|
||||||
|
write!(xml_string, "<factory type=\"{qualified_name}\">").unwrap();
|
||||||
|
xml_string += &describe_internal_body(activation, class_obj, false)?;
|
||||||
|
xml_string += "</factory>";
|
||||||
|
}
|
||||||
|
Ok(xml_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_params<'gc>(
|
||||||
|
xml_string: &mut String,
|
||||||
|
method: &Method<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc>,
|
||||||
|
) {
|
||||||
|
for (i, param) in method.signature().iter().enumerate() {
|
||||||
|
let index = i + 1;
|
||||||
|
let param_type_name = param
|
||||||
|
.param_type_name
|
||||||
|
.to_qualified_name_or_star(activation.context.gc_context);
|
||||||
|
let optional = param.default_value.is_some();
|
||||||
|
write!(
|
||||||
|
xml_string,
|
||||||
|
"<parameter index=\"{index}\" type=\"{param_type_name}\" optional=\"{optional}\"/>"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::avm2::activation::Activation;
|
use crate::avm2::activation::Activation;
|
||||||
use crate::avm2::class::{Class, ClassAttributes};
|
use crate::avm2::class::{Class, ClassAttributes};
|
||||||
use crate::avm2::globals::number::{print_with_precision, print_with_radix};
|
use crate::avm2::globals::number::{print_with_precision, print_with_radix};
|
||||||
use crate::avm2::method::{Method, NativeMethodImpl};
|
use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig};
|
||||||
use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject};
|
use crate::avm2::object::{primitive_allocator, FunctionObject, Object, TObject};
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Multiname;
|
use crate::avm2::Multiname;
|
||||||
|
@ -269,7 +269,17 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
|
||||||
let class = Class::new(
|
let class = Class::new(
|
||||||
QName::new(activation.avm2().public_namespace, "int"),
|
QName::new(activation.avm2().public_namespace, "int"),
|
||||||
Some(Multiname::new(activation.avm2().public_namespace, "Object")),
|
Some(Multiname::new(activation.avm2().public_namespace, "Object")),
|
||||||
Method::from_builtin(instance_init, "<int instance initializer>", mc),
|
Method::from_builtin_and_params(
|
||||||
|
instance_init,
|
||||||
|
"<int instance initializer>",
|
||||||
|
vec![ParamConfig {
|
||||||
|
param_name: AvmString::new_utf8(activation.context.gc_context, "value"),
|
||||||
|
param_type_name: Multiname::any(activation.context.gc_context),
|
||||||
|
default_value: Some(Value::Integer(0)),
|
||||||
|
}],
|
||||||
|
false,
|
||||||
|
mc,
|
||||||
|
),
|
||||||
Method::from_builtin(class_init, "<int class initializer>", mc),
|
Method::from_builtin(class_init, "<int class initializer>", mc),
|
||||||
mc,
|
mc,
|
||||||
);
|
);
|
||||||
|
@ -283,7 +293,13 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
|
||||||
mc,
|
mc,
|
||||||
));
|
));
|
||||||
|
|
||||||
const CLASS_CONSTANTS: &[(&str, i32)] = &[("MAX_VALUE", i32::MAX), ("MIN_VALUE", i32::MIN)];
|
// 'length' is a weird undocumented constant in int.
|
||||||
|
// We need to define it, since it shows up in 'describeType'
|
||||||
|
const CLASS_CONSTANTS: &[(&str, i32)] = &[
|
||||||
|
("MAX_VALUE", i32::MAX),
|
||||||
|
("MIN_VALUE", i32::MIN),
|
||||||
|
("length", 1),
|
||||||
|
];
|
||||||
write.define_constant_int_class_traits(
|
write.define_constant_int_class_traits(
|
||||||
activation.avm2().public_namespace,
|
activation.avm2().public_namespace,
|
||||||
CLASS_CONSTANTS,
|
CLASS_CONSTANTS,
|
||||||
|
|
|
@ -269,6 +269,9 @@ pub struct NativeMethod<'gc> {
|
||||||
/// The parameter signature of the method.
|
/// The parameter signature of the method.
|
||||||
pub signature: Vec<ParamConfig<'gc>>,
|
pub signature: Vec<ParamConfig<'gc>>,
|
||||||
|
|
||||||
|
/// The return type of this method.
|
||||||
|
pub return_type: Multiname<'gc>,
|
||||||
|
|
||||||
/// Whether or not this method accepts parameters beyond those
|
/// Whether or not this method accepts parameters beyond those
|
||||||
/// mentioned in the parameter list.
|
/// mentioned in the parameter list.
|
||||||
pub is_variadic: bool,
|
pub is_variadic: bool,
|
||||||
|
@ -318,6 +321,8 @@ impl<'gc> Method<'gc> {
|
||||||
method,
|
method,
|
||||||
name,
|
name,
|
||||||
signature,
|
signature,
|
||||||
|
// FIXME - take in the real return type. This is needed for 'describeType'
|
||||||
|
return_type: Multiname::any(mc),
|
||||||
is_variadic,
|
is_variadic,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -335,6 +340,8 @@ impl<'gc> Method<'gc> {
|
||||||
method,
|
method,
|
||||||
name,
|
name,
|
||||||
signature: Vec::new(),
|
signature: Vec::new(),
|
||||||
|
// FIXME - take in the real return type. This is needed for 'describeType'
|
||||||
|
return_type: Multiname::any(mc),
|
||||||
is_variadic: true,
|
is_variadic: true,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
@ -352,6 +359,13 @@ impl<'gc> Method<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn return_type(&self) -> Multiname<'gc> {
|
||||||
|
match self {
|
||||||
|
Method::Native(nm) => nm.return_type.clone(),
|
||||||
|
Method::Bytecode(bm) => bm.return_type.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn signature(&self) -> &[ParamConfig<'gc>] {
|
pub fn signature(&self) -> &[ParamConfig<'gc>] {
|
||||||
match self {
|
match self {
|
||||||
Method::Native(nm) => &nm.signature,
|
Method::Native(nm) => &nm.signature,
|
||||||
|
|
|
@ -375,7 +375,9 @@ impl<'gc> Multiname<'gc> {
|
||||||
uri.push_str(&ns);
|
uri.push_str(&ns);
|
||||||
|
|
||||||
if let Some(name) = self.name {
|
if let Some(name) = self.name {
|
||||||
|
if !uri.is_empty() {
|
||||||
uri.push_str(WStr::from_units(b"::"));
|
uri.push_str(WStr::from_units(b"::"));
|
||||||
|
}
|
||||||
uri.push_str(&name);
|
uri.push_str(&name);
|
||||||
} else {
|
} else {
|
||||||
uri.push_str(WStr::from_units(b"::*"));
|
uri.push_str(WStr::from_units(b"::*"));
|
||||||
|
@ -397,6 +399,16 @@ impl<'gc> Multiname<'gc> {
|
||||||
AvmString::new(mc, uri)
|
AvmString::new(mc, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `to_qualified_name`, but returns `*` if `self.is_any()` is true.
|
||||||
|
/// This is used by `describeType`
|
||||||
|
pub fn to_qualified_name_or_star(&self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
||||||
|
if self.is_any() {
|
||||||
|
AvmString::new_utf8(mc, "*")
|
||||||
|
} else {
|
||||||
|
self.to_qualified_name(mc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// note: I didn't look very deeply into how different exactly this should be
|
// note: I didn't look very deeply into how different exactly this should be
|
||||||
// this is currently generally based on to_qualified_name, without params and leading ::
|
// this is currently generally based on to_qualified_name, without params and leading ::
|
||||||
pub fn to_error_qualified_name(&self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
pub fn to_error_qualified_name(&self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
||||||
|
|
|
@ -692,6 +692,10 @@ impl<'gc> ClassObject<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn constructor(self) -> Method<'gc> {
|
||||||
|
self.0.read().constructor.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn instance_vtable(self) -> VTable<'gc> {
|
pub fn instance_vtable(self) -> VTable<'gc> {
|
||||||
self.0.read().instance_vtable
|
self.0.read().instance_vtable
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData};
|
||||||
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
|
||||||
use crate::avm2::scope::ScopeChain;
|
use crate::avm2::scope::ScopeChain;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::{Error, Multiname};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
|
@ -31,6 +31,7 @@ pub fn function_allocator<'gc>(
|
||||||
method: |_, _, _| Ok(Value::Undefined),
|
method: |_, _, _| Ok(Value::Undefined),
|
||||||
name: "<Empty Function>",
|
name: "<Empty Function>",
|
||||||
signature: vec![],
|
signature: vec![],
|
||||||
|
return_type: Multiname::any(activation.context.gc_context),
|
||||||
is_variadic: true,
|
is_variadic: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::avm2::Error;
|
||||||
use crate::avm2::Multiname;
|
use crate::avm2::Multiname;
|
||||||
use crate::avm2::TranslationUnit;
|
use crate::avm2::TranslationUnit;
|
||||||
use crate::avm2::Value;
|
use crate::avm2::Value;
|
||||||
|
use gc_arena::MutationContext;
|
||||||
use gc_arena::{Collect, Gc};
|
use gc_arena::{Collect, Gc};
|
||||||
|
|
||||||
#[derive(Debug, Collect, Clone, Copy)]
|
#[derive(Debug, Collect, Clone, Copy)]
|
||||||
|
@ -125,6 +126,14 @@ impl<'gc> PropertyClass<'gc> {
|
||||||
Ok((value, changed))
|
Ok((value, changed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self, mc: MutationContext<'gc, '_>) -> Multiname<'gc> {
|
||||||
|
match self {
|
||||||
|
PropertyClass::Class(class) => class.inner_class_definition().read().name().into(),
|
||||||
|
PropertyClass::Name(gc) => gc.0.clone(),
|
||||||
|
PropertyClass::Any => Multiname::any(mc),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolveOutcome<'gc> {
|
enum ResolveOutcome<'gc> {
|
||||||
|
|
|
@ -175,7 +175,7 @@ impl<'gc> Trait<'gc> {
|
||||||
Trait {
|
Trait {
|
||||||
name,
|
name,
|
||||||
attributes: TraitAttributes::empty(),
|
attributes: TraitAttributes::empty(),
|
||||||
kind: TraitKind::Slot {
|
kind: TraitKind::Const {
|
||||||
slot_id: 0,
|
slot_id: 0,
|
||||||
default_value: default_value.unwrap_or_else(|| default_value_for_type(&type_name)),
|
default_value: default_value.unwrap_or_else(|| default_value_for_type(&type_name)),
|
||||||
type_name,
|
type_name,
|
||||||
|
|
|
@ -97,6 +97,23 @@ impl<'gc> VTable<'gc> {
|
||||||
VTable(GcCell::allocate(mc, self.0.read().clone()))
|
VTable(GcCell::allocate(mc, self.0.read().clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolved_traits(&self) -> Ref<'_, PropertyMap<'gc, Property>> {
|
||||||
|
Ref::map(self.0.read(), |v| &v.resolved_traits)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slot_class_name(
|
||||||
|
&self,
|
||||||
|
slot_id: u32,
|
||||||
|
mc: MutationContext<'gc, '_>,
|
||||||
|
) -> Result<Multiname<'gc>, Error<'gc>> {
|
||||||
|
self.0
|
||||||
|
.read()
|
||||||
|
.slot_classes
|
||||||
|
.get(slot_id as usize)
|
||||||
|
.ok_or_else(|| "Invalid slot ID".into())
|
||||||
|
.map(|c| c.get_name(mc))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_trait(self, name: &Multiname<'gc>) -> Option<Property> {
|
pub fn get_trait(self, name: &Multiname<'gc>) -> Option<Property> {
|
||||||
self.0
|
self.0
|
||||||
.read()
|
.read()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// compiled with mxmlc
|
// compiled with mxmlc
|
||||||
|
|
||||||
package {
|
package {
|
||||||
import flash.display.MovieClip;
|
import flash.display.MovieClip;
|
||||||
|
@ -8,27 +8,79 @@ package {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: this entire test is to be replaced by more comprehensive test
|
|
||||||
// once XML gets implemented.
|
|
||||||
// This test only checks that `type.@name` looks like a string containing the type name.
|
|
||||||
|
|
||||||
import flash.utils.describeType;
|
import flash.utils.describeType;
|
||||||
|
import flash.utils.getQualifiedClassName;
|
||||||
|
import flash.utils.getQualifiedSuperclassName;
|
||||||
import flash.utils.Dictionary;
|
import flash.utils.Dictionary;
|
||||||
class C{}
|
import flash.display.DisplayObject;
|
||||||
var o = {};
|
|
||||||
|
|
||||||
var name; // mxmlc disallows .@name.toString() for some reason
|
// The order of elements in describeType(obj)) depends on the iteration order
|
||||||
|
// of the internal avmplus Traits hashtable.
|
||||||
|
// We don't currently reproduce this in Ruffle, so we can't just use 'toXMLString'
|
||||||
|
// to print the output. Instead, we use this function to re-implement 'toXMLString',
|
||||||
|
// and normalize the output by printing the children of an element in lexicographic
|
||||||
|
// order (by their stringified value)
|
||||||
|
function normalizeXML(data: XML, indent:uint = 0) {
|
||||||
|
var output = "";
|
||||||
|
for (var i = 0; i < indent; i++) {
|
||||||
|
output += " ";
|
||||||
|
};
|
||||||
|
output += "<" + data.name();
|
||||||
|
for each (var attr in data.attributes()) {
|
||||||
|
output += " " + attr.name() + "=\"" + attr + "\"";
|
||||||
|
}
|
||||||
|
if (data.children().length() == 0) {
|
||||||
|
output += "/>";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
output += ">\n";
|
||||||
|
var childStrs = []
|
||||||
|
for each (var child in data.children()) {
|
||||||
|
childStrs.push(normalizeXML(child, indent + 2));
|
||||||
|
}
|
||||||
|
childStrs.sort()
|
||||||
|
for each (var childStr in childStrs) {
|
||||||
|
for (var i = 0 ; i < indent; i++) {
|
||||||
|
output += " ";
|
||||||
|
}
|
||||||
|
output += childStr;
|
||||||
|
output += "\n"
|
||||||
|
}
|
||||||
|
for (var i = 0; i < indent; i++) {
|
||||||
|
output += " ";
|
||||||
|
};
|
||||||
|
output += "</" + data.name() + ">";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
trace(describeType(o).@name == "Object");
|
function describeXMLNormalized(val: *) {
|
||||||
name = describeType(o).@name;
|
trace(normalizeXML(describeType(val)));
|
||||||
trace(name.toString() == "Object");
|
}
|
||||||
|
|
||||||
trace(describeType(C).@name);
|
class C {}
|
||||||
name = describeType(C).@name;
|
|
||||||
trace(name.toString());
|
class Base {
|
||||||
trace(describeType(new C()).@name);
|
public function Base(optParam:* = null) {}
|
||||||
trace(describeType(int).@name);
|
public var baseProp:Object;
|
||||||
trace(describeType(1).@name);
|
public function baseMethod(): Boolean { return true }
|
||||||
trace(describeType(Class).@name);
|
public function overridenMethod(firstParam: *, secondParam: Dictionary, thirdParam: DisplayObject = null): Object { return null; }
|
||||||
trace(describeType(Dictionary).@name);
|
AS3 function as3Method() {}
|
||||||
trace(describeType(new Dictionary()).@name);
|
}
|
||||||
|
|
||||||
|
class Subclass extends Base {
|
||||||
|
public var subProp:Object;
|
||||||
|
public function subMethod() {}
|
||||||
|
public override function overridenMethod(firstParam: *, secondParam: Dictionary, thirdParam: DisplayObject = null): Object { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
describeXMLNormalized(Object);
|
||||||
|
describeXMLNormalized(new Object());
|
||||||
|
describeXMLNormalized(Subclass);
|
||||||
|
describeXMLNormalized(new Subclass());
|
||||||
|
describeXMLNormalized(C);
|
||||||
|
describeXMLNormalized(new C());
|
||||||
|
describeXMLNormalized(int);
|
||||||
|
describeXMLNormalized(1);
|
||||||
|
describeXMLNormalized(Class);
|
||||||
|
describeXMLNormalized(Dictionary);
|
||||||
|
describeXMLNormalized(new Dictionary());
|
|
@ -1,10 +1,97 @@
|
||||||
true
|
<type name="Object" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
true
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
FilePrivateNS:Test::C
|
<constant name="length" type="int"/>
|
||||||
FilePrivateNS:Test::C
|
<extendsClass type="Class"/>
|
||||||
FilePrivateNS:Test::C
|
<extendsClass type="Object"/>
|
||||||
int
|
<factory type="Object"/>
|
||||||
int
|
</type>
|
||||||
Class
|
<type name="Object" isDynamic="true" isFinal="false" isStatic="false"/>
|
||||||
flash.utils::Dictionary
|
<type name="Test.as$38::Subclass" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
flash.utils::Dictionary
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<extendsClass type="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<factory type="Test.as$38::Subclass">
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<extendsClass type="Test.as$38::Base"/>
|
||||||
|
<method name="baseMethod" declaredBy="Test.as$38::Base" returnType="Boolean"/>
|
||||||
|
<method name="overridenMethod" declaredBy="Test.as$38::Subclass" returnType="Object">
|
||||||
|
<parameter index="1" type="*" optional="false"/>
|
||||||
|
<parameter index="2" type="flash.utils::Dictionary" optional="false"/>
|
||||||
|
<parameter index="3" type="flash.display::DisplayObject" optional="true"/>
|
||||||
|
</method>
|
||||||
|
<method name="subMethod" declaredBy="Test.as$38::Subclass" returnType="*"/>
|
||||||
|
<variable name="baseProp" type="Object"/>
|
||||||
|
<variable name="subProp" type="Object"/>
|
||||||
|
</factory>
|
||||||
|
</type>
|
||||||
|
<type name="Test.as$38::Subclass" base="Test.as$38::Base" isDynamic="false" isFinal="false" isStatic="false">
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<extendsClass type="Test.as$38::Base"/>
|
||||||
|
<method name="baseMethod" declaredBy="Test.as$38::Base" returnType="Boolean"/>
|
||||||
|
<method name="overridenMethod" declaredBy="Test.as$38::Subclass" returnType="Object">
|
||||||
|
<parameter index="1" type="*" optional="false"/>
|
||||||
|
<parameter index="2" type="flash.utils::Dictionary" optional="false"/>
|
||||||
|
<parameter index="3" type="flash.display::DisplayObject" optional="true"/>
|
||||||
|
</method>
|
||||||
|
<method name="subMethod" declaredBy="Test.as$38::Subclass" returnType="*"/>
|
||||||
|
<variable name="baseProp" type="Object"/>
|
||||||
|
<variable name="subProp" type="Object"/>
|
||||||
|
</type>
|
||||||
|
<type name="Test.as$38::C" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<extendsClass type="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<factory type="Test.as$38::C">
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</factory>
|
||||||
|
</type>
|
||||||
|
<type name="Test.as$38::C" base="Object" isDynamic="false" isFinal="false" isStatic="false">
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</type>
|
||||||
|
<type name="int" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<constant name="MAX_VALUE" type="int"/>
|
||||||
|
<constant name="MIN_VALUE" type="int"/>
|
||||||
|
<constant name="length" type="int"/>
|
||||||
|
<extendsClass type="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<factory type="int">
|
||||||
|
<constructor>
|
||||||
|
<parameter index="1" type="*" optional="true"/>
|
||||||
|
</constructor>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</factory>
|
||||||
|
</type>
|
||||||
|
<type name="int" base="Object" isDynamic="false" isFinal="true" isStatic="false">
|
||||||
|
<constructor>
|
||||||
|
<parameter index="1" type="*" optional="true"/>
|
||||||
|
</constructor>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</type>
|
||||||
|
<type name="Class" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<constant name="length" type="int"/>
|
||||||
|
<extendsClass type="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<factory type="Class">
|
||||||
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</factory>
|
||||||
|
</type>
|
||||||
|
<type name="flash.utils::Dictionary" base="Class" isDynamic="true" isFinal="true" isStatic="true">
|
||||||
|
<accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
|
||||||
|
<extendsClass type="Class"/>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
<factory type="flash.utils::Dictionary">
|
||||||
|
<constructor>
|
||||||
|
<parameter index="1" type="Boolean" optional="true"/>
|
||||||
|
</constructor>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</factory>
|
||||||
|
</type>
|
||||||
|
<type name="flash.utils::Dictionary" base="Object" isDynamic="true" isFinal="false" isStatic="false">
|
||||||
|
<constructor>
|
||||||
|
<parameter index="1" type="Boolean" optional="true"/>
|
||||||
|
</constructor>
|
||||||
|
<extendsClass type="Object"/>
|
||||||
|
</type>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue