1) i'm primarily interested in data types, rather than objects, as i believe most runtime errors are the result of unexpected changes to data types.
2) i'm suggesting a type system that does away with hand annotations and rather builds them on the fly by keeping assignment history and using that to determine when statistically anomalous types appear.
However, while Hindley-Milner will find all the type errors in your program, it won't necessarily report those errors in the most logical places. That's why it's generally still the norm to annotate functions with type annotations except in the most trivial cases: knowing that a function will take a string and return a number is useful information not just for the compiler, but also the programmer.
To come back to your first point, one common solution here is the "parse, don't validate" mantra for loading data. This is the idea that when accepting unknown data (e.g. from a file or API), you first parse the data and convert it into a representation that enforces the invariants your program believes about that data. For example, that names can never be longer than twenty characters, or that ages are always positive numbers. By doing this parsing as soon as the data enters your program, you immediately handle unexpected changes to the data formats - if data changes such that it can no longer be parsed, this is an immediate error, rather than something that will only show deep in your application.
With something like Pydantic (alternatives are available), you can describe what your data should look like in terms of types, and use Pydantic to do the parsing for you.
here's an example, taking the second example on this page https://docs.pydantic.dev/latest/ with the external_data dictionary, if i add a collision like:
'wine': 9, 'wine': 1,
it seems to just silently pick the last one, which seems error prone to me. maybe there's a flag to fix it, but i couldn't find any documentation or switches that controlled this behavior. :/
with respect to the approach i mentioned above, it's more of an idea (or probably a research topic) to do quantitative analysis on the behavior of programs while they execute, rather than static analysis of the code that defines them. the latter is obviously preferable, assuming the approach can capture all of the types of analyses of interest, the quantitative approach may be more suitable for large or legacy systems where detecting changes in data shape (even if of valid type) and branching behavior may be useful for tracking down causes of, or detecting conditions for, errant behaviors.