-
Notifications
You must be signed in to change notification settings - Fork 13
72 feature error type poisoning #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
72 feature error type poisoning #107
Conversation
- Add TypeInfoKind::Error variant to represent unresolved or invalid types - Update TypeChecker to gracefully handle TypeInfoKind::Error by suppressing cascading errors - Update expression inference (Identifier, Struct, MemberAccess, FunctionCall, etc.) to return Error type on failure instead of None/cascading - Prevent spurious type mismatch errors when one of the types is Error
…soning) - Add tests for TypeInfoKind::Error variant behavior (is_error, Display, substitute, has_unresolved_params) - Add tests for cascading error suppression with undeclared variables, undefined structs/functions - Add tests for error propagation through assignments, member access, method calls, function arguments - Add tests for error types in complex expressions (binary ops, arrays, conditionals) - Register error_type_poisoning_tests module in mod.rs
|
As pointed out in the contributor guide
|
|
@SurfingBowser updated. |
|
@SurfingBowser kindly review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements Error Type Poisoning to prevent cascading errors in the type checker, following rustc's TyKind::Error model. When a type lookup fails, the type checker now uses TypeInfoKind::Error to suppress subsequent type mismatch errors that would otherwise cascade from the initial failure.
Changes:
- Added
TypeInfoKind::Errorvariant with supporting methods (is_error(), display as{unknown}) - Updated type checker to return
Errortypes on lookup failures and suppress cascading type mismatch errors - Added comprehensive test suite covering error poisoning behavior across various scenarios
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tests/src/type_checker/mod.rs | Registered new error type poisoning test module |
| tests/src/type_checker/error_type_poisoning_tests.rs | Comprehensive test suite verifying error type behavior and cascading error suppression |
| core/type-checker/src/type_info.rs | Added Error variant to TypeInfoKind enum with display and helper methods |
| core/type-checker/src/type_checker.rs | Updated type checking logic to use error types for poisoning and suppress cascading errors |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@0xGeorgii done, I Implemented Error variant now takes a String message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the only case where None is returned from this function. Is there a way to handle this better and make the function not return Option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on this? @CMI-James
|
@0xGeorgii kindly review |
|
@CMI-James thank for your contribution, here are several comments: Critical Issues
In type_checker.rs, the let cond_type = self.infer_expression(condition, ctx);
if !cond_type.is_error() {
if !cond_type.is_bool() {
self.errors.push(TypeCheckError::TypeMismatch { ... });
}
} else {
// BUG: Still pushes a TypeMismatch error when cond_type is Error!
self.errors.push(TypeCheckError::TypeMismatch {
expected: TypeInfo::boolean(),
found: TypeInfo::default(), // Also wrong: uses Unit instead of the actual Error type
context: TypeMismatchContext::Condition,
location: ...,
});
}The else branch fires when cond_type.is_error() is true. Error poisoning should suppress the cascading error, not emit one. The entire
Fix: Remove the else block entirely. When cond_type.is_error(), do nothing.
OperatorKind::And | OperatorKind::Or => {
if left_type.is_bool() && right_type.is_bool() {
TypeInfo::boolean()
} else {
self.errors.push(TypeCheckError::InvalidBinaryOperand { ... }); // CASCADE!
TypeInfo::error("Invalid binary operand")
}
}When one operand is Error, is_bool() returns false, so this falls into the error branch and emits InvalidBinaryOperand -- a cascading error. Should
OperatorKind::Add | OperatorKind::Sub | ... => {
if !left_type.is_number() || !right_type.is_number() {
self.errors.push(TypeCheckError::InvalidBinaryOperand { ... }); // CASCADE!
}
// ...
}Error types are not numbers, so this fires when one operand is Error. Same cascade issue.
A type-mismatch check before the operator-specific match: if !left_type.is_error() && !right_type.is_error() && left_type != right_type {
self.errors.push(TypeCheckError::BinaryOperandTypeMismatch { ... });
}But the arithmetic arm ALSO checks: if left_type != right_type {
self.errors.push(TypeCheckError::BinaryOperandTypeMismatch { ... });
}For non-Error mismatched arithmetic types, BOTH checks fire, producing duplicate errors. The original code only had the second check (inside the if
Old code for undefined functions: if let Some(arguments) = &function_call_expression.arguments {
for arg in arguments {
self.infer_expression(&arg.1.borrow(), ctx);
}
}
return None;New code: return TypeInfo::error("Poisoned type from error");The new code skips argument inference entirely. This means errors within arguments of an undefined function call won't be detected. This is a Significant Issues
The error messages are ad-hoc strings like "Poisoned type from error", "Expected array type", "Undefined identifier", "Method not found", etc. These strings are not user-facing (they go into TypeInfoKind::Error(String) for internal tracking), but they're inconsistent in style. Some describe what happened, some describe what was expected. Consider using a more structured approach or at minimum a consistent naming convention.
Tests primarily check that error messages contain certain keywords: But they don't verify:
Stronger tests should count errors or check for absence of specific TypeCheckError variants. Minor Issues
The test file error_type_poisoning_tests.rs wraps its module in #[cfg(test)]. Since it's already under tests/src/, the #[cfg(test)] is redundant.
|
- Fixed cascading errors in Loop, If, and Assert statements - Fixed cascading errors in binary logical and arithmetic operators - Deduplicated BinaryOperandTypeMismatch errors - Restored argument inference for undefined function calls - Fixed empty array literal returning Error type - Removed redundant #[cfg(test)] from test file
|
@0xGeorgii review. |
|
@CMI-James thanks for addressing the previous round. The critical bugs from round 1 (Loop/If/Assert cascading, binary operator guards, duplicate BinaryOperandTypeMismatch, argument inference for undefined functions) are fixed. there are remaining issues: Critical Issues
OperatorKind::Add | OperatorKind::Sub | ... => {
if !left_type.is_error() && !right_type.is_error() {
if !left_type.is_number() || !right_type.is_number() {
self.errors.push(TypeCheckError::InvalidBinaryOperand { ... });
}
}
left_type.clone() // <-- always returns left_type
}When right_type is Error but left_type is valid (e.g. 5 + unknown_var), this returns i32 -- a valid type -- even though the expression is unresolved. Downstream code will treat this as well-typed. Fix: Return TypeInfo::error(...) when either operand is Error, similar to the And/Or arm: if left_type.is_error() || right_type.is_error() {
return if left_type.is_error() { left_type.clone() } else { right_type.clone() };
}
When receiver_type.is_error(), the code returns early without inferring function call arguments. The old code explicitly inferred all arguments even when the receiver type was None (for error Fix: Infer arguments before returning the error type, matching the old None branch behavior.
TypeInfoKind derives PartialEq, so Error("foo") != Error("bar"). If two different error-producing expressions are compared (e.g. in assignment), the is_error() guards prevent issues today, but this Industry standard (rustc, rust-analyzer, chalk) uses either a unit variant Error with no payload, or Error(ErrorGuaranteed) with a zero-sized proof token. The String payload creates:
Recommendation: Use TypeInfoKind::Error (unit variant, no payload). If you want the error message for debugging, it's already captured in self.errors. Significant Issues
Eq/Ne/Lt/Le/Gt/Ge always return TypeInfo::boolean(), even when one or both operands are Error. This diverges from the pattern used by And/Or and arithmetic. The practical impact is low (comparison
The test file checks string contents (error_msg.contains("keyword")) but never verifies the total number of errors. For a feature specifically about suppressing cascading errors, tests should split let errors: Vec<&str> = error_msg.split("; ").collect();
Expression::Uzumaki returns TypeInfo::error("Uzumaki type not inferred") but pushes no TypeCheckError. The user gets no diagnostic. If this path is unreachable (uzumaki type is always pre-set), use
return_type.is_error() in Statement::Return and target_type.is_error() in Statement::VariableDefinition can never be true -- these types come from TypeInfo::new() / TypeInfo::new_with_type_params() Style / Convention Issues
|
0xGeorgii
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please address issues from my comment
Critical fixes:
- Arithmetic ops now propagate Error symmetrically (5+unknown == unknown+5)
- Method call on error receiver now infers arguments before returning
- Changed Error(String) to unit variant Error for proper equality semantics
Significant fixes:
- Comparison operators now propagate Error for consistency
- Uzumaki expression now emits diagnostic when type not inferred
- Added comments explaining defensive guards on declared types
- Tests now verify error counts using split('; ')
Style fixes:
- Fixed double space in Statement::If
- Added #[must_use] reason strings to error() and is_error()
- Added #[cfg(test)] attribute to test module
- Added doc comments to error() and is_error() methods
|
@0xGeorgii review |
Description
Closes #72
Changes proposed
What were you told to do?
I was tasked with implementing Error Type Poisoning to prevent cascading errors in the type checker. This involved adding a
TypeInfoKind::Errorvariant similar torustc'sTyKind::Errorand updating the type checker to gracefully handle these error types.What did I do?
Implemented
TypeInfoKind::ErrorErrorvariant to theTypeInfoKindenum incore/type-checker/src/type_info.rs.Displayimplementation to show{unknown}for error types.is_error()helper method toTypeInfo.substituteandhas_unresolved_paramsto handleErrorvariants neutrally.Updated Type Checker Logic
core/type-checker/src/type_checker.rsto useTypeInfoKind::Errorfor type poisoning.infer_expressionforIdentifier,Structinitialization,MemberAccess,TypeMemberAccess, andFunctionCallto returnTypeInfoKind::Errorupon lookup failure instead ofNoneor continuing with invalid types.Statement::Assign,Statement::Return,Statement::If,Statement::Loop, and others to suppress errors if either the expected or found type isError.Expression::ArrayIndexAccess,Expression::PrefixUnary, andExpression::Binaryto propagateErrortypes and suppress secondary errors (e.g., "expected array type" when the array expression itself failed).Added Tests
tests/src/type_checker/error_type_poisoning_tests.rswith comprehensive tests covering:TypeInfoKind::Errorvariant behavior (is_error(),Display,substitute,has_unresolved_params)Benefits
rustc's error handling model.AI Disclosure
AI tools (Claude) were used to assist with the following parts of this implementation:
AI-generated code (reviewed and tested before submission):
core/type-checker/src/type_info.rs: AddedTypeInfoKind::Errorvariant,is_error()method,Displaymatch arm forError, and handling insubstitute/has_unresolved_paramscore/type-checker/src/type_checker.rs: Updated type mismatch checks to skip errors when either type isError, modifiedinfer_expressionbranches to returnTypeInfoKind::Erroron lookup failurestests/src/type_checker/error_type_poisoning_tests.rs: AI assisted with test structure and test case implementationHuman contributions:
Check List (Check all the applicable boxes)
🚨Please review the contribution guideline for this repository.
Screenshots/Videos