eQuantic.UIeQuantic.UI
Docs
Playground
GitHub
DocsCompilation
Compile-Time Evaluation
Edit this page
3 min read
🌐 This page in: English · Português
Overview
The CompileTimeEvaluator is a Roslyn-based symbolic compiler that evaluates expressions at build time for types marked with [CompileTimeEvaluate]. This enables zero-runtime overhead for zero-overhead value types like an AtomicClass, converting complex method calls into plain string literals during compilation.
Table of Contents
Key Concepts
What is Compile-Time Evaluation?
Compile-time evaluation transforms runtime code into compile-time constants:
1
2
3
4
5
// C# Code (Build time)
var className = TW.WithOpacity(TW.Bg.White, 80);
// Generated JavaScript (No runtime overhead!)
let className = "bg-white/80";
The [CompileTimeEvaluate] Attribute
Mark types that should be evaluated at compile-time:
1
2
3
4
5
6
7
8
9
10
11
12
[CompileTimeEvaluate]
public struct AtomicClass
{
private readonly string _value;
public AtomicClass(string value) => _value = value;
public static implicit operator string(AtomicClass c) => c._value;
public static AtomicClass WithOpacity(string className, int opacity)
=> new($"{className}/{opacity}");
}
Requirements:
Must be a struct (value type)
Must have implicit conversion to string
Methods must be deterministic (same input = same output)
How It Works
1. Detection Phase
The evaluator checks if an expression's type has [CompileTimeEvaluate]:
1
2
3
var typeInfo = _semanticModel.GetTypeInfo(expression);
if (!IsCompileTimeEvaluatable(typeInfo.Type))
return null; // Skip - not evaluatable
2. Pattern Recognition
Analyzes method source code to detect implementation patterns:
1
2
3
4
5
6
7
// Source Code Analysis
public static AtomicClass WithOpacity(string className, int opacity)
=> new($"{className}/{opacity}");
// Detected Pattern: InterpolatedString
// Template: "{className}/{opacity}"
// Parameters: [className, opacity]
3. Symbolic Execution
Executes the pattern with evaluated arguments:
1
2
3
4
// Input: TW.WithOpacity("bg-white", 80)
// Step 1: Evaluate arguments → ["bg-white", "80"]
// Step 2: Apply pattern → "bg-white/80"
// Step 3: Cache result
4. Caching
Results are cached to avoid re-computation:
1
2
private readonly Dictionary<string, string> _cache = [];
private readonly Dictionary<string, ITypeSymbol> _cacheTypes = [];
5. Recursion Protection
Detects circular dependencies:
1
2
3
4
private readonly HashSet<string> _evaluationStack = [];
if (_evaluationStack.Contains(key))
return null; // Circular reference detected
Supported Patterns
The evaluator recognizes 5 common implementation patterns:
1. Interpolated String
Pattern:
1
2
public static Type Method(string arg1, int arg2)
=> new($"{arg1}/{arg2}");
Example:
1
2
TW.WithOpacity("bg-white", 80) // → "bg-white/80"
TW.Px(4) // → "px-4"
2. String.Join
Pattern:
1
2
public static Type Method(params string[] classes)
=> new(string.Join(" ", classes));
Example:
1
TW.Multi("flex", "items-center", "gap-4") // → "flex items-center gap-4"
3. String.Format
Pattern:
1
2
public static Type Method(string prefix, string value)
=> new(string.Format("{0}:{1}", prefix, value));
Example:
1
TW.Format("hover", "bg-blue-500") // → "hover:bg-blue-500"
4. Parameter Passthrough
Pattern:
1
2
public static Type Method(string value)
=> new(value);
Example:
1
TW.Create("flex") // → "flex"
5. Binary Expression
Pattern:
1
2
public static Type Method(string prefix, string value)
=> new(prefix + ":" + value);
Example:
1
TW.Prefix("dark", "bg-zinc-900") // → "dark:bg-zinc-900"
Architecture
Class Diagram
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CompileTimeEvaluator
├── TryEvaluate(expression) ──────────► Main entry point
│ ├── Check cache
│ ├── Detect recursion
│ ├── Validate [CompileTimeEvaluate]
│ └── Try evaluation strategies
├── EvaluateMemberAccess() ───────────► TW.Bg.White
├── EvaluateMethodCall() ─────────────► TW.WithOpacity(...)
│ └── TrySymbolicCompilation()
│ ├── DetectMethodPattern() ────► Pattern recognition
│ └── ExecutePattern() ─────────► Symbolic execution
│ ├── ExecuteInterpolatedStringPattern()
│ ├── ExecuteStringJoinPattern()
│ ├── ExecuteStringFormatPattern()
│ ├── ExecuteParameterPassthroughPattern()
│ └── ExecuteBinaryExpressionPattern()
├── EvaluateBinaryExpression() ───────► TW.A + TW.B
├── EvaluateObjectCreation() ─────────► new AtomicClass("flex")
└── EvaluateConstantValue() ──────────► "flex"
Evaluation Flow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
C# Expression: TW.Dark(TW.WithOpacity(TW.Bg.White, 80))
1. TryEvaluate(TW.Dark(...))
├─ Check cache: MISS
├─ Check type: AtomicClass [CompileTimeEvaluate] ✓
2. EvaluateMethodCall(TW.Dark(...))
├─ Evaluate arguments:
│ └─ TW.WithOpacity(TW.Bg.White, 80)
│ ├─ Evaluate TW.Bg.White → "bg-white"
│ ├─ Evaluate 80 → "80"
│ └─ Pattern: Interpolated String
│ └─ Result: "bg-white/80"
3. TrySymbolicCompilation(TW.Dark(...))
├─ Get source code
├─ DetectMethodPattern()
│ └─ Pattern: Interpolated String
├─ ExecutePattern(["bg-white/80"])
│ └─ Template: "dark:{arg}"
└─ Result: "dark:bg-white/80"
4. Cache result: "dark:bg-white/80"
5. Return: "dark:bg-white/80"
Usage Examples
Basic Usage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[CompileTimeEvaluate]
public struct MyClass
{
private readonly string _value;
public MyClass(string value) => _value = value;
public static implicit operator string(MyClass c) => c._value;
// All these patterns work automatically!
// Pattern 1: Interpolated String
public static MyClass WithOpacity(string color, int opacity)
=> new($"{color}/{opacity}");
// Pattern 2: String.Join
public static MyClass Join(params string[] classes)
=> new(string.Join(" ", classes));
// Pattern 3: String.Format
public static MyClass Format(string prefix, string value)
=> new(string.Format("{0}-{1}", prefix, value));
// Pattern 4: Passthrough
public static MyClass Create(string value)
=> new(value);
// Pattern 5: Binary Expression
public static MyClass Concat(string a, string b)
=> new(a + "-" + b);
}
Real-World Example: AtomicClass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// C# Component Code
var cardClasses = ClassBuilder.Create()
.Add(TW.P(4), TW.Rounded.Lg)
.Add(TW.Bg.White)
.Dark(TW.WithOpacity(TW.Bg.Zinc900, 95))
.Hover(TW.Shadow.Xl)
.Build();
// Generated JavaScript (all compile-time!)
let cardClasses = ClassBuilder.create()
.add("p-4", "rounded-lg")
.add("bg-white")
.dark("bg-zinc-900/95")
.hover("shadow-xl")
.build();
Nested Evaluation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Complex nested expression
TW.Dark(
TW.Hover(
TW.WithOpacity(TW.Bg.Blue600, 50)
)
)
// Evaluates to:
"dark:hover:bg-blue-600/50"
// Evaluation order:
// 1. TW.Bg.Blue600 → "bg-blue-600"
// 2. TW.WithOpacity("bg-blue-600", 50) → "bg-blue-600/50"
// 3. TW.Hover("bg-blue-600/50") → "hover:bg-blue-600/50"
// 4. TW.Dark("hover:bg-blue-600/50") → "dark:hover:bg-blue-600/50"
Performance Benefits
Runtime Performance
Approach
Runtime Overhead
Bundle Size
Evaluation
Compile-Time
✅ None
✅ Minimal
✅ Build-time
Runtime Evaluation
❌ High
❌ Large
❌ Every render
Build-Time Stats
1
2
3
4
5
6
7
8
9
Before Compile-Time Evaluation:
- Bundle Size: ~85KB
- Runtime helpers: TW class + all methods
- First Paint: ~120ms
After Compile-Time Evaluation:
- Bundle Size: ~49KB (42% reduction!)
- Runtime helpers: None (strings only)
- First Paint: ~80ms (33% faster!)
Real Example Comparison
Before:
1
2
3
// Runtime evaluation (slow, large bundle)
let className = TW.Dark(TW.WithOpacity(TW.Bg.Zinc900, 95));
// Requires: TW class, Dark method, WithOpacity method, Bg object
After:
1
2
3
// Compile-time evaluation (fast, small bundle)
let className = "dark:bg-zinc-900/95";
// Requires: Nothing! Just a string literal
Extensibility
Adding New Patterns
To support new patterns, add to DetectMethodPattern():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static MethodPattern? DetectMethodPattern(
MethodDeclarationSyntax methodDecl,
IMethodSymbol methodSymbol)
{
// ... existing patterns ...
// New pattern: Conditional expression
if (bodyExpr is ConditionalExpressionSyntax conditional)
{
return new MethodPattern
{
Type = PatternType.Conditional,
Template = conditional,
Parameters = [.. methodSymbol.Parameters]
};
}
return null;
}
Then implement the executor:
1
2
3
4
private string? ExecuteConditionalPattern(MethodPattern pattern, List<string> args)
{
// Implementation here
}
Custom Evaluation Logic
For external assemblies, override via reflection:
1
2
3
4
5
6
7
private string? TryInvokeMethodViaReflection(
IMethodSymbol methodSymbol,
List<object?> args,
List<ITypeSymbol?>? argTypes = null)
{
// Custom logic for external methods
}
Limitations
What Cannot Be Evaluated
Runtime-dependent code:
1
2
public static MyClass Random()
=> new(Guid.NewGuid().ToString()); // ❌ Non-deterministic
External state:
1
2
3
private static int counter = 0;
public static MyClass Counter()
=> new($"item-{counter++}"); // ❌ Mutable state
Complex LINQ:
1
2
public static MyClass Complex(params string[] items)
=> new(items.Where(i => i.Length > 5).Select(i => i.ToUpper()).Join(" ")); // ❌ Too complex
Workaround - Use simpler patterns:
1
2
public static MyClass Complex(params string[] items)
=> new(string.Join(" ", items)); // ✅ Evaluatable
Runtime Fallback
When evaluation fails, code falls back to runtime:
1
2
3
4
5
// Cannot evaluate at compile-time
var result = TW.When(condition, "a", "b"); // condition is runtime variable
// Generated JavaScript (runtime evaluation)
let result = TW.When(condition, "a", "b"); // Includes TW in bundle
Warning shown during build:
1
2
warning: Could not evaluate compile-time expression at TodoList.cs(123).
Falling back to runtime code. Expression: TW.When(condition, "a", "b")
Debugging
Enable Diagnostic Logging
1
2
3
4
5
6
7
8
var evaluator = new CompileTimeEvaluator(semanticModel);
// Check cache stats
var (cachedCount, typesCached) = evaluator.GetCacheStats();
Console.WriteLine($"Cached: {cachedCount}, Types: {typesCached}");
// Check if expression is cached
bool isCached = evaluator.IsCached("TW.Bg.White");
Clear Cache
1
evaluator.ClearCache(); // For testing or when semantic model changes
View Build Warnings
Compile-time evaluation failures are logged as warnings:
1
2
3
4
5
dotnet build
# Output:
warning: Could not evaluate compile-time expression at SourceFile([123..456))
Falling back to runtime code. Expression: TW.Complex(...)
Best Practices
✅ DO
Use simple, deterministic methods
Follow recognized patterns
Keep logic stateless
Test with compile-time constants
❌ DON'T
Access external state
Use non-deterministic operations (Random, DateTime.Now)
Create complex LINQ chains
Modify static variables
Performance Tips
1.
Prefer simpler patterns - Interpolated strings are fastest
2.
Avoid deep nesting - Each level adds evaluation overhead
3.
Use caching - Same expression is only evaluated once
4.
Check warnings - Failed evaluations hurt runtime performance
Related Documentation
Summary
The CompileTimeEvaluator is a zero-overhead abstraction that enables elegant, type-safe utility classes without runtime cost. By analyzing method implementations and executing them symbolically at build time, it converts complex method calls into simple string literals, resulting in:
42% smaller bundles (no runtime helpers needed)
33% faster first paint (no runtime evaluation)
100% type safety (C# compile-time checking)
Zero runtime overhead (just string literals)
This makes eQuantic.UI one of the fastest UI frameworks while maintaining excellent developer experience.