Commit a7a64a77 by Mark Mitchell Committed by Mark Mitchell

cp-tree.h (CP_INTEGRAL_TYPE_P): New macro.

	* cp-tree.h (CP_INTEGRAL_TYPE_P): New macro.
	(ARITHMETIC_TYPE_P): Adjust definition for standard conformance.
	(strip_top_quals): Declare.
	(ncp_convert): Likewise.
	(type_after_usual_arithmetic_converions): Likewise.
	(composite_pointer_type): Likewise.
	* call.c (strip_top_quals): Don't make it static.
	(promoted_arithmetic_type_p): New function.
	(conditional_conversion): Likewise.
	(null_ptr_cst_p): Allow `false' as a NULL pointer constant.
	(standard_conversion): Use same_type_p.  Don't build BASE_CONVs
	for converting a type to itself.
	(reference_binding): Honor LOOKUP_NO_TEMP_BIND.
	(implicit_conversion): Make sure the from and to types are
	complete.
	(add_builtin_candidate): Correct handling of ?: operator.
	(add_builtin_candidates): Improve documentation.
	(build_conditional_expr): New function.
	(can_convert): Implement in terms of can_convert_arg.
	(ncp_convert): New function.
	* typeck.c (type_after_usual_arithmetic_conversions): New
	function, split out from common_type.
	(composite_pointer_type): New function, split out from
	build_conditional_expr.
	(common_type): Use type_after_usual_arithmetic_conversions.
	Remove redundant attribute merging.
	(comptypes): Tidy.  Handle COMPLEX_TYPE.
	(build_binary_op_nodefault): Use null_ptr_cst_p.
	(build_conditional_expr): Remove.
	(convert_for_assignment): Use new conversion functions.

	* cp-tree.h (abstract_virtuals_error): Change declaration.
	* typeck2.c (abstract_virtuals_error): Check to see if an error
	ocurred, and return a boolean value accordingly.
	(build_functional_cast): Adjust accordingly.
	* class.c (finish_struct_1): Likewise.
	* cvt.c (ocp_convert): Likewise.
	* decl.c (cp_finish_decl): Likewise.
	(grokparams): Likewise.
	(grok_op_properties): Likewise.
	(start_function): Likewise.
	* init.c (build_new_1): Likewise.

	* pt.c (unify): Don't get confused by pointers-to-member functions.

	* search.c (build_cplus_new): Robustify.

From-SVN: r28262
parent 5cabd6f5
1999-07-26 Mark Mitchell <mark@codesourcery.com>
* cp-tree.h (CP_INTEGRAL_TYPE_P): New macro.
(ARITHMETIC_TYPE_P): Adjust definition for standard conformance.
(strip_top_quals): Declare.
(ncp_convert): Likewise.
(type_after_usual_arithmetic_converions): Likewise.
(composite_pointer_type): Likewise.
* call.c (strip_top_quals): Don't make it static.
(promoted_arithmetic_type_p): New function.
(conditional_conversion): Likewise.
(null_ptr_cst_p): Allow `false' as a NULL pointer constant.
(standard_conversion): Use same_type_p. Don't build BASE_CONVs
for converting a type to itself.
(reference_binding): Honor LOOKUP_NO_TEMP_BIND.
(implicit_conversion): Make sure the from and to types are
complete.
(add_builtin_candidate): Correct handling of ?: operator.
(add_builtin_candidates): Improve documentation.
(build_conditional_expr): New function.
(can_convert): Implement in terms of can_convert_arg.
(ncp_convert): New function.
* typeck.c (type_after_usual_arithmetic_conversions): New
function, split out from common_type.
(composite_pointer_type): New function, split out from
build_conditional_expr.
(common_type): Use type_after_usual_arithmetic_conversions.
Remove redundant attribute merging.
(comptypes): Tidy. Handle COMPLEX_TYPE.
(build_binary_op_nodefault): Use null_ptr_cst_p.
(build_conditional_expr): Remove.
(convert_for_assignment): Use new conversion functions.
* cp-tree.h (abstract_virtuals_error): Change declaration.
* typeck2.c (abstract_virtuals_error): Check to see if an error
ocurred, and return a boolean value accordingly.
(build_functional_cast): Adjust accordingly.
* class.c (finish_struct_1): Likewise.
* cvt.c (ocp_convert): Likewise.
* decl.c (cp_finish_decl): Likewise.
(grokparams): Likewise.
(grok_op_properties): Likewise.
(start_function): Likewise.
* init.c (build_new_1): Likewise.
* pt.c (unify): Don't get confused by pointers-to-member functions.
* search.c (build_cplus_new): Robustify.
1999-07-24 Richard Henderson <rth@cygnus.com> 1999-07-24 Richard Henderson <rth@cygnus.com>
* decl.c (ptr_type_node, va_list_type_node): New. * decl.c (ptr_type_node, va_list_type_node): New.
......
...@@ -3619,8 +3619,7 @@ finish_struct_1 (t) ...@@ -3619,8 +3619,7 @@ finish_struct_1 (t)
{ {
/* Never let anything with uninheritable virtuals /* Never let anything with uninheritable virtuals
make it through without complaint. */ make it through without complaint. */
if (CLASSTYPE_ABSTRACT_VIRTUALS (type)) abstract_virtuals_error (x, type);
abstract_virtuals_error (x, type);
/* Don't let signatures make it through either. */ /* Don't let signatures make it through either. */
if (IS_SIGNATURE (type)) if (IS_SIGNATURE (type))
......
...@@ -1660,7 +1660,24 @@ extern int flag_new_for_scope; ...@@ -1660,7 +1660,24 @@ extern int flag_new_for_scope;
#define INTEGRAL_CODE_P(CODE) \ #define INTEGRAL_CODE_P(CODE) \
(CODE == INTEGER_TYPE || CODE == ENUMERAL_TYPE || CODE == BOOLEAN_TYPE) (CODE == INTEGER_TYPE || CODE == ENUMERAL_TYPE || CODE == BOOLEAN_TYPE)
#define ARITHMETIC_TYPE_P(TYPE) (INTEGRAL_TYPE_P (TYPE) || FLOAT_TYPE_P (TYPE))
/* [basic.fundamental]
Types bool, char, wchar_t, and the signed and unsigned integer types
are collectively called integral types.
Note that INTEGRAL_TYPE_P, as defined in tree.h, allows enumeration
types as well, which is incorrect in C++. */
#define CP_INTEGRAL_TYPE_P(TYPE) \
(TREE_CODE ((TYPE)) == BOOLEAN_TYPE \
|| TREE_CODE ((TYPE)) == INTEGER_TYPE)
/* [basic.fundamental]
Integral and floating types are collectively called arithmetic
types. */
#define ARITHMETIC_TYPE_P(TYPE) \
(CP_INTEGRAL_TYPE_P (TYPE) || TREE_CODE (TYPE) == REAL_TYPE)
/* Mark which labels are explicitly declared. /* Mark which labels are explicitly declared.
These may be shadowed, and may be referenced from nested functions. */ These may be shadowed, and may be referenced from nested functions. */
...@@ -2767,6 +2784,8 @@ extern tree convert_default_arg PROTO((tree, tree, tree)); ...@@ -2767,6 +2784,8 @@ extern tree convert_default_arg PROTO((tree, tree, tree));
extern tree convert_arg_to_ellipsis PROTO((tree)); extern tree convert_arg_to_ellipsis PROTO((tree));
extern int is_properly_derived_from PROTO((tree, tree)); extern int is_properly_derived_from PROTO((tree, tree));
extern tree initialize_reference PROTO((tree, tree)); extern tree initialize_reference PROTO((tree, tree));
extern tree strip_top_quals PROTO((tree));
extern tree ncp_convert PROTO((tree, tree));
/* in class.c */ /* in class.c */
extern tree build_vbase_path PROTO((enum tree_code, tree, tree, tree, int)); extern tree build_vbase_path PROTO((enum tree_code, tree, tree, tree, int));
...@@ -3534,12 +3553,14 @@ extern tree build_ptrmemfunc1 PROTO((tree, tree, tree, tree, t ...@@ -3534,12 +3553,14 @@ extern tree build_ptrmemfunc1 PROTO((tree, tree, tree, tree, t
extern void expand_ptrmemfunc_cst PROTO((tree, tree *, tree *, tree *, tree *)); extern void expand_ptrmemfunc_cst PROTO((tree, tree *, tree *, tree *, tree *));
extern tree delta2_from_ptrmemfunc PROTO((tree)); extern tree delta2_from_ptrmemfunc PROTO((tree));
extern tree pfn_from_ptrmemfunc PROTO((tree)); extern tree pfn_from_ptrmemfunc PROTO((tree));
extern tree type_after_usual_arithmetic_conversions PROTO((tree, tree));
extern tree composite_pointer_type PROTO((tree, tree, tree, tree, char*));
/* in typeck2.c */ /* in typeck2.c */
extern tree error_not_base_type PROTO((tree, tree)); extern tree error_not_base_type PROTO((tree, tree));
extern tree binfo_or_else PROTO((tree, tree)); extern tree binfo_or_else PROTO((tree, tree));
extern void readonly_error PROTO((tree, const char *, int)); extern void readonly_error PROTO((tree, const char *, int));
extern void abstract_virtuals_error PROTO((tree, tree)); extern int abstract_virtuals_error PROTO((tree, tree));
extern void signature_error PROTO((tree, tree)); extern void signature_error PROTO((tree, tree));
extern void incomplete_type_error PROTO((tree, tree)); extern void incomplete_type_error PROTO((tree, tree));
extern void my_friendly_abort PROTO((int)) extern void my_friendly_abort PROTO((int))
......
...@@ -826,11 +826,8 @@ ocp_convert (type, expr, convtype, flags) ...@@ -826,11 +826,8 @@ ocp_convert (type, expr, convtype, flags)
ctor = e; ctor = e;
if (IS_AGGR_TYPE (type) && CLASSTYPE_ABSTRACT_VIRTUALS (type)) if (abstract_virtuals_error (NULL_TREE, type))
{ return error_mark_node;
abstract_virtuals_error (NULL_TREE, type);
return error_mark_node;
}
if ((flags & LOOKUP_ONLYCONVERTING) if ((flags & LOOKUP_ONLYCONVERTING)
&& ! (IS_AGGR_TYPE (dtype) && DERIVED_FROM_P (type, dtype))) && ! (IS_AGGR_TYPE (dtype) && DERIVED_FROM_P (type, dtype)))
......
...@@ -7857,14 +7857,9 @@ cp_finish_decl (decl, init, asmspec_tree, need_pop, flags) ...@@ -7857,14 +7857,9 @@ cp_finish_decl (decl, init, asmspec_tree, need_pop, flags)
if (was_temp) if (was_temp)
resume_temporary_allocation (); resume_temporary_allocation ();
if (type != error_mark_node if (!abstract_virtuals_error (decl, core_type)
&& TYPE_LANG_SPECIFIC (core_type) && (TREE_CODE (type) == FUNCTION_TYPE
&& CLASSTYPE_ABSTRACT_VIRTUALS (core_type)) || TREE_CODE (type) == METHOD_TYPE))
abstract_virtuals_error (decl, core_type);
else if ((TREE_CODE (type) == FUNCTION_TYPE
|| TREE_CODE (type) == METHOD_TYPE)
&& TYPE_LANG_SPECIFIC (TREE_TYPE (type))
&& CLASSTYPE_ABSTRACT_VIRTUALS (TREE_TYPE (type)))
abstract_virtuals_error (decl, TREE_TYPE (type)); abstract_virtuals_error (decl, TREE_TYPE (type));
if (TYPE_LANG_SPECIFIC (core_type) && IS_SIGNATURE (core_type)) if (TYPE_LANG_SPECIFIC (core_type) && IS_SIGNATURE (core_type))
...@@ -11614,13 +11609,8 @@ grokparms (first_parm, funcdef_flag) ...@@ -11614,13 +11609,8 @@ grokparms (first_parm, funcdef_flag)
type = build_pointer_type (type); type = build_pointer_type (type);
TREE_TYPE (decl) = type; TREE_TYPE (decl) = type;
} }
else if (TREE_CODE (type) == RECORD_TYPE else if (abstract_virtuals_error (decl, type))
&& TYPE_LANG_SPECIFIC (type) any_error = 1; /* Seems like a good idea. */
&& CLASSTYPE_ABSTRACT_VIRTUALS (type))
{
abstract_virtuals_error (decl, type);
any_error = 1; /* Seems like a good idea. */
}
else if (TREE_CODE (type) == RECORD_TYPE else if (TREE_CODE (type) == RECORD_TYPE
&& TYPE_LANG_SPECIFIC (type) && TYPE_LANG_SPECIFIC (type)
&& IS_SIGNATURE (type)) && IS_SIGNATURE (type))
...@@ -12033,9 +12023,7 @@ grok_op_properties (decl, virtualp, friendp) ...@@ -12033,9 +12023,7 @@ grok_op_properties (decl, virtualp, friendp)
else if (name == ansi_opname[(int) COND_EXPR]) else if (name == ansi_opname[(int) COND_EXPR])
{ {
/* 13.4.0.3 */ /* 13.4.0.3 */
pedwarn ("ANSI C++ prohibits overloading operator ?:"); cp_error ("ANSI C++ prohibits overloading operator ?:");
if (list_length (argtypes) != 4)
cp_error ("`%D' must take exactly three arguments", decl);
} }
else if (ambi_op_p (name)) else if (ambi_op_p (name))
{ {
...@@ -13113,9 +13101,7 @@ start_function (declspecs, declarator, attrs, pre_parsed_p) ...@@ -13113,9 +13101,7 @@ start_function (declspecs, declarator, attrs, pre_parsed_p)
= CP_TYPE_VOLATILE_P (TREE_TYPE (fntype)); = CP_TYPE_VOLATILE_P (TREE_TYPE (fntype));
} }
if (TYPE_LANG_SPECIFIC (TREE_TYPE (fntype)) abstract_virtuals_error (decl1, TREE_TYPE (fntype));
&& CLASSTYPE_ABSTRACT_VIRTUALS (TREE_TYPE (fntype)))
abstract_virtuals_error (decl1, TREE_TYPE (fntype));
} }
/* Effective C++ rule 15. See also c_expand_return. */ /* Effective C++ rule 15. See also c_expand_return. */
......
...@@ -2206,12 +2206,8 @@ build_new_1 (exp) ...@@ -2206,12 +2206,8 @@ build_new_1 (exp)
return error_mark_node; return error_mark_node;
} }
if (TYPE_LANG_SPECIFIC (true_type) if (abstract_virtuals_error (NULL_TREE, true_type))
&& CLASSTYPE_ABSTRACT_VIRTUALS (true_type)) return error_mark_node;
{
abstract_virtuals_error (NULL_TREE, true_type);
return error_mark_node;
}
if (TYPE_LANG_SPECIFIC (true_type) && IS_SIGNATURE (true_type)) if (TYPE_LANG_SPECIFIC (true_type) && IS_SIGNATURE (true_type))
{ {
......
...@@ -8338,10 +8338,6 @@ unify (tparms, targs, parm, arg, strict) ...@@ -8338,10 +8338,6 @@ unify (tparms, targs, parm, arg, strict)
{ {
int sub_strict; int sub_strict;
if (TREE_CODE (arg) == RECORD_TYPE && TYPE_PTRMEMFUNC_FLAG (arg))
return (unify (tparms, targs, parm,
TYPE_PTRMEMFUNC_FN_TYPE (arg), strict));
if (TREE_CODE (arg) != POINTER_TYPE) if (TREE_CODE (arg) != POINTER_TYPE)
return 1; return 1;
...@@ -8361,14 +8357,13 @@ unify (tparms, targs, parm, arg, strict) ...@@ -8361,14 +8357,13 @@ unify (tparms, targs, parm, arg, strict)
this is probably OK. */ this is probably OK. */
sub_strict = strict; sub_strict = strict;
if (TREE_CODE (TREE_TYPE (arg)) != RECORD_TYPE if (TREE_CODE (TREE_TYPE (arg)) != RECORD_TYPE)
|| TYPE_PTRMEMFUNC_FLAG (TREE_TYPE (arg)))
/* The derived-to-base conversion only persists through one /* The derived-to-base conversion only persists through one
level of pointers. */ level of pointers. */
sub_strict &= ~UNIFY_ALLOW_DERIVED; sub_strict &= ~UNIFY_ALLOW_DERIVED;
return unify (tparms, targs, TREE_TYPE (parm), TREE_TYPE return unify (tparms, targs, TREE_TYPE (parm),
(arg), sub_strict); TREE_TYPE (arg), sub_strict);
} }
case REFERENCE_TYPE: case REFERENCE_TYPE:
...@@ -8448,13 +8443,20 @@ unify (tparms, targs, parm, arg, strict) ...@@ -8448,13 +8443,20 @@ unify (tparms, targs, parm, arg, strict)
case RECORD_TYPE: case RECORD_TYPE:
case UNION_TYPE: case UNION_TYPE:
if (TYPE_PTRMEMFUNC_FLAG (parm))
return unify (tparms, targs, TYPE_PTRMEMFUNC_FN_TYPE (parm),
arg, strict);
if (TREE_CODE (arg) != TREE_CODE (parm)) if (TREE_CODE (arg) != TREE_CODE (parm))
return 1; return 1;
if (TYPE_PTRMEMFUNC_P (parm))
{
if (!TYPE_PTRMEMFUNC_P (arg))
return 1;
return unify (tparms, targs,
TYPE_PTRMEMFUNC_FN_TYPE (parm),
TYPE_PTRMEMFUNC_FN_TYPE (arg),
strict);
}
if (CLASSTYPE_TEMPLATE_INFO (parm)) if (CLASSTYPE_TEMPLATE_INFO (parm))
{ {
tree t = NULL_TREE; tree t = NULL_TREE;
......
...@@ -3130,6 +3130,10 @@ add_conversions (binfo, data) ...@@ -3130,6 +3130,10 @@ add_conversions (binfo, data)
tree method_vec = CLASSTYPE_METHOD_VEC (BINFO_TYPE (binfo)); tree method_vec = CLASSTYPE_METHOD_VEC (BINFO_TYPE (binfo));
tree *conversions = (tree *) data; tree *conversions = (tree *) data;
/* Some builtin types have no method vector, not even an empty one. */
if (!method_vec)
return NULL_TREE;
for (i = 2; i < TREE_VEC_LENGTH (method_vec); ++i) for (i = 2; i < TREE_VEC_LENGTH (method_vec); ++i)
{ {
tree tmp = TREE_VEC_ELT (method_vec, i); tree tmp = TREE_VEC_ELT (method_vec, i);
......
...@@ -125,21 +125,27 @@ readonly_error (arg, string, soft) ...@@ -125,21 +125,27 @@ readonly_error (arg, string, soft)
(*fn) ("%s of read-only location", string); (*fn) ("%s of read-only location", string);
} }
/* Print an error message for invalid use of a type which declares /* If TYPE has abstract virtual functions, issue an error about trying
virtual functions which are not inheritable. */ to create an object of that type. DECL is the object declared, or
NULL_TREE if the declaration is unavailable. Returns 1 if an error
occurred; zero if all was well. */
void int
abstract_virtuals_error (decl, type) abstract_virtuals_error (decl, type)
tree decl; tree decl;
tree type; tree type;
{ {
tree u = CLASSTYPE_ABSTRACT_VIRTUALS (type); tree u;
tree tu; tree tu;
if (!CLASS_TYPE_P (type) || !CLASSTYPE_ABSTRACT_VIRTUALS (type))
return 0;
u = CLASSTYPE_ABSTRACT_VIRTUALS (type);
if (decl) if (decl)
{ {
if (TREE_CODE (decl) == RESULT_DECL) if (TREE_CODE (decl) == RESULT_DECL)
return; return 0;
if (TREE_CODE (decl) == VAR_DECL) if (TREE_CODE (decl) == VAR_DECL)
cp_error ("cannot declare variable `%D' to be of type `%T'", cp_error ("cannot declare variable `%D' to be of type `%T'",
...@@ -170,6 +176,8 @@ abstract_virtuals_error (decl, type) ...@@ -170,6 +176,8 @@ abstract_virtuals_error (decl, type)
} }
else else
cp_error (" since type `%T' has abstract virtual functions", type); cp_error (" since type `%T' has abstract virtual functions", type);
return 1;
} }
/* Print an error message for invalid use of a signature type. /* Print an error message for invalid use of a signature type.
...@@ -1486,11 +1494,8 @@ build_functional_cast (exp, parms) ...@@ -1486,11 +1494,8 @@ build_functional_cast (exp, parms)
cp_error ("type `%T' is not yet defined", type); cp_error ("type `%T' is not yet defined", type);
return error_mark_node; return error_mark_node;
} }
if (IS_AGGR_TYPE (type) && CLASSTYPE_ABSTRACT_VIRTUALS (type)) if (abstract_virtuals_error (NULL_TREE, type))
{ return error_mark_node;
abstract_virtuals_error (NULL_TREE, type);
return error_mark_node;
}
if (parms && TREE_CHAIN (parms) == NULL_TREE) if (parms && TREE_CHAIN (parms) == NULL_TREE)
return build_c_cast (type, TREE_VALUE (parms)); return build_c_cast (type, TREE_VALUE (parms));
......
...@@ -1168,8 +1168,8 @@ class dict : public object { ...@@ -1168,8 +1168,8 @@ class dict : public object {
DISPLAYER displayer, STRINGER str_f) DISPLAYER displayer, STRINGER str_f)
{// ERROR - candidate for bad call {// ERROR - candidate for bad call
if (799 >= 800 ) cout << "Creating new dictionary..." << '\n'; ; if (799 >= 800 ) cout << "Creating new dictionary..." << '\n'; ;
if (cmp == __null ) cmp = &default_compare; if (cmp == __null ) cmp = (COMPARE) &default_compare;
if (displayer == __null ) displayer = &default_displayer; if (displayer == __null ) displayer = (DISPLAYER) &default_displayer;
if (str_f == __null ) str_f = &default_stringer; if (str_f == __null ) str_f = &default_stringer;
compare_f = cmp; compare_f = cmp;
display_f = displayer; display_f = displayer;
...@@ -1417,7 +1417,7 @@ class queue : public object { ...@@ -1417,7 +1417,7 @@ class queue : public object {
DISPLAYER displayer, STRINGER str_f) DISPLAYER displayer, STRINGER str_f)
{// ERROR - candidate for bad call {// ERROR - candidate for bad call
if (799 >= 800 ) cout << "Creating new queue..." << '\n'; ; if (799 >= 800 ) cout << "Creating new queue..." << '\n'; ;
if (displayer == __null ) displayer = &default_displayer; if (displayer == __null ) displayer = (DISPLAYER) &default_displayer;
if (str_f == __null ) str_f = &default_stringer; if (str_f == __null ) str_f = &default_stringer;
display_f = displayer; display_f = displayer;
destroy_f = destroyer; destroy_f = destroyer;
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
// Bug: g++ misinterprets typedefs of function type in class scope. // Bug: g++ misinterprets typedefs of function type in class scope.
// Build don't link: // Build don't link:
typedef int (*F1) ();
struct A { struct A {
typedef int F(); typedef int F();
F *fp; F *fp;
void* g() { return fp; } // gets bogus error - typing F1 g() { return fp; } // gets bogus error - typing
}; };
...@@ -11,11 +11,11 @@ public: ...@@ -11,11 +11,11 @@ public:
void f1b() { ok += 5; } void f1b() { ok += 5; }
void f2a() { ok += 7; } // gets bogus error XFAIL *-*-* void f2a() { ok += 7; } // gets bogus error XFAIL *-*-*
void f2b() { } void f2b() { }
const static void (*table[2][2])(); static void (*table[2][2])();
void main(); void main();
} a; } a;
const void (*A::table[2][2])() void (*A::table[2][2])()
= { { PMF2PF(&A::f1a), PMF2PF(&A::f1b) }, = { { PMF2PF(&A::f1a), PMF2PF(&A::f1b) },
{ PMF2PF(&A::f2a), PMF2PF(&A::f1b) }, { PMF2PF(&A::f2a), PMF2PF(&A::f1b) },
}; };
......
// Build don't run:
// Origin: Mark Mitchell <mark@codesourcery.com>
template <class T>
void f (T&) ;
template <>
void f (void (&)())
{
}
void g ()
{
}
void h ()
{
}
bool b;
int main ()
{
f (b ? g : h);
}
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
// Special g++ Options: // Special g++ Options:
int foo(); int foo();
const int (*bar)() = foo; // ERROR - adding const - XFAIL *-*-* const int (*bar)() = foo; // ERROR - adding const
// Build don't link:
// Origin: Jason Merrill <jason@cygnus.com>
const char *pc;
enum A { x } a;
int i;
int main()
{
return i ? *pc : a;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment