Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions tests/parser-cases/name_collision.thrift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Test case for when a local struct has the same name as an imported module
include "name_collision_imported.thrift"

// Local struct with the same name as the imported module
struct name_collision_imported {
1: required string local_field1
2: optional i32 local_field2
}

struct TestStruct {
// Should resolve to local struct 'name_collision_imported'
1: required name_collision_imported localStruct

// Should resolve to UserProfile from the imported module
2: required name_collision_imported.UserProfile importedUserProfile
}
6 changes: 6 additions & 0 deletions tests/parser-cases/name_collision_imported.thrift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file is imported and has a struct named UserProfile
struct UserProfile {
1: required string user_id
2: required string username
3: optional string email
}
37 changes: 37 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,40 @@ def test_nest_incomplete_type():

def test_issue_121():
load('parser-cases/issue_121.thrift')


def test_name_collision_with_imported_module():
"""Test that qualified names resolve correctly when a local struct
has the same name as an imported module."""
thrift = load('parser-cases/name_collision.thrift')

# The local struct 'name_collision_imported' should exist
assert hasattr(thrift, 'name_collision_imported')
local_struct = thrift.name_collision_imported

# Verify it's the local struct with the expected fields
assert local_struct.thrift_spec[1][1] == 'local_field1'
assert local_struct.thrift_spec[2][1] == 'local_field2'

# TestStruct should exist with two fields
test_struct = thrift.TestStruct
assert len(test_struct.thrift_spec) == 2

# Field 1: should reference the local struct 'name_collision_imported'
field1_spec = test_struct.thrift_spec[1]
assert field1_spec[1] == 'localStruct'
assert field1_spec[2] == local_struct

# Field 2: should reference UserProfile from the imported module
field2_spec = test_struct.thrift_spec[2]
assert field2_spec[1] == 'importedUserProfile'

# Critical assertions: field2 must NOT be the local struct
assert field2_spec[2] != local_struct

# It must be UserProfile with the correct structure
user_profile_type = field2_spec[2]
assert user_profile_type.__name__ == 'UserProfile'
assert user_profile_type.thrift_spec[1][1] == 'user_id'
assert user_profile_type.thrift_spec[2][1] == 'username'
assert user_profile_type.thrift_spec[3][1] == 'email'
26 changes: 26 additions & 0 deletions thriftpy2/parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,31 @@ def p_ref_type(p):
'''ref_type : IDENTIFIER'''
ref_type = threadlocal.thrift_stack[-1]

# For qualified names (e.g., 'module.Type'), check if the prefix matches an included module
# This handles the case where a local struct shadows an imported module name
if '.' in p[1]:
prefix = p[1].split('.', 1)[0]

# First, check if this prefix matches any included module
if hasattr(ref_type, '__thrift_meta__') and 'includes' in ref_type.__thrift_meta__:
for included_module in ref_type.__thrift_meta__['includes']:
if hasattr(included_module, '__name__') and included_module.__name__ == prefix:
# Found the included module, now resolve the rest of the path
path_parts = p[1].split('.')[1:]
current_ref = included_module
for part in path_parts:
current_ref = getattr(current_ref, part, None)
if current_ref is None:
break

if current_ref is not None:
if hasattr(current_ref, '_ttype'):
p[0] = getattr(current_ref, '_ttype'), current_ref
else:
p[0] = current_ref
return

# Original resolution logic for backward compatibility
for attr in dir(ref_type):
if attr in {'__doc__', '__loader__', '__name__', '__package__',
'__spec__', '__thrift_file__', '__thrift_meta__'}:
Expand All @@ -428,6 +453,7 @@ def p_ref_type(p):
ref_type = resolved_ref_type
break
else:
# Standard resolution for unqualified names
for index, name in enumerate(p[1].split('.')):
ref_type = getattr(ref_type, name, None)
if ref_type is None:
Expand Down
Loading