Skip to content

Commit 01615ad

Browse files
authored
Struct constructors work like built-in type constructors. (#751)
You know how you can have an expression for a built-in type like color? foo = color(1,1,1) or void func (color c) { ...} func (color(1,1,1); It was really awkward to do this with structs. So with this patch, for every struct, you can construct it by name with a function-call-like syntax, where the arguments must be the same types as the data members of the struct, in order. For example: struct RGBA { color rgb; float a; }; RGBA x = RGBA(color(1,1,1), 1); void func (RGBA x) { ... } func (RGBA(Cblah,alpha)); You don't need to explicitly declare the constructor like in C++, it just exists.
1 parent ef44fdd commit 01615ad

11 files changed

Lines changed: 177 additions & 61 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ TESTSUITE ( and-or-not-synonyms aastep arithmetic array array-derivs array-range
257257
oslc-err-format oslc-err-intoverflow
258258
oslc-err-noreturn oslc-err-notfunc
259259
oslc-err-outputparamvararray oslc-err-paramdefault
260-
oslc-err-struct-array-init oslc-err-struct-dup
260+
oslc-err-struct-array-init oslc-err-struct-ctr
261+
oslc-err-struct-dup
261262
oslc-warn-commainit
262263
oslc-variadic-macro
263264
oslc-version

src/doc/languagespec.tex

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
\emph{lg@imageworks.com}
6363
}
6464
\date{{\large Date: 6 Feb 2017 \\
65-
(with corrections, 28 Apr 2017)
65+
(with corrections, 12 May 2017)
6666
}
6767
\bigskip
6868
\bigskip
@@ -2068,14 +2068,37 @@ \section{Structures}
20682068
is similar to C or C++:
20692069

20702070
\begin{code}
2071-
struct Ray { // Define a structure type
2072-
point pos;
2073-
vector dir;
2071+
struct RGBA { // Define a structure type
2072+
color rgb;
2073+
float alpha;
20742074
};
20752075

2076-
Ray r; // Declare a structure
2077-
r.pos = point (1, 0, 0); // Assign to one field
2078-
point p = r.pos; // Read from a structure field
2076+
RGBA col; // Declare a structure
2077+
r.rgb = color (1, 0, 0); // Assign to one field
2078+
color c = r.rgb; // Read from a structure field
2079+
2080+
RGBA b = { color(.1,.2,.3), 1 }; // Member-by-member initialization
2081+
\end{code}
2082+
2083+
You can use ``constructor expressions'' for a your struct types much like
2084+
you can construct built-in types like \color or \point:
2085+
2086+
\vspace{10pt}
2087+
\hspace{0.25in} \emph{struct\_name ( first\_member\_value, ... )}
2088+
\vspace{10pt}
2089+
2090+
\noindent For example,
2091+
2092+
\begin{code}
2093+
RGBA c = RGBA(col,alpha); // Constructor syntax
2094+
2095+
RGBA add (RGBA a, RGBA b)
2096+
{
2097+
return RGBA (a.rgb+b.rgb, a.a+b.a); // return expression
2098+
}
2099+
2100+
// pass constructor expression as a parameter:
2101+
RGBA d = add (c, RGBA(color(.3,.4,.5), 0));
20792102
\end{code}
20802103

20812104
It is permitted to have a structure field that is an array, as well as to
@@ -2089,8 +2112,8 @@ \section{Structures}
20892112
float b[4]; // Ok: struct may contain an array
20902113
};
20912114

2092-
Ray rays[4]; // Ok: Array of structures
2093-
rays[0].pos = 0;
2115+
RGBA vals[4]; // Ok: Array of structures
2116+
vals[0].a = 0;
20942117

20952118
A d[5]; // NO: Array of structures that contain arrays
20962119
\end{code}

src/liboslcomp/ast.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,9 @@ ASTfunction_call::ASTfunction_call (OSLCompilerImpl *comp, ustring name,
998998
// find the things that almost matched and offer suggestions.
999999
return;
10001000
}
1001+
if (is_struct_ctr()) {
1002+
return; // It's a struct constructor
1003+
}
10011004
if (m_sym->symtype() != SymTypeFunction) {
10021005
error ("'%s' is not a function", name.c_str());
10031006
m_sym = NULL;

src/liboslcomp/ast.h

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,33 @@ class ASTNode : public OIIO::RefCnt {
339339
bool copywholearrays, int intindex,
340340
bool paraminit);
341341

342+
// Helper: type check an initializer list -- either a single item to
343+
// a scalar, or a list to an array.
344+
void typecheck_initlist (ref init, TypeSpec type, string_view name);
345+
346+
// Helper: generate code for an initializer list -- either a single
347+
// item to a scalar, or a list to an array.
348+
void codegen_initlist (ref init, TypeSpec type, Symbol *sym);
349+
350+
// Special type checking for structure member initializers.
351+
// It's in the ASTNode base class because it's used from mutiple
352+
// subclasses.
353+
TypeSpec typecheck_struct_initializers (ref init, TypeSpec type,
354+
string_view name);
355+
356+
// Special code generation for structure initializers.
357+
// It's in the ASTNode base class because it's used from mutiple
358+
// subclasses.
359+
Symbol *codegen_struct_initializers (ref init, Symbol *sym,
360+
bool is_constructor=false);
361+
362+
// Helper for param_default_literals: generate the string that gives
363+
// the initialization of the literal value (and/or the default, if
364+
// init==NULL) and append it to 'out'. Return whether the full
365+
// initialization is comprised only of literals (no init ops needed).
366+
bool one_default_literal (const Symbol *sym, ASTNode *init,
367+
std::string &out, string_view separator=" ") const;
368+
342369
void error_impl (string_view msg) const;
343370
void warning_impl (string_view msg) const;
344371

@@ -446,33 +473,11 @@ class ASTvariable_declaration : public ASTNode
446473

447474
void codegen_initializer (ref init, Symbol *sym);
448475

449-
// Special code generation for structure initializers
450-
Symbol *codegen_struct_initializers (ref init, Symbol *sym);
451-
452476
void register_struct_init (ustring name, ASTNode *init) {
453477
m_struct_field_inits.emplace_back(name, init);
454478
}
455479

456480
private:
457-
// Helper: type check an initializer list -- either a single item to
458-
// a scalar, or a list to an array.
459-
void typecheck_initlist (ref init, TypeSpec type, const char *name);
460-
461-
// Special type checking for structure initializers
462-
TypeSpec typecheck_struct_initializers (ref init, TypeSpec type,
463-
const char *name);
464-
465-
// Helper: generate code for an initializer list -- either a single
466-
// item to a scalar, or a list to an array.
467-
void codegen_initlist (ref init, TypeSpec type, Symbol *sym);
468-
469-
// Helper for param_default_literals: generate the string that gives
470-
// the initialization of the literal value (and/or the default, if
471-
// init==NULL) and append it to 'out'. Return whether the full
472-
// initialization is comprised only of literals (no init ops needed).
473-
bool param_one_default_literal (const Symbol *sym, ASTNode *init,
474-
std::string &out, const std::string &separator=" ") const;
475-
476481
ustring m_name; ///< Name of the symbol (unmangled)
477482
Symbol *m_sym; ///< Ptr to the symbol this declares
478483
bool m_isparam; ///< Is this a parameter?
@@ -859,10 +864,13 @@ class ASTfunction_call : public ASTNode
859864
FunctionSymbol *func () const { return (FunctionSymbol *)m_sym; }
860865
ref args () const { return child (0); }
861866

867+
/// Is it a struct constructor?
868+
bool is_struct_ctr () const { return m_sym && m_sym->is_structure(); }
869+
862870
/// Is it a user-defined function (as opposed to an OSL built-in)?
863871
///
864872
bool is_user_function () const {
865-
return user_function() && !user_function()->is_builtin();
873+
return !is_struct_ctr() && user_function() && !user_function()->is_builtin();
866874
}
867875

868876
/// Pointer to the ASTfunction_declaration node that defines the user
@@ -893,6 +901,9 @@ class ASTfunction_call : public ASTNode
893901
/// return false and call an appropriate error().
894902
bool typecheck_printf_args (const char *format, ASTNode *arg);
895903

904+
/// Handle "funcion calls" that are really struct ctrs.
905+
TypeSpec typecheck_struct_constructor ();
906+
896907
/// Is the argument number 'arg' read by the op?
897908
///
898909
bool argread (int arg) const;

src/liboslcomp/codegen.cpp

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -620,8 +620,8 @@ ASTNode::codegen_assign_struct (StructSpec *structspec,
620620

621621

622622
bool
623-
ASTvariable_declaration::param_one_default_literal (const Symbol *sym,
624-
ASTNode *init, std::string &out, const std::string &sep) const
623+
ASTNode::one_default_literal (const Symbol *sym, ASTNode *init,
624+
std::string &out, string_view sep) const
625625
{
626626
// FIXME -- this only works for single values or arrays made of
627627
// literals. Needs to be seriously beefed up.
@@ -792,7 +792,7 @@ ASTvariable_declaration::param_default_literals (const Symbol *sym,
792792
for (int i = 0; i==0 || init; ++i, init = init->nextptr()) {
793793
if (i)
794794
out += separator;
795-
completed &= param_one_default_literal (sym, init, out, separator);
795+
completed &= one_default_literal (sym, init, out, separator);
796796
if (! compound || ! init)
797797
break;
798798
}
@@ -831,15 +831,14 @@ ASTvariable_declaration::codegen_initializer (ref init, Symbol *sym)
831831

832832

833833
void
834-
ASTvariable_declaration::codegen_initlist (ref init, TypeSpec type,
835-
Symbol *sym)
834+
ASTNode::codegen_initlist (ref init, TypeSpec type, Symbol *sym)
836835
{
837836
// If we're doing this initialization for shader params for their
838837
// init ops, we need to take care to set the codegen method names
839838
// properly.
840839
bool paraminit = (m_compiler->codegen_method() != m_compiler->main_method_name() &&
841-
(m_sym->symtype() == SymTypeParam ||
842-
m_sym->symtype() == SymTypeOutputParam));
840+
(sym->symtype() == SymTypeParam ||
841+
sym->symtype() == SymTypeOutputParam));
843842

844843
if (type.is_structure()) {
845844
// Special case -- structure : Recurse to handle each field
@@ -850,9 +849,13 @@ ASTvariable_declaration::codegen_initlist (ref init, TypeSpec type,
850849
ustring fieldname = ustring::format ("%s.%s", sym->mangled(),
851850
field.name);
852851
Symbol *fieldsym = m_compiler->symtab().find_exact (fieldname);
853-
std::string out;
854-
if (paraminit && param_default_literals(fieldsym, init.get(), out))
855-
continue; // Skip if we had a static initalizer
852+
if (paraminit) {
853+
ASSERT (nodetype() == variable_declaration_node);
854+
ASTvariable_declaration *v = (ASTvariable_declaration *)this;
855+
std::string out;
856+
if (v->param_default_literals(fieldsym, init.get(), out))
857+
continue; // Skip if we had a static initalizer
858+
}
856859
codegen_initlist (init, fieldsym->typespec(), fieldsym);
857860
}
858861
return;
@@ -954,17 +957,18 @@ ASTvariable_declaration::codegen_initlist (ref init, TypeSpec type,
954957

955958

956959
Symbol *
957-
ASTvariable_declaration::codegen_struct_initializers (ref init, Symbol *sym)
960+
ASTNode::codegen_struct_initializers (ref init, Symbol *sym,
961+
bool is_constructor)
958962
{
959963
// If we're doing this initialization for shader params for their
960964
// init ops, we need to take care to set the codegen method names
961965
// properly.
962966
bool paraminit = (m_compiler->codegen_method() != m_compiler->main_method_name() &&
963-
(m_sym->symtype() == SymTypeParam ||
964-
m_sym->symtype() == SymTypeOutputParam));
967+
(sym->symtype() == SymTypeParam ||
968+
sym->symtype() == SymTypeOutputParam));
965969

966970
ASSERT (sym->typespec().is_structure());
967-
if (init->nodetype() != compound_initializer_node) {
971+
if (init->nodetype() != compound_initializer_node && !is_constructor) {
968972
// Just one initializer, it's a whole struct of the right type.
969973
Symbol *initsym = init->codegen (sym);
970974
if (initsym != sym) {
@@ -977,7 +981,9 @@ ASTvariable_declaration::codegen_struct_initializers (ref init, Symbol *sym)
977981
}
978982

979983
// General case -- per-field initializers
980-
init = ((ASTcompound_initializer *)init.get())->initlist();
984+
if (init->nodetype() == compound_initializer_node) {
985+
init = ((ASTcompound_initializer *)init.get())->initlist();
986+
}
981987
StructSpec *structspec (sym->typespec().structspec());
982988
for (int i = 0; init && i < structspec->numfields(); init = init->next(), ++i) {
983989
// Structure element -- assign to the i-th member field
@@ -995,7 +1001,9 @@ ASTvariable_declaration::codegen_struct_initializers (ref init, Symbol *sym)
9951001
// For parameter initialization, don't really generate ops if it
9961002
// can be statically initialized.
9971003
std::string out;
998-
if (param_default_literals (fieldsym, init.get(), out))
1004+
ASSERT (nodetype() == variable_declaration_node);
1005+
ASTvariable_declaration *v = (ASTvariable_declaration *)this;
1006+
if (v->param_default_literals (fieldsym, init.get(), out))
9991007
continue;
10001008

10011009
// Delineate and remember the init ops for this field individually
@@ -1639,6 +1647,14 @@ ASTfunction_call::argwrite (int arg) const
16391647
Symbol *
16401648
ASTfunction_call::codegen (Symbol *dest)
16411649
{
1650+
if (is_struct_ctr()) {
1651+
// Looks like function call, but is actually struct constructor
1652+
if (! dest)
1653+
dest = m_compiler->make_temporary (typespec());
1654+
codegen_struct_initializers (args(), dest, true /*is_constructor*/);
1655+
return dest;
1656+
}
1657+
16421658
// Set up a return destination if not passed one (or not the right type)
16431659
if (! typespec().is_void()) {
16441660
if (dest == NULL || ! equivalent (dest->typespec(), typespec()))

src/liboslcomp/typecheck.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ ASTvariable_declaration::typecheck (TypeSpec expected)
139139

140140

141141
void
142-
ASTvariable_declaration::typecheck_initlist (ref init, TypeSpec type,
143-
const char *name)
142+
ASTNode::typecheck_initlist (ref init, TypeSpec type, string_view name)
144143
{
145144
// Loop over a list of initializers (it's just 1 if not an array)...
146145
for (int i = 0; init; init = init->next(), ++i) {
@@ -170,8 +169,8 @@ ASTvariable_declaration::typecheck_initlist (ref init, TypeSpec type,
170169

171170

172171
TypeSpec
173-
ASTvariable_declaration::typecheck_struct_initializers (ref init, TypeSpec type,
174-
const char *name)
172+
ASTNode::typecheck_struct_initializers (ref init, TypeSpec type,
173+
string_view name)
175174
{
176175
ASSERT (type.is_structure());
177176

@@ -197,16 +196,12 @@ ASTvariable_declaration::typecheck_struct_initializers (ref init, TypeSpec type,
197196
// Initializer is itself a compound, it ought to be initializing
198197
// a field that is an array.
199198
ASTcompound_initializer *cinit = (ASTcompound_initializer *)init.get();
199+
ustring fieldname = ustring::format ("%s.%s", name, field.name);
200200
if (field.type.is_array ()) {
201-
ustring fieldname = ustring::format ("%s.%s", name,
202-
field.name.c_str());
203-
typecheck_initlist (cinit->initlist(),
204-
field.type, fieldname.c_str());
201+
typecheck_initlist (cinit->initlist(), field.type, fieldname);
205202
} else if (field.type.is_structure()) {
206-
ustring fieldname = ustring::format ("%s.%s", name,
207-
field.name.c_str());
208203
typecheck_struct_initializers (cinit->initlist(), field.type,
209-
fieldname.c_str());
204+
fieldname);
210205
} else {
211206
error ("Can't use '{...}' for a struct field that is not an array");
212207
}
@@ -225,7 +220,7 @@ ASTvariable_declaration::typecheck_struct_initializers (ref init, TypeSpec type,
225220
if (! assignable(field.type, init->typespec()))
226221
error ("Can't assign '%s' to '%s %s.%s'",
227222
type_c_str(init->typespec()),
228-
type_c_str(field.type), name, field.name.c_str());
223+
type_c_str(field.type), name, field.name);
229224
}
230225
return type;
231226
}
@@ -1117,11 +1112,31 @@ ASTfunction_call::typecheck_builtin_specialcase ()
11171112

11181113

11191114

1115+
TypeSpec
1116+
ASTfunction_call::typecheck_struct_constructor ()
1117+
{
1118+
StructSpec *structspec = m_sym->typespec().structspec();
1119+
ASSERT (structspec);
1120+
m_typespec = m_sym->typespec();
1121+
if (structspec->numfields() != (int)listlength(args())) {
1122+
error ("Constructor for '%s' has the wrong number of arguments (expected %d, got %d)",
1123+
structspec->name(), structspec->numfields(), listlength(args()));
1124+
}
1125+
return typecheck_struct_initializers (args(), m_typespec, "this");
1126+
}
1127+
1128+
1129+
11201130
TypeSpec
11211131
ASTfunction_call::typecheck (TypeSpec expected)
11221132
{
11231133
typecheck_children ();
11241134

1135+
if (is_struct_ctr()) {
1136+
// Looks like function call, but is actually struct constructor
1137+
return typecheck_struct_constructor ();
1138+
}
1139+
11251140
bool match = false;
11261141

11271142
// Look for an exact match, including expected return type
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test.osl:10: error: Cannot initialize structure 'A' with a scalar value
2+
test.osl:13: error: Constructor for 'rgba' has the wrong number of arguments (expected 2, got 1)
3+
test.osl:16: error: Constructor for 'rgba' has the wrong number of arguments (expected 2, got 3)
4+
test.osl:16: error: Too many initializers for 'struct rgba this'
5+
test.osl:19: error: Can't assign 'string' to 'color this.rgb'
6+
test.osl:19: error: Can't assign 'string' to 'float this.a'
7+
FAILED test.osl
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python
2+
3+
# command = oslc("test.osl")
4+
# don't even need that -- it's automatic
5+
failureok = 1 # this test is expected to have oslc errors

0 commit comments

Comments
 (0)