mirror of
https://github.com/nasa/trick.git
synced 2024-12-18 12:56:26 +00:00
Improve SWIG %template directive scoping - take 2 (#1753)
* Correct convert_swig regex for qualified templates Closes #1751 * Improve SWIG %template directive scoping - take 2 Refs #768 * Add nested namespace tests Refs #768 * Replace ">>" with "> >" Probably time to drop support for CentOS 7, ya know. * Add SWIG template scoping documentation Refs #1753
This commit is contained in:
parent
e8508ea575
commit
aecf6a06fd
@ -408,24 +408,51 @@ Trick may use model code with any type of inheritance. Some limitations are pres
|
||||
|
||||
### Namespaces
|
||||
|
||||
Currently one level of namespace is supported. Additional levels of namespaces are ignored. Similarly classes and enumerations embedded in other classes are ignored.
|
||||
ICG supports namespaces and nested scopes. Data recording and variable access via Trick View should work regardless of how many levels there are.
|
||||
|
||||
```C++
|
||||
namespace my_ns {
|
||||
// BB is processed
|
||||
class BB {
|
||||
public:
|
||||
std::string str;
|
||||
// Class CC is ignored.
|
||||
class CC {
|
||||
...
|
||||
Namespaces and nested scopes are similarly supported in Python contexts, such as the input file and variable server, with some caveats regarding templates.
|
||||
1. A template instantiation may be unqualified (have no use of the scope resolution operator `::`) only if its corresponding template is declared in the immediately-enclosing namespace.
|
||||
2. Otherwise, a template instantiation must be fully qualified, starting from the global namespace.
|
||||
3. Finally, instantiations of templates declared within the same class must be excluded from SWIG.
|
||||
|
||||
In the following examples, all template instantiations occur in `example::prime::Soup`. The immediately-enclosing namespace is `prime`, so only instantiations of templates declared directly in `prime` (only `Celery`) may be unqualified. All other template instantiations must be fully qualified, starting from the global namespace, even if the C++ name lookup process would find them with partial qualification.
|
||||
|
||||
```c++
|
||||
template <class T> class Potato {};
|
||||
|
||||
namespace example {
|
||||
|
||||
template <class T> class Onion {};
|
||||
|
||||
namespace peer {
|
||||
template <class T> class Raddish {};
|
||||
}
|
||||
|
||||
namespace prime {
|
||||
|
||||
namespace inner {
|
||||
template <class T> class Carrot {};
|
||||
}
|
||||
};
|
||||
// Everything enclosed in inner_ns is ignored.
|
||||
namespace inner_ns {
|
||||
...
|
||||
};
|
||||
};
|
||||
|
||||
template <class T> class Celery {};
|
||||
|
||||
class Soup {
|
||||
|
||||
public:
|
||||
template <class T> class Broth {};
|
||||
|
||||
::Potato<int> potato; // Rule 2
|
||||
example::Onion<int> onion; // Rule 2
|
||||
example::peer::Raddish<int> raddish; // Rule 2
|
||||
example::prime::inner::Carrot<int> carrot; // Rule 2
|
||||
Celery<int> celery; // Rule 1
|
||||
#ifndef SWIG
|
||||
Broth<int> broth; // Rule 3
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Function Overloading
|
||||
|
@ -57,7 +57,6 @@ my %sim ;
|
||||
my %out_of_date ;
|
||||
my ($version, $thread, $year) ;
|
||||
my %processed_templates ;
|
||||
my $global_template_typedefs ;
|
||||
|
||||
my $typedef_def = qr/typedef\s+ # the word typedef
|
||||
(?:[_A-Za-z][\s\w]*\s*) # resolved type
|
||||
@ -336,7 +335,6 @@ sub process_file() {
|
||||
}
|
||||
print OUT "\n$new_contents" ;
|
||||
print OUT "$contents\n" ;
|
||||
print OUT $global_template_typedefs ;
|
||||
|
||||
# Add a trick_cast_as macro line for each class parsed in the file. These lines must appear at the bottom of the
|
||||
# file to ensure they are not in a namespace directive and they are after the #define statements they depend on.
|
||||
@ -592,7 +590,8 @@ sub process_class($$$$$) {
|
||||
|
||||
my $extracted ;
|
||||
my ($class_name) ;
|
||||
my $template_typedefs ;
|
||||
my @qualified_template_typedefs ;
|
||||
my @unqualified_template_typedefs ;
|
||||
|
||||
## Extract the class_name from the class_string
|
||||
$class_string =~ /^(?:class|struct)\s+ # keyword class or struct
|
||||
@ -675,10 +674,6 @@ sub process_class($$$$$) {
|
||||
my ($template_type_no_sp) = $template_full_type ;
|
||||
$template_type_no_sp =~ s/\s//g ;
|
||||
|
||||
# If the type is qualified, assume it's fully qualified and put the
|
||||
# %template directive in the global namespace.
|
||||
# See https://github.com/nasa/trick/issues/768
|
||||
my $qualified = $template_type_no_sp =~ /^\w+(::)\w+</ ;
|
||||
#print "*** template_type_no_sp = $template_type_no_sp ***\n" ;
|
||||
if ( ! exists $processed_templates{$template_type_no_sp} ) {
|
||||
|
||||
@ -686,29 +681,48 @@ sub process_class($$$$$) {
|
||||
my $identifier = "${sanitized_namespace}${class_name}_${var_name}" ;
|
||||
my $trick_swig_template = "TRICK_SWIG_TEMPLATE_$identifier" ;
|
||||
|
||||
# Insert template directive immediately before intsance
|
||||
# Insert template directive immediately before instance
|
||||
# This is required as of SWIG 4
|
||||
my $typedef = "\n#ifndef $trick_swig_template\n" ;
|
||||
$typedef .= "#define $trick_swig_template\n" ;
|
||||
$typedef .= "\%template($identifier) $template_full_type;\n" ;
|
||||
$typedef .= "#endif\n" ;
|
||||
|
||||
# SWIG namespace resolution for template directives starts at the local space
|
||||
# Therefore, if the type is qualified, assume it's fully qualified and put the
|
||||
# %template directive in the global namespace by escaping the current namespace
|
||||
if ($curr_namespace ne "") {
|
||||
my $in_same_namespace = 1 ;
|
||||
if ($template_full_type =~ /^\w*(::)\w+</) {
|
||||
$in_same_namespace = 0 ;
|
||||
}
|
||||
if ($in_same_namespace eq 0) {
|
||||
$curr_namespace =~ /(.*)::/ ;
|
||||
$typedef = "\n}" . $typedef . "namespace " . $1 . " {" ;
|
||||
}
|
||||
}
|
||||
# A SWIG %template directive must:
|
||||
# 1. Appear before each template instantiation
|
||||
# 2. Be in scope where the instantiation is declared
|
||||
# 3. Not be enclosed within a different namespace
|
||||
#
|
||||
# See https://www.swig.org/Doc4.2/SWIGPlus.html#SWIGPlus_template_scoping
|
||||
#
|
||||
# Generally, convert_swig is not smart enough to put all %template
|
||||
# directives in the right scope because:
|
||||
# 1. We do not keep track of what scope each type we encounter is
|
||||
# declared in.
|
||||
# 2. We cannot easily add %template directives to already-processed
|
||||
# scopes.
|
||||
#
|
||||
# As a compromise:
|
||||
# 1. For an unqualified template instantiation, the template declaration
|
||||
# is necessarily in the current or a containing scope. Hope it's in the
|
||||
# current namespace and put the %template directive there. This will
|
||||
# create invalid SWIG input code for templates declared in a containing
|
||||
# namespace and for member templates declared in this class.
|
||||
# 2. For a qualified template instantiation, the template declaration is
|
||||
# in a (possibly nested) scope contained by the current or a containing
|
||||
# scope. Assume the instantiation is fully qualified and put it in the
|
||||
# global namespace. This will create invalid SWIG code for partially-
|
||||
# qualified instantiations.
|
||||
#
|
||||
# See https://github.com/nasa/trick/issues/768
|
||||
|
||||
if ($isSwigExcludeBlock == 0) {
|
||||
$template_typedefs .= $typedef ;
|
||||
if ($template_full_type =~ /^\w*(::\w+)+</) {
|
||||
push @qualified_template_typedefs, $typedef ;
|
||||
}
|
||||
else {
|
||||
push @unqualified_template_typedefs, $typedef ;
|
||||
}
|
||||
}
|
||||
|
||||
$processed_templates{$template_type_no_sp} = 1 ;
|
||||
@ -721,12 +735,32 @@ sub process_class($$$$$) {
|
||||
|
||||
push @$class_names_ref , "$curr_namespace$class_name" ;
|
||||
|
||||
# write out the templated variable declaration lines found in this class.
|
||||
$$new_contents_ref .= $template_typedefs."\n" ;
|
||||
# Write out the %template directives for template instantiations found in
|
||||
# this class. Put the directives for unqualified instantiations in the
|
||||
# namespace containing this class, just before the class definition.
|
||||
foreach (@unqualified_template_typedefs) {
|
||||
$$new_contents_ref .= $_ ;
|
||||
}
|
||||
|
||||
# Assume qualified template instantiations are fully qualified and put
|
||||
# their %template directives in the global namespace.
|
||||
# 1. close all namespaces, returning to the global namespace
|
||||
# 2. add the %template directives
|
||||
# 3. open all namespaces, restoring the scope
|
||||
if (@qualified_template_typedefs) {
|
||||
my @namespaces = split(/::/, $curr_namespace) ;
|
||||
$$new_contents_ref .= "}" x @namespaces . "\n";
|
||||
foreach (@qualified_template_typedefs) {
|
||||
$$new_contents_ref .= $_ ;
|
||||
}
|
||||
$$new_contents_ref .= "\n";
|
||||
foreach (@namespaces) {
|
||||
$$new_contents_ref .= "namespace " . $_ . " { " ;
|
||||
}
|
||||
}
|
||||
|
||||
$$new_contents_ref .= $my_class_contents ;
|
||||
# write the class contents and semicolon to ensure any template declarations below are after the semicolon.
|
||||
$$new_contents_ref .= $extracted . ";\n" ;
|
||||
$$new_contents_ref .= "\n" . $my_class_contents . $extracted . ";\n" ;
|
||||
|
||||
my $c_ = "$curr_namespace$class_name" ;
|
||||
$c_ =~ s/\:/_/g ;
|
||||
|
@ -1,3 +1,2 @@
|
||||
TRICK_CONVERT_SWIG_FLAGS := -s
|
||||
TRICK_CFLAGS += -Imodels
|
||||
TRICK_CXXFLAGS += -Imodels
|
||||
|
@ -1,93 +1,185 @@
|
||||
#include <vector>
|
||||
// @trick_parse{everything}
|
||||
|
||||
namespace a {
|
||||
template<class> class A {
|
||||
private:
|
||||
void operator=(const A&);
|
||||
#ifndef Classes_HH
|
||||
#define Classes_HH
|
||||
|
||||
template <class T>
|
||||
class Foo {};
|
||||
|
||||
namespace peer {
|
||||
template <class T>
|
||||
class Bar {};
|
||||
|
||||
struct Leek {
|
||||
template <class T>
|
||||
class Eggplant {};
|
||||
};
|
||||
|
||||
namespace nested {
|
||||
template <class T>
|
||||
class Broccoli {};
|
||||
|
||||
struct Carrot {
|
||||
template <class T>
|
||||
class Celery {};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace b {
|
||||
namespace prime {
|
||||
|
||||
template<class> class A {};
|
||||
template<class> class B {};
|
||||
template<class T>
|
||||
class Potato {};
|
||||
|
||||
class C {
|
||||
public:
|
||||
std::vector<int> v;
|
||||
a::A<int> a;
|
||||
a::A<int> a2;
|
||||
A<int> a3;
|
||||
A<int> a4;
|
||||
B<int> b;
|
||||
B<int> b2;
|
||||
b::B<int> b3;
|
||||
b::B<int> b4;
|
||||
B<a::A<int> > ba;
|
||||
B<a::A<int> > ba2;
|
||||
b::B<a::A<int> > ba3;
|
||||
b::B<a::A<int> > ba4;
|
||||
B<A<int> > ba5;
|
||||
B<A<int> > ba6;
|
||||
|
||||
// These don't work. Because the type is qualified, convert_swig assumes
|
||||
// it's fully qualified and puts the %template directive in the global
|
||||
// namespace. However, there is no A in the global namespace, so the wrapper
|
||||
// code fails to compile. Bonus points if you can fix this without breaking
|
||||
// something else.
|
||||
//b::B<A<int> > ba7;
|
||||
//b::B<A<int> > ba8;
|
||||
struct Turnip {
|
||||
template <class T>
|
||||
class Radish {};
|
||||
};
|
||||
|
||||
// This class is the same as the previous. Seems weird, but it reveals scoping
|
||||
// errors under SWIG 2. The replication is not necessary in SWIG 4, which has
|
||||
// better error detection.
|
||||
class D {
|
||||
public:
|
||||
std::vector<int> v;
|
||||
a::A<int> a;
|
||||
a::A<int> a2;
|
||||
A<int> a3;
|
||||
A<int> a4;
|
||||
B<int> b;
|
||||
B<int> b2;
|
||||
b::B<int> b3;
|
||||
b::B<int> b4;
|
||||
B<a::A<int> > ba;
|
||||
B<a::A<int> > ba2;
|
||||
b::B<a::A<int> > ba3;
|
||||
b::B<a::A<int> > ba4;
|
||||
B<A<int> > ba5;
|
||||
B<A<int> > ba6;
|
||||
//b::B<A<int> > ba7;
|
||||
//b::B<A<int> > ba8;
|
||||
namespace nested {
|
||||
template <class T>
|
||||
class Baz {};
|
||||
|
||||
struct Artichoke {
|
||||
template <class T>
|
||||
class Asparagus {};
|
||||
};
|
||||
}
|
||||
|
||||
struct Onion {
|
||||
// ======================= WORKING =============================================
|
||||
/**
|
||||
* We assume a qualified template instantiation is fully qualified
|
||||
* starting from the global namespace, and we put the corresponding
|
||||
* %template directive in the global namespace, which works if our
|
||||
* assumption is correct.
|
||||
*/
|
||||
::Foo<int> fooInt;
|
||||
|
||||
peer::Bar<int> barInt;
|
||||
::peer::Bar<double> barDouble;
|
||||
|
||||
peer::Leek::Eggplant<int> eggplantInt;
|
||||
::peer::Leek::Eggplant<double> eggplantDouble;
|
||||
|
||||
peer::nested::Broccoli<int> broccoliInt;
|
||||
::peer::nested::Broccoli<double> broccoliDouble;
|
||||
|
||||
peer::nested::Carrot::Celery<int> celeryInt;
|
||||
::peer::nested::Carrot::Celery<double> celeryDouble;
|
||||
|
||||
prime::Potato<int> potatoInt;
|
||||
::prime::Potato<double> potatoDouble;
|
||||
|
||||
prime::Turnip::Radish<int> radishInt;
|
||||
::prime::Turnip::Radish<double> radishDouble;
|
||||
|
||||
prime::nested::Baz<int> bazInt;
|
||||
::prime::nested::Baz<double> bazDouble;
|
||||
|
||||
prime::nested::Artichoke::Asparagus<int> asparagusint;
|
||||
::prime::nested::Artichoke::Asparagus<double> asparagusDouble;
|
||||
|
||||
/**
|
||||
* We put the %template directive for unqualified template
|
||||
* instantiations in the namespace containing the current class,
|
||||
* which only works if the template is defined in that namespace as
|
||||
* well.
|
||||
*/
|
||||
Potato<bool> potatoBool;
|
||||
|
||||
// ======================= BROKEN ==============================================
|
||||
/**
|
||||
* Because Foo is declared in the global namespace, its %template
|
||||
* directive must be in that scope as well. However, because the
|
||||
* template instation here is unqualified, we put the %template
|
||||
* directive in the containing namespace, which is an error.
|
||||
*
|
||||
* Error: 'Foo' resolves to '::Foo' and was incorrectly
|
||||
* instantiated in scope 'prime' instead of within scope ''.
|
||||
*/
|
||||
// Foo<double> fooDouble;
|
||||
|
||||
/**
|
||||
* Because these template instantiations are qualified, their %template
|
||||
* directives are placed in the global namespace. However, because they
|
||||
* are not _fully_ qualified, the generated SWIG input code is invalid.
|
||||
* For partially-qualified instantiations, the directive should either be:
|
||||
* 1. Declared in the global namespace and qualified with prime::
|
||||
* 2. Declared in the prime namespace
|
||||
*
|
||||
* We don't track enough information to do either.
|
||||
*
|
||||
* Error: Template 'Turnip::Radish' undefined.
|
||||
* Error: Template 'nested::Baz' undefined.
|
||||
* Error: Template 'nested::Artichoke::Asparagus' undefined.
|
||||
*/
|
||||
// Turnip::Radish<bool> radishBool;
|
||||
// nested::Baz<bool> bazBool;
|
||||
// nested::Artichoke::Asparagus<bool> asparagusBool;
|
||||
|
||||
template<class T>
|
||||
class InnerOnion {}; // onions have layers!
|
||||
|
||||
/**
|
||||
* All %template directives are placed before the class containing their
|
||||
* corresponding template instantiations. For member templates
|
||||
* (templates declared within the class being processed), this results
|
||||
* in the %template directive referencing the member template before it
|
||||
* has been declared, which is an error.
|
||||
*
|
||||
* Error: Template 'InnerOnion' undefined.
|
||||
* Error: Template 'Onion::InnerOnion' undefined.
|
||||
* Error: Template 'prime::Onion::InnerOnion' undefined.
|
||||
* Error: Template '::prime::Onion::InnerOnion' undefined.
|
||||
*/
|
||||
// InnerOnion<int> layer1;
|
||||
// Onion::InnerOnion<double> layer2;
|
||||
// prime::Onion::InnerOnion<bool> layer2;
|
||||
// ::prime::Onion::InnerOnion<short> layer4;
|
||||
};
|
||||
|
||||
// Same tests as above, but within a nested namespace.
|
||||
namespace nested {
|
||||
class Corn {
|
||||
::Foo<peer::Leek> fooLeek;
|
||||
|
||||
peer::Bar<Foo<long> > barFoo;
|
||||
::peer::Bar<peer::Leek> barLeek;
|
||||
|
||||
peer::Leek::Eggplant<long> eggplantLong;
|
||||
::peer::Leek::Eggplant<peer::Leek> eggplantLeek;
|
||||
|
||||
peer::nested::Broccoli<long> broccoliLong;
|
||||
::peer::nested::Broccoli<peer::Leek> broccoliLeek;
|
||||
|
||||
peer::nested::Carrot::Celery<long> celeryLong;
|
||||
::peer::nested::Carrot::Celery<peer::Leek> celeryLeek;
|
||||
|
||||
prime::Potato<long> potatoLong;
|
||||
::prime::Potato<peer::Leek> potatoLeek;
|
||||
|
||||
prime::Turnip::Radish<long> radishLong;
|
||||
::prime::Turnip::Radish<peer::Leek> radishLeek;
|
||||
|
||||
prime::nested::Baz<long> bazLong;
|
||||
::prime::nested::Baz<peer::Leek> bazLeek;
|
||||
|
||||
prime::nested::Artichoke::Asparagus<long> asparagusLong;
|
||||
::prime::nested::Artichoke::Asparagus<peer::Leek> asparagusLeek;
|
||||
|
||||
// These fail in Onion, but work here because Onion has already been processed.
|
||||
prime::Onion::InnerOnion<long> onionLong;
|
||||
::prime::Onion::InnerOnion<peer::Leek> onionLeek;
|
||||
|
||||
// These fail for the same reason as in Onion.
|
||||
// Foo<float> fooFloat;
|
||||
// Turnip::Radish<float> radishFloat;
|
||||
// nested::Baz<float> bazFloat;
|
||||
// nested::Artichoke::Asparagus<float> asparagusFloat;
|
||||
// Onion::InnerOnion<float> innerOnionFloat;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class E {
|
||||
public:
|
||||
std::vector<int> v;
|
||||
a::A<int> a;
|
||||
a::A<int> a2;
|
||||
b::B<int> b3;
|
||||
b::B<int> b4;
|
||||
b::B<a::A<int> > ba3;
|
||||
b::B<a::A<int> > ba4;
|
||||
};
|
||||
|
||||
namespace a {
|
||||
class B {
|
||||
public:
|
||||
std::vector<int> v;
|
||||
a::A<int> a;
|
||||
a::A<int> a2;
|
||||
A<int> a3;
|
||||
A<int> a4;
|
||||
b::B<int> b3;
|
||||
b::B<int> b4;
|
||||
b::B<a::A<int> > ba3;
|
||||
b::B<a::A<int> > ba4;
|
||||
//b::B<A<int> > ba7;
|
||||
//b::B<A<int> > ba8;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
@ -21,6 +21,8 @@ SIM_parse_s_define:
|
||||
path: test/SIM_parse_s_define
|
||||
SIM_target_specific_variables:
|
||||
path: test/SIM_target_specific_variables
|
||||
SIM_swig_template_scoping:
|
||||
path: test/SIM_swig_template_scoping
|
||||
SIM_test_abstract:
|
||||
path: test/SIM_test_abstract
|
||||
SIM_test_inherit:
|
||||
|
Loading…
Reference in New Issue
Block a user