Skip to content

Commit b8304ef

Browse files
Pranay Sowdaboinaqimingyuan
authored andcommitted
Fix typescript type definition for a struct type in a union.
1 parent f8f3df8 commit b8304ef

2 files changed

Lines changed: 181 additions & 2 deletions

File tree

stone/backends/tsd_types.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,17 +459,37 @@ def _generate_union_type(self, union_type, indent_spaces):
459459
variant_type_names = []
460460
if parent_type:
461461
variant_type_names.append(fmt_type_name(parent_type, namespace))
462+
463+
def _is_struct_without_enumerated_subtypes(data_type):
464+
"""
465+
:param data_type: any data type.
466+
:return: True if the given data type is a struct which has no enumerated subtypes.
467+
"""
468+
return is_struct_type(data_type) and (
469+
not data_type.has_enumerated_subtypes())
470+
462471
for variant in union_type.fields:
463472
if variant.doc:
464473
self._emit_tsdoc_header(variant.doc)
465474
variant_name = '%s%s' % (union_type_name, fmt_pascal(variant.name))
466475
variant_type_names.append(variant_name)
467-
self.emit('export interface %s {' % variant_name)
476+
477+
is_struct_without_enumerated_subtypes = _is_struct_without_enumerated_subtypes(
478+
variant.data_type)
479+
480+
if is_struct_without_enumerated_subtypes:
481+
self.emit('export interface %s extends %s {' % (
482+
variant_name, fmt_type(variant.data_type, namespace)))
483+
else:
484+
self.emit('export interface %s {' % variant_name)
485+
468486
with self.indent(dent=indent_spaces):
469487
# Since field contains non-alphanumeric character, we need to enclose
470488
# it in quotation marks.
471489
self.emit("'.tag': '%s';" % variant.name)
472-
if is_void_type(variant.data_type) is False:
490+
if is_void_type(variant.data_type) is False and (
491+
not is_struct_without_enumerated_subtypes
492+
):
473493
self.emit("%s: %s;" % (variant.name, fmt_type(variant.data_type, namespace)))
474494
self.emit('}')
475495
self.emit()

test/test_tsd_types.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,117 @@ def __init__(self):
248248
export type AliasedBaseU = BaseU;
249249
%s
250250
}
251+
"""
252+
253+
_ns3_union_spec = """\
254+
namespace ns3
255+
struct A
256+
union
257+
a1 A1
258+
a2 A2
259+
a String
260+
struct A1 extends A
261+
b Boolean
262+
struct A2 extends A
263+
c Boolean
264+
union M
265+
e Boolean
266+
f String
267+
union B
268+
w Boolean
269+
x A
270+
y M
271+
z A2
272+
"""
273+
274+
_ns3_union_spec_types = """{
275+
export interface A {
276+
a: string;
277+
}
278+
279+
/**
280+
* Reference to the A polymorphic type. Contains a .tag property to let you
281+
* discriminate between possible subtypes.
282+
*/
283+
export interface AReference extends A {
284+
/**
285+
* Tag identifying the subtype variant.
286+
*/
287+
'.tag': "a1"|"a2";
288+
}
289+
290+
export interface A1 extends A {
291+
b: boolean;
292+
}
293+
294+
/**
295+
* Reference to the A1 type, identified by the value of the .tag property.
296+
*/
297+
export interface A1Reference extends A1 {
298+
/**
299+
* Tag identifying this subtype variant. This field is only present when
300+
* needed to discriminate between multiple possible subtypes.
301+
*/
302+
'.tag': 'a1';
303+
}
304+
305+
export interface A2 extends A {
306+
c: boolean;
307+
}
308+
309+
/**
310+
* Reference to the A2 type, identified by the value of the .tag property.
311+
*/
312+
export interface A2Reference extends A2 {
313+
/**
314+
* Tag identifying this subtype variant. This field is only present when
315+
* needed to discriminate between multiple possible subtypes.
316+
*/
317+
'.tag': 'a2';
318+
}
319+
320+
export interface BW {
321+
'.tag': 'w';
322+
w: boolean;
323+
}
324+
325+
export interface BX {
326+
'.tag': 'x';
327+
x: A1Reference|A2Reference|AReference;
328+
}
329+
330+
export interface BY {
331+
'.tag': 'y';
332+
y: M;
333+
}
334+
335+
export interface BZ extends A2 {
336+
'.tag': 'z';
337+
}
338+
339+
export interface BOther {
340+
'.tag': 'other';
341+
}
342+
343+
export type B = BW | BX | BY | BZ | BOther;
344+
345+
export interface ME {
346+
'.tag': 'e';
347+
e: boolean;
348+
}
349+
350+
export interface MF {
351+
'.tag': 'f';
352+
f: string;
353+
}
354+
355+
export interface MOther {
356+
'.tag': 'other';
357+
}
358+
359+
export type M = ME | MF | MOther;
360+
%s
361+
}
251362
"""
252363

253364
_timestamp_mapping = 'type Timestamp = string'
@@ -256,6 +367,7 @@ def __init__(self):
256367

257368
@classmethod
258369
def get_ns_spec(cls):
370+
"""Returns a test namespace which imports another namespace (`ns2`)."""
259371
return cls._ns_spec
260372

261373
@classmethod
@@ -266,6 +378,7 @@ def get_ns_types_as_declaration(cls):
266378

267379
@classmethod
268380
def get_ns2_spec(cls):
381+
"""Returns a simple namespace."""
269382
return cls._ns2_spec
270383

271384
@classmethod
@@ -280,6 +393,21 @@ def get_all_types_as_namespace(cls):
280393
("\nnamespace ns2 " + cls._ns2_spec_types) % "") + "\n\n"
281394
return types
282395

396+
@classmethod
397+
def get_ns3_spec_for_union(cls):
398+
"""
399+
Returns a test namespace which has a union field with all possible types of
400+
members a union can have. It includes (1) primitive, (2) struct, (3) enumerated
401+
subtypes, and (4) a union. This spec is useful in validating the auto-generated
402+
code for a union type defined in a namespace.
403+
"""
404+
return cls._ns3_union_spec
405+
406+
@classmethod
407+
def get_ns3_types_as_declaration(cls):
408+
return (("\ndeclare module 'ns3' " + cls._ns3_union_spec_types
409+
) % cls._timestamp_def_formatted) + "\n\n"
410+
283411

284412
class TestTSDTypesE2E(unittest.TestCase):
285413

@@ -363,6 +491,37 @@ def test_tsd_types_namespace_output(self):
363491
expected_output = SpecHelper.get_all_types_as_namespace()
364492
self._verify_generated_output('output/{}'.format(output_file_name), expected_output)
365493

494+
def test_tsd_types_for_union(self):
495+
"""
496+
Test tsd types generated for a union which has all possible data types as
497+
members including primitive, struct, enumerated sub types and unions.
498+
"""
499+
# Sanity check: stone must be importable for the compiler to work
500+
__import__('stone')
501+
502+
# Compile spec by calling out to stone
503+
p = subprocess.Popen(
504+
[sys.executable,
505+
'-m',
506+
'stone.cli',
507+
'tsd_types',
508+
self.stone_output_directory,
509+
'--',
510+
self.template_file_name,
511+
'--exclude_error_types',
512+
'-i=0'],
513+
stdin=subprocess.PIPE,
514+
stderr=subprocess.PIPE)
515+
_, stderr = p.communicate(
516+
input=(SpecHelper.get_ns3_spec_for_union()).encode('utf-8'))
517+
if p.wait() != 0:
518+
raise AssertionError('Could not execute stone tool: %s' %
519+
stderr.decode('utf-8'))
520+
521+
# one file must be generated per namespace
522+
expected_ns_output = SpecHelper.get_ns3_types_as_declaration()
523+
self._verify_generated_output('output/ns3.d.ts', expected_ns_output)
524+
366525

367526
if __name__ == '__main__':
368527
unittest.main()

0 commit comments

Comments
 (0)