diff --git a/docs/documentation/building_a_simulation/Model-Source-Code.md b/docs/documentation/building_a_simulation/Model-Source-Code.md index 896845a06..daa143733 100644 --- a/docs/documentation/building_a_simulation/Model-Source-Code.md +++ b/docs/documentation/building_a_simulation/Model-Source-Code.md @@ -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 Potato {}; + +namespace example { + + template class Onion {}; + + namespace peer { + template class Raddish {}; + } + + namespace prime { + + namespace inner { + template class Carrot {}; } - }; - // Everything enclosed in inner_ns is ignored. - namespace inner_ns { - ... - }; -}; + + template class Celery {}; + + class Soup { + + public: + template class Broth {}; + + ::Potato potato; // Rule 2 + example::Onion onion; // Rule 2 + example::peer::Raddish raddish; // Rule 2 + example::prime::inner::Carrot carrot; // Rule 2 + Celery celery; // Rule 1 +#ifndef SWIG + Broth broth; // Rule 3 +#endif + }; + } + +} ``` ### Function Overloading diff --git a/libexec/trick/convert_swig b/libexec/trick/convert_swig index b7ffe9c90..a930a6067 100755 --- a/libexec/trick/convert_swig +++ b/libexec/trick/convert_swig @@ -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+ +// @trick_parse{everything} -namespace a { - template class A { - private: - void operator=(const A&); +#ifndef Classes_HH +#define Classes_HH + +template +class Foo {}; + +namespace peer { + template + class Bar {}; + + struct Leek { + template + class Eggplant {}; }; + + namespace nested { + template + class Broccoli {}; + + struct Carrot { + template + class Celery {}; + }; + } } -namespace b { - - template class A {}; - template class B {}; - - class C { - public: - std::vector v; - a::A a; - a::A a2; - A a3; - A a4; - B b; - B b2; - b::B b3; - b::B b4; - B > ba; - B > ba2; - b::B > ba3; - b::B > ba4; - B > ba5; - B > 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 > ba7; - //b::B > ba8; - }; +namespace prime { + + template + class Potato {}; - // 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 v; - a::A a; - a::A a2; - A a3; - A a4; - B b; - B b2; - b::B b3; - b::B b4; - B > ba; - B > ba2; - b::B > ba3; - b::B > ba4; - B > ba5; - B > ba6; - //b::B > ba7; - //b::B > ba8; + struct Turnip { + template + class Radish {}; }; -} -class E { - public: - std::vector v; - a::A a; - a::A a2; - b::B b3; - b::B b4; - b::B > ba3; - b::B > ba4; -}; - -namespace a { - class B { - public: - std::vector v; - a::A a; - a::A a2; - A a3; - A a4; - b::B b3; - b::B b4; - b::B > ba3; - b::B > ba4; - //b::B > ba7; - //b::B > ba8; + namespace nested { + template + class Baz {}; + + struct Artichoke { + template + 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 fooInt; + + peer::Bar barInt; + ::peer::Bar barDouble; + + peer::Leek::Eggplant eggplantInt; + ::peer::Leek::Eggplant eggplantDouble; + + peer::nested::Broccoli broccoliInt; + ::peer::nested::Broccoli broccoliDouble; + + peer::nested::Carrot::Celery celeryInt; + ::peer::nested::Carrot::Celery celeryDouble; + + prime::Potato potatoInt; + ::prime::Potato potatoDouble; + + prime::Turnip::Radish radishInt; + ::prime::Turnip::Radish radishDouble; + + prime::nested::Baz bazInt; + ::prime::nested::Baz bazDouble; + + prime::nested::Artichoke::Asparagus asparagusint; + ::prime::nested::Artichoke::Asparagus 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 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 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 radishBool; +// nested::Baz bazBool; +// nested::Artichoke::Asparagus asparagusBool; + + template + 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 layer1; +// Onion::InnerOnion layer2; +// prime::Onion::InnerOnion layer2; +// ::prime::Onion::InnerOnion layer4; }; + + // Same tests as above, but within a nested namespace. + namespace nested { + class Corn { + ::Foo fooLeek; + + peer::Bar > barFoo; + ::peer::Bar barLeek; + + peer::Leek::Eggplant eggplantLong; + ::peer::Leek::Eggplant eggplantLeek; + + peer::nested::Broccoli broccoliLong; + ::peer::nested::Broccoli broccoliLeek; + + peer::nested::Carrot::Celery celeryLong; + ::peer::nested::Carrot::Celery celeryLeek; + + prime::Potato potatoLong; + ::prime::Potato potatoLeek; + + prime::Turnip::Radish radishLong; + ::prime::Turnip::Radish radishLeek; + + prime::nested::Baz bazLong; + ::prime::nested::Baz bazLeek; + + prime::nested::Artichoke::Asparagus asparagusLong; + ::prime::nested::Artichoke::Asparagus asparagusLeek; + + // These fail in Onion, but work here because Onion has already been processed. + prime::Onion::InnerOnion onionLong; + ::prime::Onion::InnerOnion onionLeek; + + // These fail for the same reason as in Onion. +// Foo fooFloat; +// Turnip::Radish radishFloat; +// nested::Baz bazFloat; +// nested::Artichoke::Asparagus asparagusFloat; +// Onion::InnerOnion innerOnionFloat; + }; + } } + +#endif diff --git a/test_sims.yml b/test_sims.yml index 16b89cf57..a6f56dea9 100644 --- a/test_sims.yml +++ b/test_sims.yml @@ -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: