From 9c8d6563498bbfd22d05b225930409236834b867 Mon Sep 17 00:00:00 2001 From: Olly Betts Date: Thu, 5 Jan 2023 16:54:16 +1300 Subject: Parse storage class more flexibly Previously we had a hard-coded list of allowed combinations in the grammar, but this suffers from combinatorial explosion, and results in a vague `Syntax error in input` error for invalid combinations. This means we now support a number of cases which are valid C++ but weren't supported. Fixes #302 Fixes #2079 (friend constexpr) Fixes #2474 (virtual explicit) --- Examples/test-suite/cpp11_constexpr.i | 3 + Examples/test-suite/cpp11_noexcept.i | 6 ++ Source/CParse/parser.y | 177 +++++++++++++++++++++++++--------- Source/CParse/templ.c | 2 +- Source/Modules/allocate.cxx | 2 +- Source/Modules/go.cxx | 6 +- Source/Modules/lang.cxx | 4 +- Source/Modules/php.cxx | 14 +-- Source/Modules/typepass.cxx | 2 +- Source/Swig/naming.c | 4 +- 10 files changed, 158 insertions(+), 62 deletions(-) diff --git a/Examples/test-suite/cpp11_constexpr.i b/Examples/test-suite/cpp11_constexpr.i index 5ba9ff243..d830d984a 100644 --- a/Examples/test-suite/cpp11_constexpr.i +++ b/Examples/test-suite/cpp11_constexpr.i @@ -39,6 +39,9 @@ struct ConstExpressions { // Regression tests for https://github.com/swig/swig/issues/284 : explicit constexpr ConstExpressions(int) { } constexpr explicit ConstExpressions(double) { } + // Regression tests for https://github.com/swig/swig/issues/2079 : + constexpr friend bool operator==(ConstExpressions,ConstExpressions) { return true; } + friend constexpr bool operator!=(ConstExpressions,ConstExpressions) { return false; } }; %} diff --git a/Examples/test-suite/cpp11_noexcept.i b/Examples/test-suite/cpp11_noexcept.i index 8aa0baa5a..6b0584473 100644 --- a/Examples/test-suite/cpp11_noexcept.i +++ b/Examples/test-suite/cpp11_noexcept.i @@ -1,5 +1,7 @@ %module cpp11_noexcept +%ignore NoExceptAbstract::operator std::string; +%ignore NoExceptAbstract::operator int; %ignore NoExceptClass(NoExceptClass&&); %rename(Assignment) NoExceptClass::operator=; @@ -7,6 +9,7 @@ extern "C" void global_noexcept(int, bool) noexcept {} %} %inline %{ +#include extern "C" void global_noexcept(int, bool) noexcept; extern "C" void global_noexcept2(int, bool) noexcept {} @@ -43,6 +46,9 @@ struct NoExceptClass { struct NoExceptAbstract { virtual void noo4() const noexcept = 0; virtual ~NoExceptAbstract() noexcept = 0; + // Regression test for https://github.com/swig/swig/issues/2474 + virtual explicit operator std::string() const noexcept = 0; + explicit virtual operator int() const noexcept = 0; }; struct NoExceptDefaultDelete { diff --git a/Source/CParse/parser.y b/Source/CParse/parser.y index 325856074..7a9cefe7b 100644 --- a/Source/CParse/parser.y +++ b/Source/CParse/parser.y @@ -231,6 +231,49 @@ static int cplus_mode = 0; #define CPLUS_PRIVATE 2 #define CPLUS_PROTECTED 3 +/* storage classes */ + +#define SWIG_STORAGE_CLASS_EXTERNC 0x0001 +#define SWIG_STORAGE_CLASS_EXTERNCPP 0x0002 +#define SWIG_STORAGE_CLASS_EXTERN 0x0004 +#define SWIG_STORAGE_CLASS_STATIC 0x0008 +#define SWIG_STORAGE_CLASS_TYPEDEF 0x0010 +#define SWIG_STORAGE_CLASS_VIRTUAL 0x0020 +#define SWIG_STORAGE_CLASS_FRIEND 0x0040 +#define SWIG_STORAGE_CLASS_EXPLICIT 0x0080 +#define SWIG_STORAGE_CLASS_CONSTEXPR 0x0100 +#define SWIG_STORAGE_CLASS_THREAD_LOCAL 0x0200 + +/* Test if multiple bits are set in x. */ +static int multiple_bits_set(unsigned x) { return (x & (x - 1)) != 0; } + +static const char* storage_class_string(int c) { + switch (c) { + case SWIG_STORAGE_CLASS_EXTERNC: + return "extern \"C\""; + case SWIG_STORAGE_CLASS_EXTERNCPP: + return "extern \"C++\""; + case SWIG_STORAGE_CLASS_EXTERN: + return "extern"; + case SWIG_STORAGE_CLASS_STATIC: + return "static"; + case SWIG_STORAGE_CLASS_TYPEDEF: + return "typedef"; + case SWIG_STORAGE_CLASS_VIRTUAL: + return "virtual"; + case SWIG_STORAGE_CLASS_FRIEND: + return "friend"; + case SWIG_STORAGE_CLASS_EXPLICIT: + return "explicit"; + case SWIG_STORAGE_CLASS_CONSTEXPR: + return "constexpr"; + case SWIG_STORAGE_CLASS_THREAD_LOCAL: + return "thread_local"; + } + assert(0); + return ""; +} + /* include types */ static int import_mode = 0; @@ -352,7 +395,7 @@ static String *make_unnamed(void) { /* Return if the node is a friend declaration */ static int is_friend(Node *n) { - return Cmp(Getattr(n,"storage"),"friend") == 0; + return (Strstr(Getattr(n, "storage"), "friend") != NULL); } static int is_operator(String *name) { @@ -1150,10 +1193,10 @@ Printf(stdout, "comparing current: [%s] found: [%s]\n", current_scopename, found } /* look for simple typedef name in typedef list */ -static String *try_to_find_a_name_for_unnamed_structure(const char *storage, Node *decls) { +static String *try_to_find_a_name_for_unnamed_structure(const String *storage, Node *decls) { String *name = 0; Node *n = decls; - if (storage && (strcmp(storage, "typedef") == 0)) { + if (storage && Equal(storage, "typedef")) { for (; n; n = nextSibling(n)) { if (!Len(Getattr(n, "decl"))) { name = Copy(Getattr(n, "name")); @@ -1183,7 +1226,7 @@ static void update_nested_classes(Node *n) * Create the nested class/struct/union as a forward declaration. * ----------------------------------------------------------------------------- */ -static Node *nested_forward_declaration(const char *storage, const String *kind, String *sname, String *name, Node *cpp_opt_declarators) { +static Node *nested_forward_declaration(const String *storage, const String *kind, String *sname, String *name, Node *cpp_opt_declarators) { Node *nn = 0; if (sname) { @@ -1200,10 +1243,10 @@ static Node *nested_forward_declaration(const char *storage, const String *kind, /* Add any variable instances. Also add in any further typedefs of the nested type. Note that anonymous typedefs (eg typedef struct {...} a, b;) are treated as class forward declarations */ if (cpp_opt_declarators) { - int storage_typedef = (storage && (strcmp(storage, "typedef") == 0)); + int storage_typedef = (storage && Equal(storage, "typedef")); int variable_of_anonymous_type = !sname && !storage_typedef; if (!variable_of_anonymous_type) { - int anonymous_typedef = !sname && (storage && (strcmp(storage, "typedef") == 0)); + int anonymous_typedef = !sname && storage_typedef; Node *n = cpp_opt_declarators; SwigType *type = name; while (n) { @@ -1680,7 +1723,8 @@ static String *add_qualifier_to_declarator(SwigType *type, SwigType *qualifier) /* Misc */ %type identifier; %type initializer cpp_const exception_specification cv_ref_qualifier qualifiers_exception_specification; -%type storage_class extern_string; +%type storage_class; +%type storage_class_raw storage_class_list; %type parms rawparms varargs_parms ; %type templateparameterstail; %type

