Skip to content

Conversation

@asukaminato0721
Copy link
Contributor

@asukaminato0721 asukaminato0721 commented Jan 2, 2026

Summary

Fixes #1971

Added a scope-aware scan for attribute expressions, raising a clear NoAccess error when a double-underscore attribute is used while the binder is not inside any function scope.

Test Plan

add related tests

@meta-cla meta-cla bot added the cla signed label Jan 2, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review January 2, 2026 10:00
Copilot AI review requested due to automatic review settings January 2, 2026 10:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds static analysis to detect attempts to access private attributes (names starting with __ but not ending with __) from outside their defining context, addressing issue #1971 where pyrefly was not catching errors that Python's interpreter would raise.

  • Implements a new check for private attribute access in the binding phase
  • Adds test case covering module-level and class-body access to private attributes
  • Reports errors when private attributes are accessed outside function scopes

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
pyrefly/lib/binding/expr.rs Adds check_private_attribute_usage method to detect and report errors when accessing private attributes, integrated into the ensure_expr flow for attribute expressions
pyrefly/lib/test/attributes.rs Adds test case test_private_attribute_outside_class to verify error reporting for private attribute access from module level and within other class bodies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 804 to 805
if self.scopes.in_function_scope() {
return;
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for detecting invalid private attribute access appears incorrect. Using in_function_scope() will allow access from any function (including functions outside the defining class), but Python's name mangling rules mean that __secret is only accessible within the class body where it's defined. The check should verify whether the access is happening within the defining class's scope, not just any function scope. For example, a function at module level or inside a different class should still be flagged as an error.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but Python's name mangling rules mean that __secret is only accessible within the class body where it's defined.

well, this will be lated after other post issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asukaminato0721 this seems buggy:

we can't simply allow all function scope accesses. for example,

class A:
  __v = 100
  
def f():
  a = A()
  print(a.__v)
  
f()

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@kinto0
Copy link
Contributor

kinto0 commented Jan 5, 2026

I like this error. I wonder if we should also come up with a way to allow accessing name-mangled references to this private attribute. python allows accessing this with the _Cls__v attribute

@meta-codesync
Copy link

meta-codesync bot commented Jan 5, 2026

@kinto0 has imported this pull request. If you are a Meta employee, you can view this in D90122102.

@kinto0 kinto0 self-assigned this Jan 5, 2026
Comment on lines 804 to 805
if self.scopes.in_function_scope() {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asukaminato0721 this seems buggy:

we can't simply allow all function scope accesses. for example,

class A:
  __v = 100
  
def f():
  a = A()
  print(a.__v)
  
f()

@asukaminato0721
Copy link
Contributor Author

This is a bit complex, since I impl it more strict first, but the stat tells me it's not proper.

#1980 (comment)

one of the example is

https://github.com/DataDog/dd-trace-py/blob/d18e59ecf2f12cc407eb4dd4e1d647dee3276533/ddtrace/appsec/_iast/_handlers.py#L276

@kinto0 kinto0 assigned rchen152 and unassigned kinto0 Jan 5, 2026
@kinto0 kinto0 requested a review from rchen152 January 5, 2026 20:50
@rchen152
Copy link
Contributor

rchen152 commented Jan 5, 2026

@asukaminato0721 IMO it's reasonable for pyrefly to emit an error on https://github.com/DataDog/dd-trace-py/blob/d18e59ecf2f12cc407eb4dd4e1d647dee3276533/ddtrace/appsec/_iast/_handlers.py#L276. The runtime magic that the code is doing to patch _custom_protobuf_getattribute into a class is not something that I'd reasonably expect a static type checker to be able to follow. As @kinto0 noted, the current check is too lenient. I believe a more correct thing to check would be that we're in a method scope and that the attribute is being accessed on the method's self_name:

self_name: Option<Identifier>,
.

Copy link
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Marking this as changes requested to clear it from my queue.)

@asukaminato0721
Copy link
Contributor Author

I can't visit fb website so this is pub link

self_name: Option<Identifier>,

I have seem this pattern:

class F:
    ...
    def __eq__(self, o):
        return self.__v == o.__v

"#,
);

