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);
|
||||
|
||||
// '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: &[(
|
||||
&str,
|
||||
Option<NativeMethodImpl>,
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
//! `flash.utils` namespace
|
||||
|
||||
use crate::avm2::method::Method;
|
||||
use crate::avm2::object::TObject;
|
||||
use crate::avm2::QName;
|
||||
use crate::avm2::property::Property;
|
||||
use crate::avm2::{Activation, Error, Object, Value};
|
||||
use crate::avm2_stub_method;
|
||||
use crate::avm2::{ClassObject, QName};
|
||||
use crate::string::AvmString;
|
||||
use crate::string::WString;
|
||||
use instant::Instant;
|
||||
|
@ -268,15 +269,54 @@ pub fn describe_type<'gc>(
|
|||
_this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
// This method is very incomplete, and should be fully implemented
|
||||
// once we have a better way of constructing XML from the Rust side
|
||||
avm2_stub_method!(activation, "flash.utils", "describeType");
|
||||
|
||||
let value = args[0].coerce_to_object(activation)?;
|
||||
let class_obj = value.as_class_object().or_else(|| value.instance_of());
|
||||
let Some(class_obj) = class_obj else {
|
||||
return Ok(activation
|
||||
.avm2()
|
||||
.classes()
|
||||
.xml
|
||||
.construct(activation, &[])?
|
||||
.into())
|
||||
};
|
||||
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);
|
||||
|
||||
Ok(activation
|
||||
|
@ -286,3 +326,174 @@ pub fn describe_type<'gc>(
|
|||
.construct(activation, &[xml_avm_string.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::class::{Class, ClassAttributes};
|
||||
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::value::Value;
|
||||
use crate::avm2::Multiname;
|
||||
|
@ -269,7 +269,17 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
|
|||
let class = Class::new(
|
||||
QName::new(activation.avm2().public_namespace, "int"),
|
||||
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),
|
||||
mc,
|
||||
);
|
||||
|
@ -283,7 +293,13 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Cl
|
|||
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(
|
||||
activation.avm2().public_namespace,
|
||||
CLASS_CONSTANTS,
|
||||
|
|
|
@ -269,6 +269,9 @@ pub struct NativeMethod<'gc> {
|
|||
/// The parameter signature of the method.
|
||||
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
|
||||
/// mentioned in the parameter list.
|
||||
pub is_variadic: bool,
|
||||
|
@ -318,6 +321,8 @@ impl<'gc> Method<'gc> {
|
|||
method,
|
||||
name,
|
||||
signature,
|
||||
// FIXME - take in the real return type. This is needed for 'describeType'
|
||||
return_type: Multiname::any(mc),
|
||||
is_variadic,
|
||||
},
|
||||
))
|
||||
|
@ -335,6 +340,8 @@ impl<'gc> Method<'gc> {
|
|||
method,
|
||||
name,
|
||||
signature: Vec::new(),
|
||||
// FIXME - take in the real return type. This is needed for 'describeType'
|
||||
return_type: Multiname::any(mc),
|
||||
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>] {
|
||||
match self {
|
||||
Method::Native(nm) => &nm.signature,
|
||||
|
|
|
@ -375,7 +375,9 @@ impl<'gc> Multiname<'gc> {
|
|||
uri.push_str(&ns);
|
||||
|
||||
if let Some(name) = self.name {
|
||||
uri.push_str(WStr::from_units(b"::"));
|
||||
if !uri.is_empty() {
|
||||
uri.push_str(WStr::from_units(b"::"));
|
||||
}
|
||||
uri.push_str(&name);
|
||||
} else {
|
||||
uri.push_str(WStr::from_units(b"::*"));
|
||||
|
@ -397,6 +399,16 @@ impl<'gc> Multiname<'gc> {
|
|||
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
|
||||
// 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> {
|
||||
|
|
|
@ -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> {
|
||||
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::scope::ScopeChain;
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::{Error, Multiname};
|
||||
use core::fmt;
|
||||
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
@ -31,6 +31,7 @@ pub fn function_allocator<'gc>(
|
|||
method: |_, _, _| Ok(Value::Undefined),
|
||||
name: "<Empty Function>",
|
||||
signature: vec![],
|
||||
return_type: Multiname::any(activation.context.gc_context),
|
||||
is_variadic: true,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::avm2::Error;
|
|||
use crate::avm2::Multiname;
|
||||
use crate::avm2::TranslationUnit;
|
||||
use crate::avm2::Value;
|
||||
use gc_arena::MutationContext;
|
||||
use gc_arena::{Collect, Gc};
|
||||
|
||||
#[derive(Debug, Collect, Clone, Copy)]
|
||||
|
@ -125,6 +126,14 @@ impl<'gc> PropertyClass<'gc> {
|
|||
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> {
|
||||
|
|
|
@ -175,7 +175,7 @@ impl<'gc> Trait<'gc> {
|
|||
Trait {
|
||||
name,
|
||||
attributes: TraitAttributes::empty(),
|
||||
kind: TraitKind::Slot {
|
||||
kind: TraitKind::Const {
|
||||
slot_id: 0,
|
||||
default_value: default_value.unwrap_or_else(|| default_value_for_type(&type_name)),
|
||||
type_name,
|
||||
|
|
|
@ -97,6 +97,23 @@ impl<'gc> VTable<'gc> {
|
|||
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> {
|
||||
self.0
|
||||
.read()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// compiled with mxmlc
|
||||
// compiled with mxmlc
|
||||
|
||||
package {
|
||||
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.getQualifiedClassName;
|
||||
import flash.utils.getQualifiedSuperclassName;
|
||||
import flash.utils.Dictionary;
|
||||
class C{}
|
||||
var o = {};
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
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");
|
||||
name = describeType(o).@name;
|
||||
trace(name.toString() == "Object");
|
||||
function describeXMLNormalized(val: *) {
|
||||
trace(normalizeXML(describeType(val)));
|
||||
}
|
||||
|
||||
trace(describeType(C).@name);
|
||||
name = describeType(C).@name;
|
||||
trace(name.toString());
|
||||
trace(describeType(new C()).@name);
|
||||
trace(describeType(int).@name);
|
||||
trace(describeType(1).@name);
|
||||
trace(describeType(Class).@name);
|
||||
trace(describeType(Dictionary).@name);
|
||||
trace(describeType(new Dictionary()).@name);
|
||||
class C {}
|
||||
|
||||
class Base {
|
||||
public function Base(optParam:* = null) {}
|
||||
public var baseProp:Object;
|
||||
public function baseMethod(): Boolean { return true }
|
||||
public function overridenMethod(firstParam: *, secondParam: Dictionary, thirdParam: DisplayObject = null): Object { return null; }
|
||||
AS3 function as3Method() {}
|
||||
}
|
||||
|
||||
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
|
||||
true
|
||||
FilePrivateNS:Test::C
|
||||
FilePrivateNS:Test::C
|
||||
FilePrivateNS:Test::C
|
||||
int
|
||||
int
|
||||
Class
|
||||
flash.utils::Dictionary
|
||||
flash.utils::Dictionary
|
||||
<type name="Object" 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="Object"/>
|
||||
</type>
|
||||
<type name="Object" isDynamic="true" isFinal="false" isStatic="false"/>
|
||||
<type name="Test.as$38::Subclass" 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::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