From e00717647ecd235d5467a78038af67922224f9ed Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sun, 4 Oct 2026 23:13:21 +0100 Subject: [PATCH] A property that does not resolve is named in the error, in a condition too (#442) `if (o.foo)` on an object with no `foo` reported only "the condition has no value", and `const v = o.foo` only "can't resolve name: v", neither naming the property. The failure happens in the run that infers the function's return type (allowPartialResolve), where mlirGenPropertyAccessExpression gives success with no value and no message, so that a sibling method an object literal has not registered yet can resolve in a later run; the compile then fails in that run on what the caller makes of no value, and the run that would have said "Can't resolve property" is never reached. The access now emits "Can't resolve property 'foo' of type {x:s32}" in that run as well, still returning success. A message is shown only if the module fails to compile, and each retry cycle starts with none, so a property that resolves later costs nothing. The caller's own message ("the condition has no value") follows it. Part 1 of the issue (a.map(f).map(g) without the default library) was fixed by #443. Closes #442 Co-Authored-By: Claude Opus 5.5 --- tslang/lib/TypeScript/MLIRGenAccessCall.cpp | 6 ++++++ tslang/test/tester/CMakeLists.txt | 13 +++++++++++++ tslang/test/tester/property-error/conditional.ts | 7 +++++++ tslang/test/tester/property-error/const.ts | 8 ++++++++ tslang/test/tester/property-error/if.ts | 7 +++++++ tslang/test/tester/property-error/while.ts | 7 +++++++ 6 files changed, 48 insertions(+) create mode 100644 tslang/test/tester/property-error/conditional.ts create mode 100644 tslang/test/tester/property-error/const.ts create mode 100644 tslang/test/tester/property-error/if.ts create mode 100644 tslang/test/tester/property-error/while.ts diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index 479890491..e77c08c66 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -424,8 +424,14 @@ namespace mlirgen // whole discovery run over that; let the caller treat this as "unknown for // now" (same idiom as mlirGenCallExpression's `!result.value && // genContext.allowPartialResolve` case above). + // + // It still says why: a message is shown only if the module then fails to compile, + // and one that does may fail in this very run, before any other run reaches the + // access - a function's return type is inferred this way. Without it the only error + // was what the caller made of no value, "the condition has no value" in an `if` (#442). if (genContext.dummyRun || genContext.allowPartialResolve) { + emitError(location, "Can't resolve property '") << name << "' of type " << to_print(objectValue.getType()); return mlir::success(); } diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index 30583de1b..47627f105 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -3207,6 +3207,19 @@ foreach(builtin_cast_case ${builtin_cast_cases}) FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") endforeach() +# A property that does not resolve is named in the error, in a condition too: the run inferring the +# function's return type gave no value and no message, and an `if` reported only "the condition has +# no value" (#442). +foreach(property_error_name if const while conditional) + add_test(NAME test-compile-property-error-${property_error_name} + COMMAND $ --emit=obj --no-default-lib -mm=none + "${PROJECT_SOURCE_DIR}/test/tester/property-error/${property_error_name}.ts" + -o "${CMAKE_CURRENT_BINARY_DIR}/property-error-${property_error_name}.obj") + set_tests_properties(test-compile-property-error-${property_error_name} + PROPERTIES PASS_REGULAR_EXPRESSION "Can't resolve property 'foo' of type [{]x:s32[}]" + FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed") +endforeach() + # The shared-component tier under the other two models. A shared library records the model # it was built under, so both halves of a pair are built with the same flag - which is what # these run. The file pairs are the default model's, verbatim. diff --git a/tslang/test/tester/property-error/conditional.ts b/tslang/test/tester/property-error/conditional.ts new file mode 100644 index 000000000..0e576c28f --- /dev/null +++ b/tslang/test/tester/property-error/conditional.ts @@ -0,0 +1,7 @@ +// A property that does not resolve is named in the error, wherever it is read (#442): during the +// run that infers main's return type the access gave no value and said nothing, so an `if` said +// only "the condition has no value". +function main() { + const o = { x: 1 }; + print(o.foo ? 1 : 2); +} diff --git a/tslang/test/tester/property-error/const.ts b/tslang/test/tester/property-error/const.ts new file mode 100644 index 000000000..7efec61b2 --- /dev/null +++ b/tslang/test/tester/property-error/const.ts @@ -0,0 +1,8 @@ +// A property that does not resolve is named in the error, wherever it is read (#442): during the +// run that infers main's return type the access gave no value and said nothing, so an `if` said +// only "the condition has no value". +function main() { + const o = { x: 1 }; + const v = o.foo; + print(v); +} diff --git a/tslang/test/tester/property-error/if.ts b/tslang/test/tester/property-error/if.ts new file mode 100644 index 000000000..7abd3e3c7 --- /dev/null +++ b/tslang/test/tester/property-error/if.ts @@ -0,0 +1,7 @@ +// A property that does not resolve is named in the error, wherever it is read (#442): during the +// run that infers main's return type the access gave no value and said nothing, so an `if` said +// only "the condition has no value". +function main() { + const o = { x: 1 }; + if (o.foo) print(1); +} diff --git a/tslang/test/tester/property-error/while.ts b/tslang/test/tester/property-error/while.ts new file mode 100644 index 000000000..9f9fb9ebe --- /dev/null +++ b/tslang/test/tester/property-error/while.ts @@ -0,0 +1,7 @@ +// A property that does not resolve is named in the error, wherever it is read (#442): during the +// run that infers main's return type the access gave no value and said nothing, so an `if` said +// only "the condition has no value". +function main() { + const o = { x: 1 }; + while (o.foo) print(1); +}