testcase!(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is turning out to be pretty involved, it would be good to have more test cases:

  • __secret being accessed in a function outside the defining class (should error)
  • __secret being accessed in the defining class (should not error)
  • __secret being accessed in a subclass (should error)

@rchen152
Copy link
Contributor

rchen152 commented Jan 6, 2026

I can't visit fb website so this is pub link

Sorry about that! I've updated the link XD

I have seem this pattern: [...]

Oh hmm. To support that case, I think it would be easier to defer the check to the answers phase, which can be done like so:

  1. Create a new variant of BindingExpect for a private attribute access check:
    pub enum BindingExpect {
    .
  2. In expr.rs, error if the attribute is accessed outside a method, otherwise create a new BindingExpect binding for the attribute
  3. In the answers phase, validate the attribute access:
    pub fn solve_expectation(
    . At this point, we have access to the names of all the attributes defined on the class, as well as the type of the value that the attribute is accessed on, which should be enough information to determine whether the attribute access is legal.

well

Update pyrefly/lib/binding/expr.rs

Co-authored-by: Copilot <[email protected]>

Update pyrefly/lib/test/attributes.rs

Co-authored-by: Copilot <[email protected]>

fix

typo
@github-actions
Copy link

github-actions bot commented Jan 7, 2026

Diff from mypy_primer, showing the effect of this PR on open source code:

nionutils (https://github.com/nion-software/nionutils)
+ ERROR nion/utils/Event.py:82:23-45: Private attribute `__weak_listeners_mutex` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Event.py:83:22-38: Private attribute `__weak_listeners` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Event.py:90:26-37: Private attribute `__listeners` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:614:46-64: Private attribute `__master_items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:615:76-87: Private attribute `__items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:618:46-64: Private attribute `__master_items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:619:74-85: Private attribute `__items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:620:53-65: Private attribute `__selections` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:622:64-83: Private attribute `__selection_changes` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:785:42-60: Private attribute `__master_items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/ListModel.py:789:42-60: Private attribute `__master_items_key` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:238:20-35: Private attribute `__value_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:288:20-35: Private attribute `__value_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:355:20-43: Private attribute `__source_object_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:370:24-42: Private attribute `__property_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:393:20-35: Private attribute `__value_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:441:20-35: Private attribute `__value_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR nion/utils/Stream.py:458:15-30: Private attribute `__value_changed` cannot be accessed outside of its defining class [no-access]
+ ::error file=nion/utils/Event.py,line=82,col=23,endLine=82,endColumn=45,title=Pyrefly no-access::Private attribute `__weak_listeners_mutex` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Event.py,line=83,col=22,endLine=83,endColumn=38,title=Pyrefly no-access::Private attribute `__weak_listeners` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Event.py,line=90,col=26,endLine=90,endColumn=37,title=Pyrefly no-access::Private attribute `__listeners` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=614,col=46,endLine=614,endColumn=64,title=Pyrefly no-access::Private attribute `__master_items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=615,col=76,endLine=615,endColumn=87,title=Pyrefly no-access::Private attribute `__items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=618,col=46,endLine=618,endColumn=64,title=Pyrefly no-access::Private attribute `__master_items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=619,col=74,endLine=619,endColumn=85,title=Pyrefly no-access::Private attribute `__items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=620,col=53,endLine=620,endColumn=65,title=Pyrefly no-access::Private attribute `__selections` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=622,col=64,endLine=622,endColumn=83,title=Pyrefly no-access::Private attribute `__selection_changes` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=785,col=42,endLine=785,endColumn=60,title=Pyrefly no-access::Private attribute `__master_items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/ListModel.py,line=789,col=42,endLine=789,endColumn=60,title=Pyrefly no-access::Private attribute `__master_items_key` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=238,col=20,endLine=238,endColumn=35,title=Pyrefly no-access::Private attribute `__value_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=288,col=20,endLine=288,endColumn=35,title=Pyrefly no-access::Private attribute `__value_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=355,col=20,endLine=355,endColumn=43,title=Pyrefly no-access::Private attribute `__source_object_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=370,col=24,endLine=370,endColumn=42,title=Pyrefly no-access::Private attribute `__property_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=393,col=20,endLine=393,endColumn=35,title=Pyrefly no-access::Private attribute `__value_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=441,col=20,endLine=441,endColumn=35,title=Pyrefly no-access::Private attribute `__value_changed` cannot be accessed outside of its defining class
+ ::error file=nion/utils/Stream.py,line=458,col=15,endLine=458,endColumn=30,title=Pyrefly no-access::Private attribute `__value_changed` cannot be accessed outside of its defining class

AutoSplit (https://github.com/Toufool/AutoSplit)
+ ERROR src/AutoSplit.py:214:26-46: Private attribute `__reload_start_image` cannot be accessed outside of its defining class [no-access]
+ ERROR src/AutoSplit.py:246:26-53: Private attribute `__update_live_image_details` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:416:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:422:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:422:61-85: Private attribute `__capture_method_changed` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:427:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:432:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:438:26-52: Private attribute `__update_default_threshold` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:443:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:446:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:449:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:452:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ERROR src/menu_bar.py:458:26-37: Private attribute `__set_value` cannot be accessed outside of its defining class [no-access]
+ ::error file=src/AutoSplit.py,line=214,col=26,endLine=214,endColumn=46,title=Pyrefly no-access::Private attribute `__reload_start_image` cannot be accessed outside of its defining class
+ ::error file=src/AutoSplit.py,line=246,col=26,endLine=246,endColumn=53,title=Pyrefly no-access::Private attribute `__update_live_image_details` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=416,col=26,endLine=416,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=422,col=26,endLine=422,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=422,col=61,endLine=422,endColumn=85,title=Pyrefly no-access::Private attribute `__capture_method_changed` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=427,col=26,endLine=427,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=432,col=26,endLine=432,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=438,col=26,endLine=438,endColumn=52,title=Pyrefly no-access::Private attribute `__update_default_threshold` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=443,col=26,endLine=443,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=446,col=26,endLine=446,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=449,col=26,endLine=449,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=452,col=26,endLine=452,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class
+ ::error file=src/menu_bar.py,line=458,col=26,endLine=458,endColumn=37,title=Pyrefly no-access::Private attribute `__set_value` cannot be accessed outside of its defining class

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ ERROR ddtrace/appsec/_iast/_handlers.py:277:22-37: Private attribute `__saved_getattr` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/contrib/celery/test_utils.py:41:25-39: Private attribute `__dd_task_span` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/contrib/celery/test_utils.py:66:25-39: Private attribute `__dd_task_span` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/contrib/futures/test_propagation.py:436:42-57: Private attribute `__datadog_patch` cannot be accessed outside of its defining class [no-access]
+ ::error file=ddtrace/appsec/_iast/_handlers.py,line=277,col=22,endLine=277,endColumn=37,title=Pyrefly no-access::Private attribute `__saved_getattr` cannot be accessed outside of its defining class
+ ::error file=tests/contrib/celery/test_utils.py,line=41,col=25,endLine=41,endColumn=39,title=Pyrefly no-access::Private attribute `__dd_task_span` cannot be accessed outside of its defining class
+ ::error file=tests/contrib/celery/test_utils.py,line=66,col=25,endLine=66,endColumn=39,title=Pyrefly no-access::Private attribute `__dd_task_span` cannot be accessed outside of its defining class
+ ::error file=tests/contrib/futures/test_propagation.py,line=436,col=42,endLine=436,endColumn=57,title=Pyrefly no-access::Private attribute `__datadog_patch` cannot be accessed outside of its defining class

aiortc (https://github.com/aiortc/aiortc)
+ ERROR src/aiortc/rtcpeerconnection.py:709:26-32: Private attribute `__sctp` cannot be accessed outside of its defining class [no-access]
+ ERROR src/aiortc/rtcrtpreceiver.py:303:51-57: Private attribute `__kind` cannot be accessed outside of its defining class [no-access]
+ ERROR src/aiortc/rtcrtpsender.py:135:49-55: Private attribute `__kind` cannot be accessed outside of its defining class [no-access]
+ ::error file=src/aiortc/rtcpeerconnection.py,line=709,col=26,endLine=709,endColumn=32,title=Pyrefly no-access::Private attribute `__sctp` cannot be accessed outside of its defining class
+ ::error file=src/aiortc/rtcrtpreceiver.py,line=303,col=51,endLine=303,endColumn=57,title=Pyrefly no-access::Private attribute `__kind` cannot be accessed outside of its defining class
+ ::error file=src/aiortc/rtcrtpsender.py,line=135,col=49,endLine=135,endColumn=55,title=Pyrefly no-access::Private attribute `__kind` cannot be accessed outside of its defining class

Expression (https://github.com/cognitedata/Expression)
+ ERROR expression/core/mailbox.py:130:18-34: Private attribute `__process_events` cannot be accessed outside of its defining class [no-access]
+ ::error file=expression/core/mailbox.py,line=130,col=18,endLine=130,endColumn=34,title=Pyrefly no-access::Private attribute `__process_events` cannot be accessed outside of its defining class

discord.py (https://github.com/Rapptz/discord.py)
+ ERROR discord/abc.py:149:47-57: Private attribute `__iterator` cannot be accessed outside of its defining class [no-access]
+ ERROR discord/shard.py:552:96-104: Private attribute `__shards` cannot be accessed outside of its defining class [no-access]
+ ERROR discord/shard.py:557:18-25: Private attribute `__queue` cannot be accessed outside of its defining class [no-access]
+ ::error file=discord/abc.py,line=149,col=47,endLine=149,endColumn=57,title=Pyrefly no-access::Private attribute `__iterator` cannot be accessed outside of its defining class
+ ::error file=discord/shard.py,line=552,col=96,endLine=552,endColumn=104,title=Pyrefly no-access::Private attribute `__shards` cannot be accessed outside of its defining class
+ ::error file=discord/shard.py,line=557,col=18,endLine=557,endColumn=25,title=Pyrefly no-access::Private attribute `__queue` cannot be accessed outside of its defining class

PyGithub (https://github.com/PyGithub/PyGithub)
+ ERROR tests/Framework.py:251:43-61: Private attribute `__request_callback` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/GithubIntegration.py:310:67-78: Private attribute `__requester` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Github_.py:638:43-54: Private attribute `__requester` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:268:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:269:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:270:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:271:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:272:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:273:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:274:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:275:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:276:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:277:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:280:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:281:33-52: Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:286:40-49: Private attribute `__domains` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:297:23-41: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:307:27-45: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:312:40-49: Private attribute `__domains` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:319:23-41: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:337:27-45: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:342:40-49: Private attribute `__domains` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:350:23-41: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:368:27-45: Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:374:45-62: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:375:45-62: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:376:45-62: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:377:45-62: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:378:38-55: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:382:49-66: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:383:49-66: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:384:49-66: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:385:49-66: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ERROR tests/Requester.py:388:38-55: Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class [no-access]
+ ::error file=tests/Framework.py,line=251,col=43,endLine=251,endColumn=61,title=Pyrefly no-access::Private attribute `__request_callback` cannot be accessed outside of its defining class
+ ::error file=tests/GithubIntegration.py,line=310,col=67,endLine=310,endColumn=78,title=Pyrefly no-access::Private attribute `__requester` cannot be accessed outside of its defining class
+ ::error file=tests/Github_.py,line=638,col=43,endLine=638,endColumn=54,title=Pyrefly no-access::Private attribute `__requester` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=268,col=33,endLine=268,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=269,col=33,endLine=269,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=270,col=33,endLine=270,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=271,col=33,endLine=271,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=272,col=33,endLine=272,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=273,col=33,endLine=273,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=274,col=33,endLine=274,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=275,col=33,endLine=275,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=276,col=33,endLine=276,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=277,col=33,endLine=277,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=280,col=33,endLine=280,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=281,col=33,endLine=281,endColumn=52,title=Pyrefly no-access::Private attribute `__hostnameHasDomain` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=286,col=40,endLine=286,endColumn=49,title=Pyrefly no-access::Private attribute `__domains` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=297,col=23,endLine=297,endColumn=41,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=307,col=27,endLine=307,endColumn=45,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=312,col=40,endLine=312,endColumn=49,title=Pyrefly no-access::Private attribute `__domains` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=319,col=23,endLine=319,endColumn=41,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=337,col=27,endLine=337,endColumn=45,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=342,col=40,endLine=342,endColumn=49,title=Pyrefly no-access::Private attribute `__domains` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=350,col=23,endLine=350,endColumn=41,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=368,col=27,endLine=368,endColumn=45,title=Pyrefly no-access::Private attribute `__assertUrlAllowed` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=374,col=45,endLine=374,endColumn=62,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=375,col=45,endLine=375,endColumn=62,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=376,col=45,endLine=376,endColumn=62,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=377,col=45,endLine=377,endColumn=62,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=378,col=38,endLine=378,endColumn=55,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=382,col=49,endLine=382,endColumn=66,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=383,col=49,endLine=383,endColumn=66,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=384,col=49,endLine=384,endColumn=66,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=385,col=49,endLine=385,endColumn=66,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class
+ ::error file=tests/Requester.py,line=388,col=38,endLine=388,endColumn=55,title=Pyrefly no-access::Private attribute `__makeAbsoluteUrl` cannot be accessed outside of its defining class

apprise (https://github.com/caronc/apprise)
+ ERROR apprise/persistent_store.py:1360:35-46: Private attribute `__valid_key` cannot be accessed outside of its defining class [no-access]
+ ::error file=apprise/persistent_store.py,line=1360,col=35,endLine=1360,endColumn=46,title=Pyrefly no-access::Private attribute `__valid_key` cannot be accessed outside of its defining class

paasta (https://github.com/yelp/paasta)
+ ERROR paasta_tools/mesos/framework.py:74:38-45: Private attribute `__items` cannot be accessed outside of its defining class [no-access]
+ ::error file=paasta_tools/mesos/framework.py,line=74,col=38,endLine=74,endColumn=45,title=Pyrefly no-access::Private attribute `__items` cannot be accessed outside of its defining class

sockeye (https://github.com/awslabs/sockeye)
+ ERROR sockeye/decoder.py:63:17-27: Private attribute `__registry` cannot be accessed outside of its defining class [no-access]
+ ::error file=sockeye/decoder.py,line=63,col=17,endLine=63,endColumn=27,title=Pyrefly no-access::Private attribute `__registry` cannot be accessed outside of its defining class

aiohttp (https://github.com/aio-libs/aiohttp)
+ ERROR aiohttp/test_utils.py:603:20-30: Private attribute `__app_dict` cannot be accessed outside of its defining class [no-access]
+ ERROR aiohttp/test_utils.py:606:13-23: Private attribute `__app_dict` cannot be accessed outside of its defining class [no-access]
+ ::error file=aiohttp/test_utils.py,line=603,col=20,endLine=603,endColumn=30,title=Pyrefly no-access::Private attribute `__app_dict` cannot be accessed outside of its defining class
+ ::error file=aiohttp/test_utils.py,line=606,col=13,endLine=606,endColumn=23,title=Pyrefly no-access::Private attribute `__app_dict` cannot be accessed outside of its defining class

anyio (https://github.com/agronholm/anyio)
+ ERROR src/anyio/_backends/_asyncio.py:1346:37-49: Private attribute `__raw_socket` cannot be accessed outside of its defining class [no-access]
+ ERROR src/anyio/_backends/_asyncio.py:1356:37-49: Private attribute `__raw_socket` cannot be accessed outside of its defining class [no-access]
+ ERROR src/anyio/_backends/_asyncio.py:1570:65-77: Private attribute `__raw_socket` cannot be accessed outside of its defining class [no-access]
+ ::error file=src/anyio/_backends/_asyncio.py,line=1346,col=37,endLine=1346,endColumn=49,title=Pyrefly no-access::Private attribute `__raw_socket` cannot be accessed outside of its defining class
+ ::error file=src/anyio/_backends/_asyncio.py,line=1356,col=37,endLine=1356,endColumn=49,title=Pyrefly no-access::Private attribute `__raw_socket` cannot be accessed outside of its defining class
+ ::error file=src/anyio/_backends/_asyncio.py,line=1570,col=65,endLine=1570,endColumn=77,title=Pyrefly no-access::Private attribute `__raw_socket` cannot be accessed outside of its defining class

mypy (https://github.com/python/mypy)
+ ERROR mypyc/build_setup.py:73:10-17: Private attribute `__spawn` cannot be accessed outside of its defining class [no-access]
+ ERROR mypyc/lib-rt/build_setup.py:73:10-17: Private attribute `__spawn` cannot be accessed outside of its defining class [no-access]
+ ::error file=mypyc/build_setup.py,line=73,col=10,endLine=73,endColumn=17,title=Pyrefly no-access::Private attribute `__spawn` cannot be accessed outside of its defining class
+ ::error file=mypyc/lib-rt/build_setup.py,line=73,col=10,endLine=73,endColumn=17,title=Pyrefly no-access::Private attribute `__spawn` cannot be accessed outside of its defining class

kopf (https://github.com/nolar/kopf)
+ ERROR kopf/_kits/webhacks.py:88:22-34: Private attribute `__generators` cannot be accessed outside of its defining class [no-access]
+ ERROR kopf/_kits/webhacks.py:92:22-34: Private attribute `__generators` cannot be accessed outside of its defining class [no-access]
+ ::error file=kopf/_kits/webhacks.py,line=88,col=22,endLine=88,endColumn=34,title=Pyrefly no-access::Private attribute `__generators` cannot be accessed outside of its defining class
+ ::error file=kopf/_kits/webhacks.py,line=92,col=22,endLine=92,endColumn=34,title=Pyrefly no-access::Private attribute `__generators` cannot be accessed outside of its defining class

pwndbg (https://github.com/pwndbg/pwndbg)
+ ERROR pwndbg/gdblib/tui/context.py:223:47-61: Private attribute `___ansi_substr` cannot be accessed outside of its defining class [no-access]
+ ::error file=pwndbg/gdblib/tui/context.py,line=223,col=47,endLine=223,endColumn=61,title=Pyrefly no-access::Private attribute `___ansi_substr` cannot be accessed outside of its defining class

antidote (https://github.com/Finistere/antidote)
+ ERROR src/antidote/core/_catalog.py:536:54-63: Private attribute `__catalog` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/core/_catalog.py:538:54-63: Private attribute `__catalog` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/core/_catalog.py:540:23-37: Private attribute `__test_context` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_function.py:123:26-37: Private attribute `__signature` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_function.py:150:26-37: Private attribute `__signature` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_interface.py:461:29-38: Private attribute `__prepare` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_interface.py:462:18-43: Private attribute `__register_implementation` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_interface.py:485:29-38: Private attribute `__prepare` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_interface.py:486:29-38: Private attribute `__catalog` cannot be accessed outside of its defining class [no-access]
+ ERROR src/antidote/lib/interface_ext/_interface.py:486:78-89: Private attribute `__interface` cannot be accessed outside of its defining class [no-access]
+ ::error file=src/antidote/core/_catalog.py,line=536,col=54,endLine=536,endColumn=63,title=Pyrefly no-access::Private attribute `__catalog` cannot be accessed outside of its defining class
+ ::error file=src/antidote/core/_catalog.py,line=538,col=54,endLine=538,endColumn=63,title=Pyrefly no-access::Private attribute `__catalog` cannot be accessed outside of its defining class
+ ::error file=src/antidote/core/_catalog.py,line=540,col=23,endLine=540,endColumn=37,title=Pyrefly no-access::Private attribute `__test_context` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_function.py,line=123,col=26,endLine=123,endColumn=37,title=Pyrefly no-access::Private attribute `__signature` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_function.py,line=150,col=26,endLine=150,endColumn=37,title=Pyrefly no-access::Private attribute `__signature` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_interface.py,line=461,col=29,endLine=461,endColumn=38,title=Pyrefly no-access::Private attribute `__prepare` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_interface.py,line=462,col=18,endLine=462,endColumn=43,title=Pyrefly no-access::Private attribute `__register_implementation` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_interface.py,line=485,col=29,endLine=485,endColumn=38,title=Pyrefly no-access::Private attribute `__prepare` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_interface.py,line=486,col=29,endLine=486,endColumn=38,title=Pyrefly no-access::Private attribute `__catalog` cannot be accessed outside of its defining class
+ ::error file=src/antidote/lib/interface_ext/_interface.py,line=486,col=78,endLine=486,endColumn=89,title=Pyrefly no-access::Private attribute `__interface` cannot be accessed outside of its defining class

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2175 to +2177
ScopeKind::Function(_) if !in_method_scope => {
return None;
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic correctly returns None when encountering a Function scope before finding a Method scope, which prevents private attribute access in nested functions defined inside methods. However, this behavior may be overly restrictive. In Python, nested functions defined inside a method should still have access to private attributes through closure, similar to how they access self. Consider whether this restriction aligns with Python's actual runtime behavior for closures.

Suggested change
ScopeKind::Function(_) if !in_method_scope => {
return None;
}

Copilot uses AI. Check for mistakes.
@asukaminato0721 asukaminato0721 requested a review from kinto0 January 7, 2026 07:55
Copy link
Contributor

@kinto0 kinto0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@meta-codesync
Copy link

meta-codesync bot commented Jan 8, 2026

@rchen152 merged this pull request in 3493d4d.

@asukaminato0721 asukaminato0721 deleted the 1971 branch January 9, 2026 03:26
asukaminato0721 added a commit to asukaminato0721/pyrefly that referenced this pull request Jan 9, 2026
…ny error while Python interpreter gives error facebook#1971 (facebook#1980)

Summary:
Fixes facebook#1971

Added a scope-aware scan for attribute expressions, raising a clear NoAccess error when a double-underscore attribute is used while the binder is not inside any function scope.

Pull Request resolved: facebook#1980

Test Plan: add related tests

Reviewed By: kinto0

Differential Revision: D90122102

Pulled By: rchen152

fbshipit-source-id: daf147a24463f0e95b1363fa8a16efaf3d3ed7b3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reading __v from the outside of the class, pyrefly doesn't give any error while Python interpreter gives error

4 participants