22// Licensed under the Apache License, Version 2.0 (see LICENSE).
33
44use std:: fmt:: Write ;
5+ use std:: sync:: OnceLock ;
56
7+ use fnv:: FnvHashMap ;
8+
9+ use parking_lot:: Mutex ;
610use pyo3:: basic:: CompareOp ;
711use pyo3:: exceptions:: PyValueError ;
812use pyo3:: intern;
@@ -11,11 +15,50 @@ use pyo3::pybacked::PyBackedStr;
1115use pyo3:: pyclass_init:: PyClassInitializer ;
1216use pyo3:: types:: PyType ;
1317
18+ use crate :: TypeId ;
1419use crate :: externs:: address:: Address ;
1520use crate :: python:: PyComparedBool ;
1621
1722use super :: util:: { NoFieldValue , combine_hashes, raise_invalid_field_type, validate_choices} ;
1823
24+ static FIELD_TYPE_INFO_CACHE : OnceLock < Mutex < FnvHashMap < TypeId , FieldTypeInfo > > > = OnceLock :: new ( ) ;
25+
26+ struct FieldTypeInfo {
27+ none_is_valid_value : bool ,
28+ deprecated : bool ,
29+ required : bool ,
30+ /// Populated lazily the first time the default is needed.
31+ default : Option < Py < PyAny > > ,
32+ }
33+
34+ impl FieldTypeInfo {
35+ fn resolve ( cls : & Bound < ' _ , PyType > , py : Python ) -> PyResult < Self > {
36+ let removal_version: Option < PyBackedStr > =
37+ cls. getattr ( intern ! ( py, "removal_version" ) ) ?. extract ( ) ?;
38+ Ok ( Self {
39+ none_is_valid_value : cls. getattr ( intern ! ( py, "none_is_valid_value" ) ) ?. extract ( ) ?,
40+ deprecated : removal_version. is_some ( ) ,
41+ required : cls. getattr ( intern ! ( py, "required" ) ) ?. extract ( ) ?,
42+ default : None ,
43+ } )
44+ }
45+ }
46+
47+ fn with_field_type_info < R > (
48+ cls : & Bound < ' _ , PyType > ,
49+ py : Python ,
50+ f : impl FnOnce ( & mut FieldTypeInfo ) -> R ,
51+ ) -> PyResult < R > {
52+ let type_id = TypeId :: new ( cls) ;
53+ let cache = FIELD_TYPE_INFO_CACHE . get_or_init ( || Mutex :: new ( FnvHashMap :: default ( ) ) ) ;
54+ let mut locked = cache. lock ( ) ;
55+ let info = match locked. entry ( type_id) {
56+ std:: collections:: hash_map:: Entry :: Occupied ( e) => e. into_mut ( ) ,
57+ std:: collections:: hash_map:: Entry :: Vacant ( e) => e. insert ( FieldTypeInfo :: resolve ( cls, py) ?) ,
58+ } ;
59+ Ok ( f ( info) )
60+ }
61+
1962#[ pyclass( subclass, frozen, module = "pants.engine.internals.native_engine" ) ]
2063pub struct Field {
2164 pub ( crate ) value : Py < PyAny > ,
@@ -37,13 +80,9 @@ impl Field {
3780 // to None below.
3881 Self :: check_deprecated ( cls, raw_value, & address, py) ?;
3982
83+ let none_is_valid = with_field_type_info ( cls, py, |info| info. none_is_valid_value ) ?;
4084 let raw_value = match raw_value {
41- Some ( value)
42- if value. extract :: < NoFieldValue > ( ) . is_ok ( )
43- && !Self :: cls_none_is_valid_value ( cls) ? =>
44- {
45- None
46- }
85+ Some ( value) if value. extract :: < NoFieldValue > ( ) . is_ok ( ) && !none_is_valid => None ,
4786 rv => rv,
4887 } ;
4988
@@ -97,28 +136,42 @@ impl Field {
97136 address : PyRef < Address > ,
98137 py : Python < ' py > ,
99138 ) -> PyResult < Bound < ' py , PyAny > > {
100- let default = || -> PyResult < Bound < ' _ , PyAny > > {
101- if Self :: cls_required ( cls) ? {
102- // TODO: Should be `RequiredFieldMissingException`.
103- Err ( PyValueError :: new_err ( format ! (
104- "The `{}` field in target {} must be defined." ,
105- Self :: cls_alias( cls) ?,
106- * address,
107- ) ) )
108- } else {
109- Self :: cls_default ( cls)
110- }
111- } ;
139+ enum Branch < ' a , ' py > {
140+ Default ,
141+ NoneValue ,
142+ Value ( & ' a Bound < ' py , PyAny > ) ,
143+ }
112144
113- let none_is_valid_value = Self :: cls_none_is_valid_value ( cls) ?;
114- match raw_value {
115- Some ( value) if none_is_valid_value && value. extract :: < NoFieldValue > ( ) . is_ok ( ) => {
116- default ( )
145+ with_field_type_info ( cls, py, |info| -> PyResult < Bound < ' py , PyAny > > {
146+ let branch = match raw_value {
147+ Some ( value)
148+ if info. none_is_valid_value && value. extract :: < NoFieldValue > ( ) . is_ok ( ) =>
149+ {
150+ Branch :: Default
151+ }
152+ None if info. none_is_valid_value => Branch :: NoneValue ,
153+ None => Branch :: Default ,
154+ Some ( value) => Branch :: Value ( value) ,
155+ } ;
156+
157+ match branch {
158+ Branch :: NoneValue => Ok ( py. None ( ) . into_bound ( py) ) ,
159+ Branch :: Value ( value) => Ok ( value. clone ( ) ) ,
160+ Branch :: Default => {
161+ if info. required {
162+ return Err ( PyValueError :: new_err ( format ! (
163+ "The `{}` field in target {} must be defined." ,
164+ Self :: cls_alias( cls) ?,
165+ * address,
166+ ) ) ) ;
167+ }
168+ if info. default . is_none ( ) {
169+ info. default = Some ( cls. getattr ( intern ! ( py, "default" ) ) ?. unbind ( ) ) ;
170+ }
171+ Ok ( info. default . as_ref ( ) . unwrap ( ) . bind ( py) . clone ( ) )
172+ }
117173 }
118- None if none_is_valid_value => Ok ( py. None ( ) . into_bound ( py) ) ,
119- None => default ( ) ,
120- Some ( value) => Ok ( value. clone ( ) ) ,
121- }
174+ } ) ?
122175 }
123176
124177 #[ getter]
@@ -227,6 +280,10 @@ impl Field {
227280 address : & Bound < ' _ , Address > ,
228281 py : Python ,
229282 ) -> PyResult < ( ) > {
283+ let is_deprecated = with_field_type_info ( cls, py, |info| info. deprecated ) ?;
284+ if !is_deprecated {
285+ return Ok ( ( ) ) ;
286+ }
230287 if address. borrow ( ) . is_generated_target ( ) {
231288 return Ok ( ( ) ) ;
232289 }
0 commit comments