Typy
Rozdział rozdziela składnię typu, rozpoznawanie jego nazwy i obecną kontrolę
zgodności. Przyjęcie typu przez parser lub check nie potwierdza reprezentacji
ani poprawnego wykonania na wszystkich targetach.
Składnia
typeReference
: atomicType (PIPE atomicType)*
;
atomicType
: communicationType
| mapType
| genericType
| arrayType
| baseType
;
baseType
: INT_TYPE
| FLOAT_TYPE
| BOOL_TYPE
| TEXT_TYPE
| SYMBOL_TYPE
| TIME_TYPE
| DURATION_TYPE
| IDENTIFIER // user-defined types + sized types (u8, u32, f64 ...)
;
communicationType
: CHAN LT typeReference GT
| QUEUE LT typeReference GT
| STREAM LT typeReference GT
;
mapType
: MAP_TYPE LT typeReference COMMA typeReference GT
;
genericType
: IDENTIFIER LT typeReference (COMMA typeReference)* GT
;
arrayType
: LBRACKET typeReference RBRACKET
;
T01 — postacie typu
| Postać | Struktura zapisu |
|---|---|
Int, Float, Bool, Text, Symbol, Time, Duration |
Typy z osobnymi tokenami gramatyki. |
Reading, u8, Uuid |
Typ nazwany zapisany identyfikatorem. |
[T] |
Typ tablicowy z jednym typem elementu. |
Map<K,V> |
Dwa argumenty: typ klucza i typ wartości. |
Name<T,...> |
Co najmniej jeden argument typu generycznego. |
A | B |
Unia kolejnych typów atomowych. |
chan<T>, queue<T>, stream<T> |
Rodzaj komunikacji i typ elementu. |
Unie mogą występować wewnątrz argumentów, np. List<Int | Text>.
Pusta lista argumentów generycznych nie jest dozwolona. Gramatyka typu nie daje
ogólnego grupowania nawiasami ani dowolnej nazwy kwalifikowanej alias::Typ.
Składnia Name<T> nie ustanawia deklaracji własnych parametrów generycznych.
Rozpoznawanie nazw
T02 — nazwy znane analizatorowi
SemanticAnalyzer ma osobny katalog nazw: typy tokenowe, Null, typy liczbowe
u8–u64, i8–i64, f32, f64, nazwy Map, List, Option, Result,
Bytes, Decimal, Uuid, Json oraz nazwy rodzajów komunikacji.
Obecność w tym katalogu nie jest pełnym kontraktem operacji każdego z tych typów.
Pozostałe nazwy są szukane w zakresie typów zadeklarowanych lub importowanych.
Nieznany typ nazwany daje E0021 poziomu warning, a nie błąd blokujący.
W próbie parametr Missing został przyjęty z tym ostrzeżeniem.
Dla GenericTypeNode walidator przechodzi po argumentach, ale celowo nie sprawdza
samej nazwy konstruktora. Unknown<Int> przechodzi próbę bez diagnostyk.
Nie dowodzi to istnienia typu Unknown ani obsługi dowolnej liczby argumentów.
T03 — wielkość liter
Gramatyka rozpoznaje tokeny typów w ich zapisanej postaci. Katalog typów
wbudowanych i porównanie nazw w TypeEquals nie rozróżniają wielkości liter,
podczas gdy zakresy deklaracji i modułów mają własne reguły rozwiązywania nazw.
Nie można z tego wyprowadzić jednej ogólnej zasady niewrażliwości języka
na wielkość liter; zalecane zapisy pozostają takie jak w tabeli.
Obecna zgodność typów
T04 — porównanie struktury typu
SemanticAnalyzer.TypeEquals wykonuje następujące porównania:
- typ nazwany: zgodność nazwy bez rozróżniania wielkości liter;
- typ generyczny: nazwa, liczba argumentów i ich zgodność według pozycji;
- typ komunikacyjny: ten sam rodzaj i zgodny typ elementu;
- tablica: zgodny typ elementu;
- mapa: zgodny typ klucza i wartości;
- unia: ta sama liczba składników i zgodność składników według pozycji.
Nie jest to ogólny algorytm podtypowania ani dowód równoważności zbiorów unii.
Nie normalizuje w tej funkcji kolejności składników ani nie usuwa ich powtórzeń.
Nie ustanawia też niejawnej zamiany chan<Int> na stream<Int>, [Int] na
List<Int> lub dowolnego składnika na unię.
T05 — jawna adnotacja let
Dla let z jawnym typem analizator porównuje go z typem inicjalizatora,
jeżeli udało się ten typ wywnioskować. Niezgodność daje błąd E0044.
Brak wywnioskowanego typu nie jest w tej gałęzi dowodem zgodności.
Próby pokazały:
| Zapis | Wynik check |
|---|---|
let value: Int = 1 |
przyjęte |
let value: Int = "text" |
błąd E0044 |
let value: Float = 1 |
błąd E0044 |
let value: u8 = 300 |
błąd E0044 |
let value: Int | Text = 1 |
błąd E0044 |
let value: [Int] = [1,2] |
błąd E0044 |
let value: List<Int> = [1,2] |
przyjęte |
Błąd dla u8 = 300 nie jest dowodem kontroli zakresu 0–255: w tej próbie
nie zgadza się typ adnotacji z typem literału. Podobnie błąd przypisania do unii
nie ustanawia kompletnego kontraktu konstruowania i zawężania wartości unijnych.
Literał listy jest obecnie wnioskowany jako List<...>; typ tablicowy i generyczny
nie są w tym porównaniu utożsamiane.
Null, liczby i ograniczenia dziedzinowe
T06 — Null
Null nie ma osobnego tokenu typu, lecz jest rozpoznawany przez katalog semantyczny.
Parametr Text | Null przechodzi próbę bez diagnostyk. To nie dowodzi automatycznej
inicjalizacji null, konwersji, bezpiecznego dostępu ani pełnego zawężania unii.
Storage ma osobną walidację nullable i nie należy przenosić jej
szczególnych gwarancji na wszystkie konstrukcje języka.
T07 — liczby i reprezentacja
Rozmiar i reprezentacja Int, Float, Time i innych kategorii nie wynikają
z samego tokenu. Nie utożsamiamy ich automatycznie z typami Go, C++ czy JavaScript.
Nazwy liczb o określonym rozmiarze są rozpoznawane, ale samo sprawdzanie nazwy
nie potwierdza pełnej kontroli zakresów, przepełnień, zaokrągleń ani konwersji.
T08 — mapa i pozostałe ograniczenia
Map<[Int],Text> przechodzi próbę jako typ parametru. Rekurencyjna walidacja
nazw nie ustanawia więc ograniczeń porównywalności lub haszowania klucza we
wszystkich wykonawcach. Podobnie składniowa obecność Option<T> i Result<T,...>
nie jest dowodem kompletnego modelu obsługi błędów i wartości opcjonalnych.
Ograniczenia danego zastosowania, np. przenośne typy storage, są sprawdzane osobno.
Sprawdzone przykłady
Pary są sprawdzane przez rzeczywiste CLI w osobnych projektach. Potwierdzają przyjęcie lub odrzucenie źródła, nie zachowanie wygenerowanego programu.
Przykład poprawny
struct Reading {
value: Float
tags: [Text]
}
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Przykład błędny
struct Reading { value: chan<> }
Powód: Typ komunikacyjny wymaga typu elementu.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Przykład: Zgodność jawnej adnotacji let
Poprawny zapis
task Main() @server { let value: Int = 1 }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
task Main() @server { let value: Int = "text" }
Powód: Wnioskowany Text nie jest zgodny z deklarowanym Int.
Kontrola: przykład odrzucony przez check; kody: E0044.
Pełny zakres weryfikacji przykładów.
Przykład: Typ elementów tablicy
Poprawny zapis
task Main() @server { let value: List<Int> = [1,2] }
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
task Main() @server { let value: [Text] = [1,2] }
Powód: Literał [1,2] jest wnioskowany jako List
Kontrola: przykład odrzucony przez check; kody: E0044.
Pełny zakres weryfikacji przykładów.
Przykład: Składnia unii
Poprawny zapis
task Main(value: Int | Text) @server {}
Kontrola: przykład przyjęty przez check. Nie jest to test wykonania.
Błędny zapis
task Main(value: Int |) @server {}
Powód: Po separatorze unii wymagany jest kolejny typ.
Kontrola: przykład odrzucony przez check; kody: P0001.
Pełny zakres weryfikacji przykładów.
Jawne luki
- Pełna walidacja istnienia i liczby argumentów konstruktora typu generycznego.
- Jednolite diagnozowanie nierozwiązanych typów i przypadków bez ustalonego typu wyrażenia.
- Kompletny kontrakt konwersji, podtypowania oraz tworzenia i zawężania unii.
- Spójność typów tablicowych, literałów list i reprezentacji w generatorach.
- Zakresy liczb, przepełnienia, zaokrąglenia i konwersje na wszystkich targetach.
- Ograniczenia typów kluczy map i pełne kontrakty kolekcji.
- Reprezentacja, własność i serializacja typów zewnętrznych oraz wartości złożonych.
Nie uzupełniamy tych reguł zachowaniem pojedynczego języka hosta.
Podstawa w repozytorium
grammar/JunexisLang.g4: reguły przytoczone powyżej.compiler/JunexisLang.Compiler.Core/Semantics/SemanticAnalyzer.cs: katalogs_builtinTypes,ValidateTypeRef,TypeEquals,InferExprTypeoraz sprawdzanielet.compiler/JunexisLang.Compiler.Tests/Semantics/SemanticAnalyzerTests.cs: testy semantyki i typowania wyrażeń.- Rozdziały komunikacji, modułów, storage i extern: dodatkowe kontrole właściwe danej granicy.