parm_no_dox parm valparm rawvalparms valparms valptail ; @@ -2373,6 +2417,7 @@ native_directive : NATIVE LPAREN identifier RPAREN storage_class identifier SEMI $$ = new_node("native"); Setattr($$,"name",$3); Setattr($$,"wrap:name",$6); + Delete($5); add_symbols($$); } | NATIVE LPAREN identifier RPAREN storage_class type declarator SEMI { @@ -2390,6 +2435,7 @@ native_directive : NATIVE LPAREN identifier RPAREN storage_class identifier SEMI Setattr($$,"parms",$7.parms); Setattr($$,"decl",$7.type); } + Delete($5); add_symbols($$); } ; @@ -3361,16 +3407,19 @@ cpp_alternate_rettype : primitive_type { $$ = $1; } cpp_lambda_decl : storage_class AUTO idcolon EQUAL lambda_introducer lambda_template LPAREN parms RPAREN cpp_const lambda_body lambda_tail { $$ = new_node("lambda"); Setattr($$,"name",$3); + Delete($1); add_symbols($$); } | storage_class AUTO idcolon EQUAL lambda_introducer lambda_template LPAREN parms RPAREN cpp_const ARROW type lambda_body lambda_tail { $$ = new_node("lambda"); Setattr($$,"name",$3); + Delete($1); add_symbols($$); } | storage_class AUTO idcolon EQUAL lambda_introducer lambda_template lambda_body lambda_tail { $$ = new_node("lambda"); Setattr($$,"name",$3); + Delete($1); add_symbols($$); } ; @@ -3624,6 +3673,8 @@ c_constructor_decl : storage_class type LPAREN parms RPAREN ctor_end { Setattr($$,"final",$6.final); err = 0; } + } else { + Delete($1); } if (err) { Swig_error(cparse_file,cparse_line,"Syntax error in input(2).\n"); @@ -4061,7 +4112,7 @@ cpp_opt_declarators : SEMI { $$ = 0; } ------------------------------------------------------------ */ cpp_forward_class_decl : storage_class cpptype idcolon SEMI { - if ($1 && (Strcmp($1,"friend") == 0)) { + if ($1 && Strstr($1, "friend")) { /* Ignore */ $$ = 0; } else { @@ -4071,6 +4122,7 @@ cpp_forward_class_decl : storage_class cpptype idcolon SEMI { Setattr($$,"sym:weak", "1"); add_symbols($$); } + Delete($1); } ; @@ -4575,7 +4627,7 @@ cpp_members : cpp_member cpp_members { Exit(EXIT_FAILURE); } cpp_members { $$ = $3; - } + } ; /* ====================================================================== @@ -4608,7 +4660,7 @@ cpp_member_no_dox : c_declaration { $$ = $1; } | cpp_conversion_operator { $$ = $1; } | cpp_forward_class_decl { $$ = $1; } | cpp_class_decl { $$ = $1; } - | storage_class idcolon SEMI { $$ = 0; } + | storage_class idcolon SEMI { $$ = 0; Delete($1); } | cpp_using_decl { $$ = $1; } | cpp_template_decl { $$ = $1; } | cpp_catch_decl { $$ = 0; } @@ -4661,6 +4713,7 @@ cpp_constructor_decl : storage_class type LPAREN parms RPAREN ctor_end { Setattr($$,"value",$6.defarg); } else { $$ = 0; + Delete($1); } } ; @@ -4967,7 +5020,7 @@ cpp_vend : cpp_const SEMI { ; -anonymous_bitfield : storage_class anon_bitfield_type COLON expr SEMI { }; +anonymous_bitfield : storage_class anon_bitfield_type COLON expr SEMI { Delete($1); }; /* Equals type_right without the ENUM keyword and cpptype (templates etc.): */ anon_bitfield_type : primitive_type { $$ = $1; @@ -4988,45 +5041,79 @@ anon_bitfield_type : primitive_type { $$ = $1; /* ====================================================================== * PRIMITIVES * ====================================================================== */ -extern_string : EXTERN string { - if (Strcmp($2,"C") == 0) { - $$ = "externc"; - } else if (Strcmp($2,"C++") == 0) { - $$ = "extern"; +storage_class : storage_class_list { + String *r = NewStringEmpty(); + + /* Check for invalid combinations. */ + if (multiple_bits_set($1 & (SWIG_STORAGE_CLASS_EXTERN | + SWIG_STORAGE_CLASS_STATIC))) { + Swig_error(cparse_file, cparse_line, "Storage class can't be both 'static' and 'extern'"); + } + if (multiple_bits_set($1 & (SWIG_STORAGE_CLASS_EXTERNC | + SWIG_STORAGE_CLASS_EXTERN | + SWIG_STORAGE_CLASS_EXTERNCPP))) { + Swig_error(cparse_file, cparse_line, "Declaration can only be one of 'extern', 'extern \"C\"' and 'extern \"C++\"'"); + } + + if ($1 & SWIG_STORAGE_CLASS_TYPEDEF) { + Append(r, "typedef "); + } else { + if ($1 & SWIG_STORAGE_CLASS_EXTERNC) + Append(r, "externc "); + if ($1 & (SWIG_STORAGE_CLASS_EXTERN|SWIG_STORAGE_CLASS_EXTERNCPP)) + Append(r, "extern "); + if ($1 & SWIG_STORAGE_CLASS_STATIC) + Append(r, "static "); + } + if ($1 & SWIG_STORAGE_CLASS_VIRTUAL) + Append(r, "virtual "); + if ($1 & SWIG_STORAGE_CLASS_FRIEND) + Append(r, "friend "); + if ($1 & SWIG_STORAGE_CLASS_EXPLICIT) + Append(r, "explicit "); + if ($1 & SWIG_STORAGE_CLASS_CONSTEXPR) + Append(r, "constexpr "); + if ($1 & SWIG_STORAGE_CLASS_THREAD_LOCAL) + Append(r, "thread_local "); + if (Len(r) == 0) { + Delete(r); + $$ = 0; + } else { + Chop(r); + $$ = r; + } + } + | empty { $$ = 0; } + ; + +storage_class_list: storage_class_raw { $$ = $1; } + | storage_class_list storage_class_raw { + if ($1 & $2) { + Swig_error(cparse_file, cparse_line, "Repeated storage class or type specifier '%s'\n", storage_class_string($2)); + } + $$ = $1 | $2; + } + ; + +storage_class_raw : EXTERN { $$ = SWIG_STORAGE_CLASS_EXTERN; } + | EXTERN string { + if (Strcmp($2,"C") == 0) { + $$ = SWIG_STORAGE_CLASS_EXTERNC; + } else if (Strcmp($2,"C++") == 0) { + $$ = SWIG_STORAGE_CLASS_EXTERNCPP; } else { Swig_warning(WARN_PARSE_UNDEFINED_EXTERN,cparse_file, cparse_line,"Unrecognized extern type \"%s\".\n", $2); $$ = 0; } - } - ; - -storage_class : EXTERN { $$ = "extern"; } - | extern_string { $$ = $1; } - | extern_string THREAD_LOCAL { - if (Equal($1, "extern")) { - $$ = "extern thread_local"; - } else { - $$ = "externc thread_local"; - } } - | extern_string TYPEDEF { $$ = "typedef"; } - | STATIC { $$ = "static"; } - | TYPEDEF { $$ = "typedef"; } - | VIRTUAL { $$ = "virtual"; } - | FRIEND { $$ = "friend"; } - | EXPLICIT { $$ = "explicit"; } - | CONSTEXPR { $$ = "constexpr"; } - | EXPLICIT CONSTEXPR { $$ = "explicit constexpr"; } - | CONSTEXPR EXPLICIT { $$ = "explicit constexpr"; } - | STATIC CONSTEXPR { $$ = "static constexpr"; } - | CONSTEXPR STATIC { $$ = "static constexpr"; } - | THREAD_LOCAL { $$ = "thread_local"; } - | THREAD_LOCAL STATIC { $$ = "static thread_local"; } - | STATIC THREAD_LOCAL { $$ = "static thread_local"; } - | EXTERN THREAD_LOCAL { $$ = "extern thread_local"; } - | THREAD_LOCAL EXTERN { $$ = "extern thread_local"; } - | empty { $$ = 0; } - ; + | STATIC { $$ = SWIG_STORAGE_CLASS_STATIC; } + | TYPEDEF { $$ = SWIG_STORAGE_CLASS_TYPEDEF; } + | VIRTUAL { $$ = SWIG_STORAGE_CLASS_VIRTUAL; } + | FRIEND { $$ = SWIG_STORAGE_CLASS_FRIEND; } + | EXPLICIT { $$ = SWIG_STORAGE_CLASS_EXPLICIT; } + | CONSTEXPR { $$ = SWIG_STORAGE_CLASS_CONSTEXPR; } + | THREAD_LOCAL { $$ = SWIG_STORAGE_CLASS_THREAD_LOCAL; } + ; /* ------------------------------------------------------------------------------ Function parameter lists diff --git a/Source/CParse/templ.c b/Source/CParse/templ.c index 4037cbf86..3ef0e9d5f 100644 --- a/Source/CParse/templ.c +++ b/Source/CParse/templ.c @@ -166,7 +166,7 @@ static void cparse_template_expand(Node *templnode, Node *n, String *tname, Stri Append(cpatchlist, Getattr(n, "sym:name")); } } - if (checkAttribute(n, "storage", "friend")) { + if (Strstr(Getattr(n, "storage"), "friend")) { String *symname = Getattr(n, "sym:name"); if (symname) { String *stripped_name = SwigType_templateprefix(symname); diff --git a/Source/Modules/allocate.cxx b/Source/Modules/allocate.cxx index 0e1262f83..233830791 100644 --- a/Source/Modules/allocate.cxx +++ b/Source/Modules/allocate.cxx @@ -396,7 +396,7 @@ class Allocate:public Dispatcher { if (!GetFlag(c, "feature:ignore")) { String *storage = Getattr(c, "storage"); if (!((Cmp(storage, "typedef") == 0)) - && !((Cmp(storage, "friend") == 0))) { + && !Strstr(storage, "friend")) { String *name = Getattr(c, "name"); String *symname = Getattr(c, "sym:name"); Node *e = Swig_symbol_clookup_local(name, 0); diff --git a/Source/Modules/go.cxx b/Source/Modules/go.cxx index 27e3060bf..6339cc15d 100644 --- a/Source/Modules/go.cxx +++ b/Source/Modules/go.cxx @@ -2271,7 +2271,7 @@ private: } String *storage = Getattr(entry, "storage"); - if (storage && (Strcmp(storage, "typedef") == 0 || Strcmp(storage, "friend") == 0)) { + if (storage && (Strcmp(storage, "typedef") == 0 || Strstr(storage, "friend"))) { return SWIG_OK; } @@ -5620,7 +5620,7 @@ private: bool isStatic(Node *n) { String *storage = Getattr(n, "storage"); - return (storage && (Swig_storage_isstatic(n) || Strcmp(storage, "friend") == 0) && (!SmartPointer || !Getattr(n, "allocate:smartpointeraccess"))); + return (storage && (Swig_storage_isstatic(n) || Strstr(storage, "friend")) && (!SmartPointer || !Getattr(n, "allocate:smartpointeraccess"))); } /* ---------------------------------------------------------------------- @@ -5631,7 +5631,7 @@ private: bool isFriend(Node *n) { String *storage = Getattr(n, "storage"); - return storage && Strcmp(storage, "friend") == 0; + return storage && Strstr(storage, "friend"); } /* ---------------------------------------------------------------------- diff --git a/Source/Modules/lang.cxx b/Source/Modules/lang.cxx index 7a85c2d63..ba52e265c 100644 --- a/Source/Modules/lang.cxx +++ b/Source/Modules/lang.cxx @@ -882,7 +882,7 @@ int Language::cDeclaration(Node *n) { /* discards nodes following the access control rules */ if (cplus_mode != PUBLIC || !is_public(n)) { /* except for friends, they are not affected by access control */ - int isfriend = Cmp(storage, "friend") == 0; + int isfriend = (Strstr(storage, "friend") != NULL); if (!isfriend) { /* Check what the director needs. If the method is pure virtual, it is always needed. * Also wrap non-virtual protected members if asked for (allprotected mode). */ @@ -1061,7 +1061,7 @@ int Language::cDeclaration(Node *n) { int Language::functionHandler(Node *n) { String *storage = Getattr(n, "storage"); - int isfriend = CurrentClass && Cmp(storage, "friend") == 0; + int isfriend = CurrentClass && Strstr(storage, "friend"); int isstatic = CurrentClass && Swig_storage_isstatic(n) && !(SmartPointer && Getattr(n, "allocate:smartpointeraccess")); Parm *p = Getattr(n, "parms"); if (GetFlag(n, "feature:del")) { diff --git a/Source/Modules/php.cxx b/Source/Modules/php.cxx index 8e16c6969..67c73aa6b 100644 --- a/Source/Modules/php.cxx +++ b/Source/Modules/php.cxx @@ -951,7 +951,7 @@ public: void create_command(String *cname, String *fname, Node *n, bool dispatch, String *modes) { // This is for the single main zend_function_entry record ParmList *l = Getattr(n, "parms"); - if (cname && !Equal(Getattr(n, "storage"), "friend")) { + if (cname && !Strstr(Getattr(n, "storage"), "friend")) { Printf(f_h, "static PHP_METHOD(%s%s,%s);\n", prefix, cname, fname); if (wrapperType != staticmemberfn && wrapperType != staticmembervar && @@ -973,7 +973,7 @@ public: String *arginfo_id = phptypes->get_arginfo_id(); String *s = cs_entry; if (!s) s = s_entry; - if (cname && !Equal(Getattr(n, "storage"), "friend")) { + if (cname && !Strstr(Getattr(n, "storage"), "friend")) { Printf(all_cs_entry, " PHP_ME(%s%s,%s,swig_arginfo_%s,%s)\n", prefix, cname, fname, arginfo_id, modes); } else { if (dispatch) { @@ -1043,7 +1043,7 @@ public: create_command(class_name, wname, n, true, modes); - if (class_name && !Equal(Getattr(n, "storage"), "friend")) { + if (class_name && !Strstr(Getattr(n, "storage"), "friend")) { Printv(f->def, "static PHP_METHOD(", prefix, class_name, ",", wname, ") {\n", NIL); } else { Printv(f->def, "static ZEND_NAMED_FUNCTION(", wname, ") {\n", NIL); @@ -1340,7 +1340,7 @@ public: wname = Getattr(n, "staticmemberfunctionHandler:sym:name"); } else { if (class_name) { - if (Cmp(Getattr(n, "storage"), "friend") == 0 && Cmp(Getattr(n, "view"), "globalfunctionHandler") == 0) { + if (Strstr(Getattr(n, "storage"), "friend") && Cmp(Getattr(n, "view"), "globalfunctionHandler") == 0) { wname = iname; } else { wname = Getattr(n, "destructorHandler:sym:name"); @@ -1364,7 +1364,7 @@ public: phptypes = NULL; String *key; - if (class_name && !Equal(Getattr(n, "storage"), "friend")) { + if (class_name && !Strstr(Getattr(n, "storage"), "friend")) { key = NewStringf("%s:%s", class_name, wname); } else { key = NewStringf(":%s", wname); @@ -1395,7 +1395,7 @@ public: if (!overloaded) { if (!static_getter) { - if (class_name && !Equal(Getattr(n, "storage"), "friend")) { + if (class_name && !Strstr(Getattr(n, "storage"), "friend")) { Printv(f->def, "static PHP_METHOD(", prefix, class_name, ",", wname, ") {\n", NIL); } else { if (wrap_nonclass_global) { @@ -1589,7 +1589,7 @@ public: List *return_types = phptypes->process_phptype(n, 0, "tmap:out:phptype"); - if (class_name && !Equal(Getattr(n, "storage"), "friend")) { + if (class_name && !Strstr(Getattr(n, "storage"), "friend")) { if (is_member_director(n)) { String *parent = class_name; while ((parent = Getattr(php_parent_class, parent)) != NULL) { diff --git a/Source/Modules/typepass.cxx b/Source/Modules/typepass.cxx index 83ec8ad72..01949b2ba 100644 --- a/Source/Modules/typepass.cxx +++ b/Source/Modules/typepass.cxx @@ -1043,7 +1043,7 @@ class TypePass:private Dispatcher { if (Strcmp(nodeType(c), "cdecl") == 0) { if (!(Swig_storage_isstatic(c) || checkAttribute(c, "storage", "typedef") - || checkAttribute(c, "storage", "friend") + || Strstr(Getattr(c, "storage"), "friend") || (Getattr(c, "feature:extend") && !Getattr(c, "code")) || GetFlag(c, "feature:ignore"))) { diff --git a/Source/Swig/naming.c b/Source/Swig/naming.c index 517b056a7..4d7d89340 100644 --- a/Source/Swig/naming.c +++ b/Source/Swig/naming.c @@ -997,7 +997,7 @@ static int nodes_are_equivalent(Node *a, Node *b, int a_inclass) { /* friend methods */ - if (!a_inclass || (Cmp(a_storage, "friend") == 0)) { + if (!a_inclass || Strstr(a_storage, "friend")) { /* check declaration */ String *a_decl = (Getattr(a, "decl")); @@ -1056,7 +1056,7 @@ static int nodes_are_equivalent(Node *a, Node *b, int a_inclass) { return 0; } if (Equal(ta, "template") && Equal(tb, "template")) { - if (Cmp(a_storage, "friend") == 0 || Cmp(b_storage, "friend") == 0) + if (Strstr(a_storage, "friend") || Strstr(b_storage, "friend")) return 1; } } -- cgit v1.2.1