Repository: kcl-lang/kcl (language: rust)

Issue:
[WIP] Feat : Support Call Kclvm Cli by C API
<!-- Thank you for contributing to KusionStack!

Note: 

1. With pull requests:

    - Open your pull request against "main"
    - Your pull request should have no more than two commits, if not you should squash them.
    - It should pass all tests in the available continuous integration systems such as GitHub Actions.
    - You should add/modify tests to cover your proposed code changes.
    - If your pull request contains a new feature, please document it on the README.

2. Please create an issue first to describe the problem.

    We recommend that link the issue with the PR in the following question.
    For more info, check https://kusionstack.io/docs/governance/contribute/
-->

#### 1. Does this PR affect any open issues?(Y/N) and add issue references (e.g. "fix #123", "re #123".):

- [ ] N
- [x] Y 
re #125 

#### 2. What is the scope of this PR (e.g. component or file name):

<!-- You can add the scope of this change here. 
-->
        KCLVM/kclvm/spec
        KCLVM/kclvm/src/lib.rs

#### 3. Provide a description of the PR(e.g. more details, effects, motivations or doc link):

<!-- You can choose a brief description here -->
- [ ] Affects user behaviors
- [ ] Contains syntax changes
- [ ] Contains variable changes
- [x] Contains experimental features
- [ ] Performance regression: Consumes more CPU
- [ ] Performance regression: Consumes more Memory
- [ ] Other

<!-- You can add more details here.
-->
    Support user to call kclvm_cli by c_api


#### 4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link):

- [x] N
- [ ] Y 

<!-- You can add more details here.

-->

#### 5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:

<!-- You can choose a brief description here -->
- [x] Unit test
- [ ] Integration test
- [ ] Benchmark (add benchmark stats below)
- [ ] Manual test (add detailed scripts or steps below)
- [ ] Other

<!-- You can add more details here.
None
-->
KCLVM/kclvm/src/api_test.rs

#### 6. Release note

<!-- compatibility change, improvement, bugfix, and new feature need a release note -->

