Evaluate `obj.field` — dispatch on receiver type. */
| 845 | |
| 846 | /* Evaluate `obj.field` — dispatch on receiver type. */ |
| 847 | static const CBMType *eval_field_access(JavaLSPContext *ctx, TSNode node) { |
| 848 | TSNode obj = ts_node_child_by_field_name(node, "object", 6); |
| 849 | TSNode field = ts_node_child_by_field_name(node, "field", 5); |
| 850 | if (ts_node_is_null(field)) |
| 851 | return cbm_type_unknown(); |
| 852 | char *fname = java_node_text(ctx, field); |
| 853 | if (!fname) |
| 854 | return cbm_type_unknown(); |
| 855 | |
| 856 | /* Special-case: System.out, System.err — common static fields. */ |
| 857 | if (!ts_node_is_null(obj) && strcmp(ts_node_type(obj), "identifier") == 0) { |
| 858 | char *oname = java_node_text(ctx, obj); |
| 859 | if (oname && strcmp(oname, "System") == 0) { |
| 860 | if (strcmp(fname, "out") == 0 || strcmp(fname, "err") == 0) { |
| 861 | return cbm_type_named(ctx->arena, "java.io.PrintStream"); |
| 862 | } |
| 863 | if (strcmp(fname, "in") == 0) { |
| 864 | return cbm_type_named(ctx->arena, "java.io.InputStream"); |
| 865 | } |
| 866 | } |
| 867 | } |
| 868 | |
| 869 | /* Special-case: `length` on array types is `int`. */ |
| 870 | const CBMType *recv = ts_node_is_null(obj) ? cbm_type_unknown() : java_eval_expr_type(ctx, obj); |
| 871 | if (recv && recv->kind == CBM_TYPE_SLICE && strcmp(fname, "length") == 0) { |
| 872 | return cbm_type_builtin(ctx->arena, "int"); |
| 873 | } |
| 874 | |
| 875 | /* For `this.field`, try the scope chain first — process_field_decl |
| 876 | * binds class fields into the enclosing scope, which has accurate type |
| 877 | * info even when the registered class lacks field metadata. */ |
| 878 | if (!ts_node_is_null(obj) && strcmp(ts_node_type(obj), "this") == 0) { |
| 879 | const CBMType *scope_t = cbm_scope_lookup(ctx->current_scope, fname); |
| 880 | if (scope_t && !cbm_type_is_unknown(scope_t)) |
| 881 | return scope_t; |
| 882 | } |
| 883 | |
| 884 | const CBMType *res = resolve_member_type(ctx, recv, fname); |
| 885 | if (res && !cbm_type_is_unknown(res)) |
| 886 | return res; |
| 887 | |
| 888 | /* Last resort: if receiver is `this` or an unresolved identifier, fall |
| 889 | * back to scope chain by name. */ |
| 890 | if (!ts_node_is_null(obj)) { |
| 891 | const CBMType *scope_t = cbm_scope_lookup(ctx->current_scope, fname); |
| 892 | if (scope_t && !cbm_type_is_unknown(scope_t)) |
| 893 | return scope_t; |
| 894 | } |
| 895 | return cbm_type_unknown(); |
| 896 | } |
| 897 | |
| 898 | /* Lookup a field's type on a class, walking the parent chain. */ |
| 899 | const CBMType *java_lookup_field_type(JavaLSPContext *ctx, const char *class_qn, |
no test coverage detected