@@ -8,7 +8,7 @@ instead of searching for generated files or guessing from memory.
88Use this workflow:
99
10101 . Read the type error and find the module or type name involved.
11- 2 . Run ` acton sig ` for that module or public name.
11+ 2 . Run ` acton sig ` for the smallest exact module or public name.
12123 . Compare the available attributes and methods with the failing code.
13134 . Fix the source and rebuild.
1414
@@ -94,3 +94,158 @@ override you would pass to `acton build`:
9494``` sh
9595acton sig --dep directory=../directory directory.Contact
9696```
97+
98+ ## Example: selecting attributes on the wrong value
99+
100+ Some type errors mention an "unknown type" even when the value has a
101+ known expected type. This can happen when Acton checks a method body and
102+ collects constraints from dot selections before it has simplified the
103+ whole constraint set.
104+
105+ The important part is not the unknown type name alone. Read the whole
106+ error and look for the known type that must satisfy those constraints.
107+
108+ Suppose one module defines an item type, a context type, and a base class
109+ whose method takes both:
110+
111+ ``` python
112+ # pipeline.act
113+ class WorkItem (object ):
114+ title: str
115+ tags: set[str ]
116+ output_path: str
117+
118+ def __init__ (self , title : str , tags : set[str ], output_path : str ):
119+ self .title = title
120+ self .tags = tags
121+ self .output_path = output_path
122+
123+ class RunContext (object ):
124+ run_id: str
125+ user: str
126+
127+ def __init__ (self , run_id : str , user : str ):
128+ self .run_id = run_id
129+ self .user = user
130+
131+ class Step (object ):
132+ def apply (self , item : WorkItem, ctx : RunContext):
133+ raise NotImplementedError ()
134+ ```
135+
136+ A subclass can omit the local annotations because the inherited method
137+ shape already gives ` item ` and ` ctx ` their expected types:
138+
139+ ``` python
140+ # steps.act
141+ from pipeline import Step
142+
143+ class DraftStep (Step ):
144+ def apply (self , item , ctx ):
145+ print (" processing {ctx.title} " )
146+
147+ if " draft" in ctx.tags:
148+ return ctx.output_path
149+
150+ raise ValueError (" not a draft" )
151+ ```
152+
153+ The method uses ` ctx.title ` , ` ctx.tags ` , and ` ctx.output_path ` , but the
154+ inherited signature says ` ctx ` is a ` pipeline.RunContext ` . The resulting
155+ error has a common "simultaneous constraints" shape:
156+
157+ ``` text
158+ [error]: Cannot satisfy the following simultaneous constraints for the unknown types
159+ +--> steps.act@4:5-10:1
160+ |
161+ 4 | +> def apply(self, item, ctx):
162+ 5 | | print("processing {ctx.title}")
163+ : | ^--------
164+ : | `- The type of the indicated expression (which we call t0) must have an attribute title with type t4; no such type is known.
165+ 6 | |
166+ 7 | | if "draft" in ctx.tags:
167+ : | ^----------^-------
168+ : | | |- The type of the indicated expression (which we call t0) must have an attribute tags with type t1; no such type is known.
169+ : | | `- The type of the indicated expression (which we call t1) must be a subtype of t2
170+ : | |- The type of the indicated expression (inferred to be __builtin__.str) must be a subtype of t3
171+ : | `- The type of the indicated expression (which we call t2) must implement __builtin__.Container[t3]
172+ 8 | | return ctx.output_path
173+ : | ^--------------
174+ : | `- The type of the indicated expression (which we call t0) must have an attribute output_path with type None; no such type is known.
175+ : | The type of the indicated expression (which we call t4) must be a subclass of ?__builtin__.value
176+ 9 | |
177+ 10 | |> raise ValueError("not a draft")
178+ : |
179+ : `- pipeline.RunContext must be a subclass of t0
180+ -----+
181+ ```
182+
183+ Do not read this as "Acton cannot infer the type of ` ctx ` ". In this
184+ example, the inherited method signature already gives ` ctx ` the expected
185+ type ` pipeline.RunContext ` . The unknown ` t0 ` is the type variable that
186+ collected the requirements introduced by the dot selections on ` ctx ` .
187+ The final line says that ` pipeline.RunContext ` must satisfy those
188+ collected requirements.
189+
190+ The next step is to inspect the exact known type named in the error:
191+
192+ ``` sh
193+ acton sig pipeline.RunContext
194+ ```
195+
196+ That is usually better than asking for the whole module, because a large
197+ module can produce a lot of unrelated output. The signature shows the
198+ compiler-visible fields:
199+
200+ ``` python
201+ class RunContext (object , value ):
202+ @ property
203+ run_id : str
204+ @ property
205+ user : str
206+ __init__ : (run_id: str , user: str ) -> None
207+ ```
208+
209+ Now compare the failing selections with the signature. The code asked
210+ for ` title ` , ` tags ` , and ` output_path ` , but ` RunContext ` has only
211+ ` run_id ` and ` user ` . When none of the selected attributes are present,
212+ the code may be selecting on the wrong value. In this example, those
213+ attributes belong to ` WorkItem ` , so inspect that type too:
214+
215+ ``` sh
216+ acton sig pipeline.WorkItem
217+ ```
218+
219+ The fix is not to add a redundant annotation to ` ctx ` . The fix is to use
220+ the value that actually has the fields:
221+
222+ ``` python
223+ class DraftStep (Step ):
224+ def apply (self , item , ctx ):
225+ print (" processing {item.title} " )
226+
227+ if " draft" in item.tags:
228+ return item.output_path
229+
230+ raise ValueError (" not a draft" )
231+ ```
232+
233+ This pattern also appears with dependency types. If the known type comes
234+ from a dependency, still run ` acton sig ` for the exact public name shown
235+ in the error:
236+
237+ ``` sh
238+ acton sig package.module.TypeName
239+ ```
240+
241+ If you are using a local dependency override, pass the same override to
242+ ` acton sig ` that you pass to ` acton build ` :
243+
244+ ``` sh
245+ acton sig --dep package=../package package.module.TypeName
246+ ```
247+
248+ The compiler already sees the dependency signatures when it reports the
249+ error. Running ` acton sig ` does not make those signatures visible to the
250+ compiler; it makes them visible to you, so you can compare the API Acton
251+ is checking against the attributes and methods your code selects.
0 commit comments