Skip to content

Commit

Permalink
Improve SWIG %template directive scoping - take 2 (#1753)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dbankieris authored Nov 12, 2024
1 parent e8508ea commit aecf6a0
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 127 deletions.
59 changes: 43 additions & 16 deletions docs/documentation/building_a_simulation/Model-Source-Code.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 60 additions & 26 deletions libexec/trick/convert_swig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -675,40 +674,55 @@ 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} ) {

my $sanitized_namespace = $curr_namespace =~ s/:/_/gr ;
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 ;
Expand All @@ -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 ;
Expand Down
1 change: 0 additions & 1 deletion test/SIM_swig_template_scoping/S_overrides.mk
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
TRICK_CONVERT_SWIG_FLAGS := -s
TRICK_CFLAGS += -Imodels
TRICK_CXXFLAGS += -Imodels
Loading

0 comments on commit aecf6a0

Please sign in to comment.