Please refer to [Release Notes Language Style Guide](https://kusionstack.io/docs/governance/release-policy/) to write a quality release note.

```release-note
None
```

Index signature schema key type with union of literals not evaluated
## Bug Report

I want to constrain the keys of an index schema against a type alias (union with literals). Using this type for values works as expected, but using this type to constrain the key of index signature schemas does not work.

I know that I can create a "check" in the schema to validate against a set of strings, but then the question arises, how to constrain the check against the type `_foo` without duplicating the definition of `_foo`.

I also played with other types for the key e.g. `int` and kcl throws an error ("invalid index signature key type: 'int'"), which suggests that only strings are allowed, but that would make specifying a type for the key obsolete at all...  

### 1. Minimal reproduce step (Required)

```
type _foo = "foo" | "bar"

schema IndexSignature:
    [..._foo]: any

IndexSignature {
    foo = "foo"
    # this should throw an error
    baz: "baz"
}
```

<!-- a step by step guide for reproducing the bug. -->

### 2. What did you expect to see? (Required)

something like `^ expected str(foo) | str(bar), got str(baz)`

### 3. What did you see instead (Required)

```
foo: foo
baz: baz
```
### 4. What is your KCL components version? (Required)

0.11.0-linux-amd64

<!-- Paste the output of KCL component version -->



Discussion / hints:
****CLA Assistant Lite bot**** All contributors have signed the CLA  ✍️ ✅
I have read the CLA Document and I hereby sign the CLA
## Pull Request Test Coverage Report for [Build 2744948016](https://coveralls.io/builds/51191024)

* **2** of **5523**   **(0.04%)**  changed or added relevant lines in **6** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-7.6%**) to **51.731%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [kclvm/spec/src/main.rs](https://coveralls.io/builds/51191024/source?filename=kclvm%2Fspec%2Fsrc%2Fmain.rs#L2) | 1 | 3 | 33.33%
| [kclvm/spec/src/service/util.rs](https://coveralls.io/builds/51191024/source?filename=kclvm%2Fspec%2Fsrc%2Fservice%2Futil.rs#L2) | 0 | 25 | 0.0%
| [kclvm/spec/src/service/service.rs](https://coveralls.io/builds/51191024/source?filename=kclvm%2Fspec%2Fsrc%2Fservice%2Fservice.rs#L10) | 0 | 109 | 0.0%
| [kclvm/spec/src/service/api.rs](https://coveralls.io/builds/51191024/source?filename=kclvm%2Fspec%2Fsrc%2Fservice%2Fapi.rs#L10) | 0 | 153 | 0.0%
| [kclvm/spec/src/model/gpyrpc.rs](https://coveralls.io/builds/51191024/source?filename=kclvm%2Fspec%2Fsrc%2Fmodel%2Fgpyrpc.rs#L28) | 0 | 5232 | 0.0%
<!-- | **Total:** | **2** | **5523** | **0.04%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/51191024/badge)](https://coveralls.io/builds/51191024) |
| :-- | --: |
| Change from base [Build 2743999583](https://coveralls.io/builds/51186839): |  -7.6% |
| Covered Lines: | 22244 |
| Relevant Lines: | 42999 |

---
##### 💛  - [Coveralls](https://coveralls.io)



Fix patch:
diff --git a/kclvm/sema/src/resolver/config.rs b/kclvm/sema/src/resolver/config.rs
index dcf894ced..bae4a701b 100644
--- a/kclvm/sema/src/resolver/config.rs
+++ b/kclvm/sema/src/resolver/config.rs
@@ -1,4 +1,4 @@
-use std::sync::Arc;
+use std::{collections::HashSet, sync::Arc};
 
 use super::{
     scope::{ScopeKind, ScopeObject, ScopeObjectKind},
@@ -441,14 +441,27 @@ impl<'ctx> Resolver<'_> {
         None
     }
 
-    pub(crate) fn get_config_attr_err_suggestion(
+    #[inline]
+    pub(crate) fn get_config_attr_err_suggestion_from_schema(
         &self,
         attr: &str,
         schema_ty: &SchemaType,
     ) -> (Vec<String>, String) {
+        self.get_config_attr_err_suggestion(attr, schema_ty.attrs.keys())
+    }
+
+    pub(crate) fn get_config_attr_err_suggestion<I, T>(
+        &self,
+        attr: &str,
+        keys: I,
+    ) -> (Vec<String>, String)
+    where
+        T: AsRef<str>,
+        I: IntoIterator<Item = T>,
+    {
         let mut suggestion = String::new();
         // Calculate the closest miss attributes.
-        let suggs = suggestions::provide_suggestions(attr, schema_ty.attrs.keys());
+        let suggs = suggestions::provide_suggestions(attr, keys);
         if suggs.len() > 0 {
             suggestion = format!(", did you mean '{:?}'?", suggs);
         }
@@ -534,63 +547,96 @@ impl<'ctx> Resolver<'_> {
         let runtime_type = kclvm_runtime::schema_runtime_type(&schema_ty.name, &schema_ty.pkgpath);
         match self.ctx.schema_mapping.get(&runtime_type) {
             Some(schema_mapping_ty) => {
-                let schema_ty_ref = schema_mapping_ty.borrow();
-                if schema_ty_ref.get_obj_of_attr(attr).is_none()
-                    && !schema_ty_ref.is_mixin
-                    && schema_ty_ref.index_signature.is_none()
-                {
-                    let (suggs, msg) = self.get_config_attr_err_suggestion(attr, schema_ty);
-                    let mut msgs = vec![Message {
-                        range: range.clone(),
-                        style: Style::LineAndColumn,
-                        message: format!(
-                            "Cannot add member '{}' to schema '{}'{}",
-                            attr, schema_ty_ref.name, msg,
-                        ),
-                        note: None,
-                        suggested_replacement: Some(suggs),
-                    }];
-                    if let Some(attr_range) = attr_range {
-                        msgs.push(Message {
-                            range: attr_range.clone(),
-                            style: Style::LineAndColumn,
-                            message: "config attribute is defined here".to_string(),
-                            note: None,
-                            suggested_replacement: None,
-                        });
-                    }
-                    self.handler.add_error(ErrorKind::CompileError, &msgs);
-                }
+                let schema_ty = schema_mapping_ty.clone();
+                let schema_ty_ref = schema_ty.borrow();
+                self.check_config_attr_without_schema_mapping(
+                    attr,
+                    &schema_ty_ref,
+                    range,
+                    attr_range,
+                );
             }
             None => {
-                if schema_ty.get_obj_of_attr(attr).is_none()
-                    && !schema_ty.is_mixin
-                    && schema_ty.index_signature.is_none()
-                {
-                    let (suggs, msg) = self.get_config_attr_err_suggestion(attr, schema_ty);
-                    let mut msgs = vec![Message {
-                        range: range.clone(),
-                        style: Style::LineAndColumn,
-                        message: format!(
-                            "Cannot add member '{}' to schema '{}'{}",
-                            attr, schema_ty.name, msg,
-                        ),
-                        note: None,
-                        suggested_replacement: Some(suggs),
-                    }];
-                    if let Some(attr_range) = attr_range {
-                        msgs.push(Message {
-                            range: attr_range.clone(),
-                            style: Style::LineAndColumn,
-                            message: "config attribute is defined here".to_string(),
-                            note: None,
-                            suggested_replacement: None,
-                        });
+                self.check_config_attr_without_schema_mapping(attr, schema_ty, range, attr_range);
+            }
+        };
+    }
+
+    fn check_config_attr_without_schema_mapping(
+        &mut self,
+        attr: &str,
+        schema_ty: &SchemaType,
+        range: &Range,
+        attr_range: Option<&Range>,
+    ) {
+        if schema_ty.get_obj_of_attr(attr).is_none()
+            && !schema_ty.is_mixin
+            && schema_ty.index_signature.is_none()
+        {
+            let (suggs, msg) = self.get_config_attr_err_suggestion_from_schema(attr, schema_ty);
+            self.add_config_attr_error(attr, schema_ty, range, attr_range, suggs, msg);
+        }
+        if let Some(index_signature) = &schema_ty.index_signature {
+            // Here we need to check whether the key of the index signature is a string literal type or a string literal union types
+            if !index_signature.any_other {
+                match &index_signature.key_ty.kind {
+                    TypeKind::StrLit(name) => {
+                        if name != attr {
+                            let (suggs, msg) = self.get_config_attr_err_suggestion(attr, &[name]);
+                            self.add_config_attr_error(
+                                attr, schema_ty, range, attr_range, suggs, msg,
+                            );
+                        }
                     }
-                    self.handler.add_error(ErrorKind::CompileError, &msgs);
+                    TypeKind::Union(types) => {
+                        let mut keys: HashSet<String> = HashSet::default();
+                        for ty in types {
+                            if let TypeKind::StrLit(name) = &ty.kind {
+                                keys.insert(name.clone());
+                            }
+                        }
+                        if !keys.contains(attr) {
+                            let (suggs, msg) = self.get_config_attr_err_suggestion(attr, &keys);
+                            self.add_config_attr_error(
+                                attr, schema_ty, range, attr_range, suggs, msg,
+                            );
+                        }
+                    }
+                    _ => {}
                 }
             }
-        };
+        }
+    }
+
+    fn add_config_attr_error(
+        &mut self,
+        attr: &str,
+        schema_ty: &SchemaType,
+        range: &Range,
+        attr_range: Option<&Range>,
+        suggs: Vec<String>,
+        msg: String,
+    ) {
+        let mut msgs = vec![Message {
+            range: range.clone(),
+            style: Style::LineAndColumn,
+            message: format!(
+                "Cannot add member '{}' to schema '{}'{}",
+                attr, schema_ty.name, msg,
+            ),
+            note: None,
+            suggested_replacement: Some(suggs),
+        }];
+        if let Some(attr_range) = attr_range {
+            msgs.push(Message {
+                range: attr_range.clone(),
+                style: Style::LineAndColumn,
+                message: "config attribute is defined here".to_string(),
+                note: None,
+                suggested_replacement: None,
+            });
+        }
+        self.handler.add_error(ErrorKind::CompileError, &msgs);
     }
 
     /// Schema load atr


Test patch:
diff --git a/test/grammar/schema/index_signature/fail_12/main.k b/test/grammar/schema/index_signature/fail_12/main.k
new file mode 100644
index 000000000..a2d4617c0
--- /dev/null
+++ b/test/grammar/schema/index_signature/fail_12/main.k
@@ -0,0 +1,10 @@
+type _foo = "foo" | "bar"
+
+schema IndexSignature:
+    [_foo]: str
+
+IndexSignature {
+    foo = "foo"
+    # this should throw an error
+    baz: "baz"
+}
diff --git a/test/grammar/schema/index_signature/fail_12/stderr.golden b/test/grammar/schema/index_signature/fail_12/stderr.golden
new file mode 100644
index 000000000..6350c32ec
--- /dev/null
+++ b/test/grammar/schema/index_signature/fail_12/stderr.golden
@@ -0,0 +1,1 @@
+Cannot add member 'baz' to schema 'IndexSignature', did you mean '["bar"]'?
\ No newline at end of file