diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-26 00:55:08 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-26 00:55:08 +0000 |
| commit | 4a20a08c80d1170561db9a268e8116ad311714b9 (patch) | |
| tree | 093344f67a28d86809de89eda8b067f17665844a | |
| parent | ca08979910e5a234a2423e4448ad0e284bf4f508 (diff) | |
Fix for #126
| -rw-r--r-- | src/codegen/codegen_decl.c | 13 | ||||
| -rw-r--r-- | src/parser/parser.h | 2 | ||||
| -rw-r--r-- | src/parser/parser_core.c | 72 | ||||
| -rw-r--r-- | src/parser/parser_decl.c | 4 | ||||
| -rw-r--r-- | src/parser/parser_struct.c | 2 | ||||
| -rw-r--r-- | src/parser/parser_utils.c | 59 | ||||
| -rw-r--r-- | tests/generics/test_generic_string_literal.zc | 41 |
7 files changed, 130 insertions, 63 deletions
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index e5d73a0..d59511d 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -22,7 +22,8 @@ static void emit_freestanding_preamble(FILE *out) "uint64_t\n", out); fputs("#define F32 float\n#define F64 double\n", out); - fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out); + fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", + out); fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", " "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", " "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", " @@ -64,9 +65,11 @@ void emit_preamble(ParserContext *ctx, FILE *out) fputs("#define ZC_AUTO auto\n", out); fputs("#define ZC_CAST(T, x) static_cast<T>(x)\n", out); // C++ _z_str via overloads - fputs("inline const char* _z_bool_str(bool b) { return b ? \"true\" : \"false\"; }\n", out); + fputs("inline const char* _z_bool_str(bool b) { return b ? \"true\" : \"false\"; }\n", + out); fputs("inline const char* _z_str(bool) { return \"%s\"; }\n", out); - fputs("inline const char* _z_arg(bool b) { return _z_bool_str(b); }\n", out); + fputs("inline const char* _z_arg(bool b) { return _z_bool_str(b); }\n", + out); fputs("template<typename T> inline T _z_arg(T x) { return x; }\n", out); fputs("inline const char* _z_str(char) { return \"%c\"; }\n", out); fputs("inline const char* _z_str(int) { return \"%d\"; }\n", out); @@ -87,7 +90,9 @@ void emit_preamble(ParserContext *ctx, FILE *out) fputs("#define ZC_AUTO __auto_type\n", out); fputs("#define ZC_CAST(T, x) ((T)(x))\n", out); fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out); - fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : \"false\"; }\n", out); + fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : " + "\"false\"; }\n", + out); fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", " "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", " "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", " diff --git a/src/parser/parser.h b/src/parser/parser.h index 7353570..eb8682d 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -309,7 +309,7 @@ Type *find_symbol_type_info(ParserContext *ctx, const char *n); char *find_symbol_type(ParserContext *ctx, const char *n); ZenSymbol *find_symbol_entry(ParserContext *ctx, const char *n); ZenSymbol *find_symbol_in_all(ParserContext *ctx, - const char *n); // LSP flat lookup + const char *n); char *find_similar_symbol(ParserContext *ctx, const char *name); // Function registry diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index 1245a55..d575693 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -689,19 +689,25 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * } else if (strcmp(ft, "double") == 0) { - sprintf(assign, "let _f_%s = (*j).get_float(\"%s\").unwrap_or(0.0);\n", fn, fn); + sprintf(assign, "let _f_%s = (*j).get_float(\"%s\").unwrap_or(0.0);\n", fn, + fn); } else if (strcmp(ft, "bool") == 0) { - sprintf(assign, "let _f_%s = (*j).get_bool(\"%s\").unwrap_or(false);\n", fn, fn); + sprintf(assign, "let _f_%s = (*j).get_bool(\"%s\").unwrap_or(false);\n", fn, + fn); } else if (strcmp(ft, "char*") == 0) { - sprintf(assign, "let _f_%s = (*j).get_string(\"%s\").unwrap_or(\"\");\n", fn, fn); + sprintf(assign, "let _f_%s = (*j).get_string(\"%s\").unwrap_or(\"\");\n", + fn, fn); } else if (strcmp(ft, "String") == 0) { - sprintf(assign, "let _f_%s = String::new((*j).get_string(\"%s\").unwrap_or(\"\"));\n", fn, fn); + sprintf( + assign, + "let _f_%s = String::new((*j).get_string(\"%s\").unwrap_or(\"\"));\n", + fn, fn); } else if (ft && strstr(ft, "Vec") && strstr(ft, "String")) { @@ -710,7 +716,8 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * { vec_fields[vec_field_count++] = fn; } - sprintf(assign, + sprintf( + assign, "let _f_%s = Vec<String>::new();\n" "let _arr_%s = (*j).get_array(\"%s\");\n" "if _arr_%s.is_some() {\n" @@ -726,16 +733,18 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * " }\n" " }\n" "}\n", - fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn); + fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, + fn, fn, fn, fn, fn); } else { // Nested struct: call NestedType::from_json recursively sprintf(assign, - "let _opt_%s = (*j).get(\"%s\");\n" - "let _f_%s: %s;\n" - "if _opt_%s.is_some() { _f_%s = %s::from_json(_opt_%s.unwrap()).unwrap(); }\n", - fn, fn, fn, ft, fn, fn, ft, fn); + "let _opt_%s = (*j).get(\"%s\");\n" + "let _f_%s: %s;\n" + "if _opt_%s.is_some() { _f_%s = " + "%s::from_json(_opt_%s.unwrap()).unwrap(); }\n", + fn, fn, fn, ft, fn, fn, ft, fn); } strcat(body, assign); } @@ -755,7 +764,10 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * { if (f->type == NODE_FIELD) { - if (!first) strcat(body, ", "); + if (!first) + { + strcat(body, ", "); + } char init[128]; // Check if this is a Vec<String> field - clone it to avoid double-free int is_vec_field = 0; @@ -783,9 +795,8 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * strcat(body, " }); "); code = xmalloc(8192 + 1024); - sprintf(code, - "impl %s { fn from_json(j: JsonValue*) -> Result<%s> { %s } }", - name, name, body); + sprintf(code, "impl %s { fn from_json(j: JsonValue*) -> Result<%s> { %s } }", name, + name, body); } else if (0 == strcmp(trait, "ToJson")) { @@ -817,11 +828,13 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * if (strcmp(ft, "int") == 0) { - sprintf(set_call, "_obj.set(\"%s\", JsonValue::number((double)self.%s));\n", fn, fn); + sprintf(set_call, "_obj.set(\"%s\", JsonValue::number((double)self.%s));\n", + fn, fn); } else if (strcmp(ft, "double") == 0) { - sprintf(set_call, "_obj.set(\"%s\", JsonValue::number(self.%s));\n", fn, fn); + sprintf(set_call, "_obj.set(\"%s\", JsonValue::number(self.%s));\n", fn, + fn); } else if (strcmp(ft, "bool") == 0) { @@ -829,28 +842,29 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * } else if (strcmp(ft, "char*") == 0) { - sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s));\n", fn, fn); + sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s));\n", fn, + fn); } else if (strcmp(ft, "String") == 0) { - sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s.c_str()));\n", fn, fn); + sprintf(set_call, "_obj.set(\"%s\", JsonValue::string(self.%s.c_str()));\n", + fn, fn); } else if (ft && strstr(ft, "Vec") && strstr(ft, "String")) { sprintf(set_call, - "let _arr_%s = JsonValue::array();\n" - "for let _i_%s: usize = 0; _i_%s < self.%s.length(); _i_%s = _i_%s + 1 {\n" - " _arr_%s.push(JsonValue::string(self.%s.get(_i_%s).c_str()));\n" - "}\n" - "_obj.set(\"%s\", _arr_%s);\n", - fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn); + "let _arr_%s = JsonValue::array();\n" + "for let _i_%s: usize = 0; _i_%s < self.%s.length(); _i_%s = _i_%s " + "+ 1 {\n" + " _arr_%s.push(JsonValue::string(self.%s.get(_i_%s).c_str()));\n" + "}\n" + "_obj.set(\"%s\", _arr_%s);\n", + fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn); } else { // Nested struct: call to_json recursively - sprintf(set_call, - "_obj.set(\"%s\", self.%s.to_json());\n", - fn, fn); + sprintf(set_call, "_obj.set(\"%s\", self.%s.to_json());\n", fn, fn); } strcat(body, set_call); } @@ -860,9 +874,7 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * strcat(body, "return _obj;"); code = xmalloc(8192 + 1024); - sprintf(code, - "impl %s { fn to_json(self) -> JsonValue { %s } }", - name, body); + sprintf(code, "impl %s { fn to_json(self) -> JsonValue { %s } }", name, body); } if (code) diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index 48077e1..ab1516e 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -56,13 +56,13 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) strcat(buf, ","); } strcat(buf, s); - + // Check for shadowing if (is_known_generic(ctx, s)) { zpanic_at(gt, "Generic parameter '%s' shadows an existing generic parameter", s); } - + free(s); if (lexer_peek(l).type == TOK_COMMA) diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index b7dec32..84450ba 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -142,7 +142,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) n_node->trait.methods = methods; n_node->trait.generic_params = generic_params; n_node->trait.generic_param_count = generic_count; - + if (generic_count > 0) { ctx->known_generics_count -= generic_count; diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 86e1b50..f889561 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -858,6 +858,7 @@ ASTNode *copy_fields(ASTNode *fields) n->next = copy_fields(fields->next); return n; } + char *replace_in_string(const char *src, const char *old_w, const char *new_w) { if (!src || !old_w || !new_w) @@ -921,20 +922,23 @@ char *replace_in_string(const char *src, const char *old_w, const char *new_w) int newWlen = strlen(new_w); int oldWlen = strlen(old_w); + // Pass 1: Count replacements + int in_string = 0; for (i = 0; src[i] != '\0'; i++) { - if (strstr(&src[i], old_w) == &src[i]) + if (src[i] == '"' && (i == 0 || src[i - 1] != '\\')) { - // Check boundaries to ensure we match whole words only - int valid = 1; + in_string = !in_string; + } - // Check preceding character + if (!in_string && strstr(&src[i], old_w) == &src[i]) + { + // Check boundaries + int valid = 1; if (i > 0 && is_ident_char(src[i - 1])) { valid = 0; } - - // Check following character if (valid && is_ident_char(src[i + oldWlen])) { valid = 0; @@ -951,42 +955,47 @@ char *replace_in_string(const char *src, const char *old_w, const char *new_w) // Allocate result buffer result = (char *)xmalloc(i + cnt * (newWlen - oldWlen) + 1); - i = 0; - while (*src) + // Pass 2: Perform replacement + int j = 0; + in_string = 0; + + int src_idx = 0; + + while (src[src_idx] != '\0') { - if (strstr(src, old_w) == src) + if (src[src_idx] == '"' && (src_idx == 0 || src[src_idx - 1] != '\\')) { - int valid = 1; + in_string = !in_string; + } - // Check boundary relative to the *new* result buffer built so far - if (i > 0 && is_ident_char(result[i - 1])) + int replaced = 0; + if (!in_string && strstr(&src[src_idx], old_w) == &src[src_idx]) + { + int valid = 1; + if (src_idx > 0 && is_ident_char(src[src_idx - 1])) { valid = 0; } - - // Check boundary relative to the *original* source string - if (valid && is_ident_char(src[oldWlen])) + if (valid && is_ident_char(src[src_idx + oldWlen])) { valid = 0; } if (valid) { - strcpy(&result[i], new_w); - i += newWlen; - src += oldWlen; - } - else - { - result[i++] = *src++; + strcpy(&result[j], new_w); + j += newWlen; + src_idx += oldWlen; + replaced = 1; } } - else + + if (!replaced) { - result[i++] = *src++; + result[j++] = src[src_idx++]; } } - result[i] = '\0'; + result[j] = '\0'; return result; } diff --git a/tests/generics/test_generic_string_literal.zc b/tests/generics/test_generic_string_literal.zc new file mode 100644 index 0000000..5ad2cc3 --- /dev/null +++ b/tests/generics/test_generic_string_literal.zc @@ -0,0 +1,41 @@ + +struct StringChecker<T> { + dummy: T; +} + +impl StringChecker<T> { + fn check(self) -> bool { + // The generic parameter T should NOT be replaced inside these string literals + // If T is int, this should NOT become "int is a generic param" + let s1: char* = "T is a generic param"; + if (strcmp(s1, "T is a generic param") != 0) { + printf("Failed s1: '%s' != 'T is a generic param'\n", s1); + return false; + } + + // Check for T inside words + let s2: char* = "This contains T inside"; + if (strcmp(s2, "This contains T inside") != 0) { + printf("Failed s2: '%s' != 'This contains T inside'\n", s2); + return false; + } + + // Check for T at end + let s3: char* = "Ends with T"; + if (strcmp(s3, "Ends with T") != 0) { + printf("Failed s3: '%s' != 'Ends with T'\n", s3); + return false; + } + + return true; + } +} + +test "generic_string_substitution" { + // Instantiate with 'int' so that if substitution happened, 'T' would become 'int' + let checker = StringChecker<int>{ dummy: 0 }; + + if (!checker.check()) { + exit(1); + } +} |
