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,54 @@ 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+ /// None means required (no default). Some holds the default value.
30+ default : Option < Py < PyAny > > ,
31+ }
32+
33+ impl FieldTypeInfo {
34+ fn resolve ( cls : & Bound < ' _ , PyType > , py : Python ) -> PyResult < Self > {
35+ let removal_version: Option < PyBackedStr > =
36+ cls. getattr ( intern ! ( py, "removal_version" ) ) ?. extract ( ) ?;
37+ let required: bool = cls. getattr ( intern ! ( py, "required" ) ) ?. extract ( ) ?;
38+ let default = if required {
39+ None
40+ } else {
41+ Some ( cls. getattr ( intern ! ( py, "default" ) ) ?. unbind ( ) )
42+ } ;
43+ Ok ( Self {
44+ none_is_valid_value : cls. getattr ( intern ! ( py, "none_is_valid_value" ) ) ?. extract ( ) ?,
45+ deprecated : removal_version. is_some ( ) ,
46+ default,
47+ } )
48+ }
49+ }
50+
51+ fn with_field_type_info < R > (
52+ cls : & Bound < ' _ , PyType > ,
53+ py : Python ,
54+ f : impl FnOnce ( & FieldTypeInfo ) -> R ,
55+ ) -> PyResult < R > {
56+ let type_id = TypeId :: new ( cls) ;
57+ let cache = FIELD_TYPE_INFO_CACHE . get_or_init ( || Mutex :: new ( FnvHashMap :: default ( ) ) ) ;
58+ let mut locked = cache. lock ( ) ;
59+ let info = match locked. entry ( type_id) {
60+ std:: collections:: hash_map:: Entry :: Occupied ( e) => e. into_mut ( ) ,
61+ std:: collections:: hash_map:: Entry :: Vacant ( e) => e. insert ( FieldTypeInfo :: resolve ( cls, py) ?) ,
62+ } ;
63+ Ok ( f ( info) )
64+ }
65+
1966#[ pyclass( subclass, frozen, module = "pants.engine.internals.native_engine" ) ]
2067pub struct Field {
2168 pub ( crate ) value : Py < PyAny > ,
@@ -37,13 +84,9 @@ impl Field {
3784 // to None below.
3885 Self :: check_deprecated ( cls, raw_value, & address, py) ?;
3986
87+ let none_is_valid = with_field_type_info ( cls, py, |info| info. none_is_valid_value ) ?;
4088 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- }
89+ Some ( value) if value. extract :: < NoFieldValue > ( ) . is_ok ( ) && !none_is_valid => None ,
4790 rv => rv,
4891 } ;
4992
@@ -97,28 +140,29 @@ impl Field {
97140 address : PyRef < Address > ,
98141 py : Python < ' py > ,
99142 ) -> 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)
143+ with_field_type_info ( cls, py, |info| {
144+ let default = || -> PyResult < Bound < ' _ , PyAny > > {
145+ match & info. default {
146+ Some ( d) => Ok ( d. bind ( py) . clone ( ) ) ,
147+ None => Err ( PyValueError :: new_err ( format ! (
148+ "The `{}` field in target {} must be defined." ,
149+ Self :: cls_alias( cls) ?,
150+ * address,
151+ ) ) ) ,
152+ }
153+ } ;
154+
155+ match raw_value {
156+ Some ( value)
157+ if info. none_is_valid_value && value. extract :: < NoFieldValue > ( ) . is_ok ( ) =>
158+ {
159+ default ( )
160+ }
161+ None if info. none_is_valid_value => Ok ( py. None ( ) . into_bound ( py) ) ,
162+ None => default ( ) ,
163+ Some ( value) => Ok ( value. clone ( ) ) ,
110164 }
111- } ;
112-
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 ( )
117- }
118- None if none_is_valid_value => Ok ( py. None ( ) . into_bound ( py) ) ,
119- None => default ( ) ,
120- Some ( value) => Ok ( value. clone ( ) ) ,
121- }
165+ } ) ?
122166 }
123167
124168 #[ getter]
@@ -227,6 +271,10 @@ impl Field {
227271 address : & Bound < ' _ , Address > ,
228272 py : Python ,
229273 ) -> PyResult < ( ) > {
274+ let is_deprecated = with_field_type_info ( cls, py, |info| info. deprecated ) ?;
275+ if !is_deprecated {
276+ return Ok ( ( ) ) ;
277+ }
230278 if address. borrow ( ) . is_generated_target ( ) {
231279 return Ok ( ( ) ) ;
232280 }
0 commit comments