Yes, that is an unfortunate consequence of stringified type forms. There is the hack of inspecting the call stack, but that shouldn’t really be a best practice. But I would like to also point out that this isn’t really an issue with the AST based type form approach itself, but rather with using stringified type forms. The issues you point out already exist. But yeah, they will become more widespread as we introduce more features that need stringified type forms.
To my knowledge there also isn’t really a great solution for this that doesn’t involve new syntax. The structure function simply cannot know the locals/globals of the calling function if they aren’t explicitly passed in. That doesn’t really depend on how we’re evaluating type forms or how we’re storing some kind of intermediate representation. We have to either ask the user to write structure(..., "SomeTypeForm", locals=locals(), globals=globals()), introduce an entirely new syntax feature like structure(..., type: SomeTypeForm) that does the namespace capturing for us or use a workaround like an intermediate type alias or introspecting stack frames.
@DavidCEllis I think I’m missing how exactly your approach differs from the one discussed before. Yes, your implementation stores strings rather than ASTs and you’re using a new class for namespace lookups instead of just plain dicts, but how does that meaningfully change the behaviour in these regards? For the runtime evaluation that Tin outlines, how would your eval_type be passed the EvaluationContext argument that references the caller’s locals and globals, if the user isn’t passing them in?