├── .github └── workflows │ ├── ci.yml │ ├── clean.yml │ └── coveralls.yml ├── .gitignore ├── .scalafmt.conf ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties ├── metals.sbt ├── plugins.sbt └── project │ ├── metals.sbt │ └── project │ └── metals.sbt └── src ├── main ├── java │ └── co │ │ └── blocke │ │ └── scala_reflection │ │ └── Ignore.java └── scala │ └── co.blocke.scala_reflection │ ├── Clazzes.scala │ ├── Liftables.scala │ ├── Package.scala │ ├── RType.scala │ ├── RTypeRef.scala │ ├── reflect │ ├── ExtractorRegistry.scala │ ├── ReflectOnClass.scala │ ├── ReflectOnField.scala │ ├── ReflectOnType.scala │ ├── TypeExtractor.scala │ ├── TypeSymbolMapper.scala │ ├── TypeSymbolMapper2.scala │ ├── extractors │ │ ├── ArrayExtractor.scala │ │ ├── EitherExtractor.scala │ │ ├── JavaCollectionExtractor.scala │ │ ├── JavaMapExtractor.scala │ │ ├── JavaOptionalExtractor.scala │ │ ├── MapExtractor.scala │ │ ├── OptionExtractor.scala │ │ ├── SeqExtractor.scala │ │ ├── TryExtractor.scala │ │ └── TupleExtractor.scala │ └── rtypeRefs │ │ ├── AliasRef.scala │ │ ├── AppliedRef.scala │ │ ├── ArrayRef.scala │ │ ├── ClassRef.scala │ │ ├── CollectionRef.scala │ │ ├── EnumRef.scala │ │ ├── FieldInfoRef.scala │ │ ├── IterableRef.scala │ │ ├── JavaCollectionRef.scala │ │ ├── JavaMapRef.scala │ │ ├── LeftRightRef.scala │ │ ├── MapRef.scala │ │ ├── NeoTypeRef.scala │ │ ├── NetRef.scala │ │ ├── ObjectRef.scala │ │ ├── OptionRef.scala │ │ ├── PrimitiveRef.scala │ │ ├── Sealable.scala │ │ ├── SelfRefRef.scala │ │ ├── SeqRef.scala │ │ ├── SetRef.scala │ │ ├── TimeRef.scala │ │ ├── TraitRef.scala │ │ ├── TryRef.scala │ │ ├── TupleRef.scala │ │ ├── TypeMemberRef.scala │ │ ├── TypeSymbolRef.scala │ │ ├── UnkownRef.scala │ │ └── WildcardRef.scala │ ├── rtypes │ ├── AliasRType.scala │ ├── AppliedRType.scala │ ├── ArrayRType.scala │ ├── ClassRTypes.scala │ ├── CollectionRType.scala │ ├── EitherRType.scala │ ├── EnumRType.scala │ ├── FieldInfo.scala │ ├── IntersectionRType.scala │ ├── IterableRType.scala │ ├── JavaCollectionRType.scala │ ├── JavaMapRType.scala │ ├── LeftRightRType.scala │ ├── MapRType.scala │ ├── NeoTypeRType.scala │ ├── NetRTypes.scala │ ├── ObjectRType.scala │ ├── OptionRType.scala │ ├── PrimitiveRTypes.scala │ ├── SelfRefRType.scala │ ├── SeqRType.scala │ ├── SetRType.scala │ ├── TimeRTypes.scala │ ├── TraitRType.scala │ ├── TryRType.scala │ ├── TupleRType.scala │ ├── TypeMemberRType.scala │ ├── TypeSymbolRType.scala │ ├── UnionRType.scala │ ├── UnkownRType.scala │ └── WildcardRType.scala │ ├── run │ ├── Main.scalax │ └── Sample.scalax │ └── util │ ├── AdjustClassName.scala │ ├── JsonObjectBuilder.scala │ ├── Pretty.scala │ └── TypedName.scala └── test ├── java └── co │ └── blocke │ └── reflect │ ├── Change.java │ ├── ClassAnno.java │ ├── DBKey.java │ ├── FieldAnno.java │ ├── Hey.java │ ├── JavaCollections.java │ ├── JavaEnum.java │ ├── JavaOption1.java │ ├── JavaOption2.java │ ├── JavaOption3.java │ ├── JavaParam.java │ ├── JavaParamHolder.java │ ├── JavaParamHolder2.java │ ├── ParamAnno.java │ ├── Person.java │ └── You.java └── scala └── co.blocke.scala_reflection ├── Basic.scala ├── Collections.scala ├── Enums.scala ├── Inheritance.scala ├── Java.scala ├── LeftRight.scala ├── Options.scala ├── Parameters.scala └── models ├── BasicModels.scala ├── CollectionModels.scala ├── EnumModels.scala ├── InheritanceModels.scala ├── LeftRightModels.scala ├── OptionModels.scala └── ParameterModels.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**', '!update/**', '!pr/**'] 13 | push: 14 | branches: ['**', '!update/**', '!pr/**'] 15 | tags: [v*] 16 | 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | 21 | concurrency: 22 | group: ${{ github.workflow }} @ ${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build: 27 | name: Test 28 | strategy: 29 | matrix: 30 | os: [ubuntu-latest, windows-latest] 31 | scala: [3.5.2] 32 | java: [temurin@21] 33 | runs-on: ${{ matrix.os }} 34 | timeout-minutes: 60 35 | steps: 36 | - name: Ignore line ending differences in git 37 | if: contains(runner.os, 'windows') 38 | shell: bash 39 | run: bash -c 'git config --global core.autocrlf false' 40 | 41 | - uses: actions/setup-java@v4 42 | with: 43 | distribution: temurin 44 | java-version: 21 45 | 46 | - uses: actions/checkout@v4 47 | 48 | - uses: coursier/setup-action@v1 49 | 50 | - name: Install sbt 51 | shell: bash 52 | run: | 53 | cs install sbt 54 | echo "$HOME/.local/share/coursier/bin" >> $GITHUB_PATH 55 | sbt sbtVersion 56 | 57 | - name: Check that workflows are up to date 58 | shell: bash 59 | run: sbt githubWorkflowCheck 60 | 61 | - name: Test 62 | shell: bash 63 | run: sbt '++ ${{ matrix.scala }}' test 64 | 65 | - name: Make target directories 66 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 67 | shell: bash 68 | run: mkdir -p target project/target 69 | 70 | - name: Compress target directories 71 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 72 | shell: bash 73 | run: tar cf targets.tar target project/target 74 | 75 | - name: Upload target directories 76 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 77 | uses: actions/upload-artifact@v4 78 | with: 79 | name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }} 80 | path: targets.tar 81 | 82 | publish: 83 | name: Publish Artifacts 84 | needs: [build] 85 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 86 | strategy: 87 | matrix: 88 | os: [ubuntu-latest] 89 | java: [temurin@21] 90 | runs-on: ${{ matrix.os }} 91 | steps: 92 | - name: Ignore line ending differences in git 93 | if: contains(runner.os, 'windows') 94 | run: bash -c 'git config --global core.autocrlf false' 95 | 96 | - uses: actions/setup-java@v4 97 | with: 98 | distribution: temurin 99 | java-version: 21 100 | 101 | - uses: actions/checkout@v4 102 | 103 | - uses: coursier/setup-action@v1 104 | 105 | - name: Install sbt 106 | run: | 107 | cs install sbt 108 | echo "$HOME/.local/share/coursier/bin" >> $GITHUB_PATH 109 | sbt sbtVersion 110 | 111 | - name: Download target directories (3.5.2) 112 | uses: actions/download-artifact@v4 113 | with: 114 | name: target-${{ matrix.os }}-${{ matrix.java }}-3.5.2 115 | 116 | - name: Inflate target directories (3.5.2) 117 | run: | 118 | tar xf targets.tar 119 | rm targets.tar 120 | 121 | - name: Import signing key 122 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' 123 | env: 124 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 125 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 126 | run: echo $PGP_SECRET | base64 -d -i - | gpg --import 127 | 128 | - name: Import signing key and strip passphrase 129 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' 130 | env: 131 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 132 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 133 | run: | 134 | echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg 135 | echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg 136 | (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) 137 | 138 | - env: 139 | CI_SNAPSHOT_RELEASE: +publishSigned 140 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 141 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 142 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 143 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 144 | run: sbt ci-release 145 | 146 | dependency-submission: 147 | name: Submit Dependencies 148 | if: github.event.repository.fork == false && github.event_name != 'pull_request' 149 | strategy: 150 | matrix: 151 | os: [ubuntu-22.04] 152 | java: [temurin@21] 153 | runs-on: ${{ matrix.os }} 154 | steps: 155 | - name: Ignore line ending differences in git 156 | if: contains(runner.os, 'windows') 157 | run: bash -c 'git config --global core.autocrlf false' 158 | 159 | - uses: actions/setup-java@v4 160 | with: 161 | distribution: temurin 162 | java-version: 21 163 | 164 | - uses: actions/checkout@v4 165 | 166 | - uses: coursier/setup-action@v1 167 | 168 | - name: Install sbt 169 | run: | 170 | cs install sbt 171 | echo "$HOME/.local/share/coursier/bin" >> $GITHUB_PATH 172 | sbt sbtVersion 173 | 174 | - name: Submit Dependencies 175 | uses: scalacenter/sbt-dependency-submission@v2 176 | with: 177 | configs-ignore: test scala-tool scala-doc-tool test-internal 178 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /.github/workflows/coveralls.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Coveralls Publish 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - main 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | 11 | 12 | concurrency: 13 | group: ${{ github.workflow }} @ ${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | name: Build and Test 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, windows-latest] 22 | scala: [3.5.2] 23 | java: [zulu@21] 24 | runs-on: ${{ matrix.os }} 25 | timeout-minutes: 60 26 | steps: 27 | - name: Ignore line ending differences in git 28 | if: contains(runner.os, 'windows') 29 | shell: bash 30 | run: git config --global core.autocrlf false 31 | 32 | - name: Checkout current branch (full) 33 | uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | 37 | - uses: coursier/setup-action@v1 38 | 39 | - uses: actions/setup-java@v4 40 | with: 41 | distribution: temurin 42 | java-version: 21 43 | 44 | - name: Install sbt 45 | shell: bash 46 | run: | 47 | cs install sbt 48 | echo "$HOME/.local/share/coursier/bin" >> $GITHUB_PATH 49 | sbt sbtVersion 50 | 51 | - name: sbt update 52 | if: matrix.java == 'zulu@21' && steps.setup-java-zulu-21.outputs.cache-hit == 'false' 53 | shell: bash 54 | run: sbt +update 55 | 56 | - name: Build project 57 | run: sbt '++ ${{ matrix.scala }}' coverage test 58 | 59 | - run: sbt '++ ${{ matrix.scala }}' coverageReport 60 | 61 | - name: Coveralls 62 | uses: coverallsapp/github-action@v2 63 | with: 64 | fail-on-error: false # Fail if upload fails 65 | coverage-threshold: 85 # Minimum coverage percentage required 66 | env: 67 | COVERALLS_GIT_BRANCH: ${{ github.ref_name }} # Dynamically sets the branch name 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .vscode/ 3 | .metals/ 4 | .bloop/ 5 | .idea/ 6 | .idea_modules/ 7 | target/ 8 | reports/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/project/target/ 13 | project/plugins/project 14 | java_pid*.hprof 15 | *.swp 16 | tags 17 | .ensime 18 | *.iml 19 | .history 20 | *~ 21 | wip/ 22 | .dotty-ide* 23 | _site/ 24 | .bsp/ 25 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.7.14 2 | project.git = true 3 | maxColumn = 256 4 | runner.dialect = Scala3 5 | 6 | align.preset = some 7 | 8 | rewrite.rules = [Imports, RedundantBraces, SortModifiers] 9 | rewrite.imports.sort = scalastyle 10 | rewrite.redundantBraces.stringInterpolation = true 11 | 12 | rewrite.scala3.convertToNewSyntax = true 13 | rewrite.scala3.removeOptionalBraces = false 14 | 15 | docstrings.blankFirstLine = no 16 | docstrings.style = SpaceAsterisk 17 | docstrings.wrap = no 18 | 19 | newlines.sometimesBeforeColonInMethodReturnType = true 20 | lineEndings=unix 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Greg Zoller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import org.typelevel.sbt.gha.JavaSpec.Distribution 2 | import xerial.sbt.Sonatype.sonatypeCentralHost 3 | import scoverage.ScoverageKeys._ 4 | 5 | disablePlugins(TypelevelMimaPlugin) // we use our own versioning for now via gitflow-packager 6 | 7 | lazy val isCI = sys.env.get("CI").contains("true") 8 | 9 | inThisBuild(List( 10 | organization := "co.blocke", 11 | homepage := Some(url("https://github.com/gzoller/scala-reflection")), 12 | licenses := List("MIT" -> url("https://opensource.org/licenses/MIT")), 13 | developers := List( 14 | Developer( 15 | "gzoller", 16 | "Greg Zoller", 17 | "gzoller@blocke.co", 18 | url("http://www.blocke.co") 19 | ), 20 | Developer( 21 | "pjfanning", 22 | "PJ Fanning", 23 | "", 24 | url("https://github.com/pjfanning") 25 | ) 26 | ) 27 | )) 28 | 29 | name := "scala-reflection" 30 | ThisBuild / versionScheme := Some("semver-spec") 31 | ThisBuild / scmInfo := Some( 32 | ScmInfo( 33 | url("https://github.com/gzoller/scala-reflection"), 34 | "scm:git@github.com:gzoller/scala-reflection.git" 35 | ) 36 | ) 37 | ThisBuild / organization := "co.blocke" 38 | ThisBuild / scalaVersion := "3.5.2" 39 | ThisBuild / githubWorkflowScalaVersions := Seq("3.5.2") 40 | 41 | lazy val root = project 42 | .in(file(".")) 43 | .settings(settings) 44 | .settings( 45 | name := "reflection_library", 46 | Compile / packageBin / mappings += { 47 | (baseDirectory.value / "plugin.properties") -> "plugin.properties" 48 | }, 49 | doc := null, // disable dottydoc for now 50 | Compile / doc / sources := Seq(), 51 | //sources in (Compile, doc) := Seq(), 52 | Test / parallelExecution := false, 53 | scalafmtOnCompile := !isCI, 54 | libraryDependencies ++= Seq( 55 | "org.scala-lang" %% "scala3-compiler" % scalaVersion.value, 56 | "org.scala-lang" %% "scala3-tasty-inspector" % scalaVersion.value, 57 | "org.scala-lang" %% "scala3-staging" % scalaVersion.value, 58 | "io.github.kitlangton" %% "neotype" % "0.2.4", 59 | "org.scalameta" %% "munit" % "1.0.0-M9" % Test, 60 | "co.blocke" %% "listzipper" % "0.1.6" % Test 61 | ) 62 | ) 63 | 64 | ThisBuild / sonatypeCredentialHost := sonatypeCentralHost 65 | ThisBuild / githubWorkflowPublishTargetBranches := Seq( 66 | RefPredicate.Equals(Ref.Branch("main")), 67 | RefPredicate.StartsWith(Ref.Tag("v")) 68 | ) 69 | 70 | ThisBuild / githubWorkflowScalaVersions := Seq("3.5.2") 71 | ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec(Distribution.Temurin, "21")) 72 | ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest", "windows-latest") 73 | 74 | ThisBuild / githubWorkflowJobSetup := Seq( 75 | WorkflowStep.Run( 76 | name = Some("Ignore line ending differences in git"), 77 | cond = Some("contains(runner.os, 'windows')"), 78 | commands = List("bash -c 'git config --global core.autocrlf false'") 79 | ), 80 | WorkflowStep.Use( 81 | UseRef.Public("actions", "setup-java", "v4"), 82 | params = Map( 83 | "distribution" -> "temurin", 84 | "java-version" -> "21" 85 | ) 86 | ), 87 | WorkflowStep.Use( 88 | UseRef.Public("actions", "checkout", "v4") 89 | ), 90 | WorkflowStep.Use( 91 | UseRef.Public("coursier", "setup-action", "v1") 92 | ), 93 | WorkflowStep.Run( 94 | name = Some("Install sbt"), 95 | commands = List( 96 | "cs install sbt", 97 | "echo \"$HOME/.local/share/coursier/bin\" >> $GITHUB_PATH", 98 | "sbt sbtVersion" 99 | ) 100 | ) 101 | ) 102 | 103 | ThisBuild / githubWorkflowPublish := Seq( 104 | WorkflowStep.Sbt( 105 | List("ci-release"), 106 | env = Map( 107 | "PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}", 108 | "PGP_SECRET" -> "${{ secrets.PGP_SECRET }}", 109 | "SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}", 110 | "SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}", 111 | "CI_SNAPSHOT_RELEASE" -> "+publishSigned" 112 | ) 113 | ) 114 | ) 115 | 116 | //========================== 117 | // Settings 118 | //========================== 119 | lazy val settings = Seq( 120 | javacOptions ++= Seq("--release", "21"), 121 | scalacOptions ++= compilerOptions, 122 | scalacOptions := scalacOptions.value.distinct, 123 | testFrameworks += new TestFramework("munit.Framework") 124 | ) 125 | 126 | lazy val compilerOptions = Seq( 127 | "-unchecked", 128 | "-feature", 129 | "-language:implicitConversions", 130 | "-deprecation", 131 | // "-explain", 132 | // "-experimental", 133 | "-encoding", 134 | "utf8" 135 | ) 136 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.1 2 | -------------------------------------------------------------------------------- /project/metals.sbt: -------------------------------------------------------------------------------- 1 | // format: off 2 | // DO NOT EDIT! This file is auto-generated. 3 | 4 | // This file enables sbt-bloop to create bloop config files. 5 | 6 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.8") 7 | 8 | // format: on 9 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // native packager and scoverage are conflicting... 2 | libraryDependencySchemes ++= Seq( 3 | "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always 4 | ) 5 | //addSbtPlugin("co.blocke" % "gitflow-packager" % "0.1.37") 6 | //addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") 7 | //addSbtPlugin("org.typelevel" % "sbt-typelevel-sonatype-ci-release" % "0.5.0-M6") 8 | //addSbtPlugin("org.typelevel" % "sbt-typelevel-github-actions" % "0.7.1") 9 | //addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.7.2") 10 | //addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12") 11 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 12 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.7.7") 13 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") 14 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12") 15 | addSbtPlugin("co.blocke" % "gitflow-packager" % "0.1.37") -------------------------------------------------------------------------------- /project/project/metals.sbt: -------------------------------------------------------------------------------- 1 | // format: off 2 | // DO NOT EDIT! This file is auto-generated. 3 | 4 | // This file enables sbt-bloop to create bloop config files. 5 | 6 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.8") 7 | 8 | // format: on 9 | -------------------------------------------------------------------------------- /project/project/project/metals.sbt: -------------------------------------------------------------------------------- 1 | // format: off 2 | // DO NOT EDIT! This file is auto-generated. 3 | 4 | // This file enables sbt-bloop to create bloop config files. 5 | 6 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "2.0.8") 7 | 8 | // format: on 9 | -------------------------------------------------------------------------------- /src/main/java/co/blocke/scala_reflection/Ignore.java: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** Annotation for Java getters or setters to tell reflector to ignore the decorated property for 6 | * the purposes of reflection. 7 | */ 8 | 9 | @Inherited 10 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface Ignore { 13 | } -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/Clazzes.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | object Clazzes: 4 | 5 | // ======= Scala Class Names ======= 6 | val BIG_DECIMAL_CLASS = "scala.math.BigDecimal" 7 | val BIG_INT_CLASS = "scala.math.BigInt" 8 | val BOOLEAN_CLASS = "scala.Boolean" 9 | val BYTE_CLASS = "scala.Byte" 10 | val CHAR_CLASS = "scala.Char" 11 | val DOUBLE_CLASS = "scala.Double" 12 | val FLOAT_CLASS = "scala.Float" 13 | val INT_CLASS = "scala.Int" 14 | val LONG_CLASS = "scala.Long" 15 | val SHORT_CLASS = "scala.Short" 16 | val STRING_CLASS = "java.lang.String" 17 | val ANY_CLASS = "scala.Any" 18 | val ANYVAL_CLASS = "scala.AnyVal" 19 | val ENUMERATION_CLASS = "scala.Enumeration.Value" 20 | 21 | // ======= Java Class Names ======= 22 | val JBIG_DECIMAL_CLASS = "java.math.BigDecimal" 23 | val JBIG_INTEGER_CLASS = "java.math.BigInteger" 24 | val JBOOLEAN_CLASS = "java.lang.Boolean" 25 | val JBYTE_CLASS = "java.lang.Byte" 26 | val JCHARACTER_CLASS = "java.lang.Character" 27 | val JDOUBLE_CLASS = "java.lang.Double" 28 | val JFLOAT_CLASS = "java.lang.Float" 29 | val JINTEGER_CLASS = "java.lang.Integer" 30 | val JLONG_CLASS = "java.lang.Long" 31 | val JSHORT_CLASS = "java.lang.Short" 32 | val JOBJECT_CLASS = "java.lang.Object" 33 | val JNUMBER_CLASS = "java.lang.Number" 34 | val UUID_CLASS = "java.util.UUID" 35 | val URL_CLASS = "java.net.URL" 36 | val URI_CLASS = "java.net.URI" 37 | 38 | // ======= Time Class Names ======= 39 | val DURATION_CLASS = "java.time.Duration" 40 | val INSTANT_CLASS = "java.time.Instant" 41 | val LOCALDATE_CLASS = "java.time.LocalDate" 42 | val LOCALDATETIME_CLASS = "java.time.LocalDateTime" 43 | val LOCALTIME_CLASS = "java.time.LocalTime" 44 | val MONTHDAY_CLASS = "java.time.MonthDay" 45 | val OFFSETDATETIME_CLASS = "java.time.OffsetDateTime" 46 | val OFFSETTIME_CLASS = "java.time.OffsetTime" 47 | val PERIOD_CLASS = "java.time.Period" 48 | val YEAR_CLASS = "java.time.Year" 49 | val YEARMONTH_CLASS = "java.time.YearMonth" 50 | val ZONEDDATETIME_CLASS = "java.time.ZonedDateTime" 51 | val ZONEID_CLASS = "java.time.ZoneId" 52 | val ZONEOFFSET_CLASS = "java.time.ZoneOffset" 53 | 54 | /** Union and intersection types are only denoted internally as scala.Matchable, which is so generic it isn't helpful. 55 | * These are synthetic marker class names to differentiate a union type. Following Scala naming conventions, but be clear--there 56 | * is (currently) no such class as "scala.Union" or "scala.Intersection" and bad things will happen if you try to 57 | * instantiate one! 58 | */ 59 | val UNION_CLASS = "scala.Union" 60 | val INTERSECTION_CLASS = "scala.Intersection" 61 | 62 | // ======= Class Instances ======= 63 | val ArrayClazz = Class.forName("scala.Array") 64 | val EitherClazz = Class.forName("scala.util.Either") 65 | val JCollectionClazz = classOf[java.util.Collection[?]] 66 | val JMapClazz = classOf[java.util.Map[?, ?]] 67 | val MapClazz = Class.forName("scala.collection.Map") 68 | val OptionalClazz = Class.forName("java.util.Optional") 69 | val OptionClazz = Class.forName("scala.Option") 70 | val SeqClazz = Class.forName("scala.collection.Seq") 71 | val SetClazz = Class.forName("scala.collection.Set") 72 | val IterableClazz = Class.forName("scala.collection.Iterable") 73 | val TryClazz = Class.forName("scala.util.Try") 74 | 75 | val AnyClazz = classOf[Any] 76 | 77 | // Class Ops 78 | extension (c: Class[?]) 79 | def =:=(other: Class[?]): Boolean = c == other 80 | def <:<(other: Class[?]): Boolean = other.isAssignableFrom(c) 81 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/Liftables.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import scala.quoted.* 4 | 5 | object Liftables: 6 | 7 | given TypeSymbolToExpr: ToExpr[TypeSymbol] with { 8 | def apply(x: TypeSymbol)(using Quotes): Expr[TypeSymbol] = 9 | val s = Expr(x.toString) 10 | '{ $s.asInstanceOf[TypeSymbol] } 11 | } 12 | 13 | given OptTypeSymbolToExpr: ToExpr[Option[TypeSymbol]] with { 14 | def apply(x: Option[TypeSymbol])(using Quotes): Expr[Option[TypeSymbol]] = 15 | val opt = Expr(x.map(_.toString)) 16 | '{ $opt.asInstanceOf[Option[TypeSymbol]] } 17 | } 18 | 19 | given ListTypeSymbolToExpr: ToExpr[List[TypeSymbol]] with { 20 | def apply(x: List[TypeSymbol])(using Quotes): Expr[List[TypeSymbol]] = 21 | val syms = Expr(x.map(_.toString)) 22 | '{ $syms.asInstanceOf[List[TypeSymbol]] } 23 | } 24 | 25 | given TypedNameToExpr: ToExpr[TypedName] with { 26 | def apply(x: TypedName)(using Quotes): Expr[TypedName] = 27 | val s = Expr(x.toString) 28 | '{ $s.asInstanceOf[TypedName] } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/Package.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | // import scala.quoted.Quotes 4 | import scala.quoted.{Expr, Quotes, Type} 5 | 6 | /** Mnemonic symbol for a type--typically a paramaterized type, e.g. Foo[T], where T is the symbol */ 7 | opaque type TypeSymbol = String 8 | given listOstring2TypeSymbol: Conversion[List[String], List[TypeSymbol]] with 9 | def apply(x: List[String]): List[TypeSymbol] = x.asInstanceOf[List[TypeSymbol]] 10 | 11 | /** Class name also having any type parameters listed (if any) */ 12 | opaque type TypedName = String 13 | given string2typedname: Conversion[String, TypedName] with 14 | def apply(x: String): TypedName = x.asInstanceOf[TypedName] 15 | 16 | class ReflectException(msg: String) extends Exception(msg) 17 | 18 | val NONE = "" 19 | 20 | val NEOTYPE = "neotype.Newtype" 21 | 22 | def annoSymToString(quotes: Quotes)(terms: List[quotes.reflect.Term]): Map[String, String] = 23 | import quotes.reflect.* 24 | terms.collect { 25 | case NamedArg(argName, Literal(BooleanConstant(argValue))) => (argName -> argValue.toString) 26 | case NamedArg(argName, Literal(ByteConstant(argValue))) => (argName -> argValue.toString) 27 | case NamedArg(argName, Literal(ShortConstant(argValue))) => (argName -> argValue.toString) 28 | case NamedArg(argName, Literal(CharConstant(argValue))) => (argName -> argValue.toString) 29 | case NamedArg(argName, Literal(IntConstant(argValue))) => (argName -> argValue.toString) 30 | case NamedArg(argName, Literal(LongConstant(argValue))) => (argName -> argValue.toString) 31 | case NamedArg(argName, Literal(FloatConstant(argValue))) => (argName -> argValue.toString) 32 | case NamedArg(argName, Literal(DoubleConstant(argValue))) => (argName -> argValue.toString) 33 | case NamedArg(argName, Literal(StringConstant(argValue))) => (argName -> argValue) 34 | }.toMap 35 | 36 | // Handy "break"-able fold iterator. Return Right to stop/complete. 37 | def foldLeftBreak[A, B](as: List[A])(init: B)(op: (A, B) => Either[B, B]): B = 38 | as match { 39 | case Nil => init 40 | case a :: as => 41 | op(a, init) match { 42 | case Right(b) => b 43 | case Left(b) => foldLeftBreak(as)(b)(op) 44 | } 45 | } 46 | 47 | def ofOption[T](xs: Option[Expr[T]])(using Type[T])(using q: Quotes): Expr[Option[T]] = 48 | if xs.isEmpty then '{ None } 49 | else '{ Some(${ xs.get }) } 50 | 51 | enum Language { 52 | case Scala, Java 53 | } 54 | 55 | // Extract Annotations from symbol 56 | def getAnnotationParamNames(quotes: Quotes)(annotationType: quotes.reflect.TypeRepr): List[String] = 57 | val ctorParams = 58 | annotationType.typeSymbol.primaryConstructor.paramSymss.flatten 59 | ctorParams.map(_.name) 60 | 61 | def extractAnnotationInfo(quotes: Quotes)(annos: List[quotes.reflect.Term]): Map[String, Map[String, String]] = 62 | import quotes.reflect.* 63 | 64 | def getAnnotationParamNames(annotType: TypeRepr): List[String] = 65 | annotType.typeSymbol.primaryConstructor.paramSymss.flatten.map(_.name) 66 | 67 | annos.map { 68 | case Apply(Select(New(annotTypeTree), _), args) => 69 | val annotType = annotTypeTree.tpe 70 | val paramNames = getAnnotationParamNames(annotType) 71 | 72 | val paramMap: Map[String, String] = args.zipWithIndex.map { 73 | case (NamedArg(argName, Literal(StringConstant(value))), _) => 74 | argName -> value 75 | case (NamedArg(argName, Literal(IntConstant(value))), _) => 76 | argName -> value.toString 77 | case (Literal(StringConstant(value)), idx) => 78 | val name = paramNames.lift(idx).getOrElse(s"param$idx") 79 | name -> value 80 | case (Literal(IntConstant(value)), idx) => 81 | val name = paramNames.lift(idx).getOrElse(s"param$idx") 82 | name -> value.toString 83 | case (otherArg, idx) => 84 | val name = paramNames.lift(idx).getOrElse(s"param$idx") 85 | name -> otherArg.show 86 | }.toMap 87 | 88 | annotType.typeSymbol.fullName -> paramMap 89 | 90 | case other => 91 | "unknown" -> Map("raw" -> other.show) 92 | }.toMap 93 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/RType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import scala.quoted.* 4 | 5 | trait RType[R]: 6 | type T = R // R is saved for accessibility during casting, ie myRType.asInstanceOf[fooRType.T] 7 | val name: String 8 | val typedName: TypedName 9 | 10 | lazy val clazz = Class.forName(name) 11 | def toType(quotes: Quotes): quoted.Type[R] = 12 | quotes.reflect.TypeRepr.typeConstructorOf(clazz).asType.asInstanceOf[Type[R]] 13 | 14 | def pretty: String = util.Pretty(this) 15 | // Stuff needed for equality tests, proper behavior in a Map, etc... 16 | override def hashCode: Int = typedName.hashCode 17 | override def equals(obj: Any) = this.hashCode == obj.hashCode 18 | 19 | object RType: 20 | import scala.quoted.staging.* 21 | given Compiler = Compiler.make(getClass.getClassLoader) 22 | 23 | // Chache of the "of()" (non-macro) reflected RTypes 24 | val rtypeCache = scala.collection.mutable.Map.empty[TypedName, RType[?]] 25 | 26 | // ----------------------------- 27 | // << MACRO ENTRY: of[T] >> (Tasty Reflection) 28 | // ----------------------------- 29 | 30 | inline def of[T]: RType[T] = ${ ofImpl[T]() } 31 | 32 | def ofImpl[T]()(using t: Type[T])(using quotes: Quotes): Expr[RType[T]] = 33 | import quotes.reflect.* 34 | reflect.ReflectOnType[T](quotes)(TypeRepr.of[T])(using scala.collection.mutable.Map.empty[TypedName, Boolean]).expr 35 | 36 | // ------------------------ 37 | // << NON-MACRO ENTRY >> (Tasty Inspection) 38 | // ------------------------ 39 | def of(clazz: Class[?]): RType[?] = 40 | rtypeCache.getOrElse( 41 | clazz.getName, { 42 | val newRType = { 43 | val fn = (quotes: Quotes) ?=> reflect.ReflectOnType(quotes)(quotes.reflect.TypeRepr.typeConstructorOf(clazz), false)(using scala.collection.mutable.Map.empty[TypedName, Boolean]).expr 44 | run(fn) 45 | }.asInstanceOf[RType[?]] 46 | rtypeCache.synchronized { 47 | rtypeCache.put(clazz.getName, newRType) 48 | } 49 | newRType 50 | } 51 | ) 52 | 53 | // ------------------------ 54 | // << NON-MACRO ENTRY: inTermsOf >> (Tasty Inspection) 55 | // ------------------------ 56 | inline def inTermsOf[T](clazz: Class[?]): RType[?] = 57 | of[T] match { 58 | case traitRType: rtypes.TraitRType[?] if traitRType.typeParamSymbols.nonEmpty => 59 | val classRType = of(clazz).asInstanceOf[rtypes.ScalaClassRType[?]] 60 | 61 | val fn = (quotes: Quotes) ?=> { 62 | import quotes.reflect.* 63 | 64 | val seenBefore = scala.collection.mutable.Map.empty[TypedName, Boolean] 65 | 66 | val paths = classRType.typePaths.getOrElse(traitRType.name, throw new ReflectException(s"No path in class ${classRType.name} for trait ${traitRType.name}")) 67 | implicit val tt = traitRType.toType(quotes) 68 | val typeParamTypes = reflect.TypeSymbolMapper.runPath(quotes)(paths, TypeRepr.of[traitRType.T]) 69 | 70 | // Apply type param paths from classRType against traitRType 71 | val classQuotedTypeRepr = TypeRepr.typeConstructorOf(classRType.clazz) 72 | reflect.ReflectOnType(quotes)(classQuotedTypeRepr.appliedTo(typeParamTypes))(using seenBefore).expr 73 | } 74 | run(fn) 75 | case traitRType: rtypes.TraitRType[?] => of(clazz) 76 | case x => throw new ReflectException(s"${x.name} is not of type trait") 77 | } 78 | 79 | // ------------------------------ 80 | // << MACRO ENTRY: ScalaJS >> (EXPERIMENTAL) 81 | // ------------------------------ 82 | inline def ofJS[T]: String = ${ ofJSImpl[T]() } 83 | 84 | def ofJSImpl[T]()(using t: Type[T])(using quotes: Quotes): Expr[String] = 85 | import quotes.reflect.* 86 | val ref = reflect.ReflectOnType[T](quotes)(TypeRepr.of[T])(using scala.collection.mutable.Map.empty[TypedName, Boolean]) 87 | val sb = new StringBuilder() 88 | ref.asJson(sb) 89 | Expr(sb.toString) 90 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/RTypeRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import scala.quoted.* 4 | 5 | /** Type Reference (Ref), which includes all the data ultimately needed to construct an RType[R]. Macros are defined by two sides of 6 | * the "blood-brain-barrier": compile-time and run-time. At compile-time we know important data like Type[R] and others that are 7 | * derived from Quotes. This data cannot survive the trip across the barrier to run-time, and frankly it's not needed over there. 8 | * Refs track Type and Expr data on the compile-side of the barrier. 9 | * 10 | * Refs have an expr val which is the Expr (expression) that will generate the associated RType on the run-time side of the 11 | * barrier. The RType will be very similar but missing the compile-time data like Type. 12 | */ 13 | trait RTypeRef[R]: 14 | type T = R // R is saved for accessibility during casting, ie myRType.asInstanceOf[fooRType.T] 15 | val name: String 16 | val typedName: TypedName 17 | val expr: Expr[RType[R]] // RTypeRef -> Expr[RType] 18 | val refType: Type[R] 19 | val unitVal: Expr[R] // default, unassigned value for a field during class construction 20 | val isNullable: Boolean = true 21 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit 22 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/ExtractorRegistry.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | 4 | import extractors.* 5 | 6 | object ExtractorRegistry: 7 | 8 | lazy val extractors: List[TypeExtractor[?]] = 9 | List( 10 | OptionExtractor(), 11 | EitherExtractor(), 12 | MapExtractor(), 13 | SeqExtractor(), 14 | ArrayExtractor(), 15 | TupleExtractor(), 16 | TryExtractor(), 17 | JavaCollectionExtractor(), 18 | JavaOptionalExtractor(), 19 | JavaMapExtractor() 20 | ) 21 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/ReflectOnField.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | 4 | import scala.quoted.* 5 | import rtypeRefs.* 6 | 7 | object ReflectOnField: 8 | 9 | // Scala case class field reflection 10 | def apply(quotes: Quotes)( 11 | fieldType: RTypeRef[?], 12 | valDef: quotes.reflect.ValDef, 13 | index: Int, 14 | dad: Option[ClassRef[?]], 15 | fieldDefaultMethods: Map[Int, (String, String)], 16 | isNonValConstructorField: Boolean = false 17 | ): FieldInfoRef = 18 | 19 | // Get any field annotations (from body of class--they're not on the constructor fields) 20 | val fieldAnnos = dad match { 21 | case _ => 22 | val baseAnnos = dad 23 | .flatMap(_.fields.find(_.name == valDef.name)) 24 | .map(_.annotations) 25 | .getOrElse(Map.empty[String, Map[String, String]]) 26 | baseAnnos ++ extractAnnotationInfo(quotes)(valDef.symbol.annotations) 27 | } 28 | 29 | // Figure out the original type symbols, i.e. T, (if any) 30 | val valTypeRef = valDef.tpt.tpe.asInstanceOf[quotes.reflect.TypeRef] 31 | val isTypeParam = valTypeRef.typeSymbol.flags.is(quotes.reflect.Flags.Param) 32 | val originalTypeSymbol = if isTypeParam then Some(valTypeRef.name.asInstanceOf[TypeSymbol]) else None 33 | 34 | ScalaFieldInfoRef( 35 | index, 36 | valDef.name, 37 | fieldType, 38 | fieldAnnos, 39 | fieldDefaultMethods.get(index), 40 | originalTypeSymbol, 41 | isNonValConstructorField 42 | )(using quotes) 43 | 44 | // Reflect on any fields in a Scala plain class (non-case class) that are NOT in the constructor, i.e. defined in the body 45 | def nonCaseScalaField[Q]( 46 | quotes: Quotes 47 | )(symbol: quotes.reflect.Symbol, classRepr: quotes.reflect.TypeRepr, constructorFieldNames: List[String])(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): List[NonConstructorFieldInfoRef] = 48 | import quotes.reflect.* 49 | 50 | // Include inherited methods (var & def), including inherited! 51 | // Produces (val , method _=) 52 | symbol.methodMembers 53 | .filter(_.name.endsWith("_=")) 54 | .filterNot(f => constructorFieldNames.contains(f.name.dropRight(2))) // filter out any 'var' constructor fields that slip through... 55 | .map { setter => 56 | // Trying to get the setter... which could be a val (field) if declared is a var, or it could be a method 57 | // in the case of user-written getter/setter... OR it could be defined in the superclass 58 | symbol.fieldMember(setter.name.dropRight(2)) match { 59 | case dotty.tools.dotc.core.Symbols.NoSymbol => 60 | symbol.methodMember(setter.name.dropRight(2)) match { 61 | case Nil => 62 | throw new ReflectException( 63 | s"Can't find field getter ${setter.name.dropRight(2)} in class ${symbol.fullName} or its superclass(es)." 64 | ) 65 | case getter => 66 | (getter.head, setter) 67 | } 68 | case getter: Symbol => 69 | (getter, setter) 70 | } 71 | } 72 | .filterNot { (getterSym, setterSym) => 73 | getterSym.annotations.map(_.tpe.typeSymbol.fullName).contains("co.blocke.scala_reflection.Ignore") || 74 | setterSym.annotations.map(_.tpe.typeSymbol.fullName).contains("co.blocke.scala_reflection.Ignore") 75 | } 76 | .zipWithIndex 77 | .map { case ((getter, setter), i) => 78 | // Pull out field annotations (either getter or setter can be annotated) 79 | val varAnnos = (getter.annotations ++: setter.annotations).map { a => 80 | val Apply(_, params) = a: @unchecked 81 | (a.symbol.signature.resultSig, annoSymToString(quotes)(params)) 82 | }.toMap 83 | 84 | NonConstructorFieldInfoRef( 85 | i, 86 | getter.name, // field name is same as getter method name 87 | getter.name, 88 | setter.name, 89 | getter.isValDef, 90 | applyTypeToField(quotes)(getter, classRepr), 91 | varAnnos 92 | )(using quotes) 93 | } 94 | .sortBy(_.getterLabel) // sorted for consistent ordering for testing ;-) 95 | 96 | // Reflect on any fields in a Scala plain class (non-case class) that are NOT in the constructor, i.e. defined in the body 97 | def javaFields[Q]( 98 | quotes: Quotes 99 | )(symbol: quotes.reflect.Symbol, classRepr: quotes.reflect.TypeRepr)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): List[NonConstructorFieldInfoRef] = 100 | import quotes.reflect.* 101 | 102 | val getterMethods = symbol.methodMembers.filter(_.name.startsWith("get")) 103 | val setterMethods = getterMethods.map(g => symbol.methodMember("set" + g.name.drop(3)).headOption) 104 | getterMethods 105 | .zip(setterMethods) 106 | .collect { 107 | case (g, s) if s.isDefined => 108 | val chars = g.name.drop(3).toCharArray() 109 | chars(0) = chars(0).toLower 110 | (String(chars), g, s.get) 111 | } 112 | .filterNot { (_, getterSym, setterSym) => 113 | getterSym.annotations.map(_.tpe.typeSymbol.fullName).contains("co.blocke.scala_reflection.Ignore") || 114 | setterSym.annotations.map(_.tpe.typeSymbol.fullName).contains("co.blocke.scala_reflection.Ignore") 115 | } 116 | .zipWithIndex 117 | .map { case ((fieldName, getter, setter), i) => 118 | val fieldType = setter.tree match { 119 | case dd: DefDef => 120 | dd.paramss.head.params.head match { 121 | case v: ValDef => applyTypeToField(quotes)(getter, classRepr) 122 | case t: TypeDef => UnknownRef[Q]("Where do we go from here?")(using quotes)(using classRepr.asType.asInstanceOf[Type[Q]]) 123 | } 124 | } 125 | val varAnnos = (getter.annotations ++: setter.annotations).map { a => 126 | val Apply(_, params) = a: @unchecked 127 | (a.symbol.signature.resultSig, annoSymToString(quotes)(params)) 128 | }.toMap 129 | NonConstructorFieldInfoRef( 130 | i, 131 | fieldName, 132 | getter.name, 133 | setter.name, 134 | false, 135 | fieldType, 136 | varAnnos, 137 | None 138 | )(using quotes) 139 | } 140 | .sortBy(_.getterLabel) // sorted for consistent ordering for testing ;-) 141 | 142 | def applyTypeToField(quotes: Quotes)(symbol: quotes.reflect.Symbol, classRepr: quotes.reflect.TypeRepr)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[?] = 143 | import quotes.reflect.* 144 | 145 | implicit val q = quotes 146 | symbol.tree match { 147 | case v: ValDef => 148 | classRepr.memberType(symbol).asType match 149 | case '[t] => 150 | ReflectOnType[t](quotes)(classRepr.memberType(symbol)) 151 | case d: DefDef => 152 | classRepr.memberType(symbol) match { 153 | case m: MethodType => // normal method w/parens 154 | m.resType.asType match 155 | case '[t] => 156 | ReflectOnType[t](quotes)(m.resType) 157 | case b: ByNameType => // ExprType, which is a method (like a setter) with no parens 158 | b.underlying.asType match 159 | case '[t] => 160 | ReflectOnType[t](quotes)(b.underlying) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/TypeExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | 4 | import scala.quoted.Quotes 5 | 6 | trait TypeExtractor[T <: RTypeRef[?]]: 7 | 8 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean 9 | 10 | def extractInfo[R]( 11 | quotes: Quotes 12 | )( 13 | t: quotes.reflect.TypeRepr, 14 | tob: List[quotes.reflect.TypeRepr], 15 | symbol: quotes.reflect.Symbol 16 | )(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] 17 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/TypeSymbolMapper.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | 4 | import scala.quoted.* 5 | import scala.annotation.tailrec 6 | import rtypeRefs.* 7 | 8 | /** The goal of TypeSymbolFinder is to deep-dive through a class and find where in the type tree each type parameter is located. 9 | * For example: 10 | * 11 | * case class Bar[T]( stuff: List[T]) 12 | * case class Foo[A,B]( thing: A, other: Bar[B] ) 13 | * 14 | * We'd fiscover that A is found on the first argument of Foo, and that B is found by diving into Bar and ultimately List to find B. 15 | * 16 | * NOTE: While this funcionality is very similar to TypeSymbolMapper2, the latter is designed for compile-time type symbol mapping, 17 | * while this code is focused on runtime mapping, which is more generalized. 18 | */ 19 | object TypeSymbolMapper: 20 | 21 | extension (list: List[TypeRecord]) 22 | def indexOf2(elem: TypeSymbol): Int = 23 | list.indexWhere(_.typeSymbol == elem, 0) 24 | 25 | case class TypeRecord( 26 | typeSymbol: TypeSymbol, 27 | path: List[Int], 28 | isFound: Boolean = false 29 | ): 30 | def pushPath(i: Int, setFound: Boolean = false): TypeRecord = 31 | if !isFound then this.copy(path = path :+ i, isFound = setFound) 32 | else this 33 | def popPath(): TypeRecord = 34 | if !isFound then this.copy(path = path.dropRight(1)) 35 | else this 36 | 37 | def mapTypeSymbolsForClass( 38 | quotes: Quotes 39 | )(classDef: quotes.reflect.ClassDef, paramSymbols: List[TypeSymbol]): Map[String, List[List[Int]]] = 40 | import quotes.reflect.* 41 | implicit val q = quotes 42 | val seenBefore = scala.collection.mutable.Map.empty[TypedName, Boolean] 43 | 44 | inline def isSealed(symbol: Symbol) = symbol.flags.is(quotes.reflect.Flags.Sealed) 45 | 46 | classDef.parents 47 | .map(_.asInstanceOf[TypeTree].tpe) 48 | .collect { 49 | case a: AppliedType if !isSealed(a.typeSymbol) => // for each AppliedType ancestor of this class... 50 | val extendsType = 51 | a.asType match 52 | case '[t] => 53 | ReflectOnType[t](quotes)(a)(using seenBefore) 54 | val initialMap = paramSymbols.map(ts => TypeRecord(ts, Nil)) 55 | val result = navLevel(extendsType, -1, initialMap) match { 56 | case Left(r) => 57 | r // Doesn't matter if finished or not--return it. The Left/Right was for navLevel internal abort-when-done 58 | case Right(r) => 59 | r 60 | } 61 | (extendsType.name, result.map(_.path)) 62 | } 63 | .toMap 64 | 65 | private def navLevel( 66 | rt: RTypeRef[?], 67 | index: Int, 68 | paramList: List[TypeRecord] 69 | ): Either[List[TypeRecord], List[TypeRecord]] = // Left => More to do, Right => complete 70 | rt match { 71 | case ap: AppliedRef => 72 | val initialList = 73 | if index < 0 then paramList 74 | else paramList.map(_.pushPath(index)) // add current path to all un-found symbols 75 | 76 | // Special abortable foldLeft, so for large classes we don't have to recuse the entire deep class structure once we find all the types 77 | val afterAppliedList = foldLeftBreak((0 to ap.selectLimit - 1).toList)(initialList) { (i, pList) => 78 | navLevel(ap.select(i), i, pList) 79 | } 80 | 81 | val cleanedList = 82 | afterAppliedList.map(_.popPath()) // revert any paths that weren't found in our deep dive into AppliedType 83 | val numLeftToFind = cleanedList.foldLeft(cleanedList.size) { (numLeft, rec) => 84 | if rec.isFound then numLeft - 1 else numLeft 85 | } 86 | if numLeftToFind == 0 then Right(cleanedList) 87 | else Left(cleanedList) 88 | 89 | case ts: TypeSymbolRef => 90 | paramList.indexOf2(ts.name.asInstanceOf[TypeSymbol]) match { 91 | case i if (i >= 0 && !paramList(i).isFound) => 92 | Left(paramList.updated(i, paramList(i).pushPath(index, true))) 93 | case _ => 94 | Left(paramList) // do nothing--not found, or already found 95 | } 96 | 97 | case _ => // another RType with no type parameter involvement, e.g. primitive type 98 | Left(paramList) 99 | } 100 | 101 | def runPath(quotes: Quotes)(path: List[List[Int]], traitTypeRepr: quotes.reflect.TypeRepr): List[quotes.reflect.TypeRepr] = 102 | import quotes.reflect.* 103 | 104 | @tailrec 105 | def _runPath(onePath: List[Int], repr: TypeRepr): TypeRepr = 106 | if onePath == Nil then repr 107 | else 108 | val AppliedType(_, tob) = repr.asInstanceOf[AppliedType] 109 | _runPath(onePath.tail, tob(onePath.head)) 110 | 111 | path.map(p => _runPath(p, traitTypeRepr)) 112 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/TypeSymbolMapper2.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | 4 | import scala.quoted.* 5 | 6 | /** The goal of TypeSymbolFinder is to deep-dive through a class and find where in the type tree each type parameter is located. 7 | * For example: 8 | * 9 | * case class Bar[T]( stuff: List[T]) 10 | * case class Foo[A,B]( thing: A, other: Bar[B] ) 11 | * 12 | * We'd fiscover that A is found on the first argument of Foo, and that B is found by diving into Bar and ultimately List to find B. 13 | */ 14 | object TypeSymbolMapper2: 15 | 16 | type NavPath = List[Int] 17 | 18 | case class TypeRecord( 19 | path: List[Int] = Nil, 20 | isFound: Boolean = false 21 | ): 22 | def pushPath(i: Int, setFound: Boolean = false): TypeRecord = 23 | if !isFound then this.copy(path = path :+ i, isFound = setFound) 24 | else this 25 | def popPath(): TypeRecord = 26 | if !isFound then this.copy(path = path.dropRight(1)) 27 | else this 28 | 29 | def mapTypeSymbolsForClass( 30 | quotes: Quotes 31 | )(lookForTypeParams: List[TypeSymbol], inHere: List[quotes.reflect.TypeRepr]): Map[TypeSymbol, TypeRecord] = 32 | deepDive(quotes)(lookForTypeParams.map(s => (s, TypeRecord())).toMap, inHere) 33 | 34 | private def deepDive(quotes: Quotes)(soFar: Map[TypeSymbol, TypeRecord], inHere: List[quotes.reflect.TypeRepr]): Map[TypeSymbol, TypeRecord] = 35 | import quotes.reflect.* 36 | inHere.zipWithIndex.foldLeft(soFar) { case (acc, (typeRepr, i)) => 37 | typeRepr match 38 | case AppliedType(_, tob) => 39 | val allFound = acc.values.foldLeft(true) { case (acc, r) => acc && r.isFound } 40 | if allFound then acc 41 | else deepDive(quotes)(acc.map { case (k, v) => (k, v.pushPath(i, false)) }.toMap, tob).map { case (k, v) => (k, v.popPath()) }.toMap 42 | case TypeRef(_, name) => 43 | if acc.contains(name.asInstanceOf[TypeSymbol]) then acc.updated(name.asInstanceOf[TypeSymbol], acc(name.asInstanceOf[TypeSymbol]).pushPath(i, true)) 44 | else acc 45 | } 46 | 47 | def applyPath(quotes: Quotes)(originalSyms: List[quotes.reflect.TypeRef], args: List[quotes.reflect.TypeRepr], path: Map[TypeSymbol, TypeRecord]): List[quotes.reflect.TypeRepr] = 48 | import quotes.reflect.* 49 | def resolveType(path: List[Int], argList: List[TypeRepr] = args): TypeRepr = 50 | argList(path.head) match { 51 | case e: AppliedType if path.size == 1 => e 52 | case AppliedType(_, tob) => resolveType(path.tail, tob) 53 | case e => e 54 | } 55 | originalSyms.map(s => 56 | path 57 | .get(s.name.asInstanceOf[TypeSymbol]) 58 | .map { p => 59 | if p.isFound then resolveType(p.path) else s 60 | } 61 | .getOrElse(s) 62 | ) 63 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/ArrayExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import Clazzes.* 6 | import rtypeRefs.* 7 | import scala.quoted.* 8 | 9 | case class ArrayExtractor() extends TypeExtractor[ArrayRef[?]]: 10 | 11 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 12 | // Try here because non-library symbol won't have a class and will explode. 13 | scala.util.Try(ArrayClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 14 | 15 | def extractInfo[R]( 16 | quotes: Quotes 17 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 18 | implicit val q = quotes 19 | import quotes.reflect.* 20 | 21 | val typeParamSymbols = List("A") 22 | val elementRef = tob.head.dealias match 23 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 24 | low.asType match 25 | case '[l] => 26 | high.asType match 27 | case '[h] => 28 | WildcardRef( 29 | reflect.ReflectOnType[l](quotes)(low), 30 | reflect.ReflectOnType[h](quotes)(high) 31 | ) 32 | case _ => 33 | tob.head.asType match 34 | case '[u] => 35 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 36 | else reflect.ReflectOnType[u](quotes)(tob.head) 37 | 38 | val a = quotes.reflect.AppliedType(t, tob).asType 39 | a match 40 | case '[t] => 41 | ArrayRef[t]( 42 | mangleArrayClassName(elementRef), 43 | typeParamSymbols, 44 | elementRef 45 | ).asInstanceOf[RTypeRef[R]] 46 | 47 | private def mangleArrayClassName(ref: RTypeRef[?]): String = 48 | val mangled = ref match { 49 | case _: TypeSymbolRef => "Ljava.lang.Object;" 50 | case c: ArrayRef[?] => mangleArrayClassName(c.elementRef) 51 | // case c: rtypes.JavaArrayInfo => mangleArrayClassName(c.elementType) 52 | case p: BooleanRef => "Z" 53 | case p: ByteRef => "B" 54 | case p: CharRef => "C" 55 | case p: DoubleRef => "D" 56 | case p: FloatRef => "F" 57 | case p: IntRef => "I" 58 | case p: LongRef => "J" 59 | case p: ShortRef => "S" 60 | case p: AnyRef => "Ljava.lang.Object;" 61 | case c => "L" + c.name + ";" 62 | } 63 | "[" + mangled 64 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/EitherExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import rtypeRefs.* 6 | import scala.quoted.* 7 | 8 | case class EitherExtractor() extends TypeExtractor[LeftRightRef[?]]: 9 | 10 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = symbol.fullName == Clazzes.EitherClazz.getName 11 | 12 | def extractInfo[R]( 13 | quotes: Quotes 14 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 15 | implicit val q = quotes 16 | import quotes.reflect.* 17 | 18 | val typeParamSymbols = List("L", "R") 19 | 20 | val leftRef = 21 | tob(0).dealias match 22 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 23 | low.asType match 24 | case '[l] => 25 | high.asType match 26 | case '[h] => 27 | WildcardRef( 28 | reflect.ReflectOnType[l](quotes)(low), 29 | reflect.ReflectOnType[h](quotes)(high) 30 | ) 31 | case _ => 32 | tob(0).asType match 33 | case '[u] => 34 | if tob(0).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(0).typeSymbol.name)(using quotes)(using Type.of[Any]) 35 | else reflect.ReflectOnType[u](quotes)(tob(0)) 36 | val rightRef = 37 | tob(1).dealias match 38 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 39 | low.asType match 40 | case '[l] => 41 | high.asType match 42 | case '[h] => 43 | WildcardRef( 44 | reflect.ReflectOnType[l](quotes)(low), 45 | reflect.ReflectOnType[h](quotes)(high) 46 | ) 47 | case _ => 48 | tob(1).asType match 49 | case '[u] => 50 | if tob(1).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(1).typeSymbol.name)(using quotes)(using Type.of[Any]) 51 | else reflect.ReflectOnType[u](quotes)(tob(1)) 52 | 53 | val a = quotes.reflect.AppliedType(t, tob).asType 54 | a match 55 | case '[t] => 56 | LeftRightRef[t]( 57 | t.classSymbol.get.fullName, 58 | typeParamSymbols, 59 | leftRef, 60 | rightRef, 61 | rtypeRefs.LRKind.EITHER 62 | ).asInstanceOf[RTypeRef[R]] 63 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/JavaCollectionExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import rtypeRefs.* 6 | import Clazzes.* 7 | import scala.quoted.* 8 | import scala.util.Try 9 | 10 | case class JavaCollectionExtractor() extends TypeExtractor[JavaCollectionRef[?]]: 11 | 12 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 13 | Try(Class.forName(symbol.fullName) <:< JCollectionClazz).toOption.getOrElse(false) 14 | || symbol.fullName == "java.lang.Iterable" 15 | 16 | def extractInfo[R]( 17 | quotes: Quotes 18 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 19 | implicit val q = quotes 20 | import quotes.reflect.* 21 | 22 | val typeParamSymbols = List("A") 23 | val elementRef = tob.head.dealias match 24 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 25 | low.asType match 26 | case '[l] => 27 | high.asType match 28 | case '[h] => 29 | WildcardRef( 30 | reflect.ReflectOnType[l](quotes)(low), 31 | reflect.ReflectOnType[h](quotes)(high) 32 | ) 33 | case _ => 34 | tob.head.asType match 35 | case '[u] => 36 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 37 | else reflect.ReflectOnType[u](quotes)(tob.head) 38 | 39 | val a = quotes.reflect.AppliedType(t, tob).asType 40 | a match 41 | case '[t] => 42 | JavaCollectionRef[t]( 43 | t.classSymbol.get.fullName, 44 | typeParamSymbols, 45 | elementRef 46 | ).asInstanceOf[RTypeRef[R]] 47 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/JavaMapExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import Clazzes.* 6 | import rtypeRefs.* 7 | import scala.quoted.* 8 | 9 | case class JavaMapExtractor() extends TypeExtractor[JavaMapRef[?]]: 10 | 11 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 12 | // Try here because non-library symbol won't have a class and will explode. 13 | scala.util.Try(JMapClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 14 | 15 | def extractInfo[R]( 16 | quotes: Quotes 17 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 18 | implicit val q = quotes 19 | import quotes.reflect.* 20 | 21 | val typeParamSymbols = List("K", "V") 22 | val elementRef = 23 | tob(0).dealias match 24 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 25 | low.asType match 26 | case '[l] => 27 | high.asType match 28 | case '[h] => 29 | WildcardRef( 30 | reflect.ReflectOnType[l](quotes)(low), 31 | reflect.ReflectOnType[h](quotes)(high) 32 | ) 33 | case _ => 34 | tob(0).asType match 35 | case '[u] => 36 | if tob(0).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(0).typeSymbol.name)(using quotes)(using Type.of[Any]) 37 | else reflect.ReflectOnType[u](quotes)(tob(0)) 38 | val elementRef2 = 39 | tob(1).dealias match 40 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 41 | low.asType match 42 | case '[l] => 43 | high.asType match 44 | case '[h] => 45 | WildcardRef( 46 | reflect.ReflectOnType[l](quotes)(low), 47 | reflect.ReflectOnType[h](quotes)(high) 48 | ) 49 | case _ => 50 | tob(1).asType match 51 | case '[u] => 52 | if tob(1).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(1).typeSymbol.name)(using quotes)(using Type.of[Any]) 53 | else reflect.ReflectOnType[u](quotes)(tob(1)) 54 | 55 | val a = quotes.reflect.AppliedType(t, tob) 56 | a.asType match 57 | case '[t] => 58 | val typeName = a.tycon.typeSymbol.fullName 59 | JavaMapRef[t]( 60 | t.classSymbol.get.fullName, 61 | typeName == "java.util.LinkedHashMap", 62 | typeParamSymbols, 63 | elementRef, 64 | elementRef2 65 | ).asInstanceOf[RTypeRef[R]] 66 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/JavaOptionalExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import Clazzes.* 6 | import rtypeRefs.* 7 | import scala.quoted.* 8 | import scala.util.Try 9 | 10 | case class JavaOptionalExtractor() extends TypeExtractor[JavaOptionalRef[?]]: 11 | 12 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 13 | Try(Class.forName(symbol.fullName) =:= OptionalClazz).toOption.getOrElse(false) 14 | 15 | def extractInfo[R]( 16 | quotes: Quotes 17 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 18 | implicit val q = quotes 19 | import quotes.reflect.* 20 | 21 | val typeParamSymbols = List("A") 22 | val optionOfRef = tob.head.dealias match 23 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 24 | low.asType match 25 | case '[l] => 26 | high.asType match 27 | case '[h] => 28 | WildcardRef( 29 | reflect.ReflectOnType[l](quotes)(low), 30 | reflect.ReflectOnType[h](quotes)(high) 31 | ) 32 | case _ => 33 | tob.head.asType match 34 | case '[u] => 35 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 36 | else reflect.ReflectOnType[u](quotes)(tob.head) 37 | 38 | val a = quotes.reflect.AppliedType(t, tob).asType 39 | a match 40 | case '[t] => 41 | JavaOptionalRef[t]( 42 | t.classSymbol.get.fullName, 43 | typeParamSymbols, 44 | optionOfRef 45 | ).asInstanceOf[RTypeRef[R]] 46 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/MapExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import Clazzes.* 6 | import rtypeRefs.* 7 | import scala.quoted.* 8 | 9 | object MapType { 10 | type IsMap[A <: scala.collection.Map[?, ?]] = A 11 | 12 | val insertionOrderMapTypes = Set( 13 | "scala.collection.immutable.ListMap", 14 | "scala.collection.immutable.LinkedHashMap", // Scala 2.13+ 15 | "scala.collection.immutable.SeqMap", 16 | "scala.collection.mutable.LinkedHashMap", 17 | "scala.collection.mutable.ListMap", 18 | "scala.collection.mutable.SeqMap" 19 | ) 20 | } 21 | 22 | case class MapExtractor() extends TypeExtractor[MapRef[?]]: 23 | 24 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 25 | // Try here because non-library symbol won't have a class and will explode. 26 | scala.util.Try(MapClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 27 | 28 | def extractInfo[R]( 29 | quotes: Quotes 30 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 31 | implicit val q = quotes 32 | import quotes.reflect.* 33 | 34 | val typeParamSymbols = List("K", "V") 35 | 36 | val elementRef = 37 | tob(0).dealias match 38 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 39 | low.asType match 40 | case '[l] => 41 | high.asType match 42 | case '[h] => 43 | WildcardRef( 44 | reflect.ReflectOnType[l](quotes)(low), 45 | reflect.ReflectOnType[h](quotes)(high) 46 | ) 47 | case _ => 48 | tob(0).asType match 49 | case '[u] => 50 | if tob(0).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(0).typeSymbol.name)(using quotes)(using Type.of[Any]) 51 | else reflect.ReflectOnType[u](quotes)(tob(0)) 52 | val elementRef2 = 53 | tob(1).dealias match 54 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 55 | low.asType match 56 | case '[l] => 57 | high.asType match 58 | case '[h] => 59 | WildcardRef( 60 | reflect.ReflectOnType[l](quotes)(low), 61 | reflect.ReflectOnType[h](quotes)(high) 62 | ) 63 | case _ => 64 | tob(1).asType match 65 | case '[u] => 66 | if tob(1).typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob(1).typeSymbol.name)(using quotes)(using Type.of[Any]) 67 | else reflect.ReflectOnType[u](quotes)(tob(1)) 68 | 69 | val a = quotes.reflect.AppliedType(t, tob) 70 | a.asType match 71 | case '[MapType.IsMap[t]] => 72 | val typeName = a.tycon.typeSymbol.fullName 73 | val maintainsOrder = MapType.insertionOrderMapTypes.contains(typeName) 74 | MapRef[t]( 75 | t.classSymbol.get.fullName, 76 | maintainsOrder, 77 | typeParamSymbols, 78 | elementRef, 79 | elementRef2 80 | ).asInstanceOf[RTypeRef[R]] 81 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/OptionExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import rtypeRefs.* 6 | import scala.quoted.* 7 | import rtypeRefs.{ScalaOptionRef, TypeSymbolRef} 8 | 9 | case class OptionExtractor() extends TypeExtractor[ScalaOptionRef[?]]: 10 | 11 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = symbol.fullName == Clazzes.OptionClazz.getName 12 | 13 | def extractInfo[R]( 14 | quotes: Quotes 15 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 16 | implicit val q = quotes 17 | import quotes.reflect.* 18 | 19 | val typeParamSymbols = List("A") 20 | val optionOfRef = tob.head.dealias match 21 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 22 | low.asType match 23 | case '[l] => 24 | high.asType match 25 | case '[h] => 26 | WildcardRef( 27 | reflect.ReflectOnType[l](quotes)(low), 28 | reflect.ReflectOnType[h](quotes)(high) 29 | ) 30 | case _ => 31 | tob.head.asType match 32 | case '[u] => 33 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 34 | else reflect.ReflectOnType[u](quotes)(tob.head) 35 | 36 | val a = quotes.reflect.AppliedType(t, tob).asType 37 | a match 38 | case '[t] => 39 | ScalaOptionRef[t]( 40 | t.classSymbol.get.fullName, 41 | typeParamSymbols, 42 | optionOfRef 43 | ).asInstanceOf[RTypeRef[R]] 44 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/SeqExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import rtypeRefs.* 6 | import Clazzes.* 7 | import scala.quoted.* 8 | 9 | object SeqType { 10 | type IsSeq[A <: scala.collection.Seq[?]] = A 11 | type IsSet[A <: scala.collection.Set[?]] = A 12 | type IsIterable[A <: scala.collection.Iterable[?]] = A 13 | } 14 | 15 | case class SeqExtractor() extends TypeExtractor[SeqRef[?]]: 16 | 17 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = 18 | // Try here because non-library symbol won't have a class and will explode. 19 | val isSeq = scala.util.Try(SeqClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 20 | val isSet = scala.util.Try(SetClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 21 | val isIterable = scala.util.Try(IterableClazz.isAssignableFrom(Class.forName(symbol.fullName))).toOption.getOrElse(false) 22 | isSeq || isSet || isIterable 23 | 24 | def extractInfo[R]( 25 | quotes: Quotes 26 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 27 | implicit val q = quotes 28 | import quotes.reflect.* 29 | 30 | val typeParamSymbols = List("A") 31 | val seqOfRef = tob.head.dealias match 32 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 33 | low.asType match 34 | case '[l] => 35 | high.asType match 36 | case '[h] => 37 | WildcardRef( 38 | reflect.ReflectOnType[l](quotes)(low), 39 | reflect.ReflectOnType[h](quotes)(high) 40 | ) 41 | case _ => 42 | tob.head.asType match 43 | case '[u] => 44 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 45 | else reflect.ReflectOnType[u](quotes)(tob.head) 46 | 47 | quotes.reflect.AppliedType(t, tob).asType.asInstanceOf[Type[? <: Seq[?]]] match 48 | case '[SeqType.IsSeq[t]] => 49 | SeqRef[t]( 50 | t.classSymbol.get.fullName, 51 | typeParamSymbols, 52 | seqOfRef 53 | ).asInstanceOf[RTypeRef[R]] 54 | case '[SeqType.IsSet[t]] => 55 | SetRef[t]( 56 | t.classSymbol.get.fullName, 57 | typeParamSymbols, 58 | seqOfRef 59 | ).asInstanceOf[RTypeRef[R]] 60 | case '[SeqType.IsIterable[t]] => 61 | IterableRef[t]( 62 | t.classSymbol.get.fullName, 63 | typeParamSymbols, 64 | seqOfRef 65 | ).asInstanceOf[RTypeRef[R]] 66 | case '[z] => 67 | throw new Exception("Unknown seq type... " + Type.show[z]) 68 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/TryExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import rtypeRefs.* 6 | import Clazzes.* 7 | import scala.quoted.* 8 | 9 | case class TryExtractor() extends TypeExtractor[TryRef[?]]: 10 | 11 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = symbol.fullName == TryClazz.getName 12 | 13 | def extractInfo[R]( 14 | quotes: Quotes 15 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 16 | implicit val q = quotes 17 | import quotes.reflect.* 18 | 19 | val typeParamSymbols = List("A") 20 | val tryOfRef = tob.head.dealias match 21 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 22 | low.asType match 23 | case '[l] => 24 | high.asType match 25 | case '[h] => 26 | WildcardRef( 27 | reflect.ReflectOnType[l](quotes)(low), 28 | reflect.ReflectOnType[h](quotes)(high) 29 | ) 30 | case _ => 31 | tob.head.asType match 32 | case '[u] => 33 | if tob.head.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(tob.head.typeSymbol.name)(using quotes)(using Type.of[Any]) 34 | else reflect.ReflectOnType[u](quotes)(tob.head) 35 | 36 | val a = quotes.reflect.AppliedType(t, tob).asType 37 | a match 38 | case '[t] => 39 | TryRef[t]( 40 | t.classSymbol.get.fullName, 41 | typeParamSymbols, 42 | tryOfRef 43 | ).asInstanceOf[RTypeRef[R]] 44 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/extractors/TupleExtractor.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package extractors 4 | 5 | import Clazzes.* 6 | import rtypeRefs.* 7 | import scala.quoted.* 8 | import scala.util.matching.Regex 9 | 10 | case class TupleExtractor() extends TypeExtractor[TupleRef[?]]: 11 | 12 | val tupleFullName: Regex = """scala.Tuple(\d+)""".r 13 | 14 | def matches(quotes: Quotes)(symbol: quotes.reflect.Symbol): Boolean = tupleFullName.matches(symbol.fullName) 15 | 16 | def extractInfo[R]( 17 | quotes: Quotes 18 | )(t: quotes.reflect.TypeRepr, tob: List[quotes.reflect.TypeRepr], symbol: quotes.reflect.Symbol)(using seenBefore: scala.collection.mutable.Map[TypedName, Boolean]): RTypeRef[R] = 19 | implicit val q = quotes 20 | import quotes.reflect.* 21 | 22 | val elementTypes = 23 | tob.map { oneTob => 24 | oneTob.dealias match 25 | case TypeBounds(low, high) => // Detect wildcards: List[? <: Thing] 26 | low.asType match 27 | case '[l] => 28 | high.asType match 29 | case '[h] => 30 | WildcardRef( 31 | reflect.ReflectOnType[l](quotes)(low), 32 | reflect.ReflectOnType[h](quotes)(high) 33 | ) 34 | case _ => 35 | oneTob.asType match 36 | case '[u] => 37 | if oneTob.typeSymbol.flags.is(quotes.reflect.Flags.Param) then TypeSymbolRef(oneTob.typeSymbol.name)(using quotes)(using Type.of[Any]) 38 | else reflect.ReflectOnType[u](quotes)(oneTob) 39 | } 40 | val (_, typeParamSymbols) = elementTypes.foldLeft(('A', List.empty[String])) { case ((sym, acc), b) => 41 | ((sym + 1).toChar, acc :+ sym.toString()) 42 | } 43 | 44 | val a = quotes.reflect.AppliedType(t, tob).asType 45 | a match 46 | case '[t] => 47 | TupleRef[t]( 48 | t.classSymbol.get.fullName, 49 | typeParamSymbols, 50 | elementTypes 51 | ).asInstanceOf[RTypeRef[R]] 52 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/AliasRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.AliasRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class AliasRef[R]( 10 | definedType: String, 11 | unwrappedType: RTypeRef[?] // Aliases with a parameterized wrapped type are not currently supported, so ConcreteType here. 12 | )(using quotes: Quotes)(using tt: Type[R]) 13 | extends RTypeRef[R]: 14 | import quotes.reflect.* 15 | 16 | val name: String = definedType.drop(definedType.lastIndexOf('.') + 1) 17 | val typedName: TypedName = name 18 | val refType = tt 19 | 20 | val unitVal = unwrappedType.unitVal.asInstanceOf[Expr[R]] 21 | 22 | val expr = 23 | Apply( 24 | TypeApply( 25 | Select.unique(New(TypeTree.of[AliasRType[R]]), ""), 26 | List(TypeTree.of[R]) 27 | ), 28 | List( 29 | Expr(definedType).asTerm, 30 | unwrappedType.expr.asTerm 31 | ) 32 | ).asExprOf[RType[R]] 33 | 34 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 35 | JsonObjectBuilder(quotes)( 36 | sb, 37 | List( 38 | JsonField("rtype", "AliasRType"), 39 | JsonField("name", this.name), 40 | JsonField("typedName", this.typedName), 41 | JsonField("definedType", this.definedType), 42 | JsonField("unwrappedType", this.unwrappedType) 43 | ) 44 | ) 45 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/AppliedRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | /** This RType mixin needed because all AppliedTypes don't have parameters. 6 | * For examlpe a case class could be an applied type (isAppliedType=true) or not. A collection is always applied. 7 | */ 8 | trait AppliedRef: 9 | self: RTypeRef[?] => 10 | 11 | val typeParamSymbols: List[TypeSymbol] 12 | 13 | // Selecting is for creating and navigating paths through type parameters. A Map[K,V] has a select limit of 2, 14 | // so select(0) gives you the Ref for K and select(1) gives the Ref for V. 15 | def selectLimit: Int 16 | def select(i: Int): RTypeRef[?] 17 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/ArrayRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.ArrayRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | import Liftables.TypeSymbolToExpr 9 | 10 | /** Reference to a Scala Array 11 | * @param name simple name of the class 12 | * @param typeParamSymbols List (of 1) parameter symbole, i.e. "A", as in Array[A] 13 | * @param elementRef Ref for the element type of the array, eg. Array[Person] 14 | */ 15 | case class ArrayRef[R]( 16 | name: String, 17 | typeParamSymbols: List[TypeSymbol], 18 | elementRef: RTypeRef[?] 19 | )(using quotes: Quotes)(using tt: Type[R]) 20 | extends RTypeRef[R] 21 | with CollectionRef[R]: 22 | import quotes.reflect.* 23 | val refType = tt 24 | 25 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 26 | 27 | val isMutable = true 28 | 29 | val expr = 30 | Apply( 31 | TypeApply( 32 | Select.unique(New(TypeTree.of[ArrayRType[R]]), ""), 33 | List(TypeTree.of[R]) 34 | ), 35 | List( 36 | Expr(name).asTerm, 37 | Expr(typeParamSymbols).asTerm, 38 | elementRef.expr.asTerm 39 | ) 40 | ).asExprOf[RType[R]] 41 | 42 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 43 | JsonObjectBuilder(quotes)( 44 | sb, 45 | List( 46 | JsonField("rtype", "ArrayRType"), 47 | JsonField("name", name), 48 | JsonField("typedName", this.typedName), 49 | JsonField("typeParamSymbols", this.typeParamSymbols), 50 | JsonField("elementType", this.elementRef) 51 | ) 52 | ) 53 | 54 | // override def toType(quotes: Quotes): quoted.Type[R] = 55 | // import quotes.reflect.* 56 | // val collectionType: quoted.Type[R] = 57 | // quotes.reflect.TypeRepr.typeConstructorOf(Class.forName(name)).asType.asInstanceOf[quoted.Type[R]] 58 | // val elType = elementRef.toType(quotes) 59 | // val collectionTypeRepr = TypeRepr.of[R](using collectionType) 60 | // val elTypeRepr = TypeRepr.of[elementRef.T](using elType) 61 | // AppliedType(collectionTypeRepr, List(elTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 62 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/ClassRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.{JavaClassRType, NonConstructorFieldInfo, ScalaClassRType, TypeMemberRType} 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | trait ClassRef[R] extends RTypeRef[R] with AppliedRef: 10 | self: RTypeRef[?] => 11 | 12 | val fields: List[FieldInfoRef] 13 | val typeParamSymbols: List[TypeSymbol] 14 | val typeParamValues: List[RTypeRef[?]] 15 | val annotations: Map[String, Map[String, String]] 16 | val mixins: List[String] 17 | 18 | val selectLimit: Int = fields.size 19 | def select(i: Int): RTypeRef[?] = 20 | if i >= 0 && i < selectLimit then fields(i).fieldRef 21 | else throw new ReflectException(s"AppliedType select index $i out of range for $name") 22 | 23 | //------------------------------------------------------------------------------ 24 | 25 | case class ScalaClassRef[R]( 26 | name: String, 27 | typedName: TypedName, 28 | typeParamSymbols: List[TypeSymbol], 29 | typeParamValues: List[RTypeRef[?]], 30 | typeMembers: List[TypeMemberRef], 31 | fields: List[FieldInfoRef], 32 | annotations: Map[String, Map[String, String]], 33 | mixins: List[String], 34 | isAppliedType: Boolean, 35 | isValueClass: Boolean, 36 | isCaseClass: Boolean, 37 | isAbstractClass: Boolean, 38 | nonConstructorFields: List[NonConstructorFieldInfoRef] = Nil, // Populated for non-case classes only 39 | sealedChildren: List[RTypeRef[?]] = Nil, // Populated only if this is a sealed class or abstract class 40 | childrenAreObject: Boolean = false, 41 | typePaths: Map[String, List[List[Int]]] 42 | )(using quotes: Quotes)(using tt: Type[R]) 43 | extends ClassRef[R] 44 | with Sealable: 45 | import quotes.reflect.* 46 | import Liftables.{ListTypeSymbolToExpr, TypedNameToExpr} 47 | 48 | val refType = tt 49 | 50 | val unitVal = 51 | // If this is a value class we can't assume null (could be an Int wrapped). So we need to 52 | // construct one of these beasties with the unit val of the specific wrapped type. Kinda doesn't matter, 53 | // but this way should guarantee no class cast exceptions. 54 | if isValueClass then 55 | refType match { 56 | case '[c] => 57 | val tpe = TypeRepr.of[c] 58 | val primaryConstructor = tpe.classSymbol.get.primaryConstructor 59 | val constructor = Select(New(Inferred(tpe)), primaryConstructor) 60 | val argss = List(List(fields(0).fieldRef.unitVal.asTerm)) 61 | argss.tail.foldLeft(Apply(constructor, argss.head))((acc, args) => Apply(acc, args)).asExprOf[R] 62 | } 63 | else '{ null.asInstanceOf[R] }.asExprOf[R] 64 | 65 | def isSealed: Boolean = sealedChildren.nonEmpty 66 | 67 | val expr = 68 | Apply( 69 | TypeApply( 70 | Select.unique(New(TypeTree.of[ScalaClassRType]), ""), 71 | List(TypeTree.of[R]) 72 | ), 73 | List( 74 | Expr(name).asTerm, 75 | Expr(typedName).asTerm, 76 | Expr(typeParamSymbols).asTerm, 77 | Expr.ofList(typeParamValues.map(_.expr)).asTerm, 78 | Expr.ofList(typeMembers.map(_.expr.asInstanceOf[Expr[TypeMemberRType]])).asTerm, 79 | Expr.ofList(fields.map(_.expr)).asTerm, 80 | Expr(annotations).asTerm, 81 | Expr(mixins).asTerm, 82 | Expr(isAppliedType).asTerm, 83 | Expr(isValueClass).asTerm, 84 | Expr(isCaseClass).asTerm, 85 | Expr(isAbstractClass).asTerm, 86 | Expr(typePaths).asTerm, 87 | Expr.ofList(nonConstructorFields.map(_.expr.asInstanceOf[Expr[NonConstructorFieldInfo]])).asTerm, 88 | Expr.ofList(sealedChildren.map(_.expr)).asTerm, 89 | Expr(childrenAreObject).asTerm 90 | ) 91 | ).asExprOf[RType[R]] 92 | 93 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 94 | JsonObjectBuilder(quotes)( 95 | sb, 96 | List( 97 | JsonField("rtype", "ScalaClassRType"), 98 | JsonField("name", this.name), 99 | JsonField("typedName", this.typedName), 100 | JsonField("typeParamSymbols", this.typeParamSymbols), 101 | JsonField("typeParamValues", this.typeParamValues), 102 | JsonField("typeMembers", this.typeMembers), 103 | JsonField("fields", this.fields), 104 | JsonField("annotations", this.annotations), 105 | JsonField("mixins", this.mixins), 106 | JsonField("isAppliedType", this.isAppliedType), 107 | JsonField("isValueClass", this.isValueClass), 108 | JsonField("isCaseClass", this.isCaseClass), 109 | JsonField("isAbstractClass", this.isAbstractClass), 110 | JsonField("nonConstructorFields", this.nonConstructorFields), 111 | JsonField("sealedChildren", this.sealedChildren), 112 | JsonField("childrenAreObject", this.childrenAreObject) 113 | ) 114 | ) 115 | 116 | //------------------------------------------------------------------------------ 117 | 118 | /** Java class reflection has a special problem... we need the class file, which isn't available during compilation (i.e. inside a macro). 119 | * So we need an internal-use-only field (classType) where we store Type[T] for the Java class--which we know during reflection. 120 | */ 121 | 122 | case class JavaClassRef[R]( 123 | name: String, 124 | fields: List[FieldInfoRef], 125 | typeParamSymbols: List[TypeSymbol], 126 | typeParamValues: List[RTypeRef[?]], 127 | annotations: Map[String, Map[String, String]], 128 | mixins: List[String] 129 | // classType: Option[Type[_]] = None // Internal use only! (fixes broken Classloader for Java classes inside a macro) 130 | )(using quotes: Quotes)(using tt: Type[R]) 131 | extends ClassRef[R]: 132 | import quotes.reflect.* 133 | import Liftables.ListTypeSymbolToExpr 134 | 135 | val typedName: TypedName = name 136 | val refType = tt 137 | 138 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 139 | 140 | val expr = 141 | Apply( 142 | TypeApply( 143 | Select.unique(New(TypeTree.of[JavaClassRType]), ""), 144 | List(TypeTree.of[R]) 145 | ), 146 | List( 147 | Expr(name).asTerm, 148 | Expr.ofList(fields.map(_.expr)).asTerm, 149 | Expr(typeParamSymbols).asTerm, 150 | Expr.ofList(typeParamValues.map(_.expr)).asTerm, 151 | Expr(annotations).asTerm, 152 | Expr(mixins).asTerm 153 | ) 154 | ).asExprOf[RType[R]] 155 | 156 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 157 | JsonObjectBuilder(quotes)( 158 | sb, 159 | List( 160 | JsonField("rtype", "JavaClassRType"), 161 | JsonField("name", this.name), 162 | JsonField("typedName", this.typedName), 163 | JsonField("typeParamSymbols", this.typeParamSymbols), 164 | JsonField("typeParamValues", this.typeParamValues), 165 | JsonField("fields", this.fields), 166 | JsonField("annotations", this.annotations), 167 | JsonField("mixins", this.mixins) 168 | ) 169 | ) 170 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/CollectionRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | /** Marker trait for all Scala/Java collections */ 6 | trait CollectionRef[R] extends AppliedRef: 7 | self: RTypeRef[R] => 8 | 9 | val typedName: TypedName = name + "[" + elementRef.typedName + "]" 10 | 11 | val elementRef: RTypeRef[?] 12 | val selectLimit: Int = 1 13 | 14 | def select(i: Int): RTypeRef[?] = 15 | if i == 0 then elementRef 16 | else throw new ReflectException(s"AppliedType select index $i out of range for ${self.name}") 17 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/EnumRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.{JavaEnumRType, ScalaEnumerationRType, ScalaEnumRType} 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Something to smooth the differences between the 2.x Enumeration class and the 3.x Enum class 10 | */ 11 | trait EnumRef[R] extends RTypeRef[R]: 12 | val values: List[String] 13 | 14 | val expr: Expr[RType[R]] 15 | 16 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 17 | JsonObjectBuilder(quotes)( 18 | sb, 19 | List( 20 | JsonField("rtype", util.Pretty.lastPart(this.getClass.getName)), 21 | JsonField("name", this.name), 22 | JsonField("typedName", this.typedName), 23 | JsonField("values", this.values) 24 | ) 25 | ) 26 | 27 | //---------------------------------------------------------< Scala 3 Enum 28 | 29 | case class ScalaEnumRef[R]( 30 | name: String, 31 | values: List[String] 32 | )(using quotes: Quotes)(using tt: Type[R]) 33 | extends EnumRef[R]: 34 | import quotes.reflect.* 35 | val typedName: TypedName = name 36 | val refType = tt 37 | 38 | val unitVal = '{ null.asInstanceOf[R] } 39 | 40 | val expr = 41 | Apply( 42 | TypeApply( 43 | Select.unique(New(TypeTree.of[ScalaEnumRType[R]]), ""), 44 | List(TypeTree.of[R]) 45 | ), 46 | List( 47 | Expr(name).asTerm, 48 | Expr(values).asTerm 49 | ) 50 | ).asExprOf[RType[R]] 51 | 52 | //---------------------------------------------------------< Scala 2 Enumeration 53 | 54 | case class ScalaEnumerationRef[R]( 55 | name: String, 56 | values: List[String] 57 | )(using quotes: Quotes)(using tt: Type[R]) 58 | extends EnumRef[R]: 59 | import quotes.reflect.* 60 | val typedName: TypedName = name 61 | val refType = tt 62 | 63 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 64 | 65 | val expr = 66 | Apply( 67 | TypeApply( 68 | Select.unique(New(TypeTree.of[ScalaEnumerationRType[R]]), ""), 69 | List(TypeTree.of[R]) 70 | ), 71 | List( 72 | Expr(name).asTerm, 73 | Expr(values).asTerm 74 | ) 75 | ).asExprOf[RType[R]] 76 | 77 | //---------------------------------------------------------< Java Enumeration 78 | 79 | // When we get here: we can use class.getEnumConstants() to return array of T, the valid values of a Java enum 80 | case class JavaEnumRef[R]( 81 | name: String, 82 | values: List[String] 83 | )(using quotes: Quotes)(using tt: Type[R]) 84 | extends EnumRef[R]: 85 | import quotes.reflect.* 86 | val typedName: TypedName = name 87 | val refType = tt 88 | 89 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 90 | 91 | val expr = 92 | Apply( 93 | TypeApply( 94 | Select.unique(New(TypeTree.of[JavaEnumRType[R]]), ""), 95 | List(TypeTree.of[R]) 96 | ), 97 | List( 98 | Expr(name).asTerm, 99 | Expr(values).asTerm 100 | ) 101 | ).asExprOf[RType[R]] 102 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/FieldInfoRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.{FieldInfo, NonConstructorFieldInfo, ScalaFieldInfo} 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Base information we keep for all class fields, regardless of whether Scala or Java 10 | */ 11 | trait FieldInfoRef: 12 | val index: Int 13 | val name: String 14 | val fieldRef: RTypeRef[?] 15 | val originalSymbol: Option[TypeSymbol] 16 | val annotations: Map[String, Map[String, String]] 17 | 18 | val expr: Expr[FieldInfo] 19 | 20 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 21 | JsonObjectBuilder(quotes)( 22 | sb, 23 | List( 24 | JsonField("name", this.name), 25 | JsonField("fieldType", this.fieldRef), 26 | JsonField("originalSymbol", this.originalSymbol), 27 | JsonField("annotations", this.annotations) 28 | ) 29 | ) 30 | 31 | //------------------------------------------------------------ 32 | 33 | /** Describes reflected information we gleen from a Scala class field 34 | * 35 | * @param index 36 | * @param name 37 | * @param fieldType 38 | * @param annotations 39 | * @param defaultValueAccessorName 40 | * @param originalSymbol 41 | * @param isNonValConstructorField 42 | */ 43 | case class ScalaFieldInfoRef( 44 | index: Int, 45 | name: String, 46 | fieldRef: RTypeRef[?], 47 | annotations: Map[String, Map[String, String]], 48 | defaultValueAccessorName: Option[(String, String)], // (companion class name, method) 49 | originalSymbol: Option[TypeSymbol], 50 | isNonValConstructorField: Boolean = false // meaningful for non-case classes 51 | )(using quotes: Quotes) 52 | extends FieldInfoRef: 53 | import quotes.reflect.* 54 | import Liftables.OptTypeSymbolToExpr 55 | 56 | val expr = 57 | Apply( 58 | Select.unique(New(TypeTree.of[ScalaFieldInfo]), ""), 59 | List( 60 | Expr(index).asTerm, 61 | Expr(name).asTerm, 62 | fieldRef.expr.asTerm, 63 | Expr(annotations).asTerm, 64 | Expr(defaultValueAccessorName).asTerm, 65 | Expr(originalSymbol).asTerm, 66 | Expr(isNonValConstructorField).asTerm 67 | ) 68 | ).asExprOf[FieldInfo] 69 | 70 | //------------------------------------------------------------ 71 | 72 | case class NonConstructorFieldInfoRef( 73 | index: Int, 74 | name: String, 75 | getterLabel: String, 76 | setterLabel: String, 77 | getterIsVal: Boolean, 78 | fieldRef: RTypeRef[?], 79 | annotations: Map[String, Map[String, String]], 80 | originalSymbol: Option[TypeSymbol] = None 81 | )(using quotes: Quotes) 82 | extends FieldInfoRef: 83 | import quotes.reflect.* 84 | import Liftables.OptTypeSymbolToExpr 85 | 86 | val expr = 87 | Apply( 88 | Select.unique(New(TypeTree.of[NonConstructorFieldInfo]), ""), 89 | List( 90 | Expr(index).asTerm, 91 | Expr(name).asTerm, 92 | Expr(getterLabel).asTerm, 93 | Expr(setterLabel).asTerm, 94 | Expr(getterIsVal).asTerm, 95 | fieldRef.expr.asTerm, 96 | Expr(annotations).asTerm, 97 | Expr(originalSymbol).asTerm 98 | ) 99 | ).asExprOf[FieldInfo] 100 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/IterableRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.IterableRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Arity 1 Collections, e.g. List, Set, Seq */ 10 | case class IterableRef[R <: scala.collection.Iterable[?]]( 11 | name: String, 12 | typeParamSymbols: List[TypeSymbol], 13 | elementRef: RTypeRef[?] 14 | )(using quotes: Quotes)(using tt: Type[R]) 15 | extends RTypeRef[R] 16 | with CollectionRef[R]: 17 | import quotes.reflect.* 18 | import Liftables.ListTypeSymbolToExpr 19 | 20 | val refType = tt 21 | val isMutable = name.contains(".mutable.") 22 | 23 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 24 | 25 | val expr = 26 | Apply( 27 | TypeApply( 28 | Select.unique(New(TypeTree.of[IterableRType[R]]), ""), 29 | List(TypeTree.of[R]) 30 | ), 31 | List( 32 | Expr(name).asTerm, 33 | Expr(typeParamSymbols).asTerm, 34 | elementRef.expr.asTerm 35 | ) 36 | ).asExprOf[RType[R]] 37 | 38 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 39 | JsonObjectBuilder(quotes)( 40 | sb, 41 | List( 42 | JsonField("rtype", "IterableRType"), 43 | JsonField("name", this.name), 44 | JsonField("typedName", this.typedName), 45 | JsonField("typeParamSymbols", this.typeParamSymbols), 46 | JsonField("elementType", this.elementRef) 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/JavaCollectionRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.JavaCollectionRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class JavaCollectionRef[R]( 10 | name: String, 11 | typeParamSymbols: List[TypeSymbol], 12 | elementRef: RTypeRef[?] 13 | )(using quotes: Quotes)(using tt: Type[R]) 14 | extends RTypeRef[R] 15 | with CollectionRef[R]: 16 | import quotes.reflect.* 17 | import Liftables.ListTypeSymbolToExpr 18 | 19 | val refType = tt 20 | 21 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 22 | 23 | val expr = 24 | Apply( 25 | TypeApply( 26 | Select.unique(New(TypeTree.of[JavaCollectionRType[R]]), ""), 27 | List(TypeTree.of[R]) 28 | ), 29 | List( 30 | Expr(name).asTerm, 31 | Expr(typeParamSymbols).asTerm, 32 | elementRef.expr.asTerm 33 | ) 34 | ).asExprOf[RType[R]] 35 | 36 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 37 | JsonObjectBuilder(quotes)( 38 | sb, 39 | List( 40 | JsonField("rtype", "JavaCollectionRType"), 41 | JsonField("name", this.name), 42 | JsonField("typedName", this.typedName), 43 | JsonField("typeParamSymbols", this.typeParamSymbols), 44 | JsonField("elementType", this.elementRef) 45 | ) 46 | ) 47 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/JavaMapRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.JavaMapRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Arity 2 Collections, Map flavors, basiclly */ 10 | case class JavaMapRef[R]( 11 | name: String, 12 | isOrdered: Boolean, 13 | typeParamSymbols: List[TypeSymbol], 14 | elementRef: RTypeRef[?], // map key 15 | elementRef2: RTypeRef[?] // map value 16 | )(using quotes: Quotes)(using tt: Type[R]) 17 | extends RTypeRef[R] 18 | with CollectionRef[R]: 19 | import quotes.reflect.* 20 | import Liftables.ListTypeSymbolToExpr 21 | 22 | val refType = tt 23 | 24 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 25 | 26 | override val typedName: TypedName = name + "[" + elementRef.typedName + "," + elementRef2.typedName + "]" 27 | override val selectLimit: Int = 2 28 | override def select(i: Int): RTypeRef[?] = 29 | i match { 30 | case 0 => elementRef 31 | case 1 => elementRef2 32 | case _ => throw new ReflectException(s"AppliedType select index $i out of range for $name") 33 | } 34 | 35 | val expr = 36 | Apply( 37 | TypeApply( 38 | Select.unique(New(TypeTree.of[JavaMapRType[R]]), ""), 39 | List(TypeTree.of[R]) 40 | ), 41 | List( 42 | Expr(name).asTerm, 43 | Expr(isOrdered).asTerm, 44 | Expr(typeParamSymbols).asTerm, 45 | elementRef.expr.asTerm, 46 | elementRef2.expr.asTerm 47 | ) 48 | ).asExprOf[RType[R]] 49 | 50 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 51 | JsonObjectBuilder(quotes)( 52 | sb, 53 | List( 54 | JsonField("rtype", "JavaMapRType"), 55 | JsonField("name", this.name), 56 | JsonField("typedName", this.typedName), 57 | JsonField("isOrdered", this.isOrdered), 58 | JsonField("typeParamSymbols", this.typeParamSymbols), 59 | JsonField("elementType", this.elementRef), 60 | JsonField("elementType2", this.elementRef2) 61 | ) 62 | ) 63 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/LeftRightRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import scala.annotation.tailrec 7 | import rtypes.{EitherRType, IntersectionRType, UnionRType} 8 | import util.{JsonField, JsonObjectBuilder} 9 | 10 | enum LRKind(name: String) { 11 | case EITHER extends LRKind("EitherRType") 12 | case INTERSECTION extends LRKind("IntersectionRType") 13 | case UNION extends LRKind("UnionRType") 14 | override def toString: String = this.name 15 | } 16 | 17 | given LRKindToExpr: ToExpr[LRKind] with { 18 | def apply(x: LRKind)(using Quotes): Expr[LRKind] = 19 | x match 20 | case LRKind.EITHER => '{ LRKind.EITHER } 21 | case LRKind.INTERSECTION => '{ LRKind.INTERSECTION } 22 | case LRKind.UNION => '{ LRKind.UNION } 23 | } 24 | 25 | /** Marker trait for all Scala/Java left/right types (either, intersection, union) */ 26 | case class LeftRightRef[R]( 27 | name: String, 28 | typeParamSymbols: List[TypeSymbol], 29 | leftRef: RTypeRef[?], 30 | rightRef: RTypeRef[?], 31 | lrkind: LRKind 32 | )(using quotes: Quotes)(using tt: Type[R]) 33 | extends RTypeRef[R] 34 | with AppliedRef: 35 | import quotes.reflect.* 36 | import Liftables.ListTypeSymbolToExpr 37 | 38 | val typedName: TypedName = name + "[" + leftRef.typedName + "," + rightRef.typedName + "]" 39 | val refType = tt 40 | 41 | val unitVal = lrkind match { 42 | case LRKind.EITHER => '{ null }.asExprOf[R] 43 | case _ => leftRef.unitVal.asInstanceOf[Expr[R]] 44 | } 45 | 46 | val selectLimit: Int = 2 47 | 48 | def select(i: Int): RTypeRef[?] = 49 | i match { 50 | case 0 => leftRef 51 | case 1 => rightRef 52 | case _ => throw new ReflectException(s"AppliedType select index $i out of range for $name") 53 | } 54 | 55 | type LRType[X] = X match 56 | case scala.util.Either[?, ?] => EitherRType[R] 57 | case ? & ? => IntersectionRType[R] 58 | case ? | ? => UnionRType[R] 59 | 60 | private val reprMap = Map( 61 | LRKind.EITHER -> { () => TypeRepr.typeConstructorOf(classOf[EitherRType[R]]) }, 62 | LRKind.INTERSECTION -> { () => TypeRepr.typeConstructorOf(classOf[IntersectionRType[R]]) }, 63 | LRKind.UNION -> { () => TypeRepr.typeConstructorOf(classOf[UnionRType[R]]) } 64 | ) 65 | 66 | val expr = 67 | implicit val q = quotes 68 | val pt = reprMap(lrkind)().asType.asInstanceOf[Type[LRType[R]]] 69 | tt match 70 | case '[t] => 71 | Apply( 72 | TypeApply( 73 | Select.unique(New(TypeTree.of[LRType[R]](using pt)), ""), 74 | List(TypeTree.of[R]) 75 | ), 76 | List( 77 | Expr(name).asTerm, 78 | Expr(typeParamSymbols).asTerm, 79 | leftRef.expr.asTerm, 80 | rightRef.expr.asTerm 81 | ) 82 | ).asExprOf[RType[R]] 83 | 84 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 85 | JsonObjectBuilder(quotes)( 86 | sb, 87 | List( 88 | JsonField("rtype", lrkind.toString), 89 | JsonField("name", name), 90 | JsonField("typedName", typedName), 91 | JsonField("typeParamSymbols", typeParamSymbols), 92 | JsonField("leftType", leftRef), 93 | JsonField("rightType", rightRef) 94 | ) 95 | ) 96 | 97 | // Some candy for ScalaJack 98 | lazy val hasOptionChild: Option[(String, Language)] = { 99 | @tailrec 100 | def loop(current: LeftRightRef[?], path: String): (String, Language) = 101 | current.rightRef match { 102 | case _: ScalaOptionRef[?] => (path + "r", Language.Scala) 103 | case _: JavaOptionalRef[?] => (path + "r", Language.Java) 104 | case t: LeftRightRef[?] => loop(t, path + "r") 105 | case _ => 106 | current.leftRef match { 107 | case _: ScalaOptionRef[?] => (path + "l", Language.Scala) 108 | case _: JavaOptionalRef[?] => (path + "l", Language.Java) 109 | case t: LeftRightRef[?] => loop(t, path + "l") 110 | case _ => ("", Language.Scala) 111 | } 112 | } 113 | 114 | loop(this, "") match { 115 | case (recipe, lang) if recipe.nonEmpty => Some((recipe, lang)) 116 | case _ => None 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/MapRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.MapRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Arity 2 Collections, Map flavors, basiclly */ 10 | case class MapRef[R <: scala.collection.Map[?, ?]]( 11 | name: String, 12 | isOrdered: Boolean, 13 | typeParamSymbols: List[TypeSymbol], 14 | elementRef: RTypeRef[?], // map key 15 | elementRef2: RTypeRef[?] // map value 16 | )(using quotes: Quotes)(using tt: Type[R]) 17 | extends RTypeRef[R] 18 | with CollectionRef[R]: 19 | import quotes.reflect.* 20 | import Liftables.ListTypeSymbolToExpr 21 | 22 | override val typedName: TypedName = name + "[" + elementRef.typedName + "," + elementRef2.typedName + "]" 23 | 24 | val refType = tt 25 | val isMutable = name.contains(".mutable.") 26 | 27 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 28 | 29 | override val selectLimit: Int = 2 30 | override def select(i: Int): RTypeRef[?] = 31 | i match { 32 | case 0 => elementRef 33 | case 1 => elementRef2 34 | case _ => throw new ReflectException(s"AppliedType select index $i out of range for $name") 35 | } 36 | 37 | val expr = 38 | Apply( 39 | TypeApply( 40 | Select.unique(New(TypeTree.of[MapRType[R]]), ""), 41 | List(TypeTree.of[R]) 42 | ), 43 | List( 44 | Expr(name).asTerm, 45 | Expr(isOrdered).asTerm, 46 | Expr(typeParamSymbols).asTerm, 47 | elementRef.expr.asTerm, 48 | elementRef2.expr.asTerm 49 | ) 50 | ).asExprOf[RType[R]] 51 | 52 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 53 | JsonObjectBuilder(quotes)( 54 | sb, 55 | List( 56 | JsonField("rtype", "MapRType"), 57 | JsonField("name", this.name), 58 | JsonField("typedName", this.typedName), 59 | JsonField("isOrdered", this.isOrdered), 60 | JsonField("typeParamSymbols", this.typeParamSymbols), 61 | JsonField("elementType", this.elementRef), 62 | JsonField("elementType2", this.elementRef2) 63 | ) 64 | ) 65 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/NeoTypeRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.NeoTypeRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Ideally we'd like to know the actual wrapped type in the NeoTypeRef, but sadly this is 10 | * impossible to know at compile time. Reflection here only gives us a Type and nothing 11 | * more. The 'given' at runtime actually binds it to an actual (typed) implementation. 12 | * So here we just record that this is a NeoType of a given type name and reftype. 13 | * 14 | * @param name 15 | * @param quotes 16 | * @param tt 17 | */ 18 | case class NeoTypeRef[R]( 19 | name: String, 20 | typedName: TypedName 21 | )(using quotes: Quotes)(using tt: Type[R]) 22 | extends RTypeRef[R]: 23 | import quotes.reflect.* 24 | import Liftables.TypedNameToExpr 25 | 26 | val refType = tt 27 | 28 | // Ugly casting directly into internal Quotes/Compiler implementation. Eeek! 29 | // Only way to get to Symbol.info equivalent, which is only exposed "naturally" in experimental context, 30 | // which doesn't work for us at all. This does the same thing, brute-forcing the issue. 31 | val wrappedTypeRef = 32 | val s = Symbol.requiredModule(typedName.toString).methodMember("validate").head.asInstanceOf[dotty.tools.dotc.core.Symbols.Symbol] 33 | implicit val c: dotty.tools.dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx 34 | s.denot.info.asInstanceOf[MethodType] match 35 | case MethodType(_, pps, _) => 36 | reflect.ReflectOnType[T](quotes)(pps.head)(using scala.collection.mutable.Map.empty[TypedName, Boolean]) 37 | 38 | val unitVal = '{ null.asInstanceOf[R] } 39 | 40 | val expr = 41 | Apply( 42 | TypeApply( 43 | Select.unique(New(TypeTree.of[NeoTypeRType[R]]), ""), 44 | List(TypeTree.of[R]) 45 | ), 46 | List( 47 | Expr(name).asTerm, 48 | Expr(typedName).asTerm 49 | ) 50 | ).asExprOf[RType[R]] 51 | 52 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 53 | JsonObjectBuilder(quotes)( 54 | sb, 55 | List( 56 | JsonField("rtype", "NeoTypeRType"), 57 | JsonField("name", this.name), 58 | JsonField("typedName", this.typedName) 59 | ) 60 | ) 61 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/NetRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | /** This file is very much like PrimitiveRef.scala in that holds network types (vs complex types like collections). 6 | * In theory we could have bundled all this under PrimitiveRef, but technically these types are not language primitives 7 | * or wrappers around language primitives, so in the name of integrity we'll separate them out here. 8 | */ 9 | 10 | import scala.quoted.* 11 | import rtypes.* 12 | import Clazzes.* 13 | import util.{JsonField, JsonObjectBuilder} 14 | 15 | /** Reference for all Java & Scala primitive types 16 | */ 17 | 18 | trait NetRef 19 | 20 | case class URLRef()(using quotes: Quotes)(using tt: Type[java.net.URL]) extends RTypeRef[java.net.URL] with NetRef: 21 | import quotes.reflect.* 22 | val name = Clazzes.URL_CLASS 23 | val typedName: TypedName = name 24 | override val isNullable = true 25 | val refType = tt 26 | 27 | val unitVal = '{ null.asInstanceOf[java.net.URL] }.asExprOf[java.net.URL] 28 | 29 | val expr = 30 | Apply( 31 | Select.unique(New(TypeTree.of[URLRType]), ""), 32 | Nil 33 | ).asExprOf[RType[java.net.URL]] 34 | 35 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 36 | JsonObjectBuilder(quotes)( 37 | sb, 38 | List( 39 | JsonField("rtype", "URLRType"), 40 | JsonField("name", name) 41 | ) 42 | ) 43 | 44 | case class URIRef()(using quotes: Quotes)(using tt: Type[java.net.URI]) extends RTypeRef[java.net.URI] with NetRef: 45 | import quotes.reflect.* 46 | val name = Clazzes.URI_CLASS 47 | val typedName: TypedName = name 48 | override val isNullable = true 49 | val refType = tt 50 | 51 | val unitVal = '{ null.asInstanceOf[java.net.URI] }.asExprOf[java.net.URI] 52 | 53 | val expr = 54 | Apply( 55 | Select.unique(New(TypeTree.of[URIRType]), ""), 56 | Nil 57 | ).asExprOf[RType[java.net.URI]] 58 | 59 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 60 | JsonObjectBuilder(quotes)( 61 | sb, 62 | List( 63 | JsonField("rtype", "URIRType"), 64 | JsonField("name", name) 65 | ) 66 | ) 67 | 68 | // Not really a "net" thing, but... here t'is... 69 | case class UUIDRef()(using quotes: Quotes)(using tt: Type[java.util.UUID]) extends RTypeRef[java.util.UUID] with NetRef: 70 | import quotes.reflect.* 71 | val name = Clazzes.UUID_CLASS 72 | val typedName: TypedName = name 73 | override val isNullable = true 74 | val refType = tt 75 | 76 | val unitVal = '{ null.asInstanceOf[java.util.UUID] }.asExprOf[java.util.UUID] 77 | 78 | val expr = 79 | Apply( 80 | Select.unique(New(TypeTree.of[UUIDRType]), ""), 81 | Nil 82 | ).asExprOf[RType[java.util.UUID]] 83 | 84 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 85 | JsonObjectBuilder(quotes)( 86 | sb, 87 | List( 88 | JsonField("rtype", "UUIDRType"), 89 | JsonField("name", name) 90 | ) 91 | ) 92 | 93 | object NetRef: 94 | // Pre-bake primitive types w/cached builder functions 95 | protected[scala_reflection] val simpleTypeMap = Map( 96 | URL_CLASS.asInstanceOf[TypedName] -> { (quotes: Quotes) => URLRef()(using quotes)(using Type.of[java.net.URL](using quotes)) }, 97 | URI_CLASS.asInstanceOf[TypedName] -> { (quotes: Quotes) => URIRef()(using quotes)(using Type.of[java.net.URI](using quotes)) }, 98 | UUID_CLASS.asInstanceOf[TypedName] -> { (quotes: Quotes) => UUIDRef()(using quotes)(using Type.of[java.util.UUID](using quotes)) } 99 | ) 100 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/ObjectRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.ObjectRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class ObjectRef[R]( 10 | name: String 11 | )(using quotes: Quotes)(using tt: Type[R]) 12 | extends RTypeRef[R]: 13 | import quotes.reflect.* 14 | 15 | val typedName = name 16 | val refType = tt 17 | 18 | val unitVal = Ref(TypeRepr.of[R].typeSymbol).asExprOf[R] // get object instance 19 | 20 | val expr = 21 | Apply( 22 | TypeApply( 23 | Select.unique(New(TypeTree.of[ObjectRType[R]]), ""), 24 | List(TypeTree.of[R]) 25 | ), 26 | List( 27 | Expr(name).asTerm 28 | ) 29 | ).asExprOf[RType[R]] 30 | 31 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 32 | JsonObjectBuilder(quotes)( 33 | sb, 34 | List( 35 | JsonField("rtype", "ObjectRType"), 36 | JsonField("name", this.name), 37 | JsonField("typedName", this.typedName) 38 | ) 39 | ) 40 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/OptionRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.{JavaOptionalRType, ScalaOptionRType} 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | trait OptionRef[R] extends RTypeRef[R] with AppliedRef: 10 | val typeParamSymbols: List[TypeSymbol] 11 | val optionParamType: RTypeRef[?] 12 | 13 | val selectLimit: Int = 1 14 | def select(i: Int): RTypeRef[?] = 15 | if i == 0 then optionParamType 16 | else throw new ReflectException(s"AppliedType select index $i out of range for $name") 17 | 18 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 19 | JsonObjectBuilder(quotes)( 20 | sb, 21 | List( 22 | JsonField("rtype", util.Pretty.lastPart(this.getClass.getName).replace("Ref", "RType")), 23 | JsonField("name", this.name), 24 | JsonField("typedName", this.typedName), 25 | JsonField("typeParamSymbols", this.typeParamSymbols), 26 | JsonField("optionParamType", this.optionParamType) 27 | ) 28 | ) 29 | 30 | //------------------- 31 | 32 | case class ScalaOptionRef[R]( 33 | name: String, 34 | typeParamSymbols: List[TypeSymbol], 35 | optionParamType: RTypeRef[?] 36 | )(using quotes: Quotes)(using tt: Type[R]) 37 | extends OptionRef[R]: 38 | import quotes.reflect.* 39 | import Liftables.ListTypeSymbolToExpr 40 | 41 | val typedName: TypedName = name + "[" + optionParamType.typedName + "]" 42 | val refType = tt 43 | 44 | val unitVal = '{ None }.asExprOf[R] 45 | 46 | val expr = 47 | Apply( 48 | TypeApply( 49 | Select.unique(New(TypeTree.of[ScalaOptionRType[R]]), ""), 50 | List(TypeTree.of[R]) 51 | ), 52 | List( 53 | Expr(name).asTerm, 54 | Expr(typeParamSymbols).asTerm, 55 | optionParamType.expr.asTerm 56 | ) 57 | ).asExprOf[RType[R]] 58 | 59 | //------------------- 60 | 61 | case class JavaOptionalRef[R]( 62 | name: String, 63 | typeParamSymbols: List[TypeSymbol], 64 | optionParamType: RTypeRef[?] 65 | )(using quotes: Quotes)(using tt: Type[R]) 66 | extends OptionRef[R]: 67 | import quotes.reflect.* 68 | import Liftables.ListTypeSymbolToExpr 69 | 70 | val typedName: TypedName = name + "[" + optionParamType.typedName + "]" 71 | val refType = tt 72 | 73 | val unitVal = '{ java.util.Optional.empty().asInstanceOf[R] }.asExprOf[R] 74 | 75 | val expr = 76 | Apply( 77 | TypeApply( 78 | Select.unique(New(TypeTree.of[JavaOptionalRType[R]]), ""), 79 | List(TypeTree.of[R]) 80 | ), 81 | List( 82 | Expr(name).asTerm, 83 | Expr(typeParamSymbols).asTerm, 84 | optionParamType.expr.asTerm 85 | ) 86 | ).asExprOf[RType[R]] 87 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/Sealable.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | // Common ancestor of ScalaClassRef and TraitRef because both can be sealed 6 | 7 | trait Sealable: 8 | val name: String 9 | val typedName: TypedName 10 | val sealedChildren: List[RTypeRef[?]] 11 | val childrenAreObject: Boolean 12 | def isSealed: Boolean 13 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/SelfRefRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.SelfRefRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Placeholder RType to be lazy-resolved, used for self-referencing types. This is needed because without it, reflecting on 10 | * a self-referencing type will enter an endless loop until the stack explodes. This RType is immediately inserted into the 11 | * type cache so that when the self-reference comes there's something in the cache to find. 12 | * When one of these is encountered in the wild, just re-Reflect on the infoClass and you'll get the non-SelfRef (i.e. normal) RType 13 | */ 14 | case class SelfRefRef[R]( 15 | name: String, 16 | typedName: TypedName 17 | )(using quotes: Quotes)(using tt: Type[R]) 18 | extends RTypeRef[R]: 19 | import quotes.reflect.* 20 | import Liftables.TypedNameToExpr 21 | 22 | val refType = tt 23 | 24 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 25 | 26 | val expr = 27 | Apply( 28 | TypeApply( 29 | Select.unique(New(TypeTree.of[SelfRefRType[R]]), ""), 30 | List(TypeTree.of[R]) 31 | ), 32 | List( 33 | Expr(name).asTerm, 34 | Expr(typedName).asTerm 35 | ) 36 | ).asExprOf[RType[R]] 37 | 38 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 39 | JsonObjectBuilder(quotes)( 40 | sb, 41 | List( 42 | JsonField("rtype", "SelfRefRType"), 43 | JsonField("name", this.name), 44 | JsonField("typedName", this.typedName) 45 | ) 46 | ) 47 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/SeqRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.SeqRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Arity 1 Collections, e.g. List, Set, Seq */ 10 | case class SeqRef[R <: scala.collection.Seq[?]]( 11 | name: String, 12 | typeParamSymbols: List[TypeSymbol], 13 | elementRef: RTypeRef[?] 14 | )(using quotes: Quotes)(using tt: Type[R]) 15 | extends RTypeRef[R] 16 | with CollectionRef[R]: 17 | import quotes.reflect.* 18 | import Liftables.ListTypeSymbolToExpr 19 | 20 | val refType = tt 21 | val isMutable = name.contains(".mutable.") 22 | 23 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 24 | 25 | val expr = 26 | Apply( 27 | TypeApply( 28 | Select.unique(New(TypeTree.of[SeqRType[R]]), ""), 29 | List(TypeTree.of[R]) 30 | ), 31 | List( 32 | Expr(name).asTerm, 33 | Expr(typeParamSymbols).asTerm, 34 | elementRef.expr.asTerm 35 | ) 36 | ).asExprOf[RType[R]] 37 | 38 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 39 | JsonObjectBuilder(quotes)( 40 | sb, 41 | List( 42 | JsonField("rtype", "SeqRType"), 43 | JsonField("name", this.name), 44 | JsonField("typedName", this.typedName), 45 | JsonField("typeParamSymbols", this.typeParamSymbols), 46 | JsonField("elementType", this.elementRef) 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/SetRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.SetRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** Arity 1 Collections, e.g. List, Set, Seq */ 10 | case class SetRef[R <: scala.collection.Set[?]]( 11 | name: String, 12 | typeParamSymbols: List[TypeSymbol], 13 | elementRef: RTypeRef[?] 14 | )(using quotes: Quotes)(using tt: Type[R]) 15 | extends RTypeRef[R] 16 | with CollectionRef[R]: 17 | import quotes.reflect.* 18 | import Liftables.ListTypeSymbolToExpr 19 | 20 | val refType = tt 21 | val isMutable = name.contains(".mutable.") 22 | 23 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 24 | 25 | val expr = 26 | Apply( 27 | TypeApply( 28 | Select.unique(New(TypeTree.of[SetRType[R]]), ""), 29 | List(TypeTree.of[R]) 30 | ), 31 | List( 32 | Expr(name).asTerm, 33 | Expr(typeParamSymbols).asTerm, 34 | elementRef.expr.asTerm 35 | ) 36 | ).asExprOf[RType[R]] 37 | 38 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 39 | JsonObjectBuilder(quotes)( 40 | sb, 41 | List( 42 | JsonField("rtype", "SetRType"), 43 | JsonField("name", this.name), 44 | JsonField("typedName", this.typedName), 45 | JsonField("typeParamSymbols", this.typeParamSymbols), 46 | JsonField("elementType", this.elementRef) 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/TraitRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.TraitRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class TraitRef[R]( 10 | name: String, 11 | typedName: TypedName, 12 | fields: List[FieldInfoRef], 13 | typeParamSymbols: List[TypeSymbol] = Nil, // Like T,U 14 | typeParamValues: List[RTypeRef[?]] = Nil, // Like Int, Boolean 15 | sealedChildren: List[RTypeRef[?]] = Nil, // Populated only if this is a sealed trait 16 | childrenAreObject: Boolean = false 17 | )(using quotes: Quotes)(using tt: Type[R]) 18 | extends RTypeRef[R] 19 | with AppliedRef 20 | with Sealable: 21 | import quotes.reflect.* 22 | import Liftables.{ListTypeSymbolToExpr, TypedNameToExpr} 23 | 24 | val refType = tt 25 | 26 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 27 | 28 | val selectLimit: Int = fields.size 29 | def select(i: Int): RTypeRef[?] = 30 | if i >= 0 && i < selectLimit then fields(i).fieldRef 31 | else throw new ReflectException(s"AppliedType select index $i out of range for $name") 32 | 33 | def isSealed: Boolean = sealedChildren.nonEmpty 34 | 35 | val expr = 36 | Apply( 37 | TypeApply( 38 | Select.unique(New(TypeTree.of[TraitRType]), ""), 39 | List(TypeTree.of[R]) 40 | ), 41 | List( 42 | Expr(name).asTerm, 43 | Expr(typedName).asTerm, 44 | Expr.ofList(fields.map(_.expr)).asTerm, 45 | Expr(typeParamSymbols).asTerm, 46 | Expr.ofList(typeParamValues.map(_.expr)).asTerm, 47 | Expr.ofList(sealedChildren.map(_.expr)).asTerm, 48 | Expr(childrenAreObject).asTerm 49 | ) 50 | ).asExprOf[RType[R]] 51 | 52 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 53 | JsonObjectBuilder(quotes)( 54 | sb, 55 | List( 56 | JsonField("rtype", "TraitRType"), 57 | JsonField("name", this.name), 58 | JsonField("typedName", this.typedName), 59 | JsonField("typeParamSymbols", this.typeParamSymbols), 60 | JsonField("typeParamValues", this.typeParamValues), 61 | JsonField("sealedChildren", this.sealedChildren), 62 | JsonField("childrenAreObject", this.childrenAreObject) 63 | ) 64 | ) 65 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/TryRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.TryRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class TryRef[R]( 10 | name: String, 11 | typeParamSymbols: List[TypeSymbol], 12 | tryRef: RTypeRef[?] 13 | )(using quotes: Quotes)(using tt: Type[R]) 14 | extends RTypeRef[R] 15 | with AppliedRef: 16 | import quotes.reflect.* 17 | import Liftables.ListTypeSymbolToExpr 18 | 19 | val typedName: TypedName = name + "[" + tryRef.typedName + "]" 20 | val refType = tt 21 | 22 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 23 | 24 | val selectLimit: Int = 1 25 | 26 | def select(i: Int): RTypeRef[?] = 27 | if i == 0 then tryRef 28 | else throw new ReflectException(s"AppliedType select index $i out of range for $name") 29 | 30 | val expr = 31 | Apply( 32 | TypeApply( 33 | Select.unique(New(TypeTree.of[TryRType[R]]), ""), 34 | List(TypeTree.of[R]) 35 | ), 36 | List( 37 | Expr(name).asTerm, 38 | Expr(typeParamSymbols).asTerm, 39 | tryRef.expr.asTerm 40 | ) 41 | ).asExprOf[RType[R]] 42 | 43 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 44 | JsonObjectBuilder(quotes)( 45 | sb, 46 | List( 47 | JsonField("rtype", "TryRType"), 48 | JsonField("name", this.name), 49 | JsonField("typedName", this.typedName), 50 | JsonField("typeParamSymbols", this.typeParamSymbols), 51 | JsonField("tryType", this.tryRef) 52 | ) 53 | ) 54 | 55 | // candy for ScalaJack 56 | lazy val hasOptionChild: Option[Language] = 57 | tryRef match 58 | case o: ScalaOptionRef[?] => Some(Language.Scala) 59 | case o: JavaOptionalRef[?] => Some(Language.Java) 60 | case lr: LeftRightRef[?] => lr.hasOptionChild.map(_._2) 61 | case _ => None 62 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/TupleRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.TupleRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class TupleRef[R]( 10 | name: String, 11 | typeParamSymbols: List[TypeSymbol], 12 | tupleRefs: List[RTypeRef[?]] 13 | )(using quotes: Quotes)(using tt: Type[R]) 14 | extends RTypeRef[R] 15 | with AppliedRef: 16 | import quotes.reflect.* 17 | import Liftables.ListTypeSymbolToExpr 18 | 19 | val typedName: TypedName = name + tupleRefs.map(_.typedName).toList.mkString("[", ",", "]") 20 | val refType = tt 21 | 22 | val unitVal = '{ null }.asExprOf[R] 23 | 24 | val selectLimit: Int = tupleRefs.size 25 | 26 | def select(i: Int): RTypeRef[?] = 27 | if i >= 0 && i <= tupleRefs.size - 1 then tupleRefs(i) 28 | else throw new ReflectException(s"AppliedType select index $i out of range for $name") 29 | 30 | val expr = 31 | Apply( 32 | TypeApply( 33 | Select.unique(New(TypeTree.of[TupleRType[R]]), ""), 34 | List(TypeTree.of[R]) 35 | ), 36 | List( 37 | Expr(name).asTerm, 38 | Expr(typeParamSymbols).asTerm, 39 | Expr.ofList(tupleRefs.map(_.expr)).asTerm 40 | ) 41 | ).asExprOf[RType[R]] 42 | 43 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 44 | JsonObjectBuilder(quotes)( 45 | sb, 46 | List( 47 | JsonField("rtype", "TupleRType"), 48 | JsonField("name", this.name), 49 | JsonField("typedName", this.typedName), 50 | JsonField("typeParamSymbols", this.typeParamSymbols), 51 | JsonField("tupleTypes", this.tupleRefs) 52 | ) 53 | ) 54 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/TypeMemberRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.TypeMemberRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class TypeMemberRef( 10 | name: String, 11 | typeSymbol: Option[TypeSymbol], 12 | memberType: RTypeRef[?] 13 | )(using quotes: Quotes)(using tt: Type[Any]) 14 | extends RTypeRef[Any]: 15 | import quotes.reflect.* 16 | import Liftables.TypeSymbolToExpr 17 | 18 | val typedName: TypedName = name 19 | val refType = tt 20 | 21 | val unitVal = '{ null } 22 | 23 | val expr = 24 | Apply( 25 | Select.unique(New(TypeTree.of[TypeMemberRType]), ""), 26 | List( 27 | Expr(name).asTerm, 28 | Expr(typeSymbol).asTerm, 29 | memberType.expr.asTerm 30 | ) 31 | ).asExprOf[RType[Any]] 32 | 33 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 34 | JsonObjectBuilder(quotes)( 35 | sb, 36 | List( 37 | JsonField("rtype", "TypeMemberRType"), 38 | JsonField("name", this.name), 39 | JsonField("typeSymbol", this.typeSymbol), 40 | JsonField("memberType", this.memberType) 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/TypeSymbolRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.TypeSymbolRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** RType for an unassigned type symbol, e.g. Foo[T] 10 | */ 11 | 12 | case class TypeSymbolRef(name: String)(using quotes: Quotes)(using tt: Type[Any]) extends RTypeRef[Any]: 13 | import quotes.reflect.* 14 | 15 | val typedName: TypedName = name 16 | val refType = tt 17 | 18 | val unitVal = '{ "" } 19 | 20 | val expr = 21 | Apply( 22 | Select.unique(New(TypeTree.of[TypeSymbolRType]), ""), 23 | List( 24 | Expr(name).asTerm 25 | ) 26 | ).asExprOf[RType[Any]] 27 | 28 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 29 | JsonObjectBuilder(quotes)( 30 | sb, 31 | List( 32 | JsonField("rtype", "TypeSymbolRType"), 33 | JsonField("name", this.name) 34 | ) 35 | ) 36 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/UnkownRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import rtypes.UnknownRType 6 | import scala.quoted.* 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | case class UnknownRef[R](name: String)(using quotes: Quotes)(using tt: Type[R]) extends RTypeRef[R]: 10 | import quotes.reflect.* 11 | 12 | val typedName: TypedName = name 13 | val refType = tt 14 | 15 | val unitVal = '{ null.asInstanceOf[R] }.asExprOf[R] 16 | 17 | val expr = 18 | Apply( 19 | TypeApply( 20 | Select.unique(New(TypeTree.of[UnknownRType[R]]), ""), 21 | List(TypeTree.of[R]) 22 | ), 23 | List( 24 | Expr(name).asTerm 25 | ) 26 | ).asExprOf[RType[R]] 27 | 28 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 29 | JsonObjectBuilder(quotes)( 30 | sb, 31 | List( 32 | JsonField("rtype", "UnknownRType"), 33 | JsonField("name", this.name), 34 | JsonField("typedName", this.typedName) 35 | ) 36 | ) 37 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/reflect/rtypeRefs/WildcardRef.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package reflect 3 | package rtypeRefs 4 | 5 | import scala.quoted.* 6 | import rtypes.WildcardRType 7 | import util.{JsonField, JsonObjectBuilder} 8 | 9 | /** RType for an unassigned type symbol, e.g. Foo[T] 10 | */ 11 | 12 | object WildcardRef: 13 | def apply(lowBoundsRef: RTypeRef[?], highBoundsRef: RTypeRef[?])(using quotes: Quotes)(using tt: Type[Any]): WildcardRef = 14 | val low = if lowBoundsRef.name.contains("scala.Nothing") || lowBoundsRef.name.contains("scala.Any") then None else Some(lowBoundsRef) 15 | val hi = if highBoundsRef.name.contains("scala.Nothing") || highBoundsRef.name.contains("scala.Any") then None else Some(highBoundsRef) 16 | WildcardRef("?", low, hi) 17 | 18 | case class WildcardRef(name: String, lowBoundsRef: Option[RTypeRef[?]], highBoundsRef: Option[RTypeRef[?]])(using quotes: Quotes)(using tt: Type[Any]) extends RTypeRef[Any]: 19 | import quotes.reflect.* 20 | 21 | val typedName: TypedName = name 22 | val refType = tt 23 | 24 | val unitVal = '{ "" } 25 | 26 | val expr = 27 | Apply( 28 | Select.unique(New(TypeTree.of[WildcardRType]), ""), 29 | List( 30 | Expr(name).asTerm, 31 | ofOption(lowBoundsRef.map(_.expr)).asTerm, 32 | ofOption(highBoundsRef.map(_.expr)).asTerm 33 | ) 34 | ).asExprOf[RType[Any]] 35 | 36 | def asJson(sb: StringBuilder)(using quotes: Quotes): Unit = 37 | JsonObjectBuilder(quotes)( 38 | sb, 39 | List( 40 | JsonField("rtype", "WildcardRType"), 41 | JsonField("name", this.name), 42 | JsonField("lowBoundsType", this.lowBoundsRef), 43 | JsonField("highBoundsType", this.highBoundsRef) 44 | ) 45 | ) 46 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/AliasRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class AliasRType[T]( 5 | definedType: String, 6 | unwrappedType: RType[?] // Aliases with a parameterized wrapped type are not currently supported, so ConcreteType here. 7 | ) extends RType[T]: 8 | 9 | val name: String = definedType.drop(definedType.lastIndexOf('.') + 1) 10 | val typedName: TypedName = name 11 | override lazy val clazz = unwrappedType.clazz 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/AppliedRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** This RType mixin needed because all AppliedTypes don't have parameters. 5 | * For examlpe a case class could be an applied type (isAppliedType=true) or not. A collection is always applied. 6 | */ 7 | trait AppliedRType: 8 | self: RType[?] => 9 | 10 | val typeParamSymbols: List[TypeSymbol] 11 | def typeParamValues: List[RType[?]] 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/ArrayRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class ArrayRType[R]( 5 | name: String, 6 | typeParamSymbols: List[TypeSymbol], 7 | elementType: RType[?] 8 | ) extends RType[R] 9 | with CollectionRType[R]: 10 | 11 | val typedName = name + "[" + elementType.typedName + "]" 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/ClassRTypes.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.* 5 | 6 | trait ClassRType[R] extends RType[R] with AppliedRType: 7 | val name: String 8 | val fields: List[FieldInfo] 9 | val typeParamSymbols: List[TypeSymbol] 10 | val typeParamValues: List[RType[?]] 11 | val annotations: Map[String, Map[String, String]] 12 | val mixins: List[String] 13 | 14 | //------------------------------------------------------------------------------ 15 | 16 | case class ScalaClassRType[R]( 17 | name: String, 18 | typedName: TypedName, 19 | typeParamSymbols: List[TypeSymbol], 20 | typeParamValues: List[RType[?]], // Like Int, Boolean 21 | typeMembers: List[TypeMemberRType], 22 | fields: List[FieldInfo], 23 | annotations: Map[String, Map[String, String]], 24 | mixins: List[String], 25 | isAppliedType: Boolean, 26 | isValueClass: Boolean, 27 | isCaseClass: Boolean, 28 | isAbstractClass: Boolean, 29 | typePaths: Map[String, List[List[Int]]], // pre-computed path to each type symbol used (to get concrete types) 30 | nonConstructorFields: List[NonConstructorFieldInfo] = Nil, // Populated for non-case classes only 31 | sealedChildren: List[RType[?]] = Nil, // Populated only if this is a sealed class or abstract class 32 | childrenAreObject: Boolean = false 33 | ) extends ClassRType[R]: 34 | 35 | override def equals(obj: Any) = 36 | obj match { 37 | case s: ScalaClassRType[?] if s.name == this.name => s.fields.toList == this.fields.toList 38 | case _ => false 39 | } 40 | 41 | override lazy val clazz = Class.forName(util.AdjustClassName(name)) 42 | 43 | def isSealed: Boolean = sealedChildren.nonEmpty 44 | 45 | override def toType(quotes: Quotes): quoted.Type[R] = 46 | import quotes.reflect.* 47 | val classType: quoted.Type[R] = quotes.reflect.TypeRepr.typeConstructorOf(clazz).asType.asInstanceOf[quoted.Type[R]] 48 | val classTypeRepr = TypeRepr.of[R](using classType) 49 | val fieldTypes = fields.map { f => 50 | val oneFieldType = f.fieldType.toType(quotes) 51 | TypeRepr.of[f.fieldType.T](using oneFieldType) 52 | } 53 | AppliedType(classTypeRepr, fieldTypes).asType.asInstanceOf[quoted.Type[R]] 54 | 55 | //------------------------------------------------------------------------------ 56 | 57 | /** Java class reflection has a special problem... we need the class file, which isn't available during compilation (i.e. inside a macro). 58 | * So we need an internal-use-only field (classType) where we store Type[T] for the Java class--which we know during reflection. 59 | */ 60 | case class JavaClassRType[R]( 61 | name: String, 62 | fields: List[FieldInfo], 63 | typeParamSymbols: List[TypeSymbol], 64 | typeParamValues: List[RType[?]], 65 | annotations: Map[String, Map[String, String]], 66 | mixins: List[String] 67 | ) extends ClassRType[R]: 68 | 69 | val typedName: TypedName = name 70 | 71 | override def toType(quotes: Quotes): quoted.Type[R] = 72 | import quotes.reflect.* 73 | val classType: quoted.Type[?] = 74 | quotes.reflect.TypeRepr.typeConstructorOf(clazz).asType 75 | val classTypeRepr = TypeRepr.of[R](using classType.asInstanceOf[quoted.Type[R]]) 76 | val fieldTypes = fields.map { f => 77 | val oneFieldType = f.fieldType.toType(quotes) 78 | TypeRepr.of[f.fieldType.T](using oneFieldType) 79 | } 80 | AppliedType(classTypeRepr, fieldTypes).asType.asInstanceOf[quoted.Type[R]] 81 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/CollectionRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | /** This RType mixin needed because all AppliedTypes don't have parameters. 7 | * For examlpe a case class could be an applied type (isAppliedType=true) or not. A collection is always applied. 8 | */ 9 | trait CollectionRType[R] extends AppliedRType: 10 | self: RType[?] => 11 | 12 | val elementType: RType[?] 13 | def typeParamValues: List[RType[?]] = List(elementType) 14 | 15 | override def toType(quotes: Quotes): quoted.Type[R] = 16 | import quotes.reflect.* 17 | val collectionType: quoted.Type[R] = 18 | quotes.reflect.TypeRepr.typeConstructorOf(self.clazz).asType.asInstanceOf[quoted.Type[R]] 19 | val elType: quoted.Type[elementType.T] = elementType.toType(quotes) 20 | val collectionTypeRepr = TypeRepr.of[R](using collectionType) 21 | val elTypeRepr = TypeRepr.of[elementType.T](using elType) 22 | AppliedType(collectionTypeRepr, List(elTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 23 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/EitherRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | case class EitherRType[R]( 7 | name: String, 8 | typeParamSymbols: List[TypeSymbol], 9 | leftType: RType[?], 10 | rightType: RType[?] 11 | ) extends RType[R] 12 | with LeftRightRType[R]: 13 | 14 | val typedName: TypedName = name + "[" + leftType.typedName + "," + rightType.typedName + "]" 15 | def typeParamValues: List[RType[?]] = List(leftType, rightType) 16 | 17 | override def toType(quotes: Quotes): quoted.Type[R] = 18 | import quotes.reflect.* 19 | val lrType: quoted.Type[R] = 20 | quotes.reflect.TypeRepr.typeConstructorOf(clazz).asType.asInstanceOf[quoted.Type[R]] 21 | val leftParamType: quoted.Type[leftType.T] = leftType.toType(quotes) 22 | val rightParamType: quoted.Type[rightType.T] = rightType.toType(quotes) 23 | val lrTypeRepr = TypeRepr.of[R](using lrType) 24 | val leftParamTypeRepr = TypeRepr.of[leftType.T](using leftParamType) 25 | val rightParamTypeRepr = TypeRepr.of[rightType.T](using rightParamType) 26 | AppliedType(lrTypeRepr, List(leftParamTypeRepr, rightParamTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 27 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/EnumRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Something to smooth the differences between the 2.x Enumeration class and the 3.x Enum class 5 | */ 6 | trait EnumRType[R] extends RType[R]: 7 | val values: List[String] 8 | def ordinal(v: String): Option[Int] 9 | def valueAt(i: Int): Option[String] 10 | 11 | //---------------------------------------------------------< Scala 3 Enum 12 | 13 | case class ScalaEnumRType[R]( 14 | name: String, 15 | values: List[String] 16 | ) extends EnumRType[R]: 17 | val typedName: TypedName = name 18 | def ordinal(v: String): Option[Int] = values.indexOf(v) match { 19 | case -1 => None 20 | case i => Some(i) 21 | } 22 | def valueAt(i: Int): Option[String] = 23 | if i < 0 || i >= values.size then None 24 | else Some(values(i)) 25 | 26 | //---------------------------------------------------------< Scala 2 Enumeration 27 | 28 | case class ScalaEnumerationRType[R]( 29 | name: String, 30 | values: List[String] 31 | ) extends EnumRType[R]: 32 | val typedName: TypedName = name 33 | 34 | lazy val byName: Map[String, Int] = 35 | // We actually have to instantiate the Scala 2 Enumeration to extract all the goodies from it... 36 | val clazz = Class.forName(name) 37 | val valuesMethod = clazz.getMethod("values") 38 | val valueSet = valuesMethod.invoke(clazz) 39 | valueSet 40 | .asInstanceOf[scala.collection.immutable.AbstractSet[Enumeration#Value]] 41 | .map(one => (one.toString, one.id)) 42 | .toMap 43 | 44 | lazy val byId = byName.map(_.swap) 45 | 46 | def ordinal(v: String): Option[Int] = byName.get(v) 47 | def valueAt(i: Int): Option[String] = byId.get(i) 48 | 49 | //---------------------------------------------------------< Java Enumeration 50 | 51 | // When we get here: we can use class.getEnumConstants() to return array of T, the valid values of a Java enum 52 | case class JavaEnumRType[R]( 53 | name: String, 54 | values: List[String] 55 | ) extends EnumRType[R]: 56 | val typedName: TypedName = name 57 | 58 | def ordinal(v: String): Option[Int] = values.indexOf(v) match { 59 | case -1 => None 60 | case i => Some(i) 61 | } 62 | def valueAt(i: Int): Option[String] = 63 | if i < 0 || i >= values.size then None 64 | else Some(values(i)) 65 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/FieldInfo.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Base information we keep for all class fields, regardless of whether Scala or Java 5 | */ 6 | trait FieldInfo: 7 | val index: Int 8 | val name: String 9 | val fieldType: RType[?] 10 | val originalSymbol: Option[TypeSymbol] 11 | val annotations: Map[String, Map[String, String]] 12 | lazy val defaultValue: Option[Object] 13 | 14 | //------------------------------------------------------------ 15 | 16 | /** Describes reflected information we gleen from a Scala class field 17 | * 18 | * @param index 19 | * @param name 20 | * @param fieldType 21 | * @param annotations 22 | * @param defaultValueAccessorName 23 | * @param originalSymbol 24 | * @param isNonValConstructorField 25 | */ 26 | case class ScalaFieldInfo( 27 | index: Int, 28 | name: String, 29 | fieldType: RType[?], 30 | annotations: Map[String, Map[String, String]], 31 | defaultValueAccessorName: Option[(String, String)], // (companion class name, method) 32 | originalSymbol: Option[TypeSymbol], 33 | isNonValConstructorField: Boolean = false // meaningful for non-case classes 34 | ) extends FieldInfo: 35 | 36 | /** Default values of constructor fields, where present. This is a rare case where the clunky Java reflection way of getting 37 | * this information is better... we can get the default values and conveniently store them with the RType, vs some separate 38 | * macro call otherwise. 39 | */ 40 | lazy val defaultValue: Option[Object] = defaultValueAccessorName.map { (companionClass, accessor) => 41 | val companion = Class.forName(companionClass) 42 | val cons = companion.getDeclaredConstructors() 43 | cons(0).setAccessible(true) 44 | companion.getMethod(accessor).invoke(cons(0).newInstance()) 45 | } 46 | 47 | //------------------------------------------------------------ 48 | 49 | case class NonConstructorFieldInfo( 50 | index: Int, 51 | name: String, 52 | getterLabel: String, 53 | setterLabel: String, 54 | getterIsVal: Boolean, 55 | fieldType: RType[?], 56 | annotations: Map[String, Map[String, String]], 57 | originalSymbol: Option[TypeSymbol] = None 58 | ) extends FieldInfo: 59 | 60 | // Can't have default values for non-constructor fields b/c we can't instantiate 61 | // classes w/o knowing all the constructor parameters 62 | lazy val defaultValue: Option[Object] = None 63 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/IntersectionRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | case class IntersectionRType[R]( 7 | name: String, 8 | typeParamSymbols: List[TypeSymbol], 9 | leftType: RType[?], 10 | rightType: RType[?] 11 | ) extends RType[R] 12 | with LeftRightRType[R]: 13 | 14 | val typedName: TypedName = name + "[" + leftType.typedName + "," + rightType.typedName + "]" 15 | def typeParamValues: List[RType[?]] = List(leftType, rightType) 16 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 17 | 18 | override def toType(quotes: Quotes): quoted.Type[R] = 19 | import quotes.reflect.* 20 | val leftParamType: quoted.Type[leftType.T] = leftType.toType(quotes) 21 | val rightParamType: quoted.Type[rightType.T] = rightType.toType(quotes) 22 | val leftParamTypeRepr = TypeRepr.of[leftType.T](using leftParamType) 23 | val rightParamTypeRepr = TypeRepr.of[rightType.T](using rightParamType) 24 | AndType(leftParamTypeRepr, rightParamTypeRepr).asType.asInstanceOf[quoted.Type[R]] 25 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/IterableRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Arity 1 Collections, e.g. List, Set, Seq */ 5 | case class IterableRType[R]( 6 | name: String, 7 | typeParamSymbols: List[TypeSymbol], 8 | elementType: RType[?] 9 | ) extends RType[R] 10 | with CollectionRType[R]: 11 | 12 | val typedName: TypedName = name + "[" + elementType.typedName + "]" 13 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/JavaCollectionRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class JavaCollectionRType[R]( 5 | name: String, 6 | typeParamSymbols: List[TypeSymbol], 7 | elementType: RType[?] 8 | ) extends RType[R] 9 | with CollectionRType[R]: 10 | 11 | val typedName: TypedName = name + "[" + elementType.typedName + "]" 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/JavaMapRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | /** Arity 2 Collections, Map flavors, basiclly */ 7 | case class JavaMapRType[R]( 8 | name: String, 9 | isOrdered: Boolean, 10 | typeParamSymbols: List[TypeSymbol], 11 | elementType: RType[?], // map key 12 | elementType2: RType[?] // map value 13 | ) extends RType[R] 14 | with CollectionRType[R]: 15 | 16 | val typedName: TypedName = name + "[" + elementType.typedName + "," + elementType2.typedName + "]" 17 | 18 | override def toType(quotes: Quotes): quoted.Type[R] = 19 | import quotes.reflect.* 20 | val mapType: quoted.Type[R] = super.toType(quotes) 21 | val keyParamType: quoted.Type[elementType.T] = elementType.toType(quotes) 22 | val valueParamType: quoted.Type[elementType2.T] = elementType2.toType(quotes) 23 | val mapTypeRepr = TypeRepr.of[R](using mapType) 24 | val keyParamTypeRepr = TypeRepr.of[elementType.T](using keyParamType) 25 | val valueParamTypeRepr = TypeRepr.of[elementType2.T](using valueParamType) 26 | AppliedType(mapTypeRepr, List(keyParamTypeRepr, valueParamTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 27 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/LeftRightRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | trait LeftRightRType[R] extends AppliedRType: 5 | self: RType[?] => 6 | 7 | val leftType: RType[?] 8 | val rightType: RType[?] 9 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/MapRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | /** Arity 2 Collections, Map flavors, basiclly */ 7 | case class MapRType[R]( 8 | name: String, 9 | isOrdered: Boolean, 10 | typeParamSymbols: List[TypeSymbol], 11 | elementType: RType[?], // map key 12 | elementType2: RType[?] // map value 13 | ) extends RType[R] 14 | with AppliedRType: 15 | 16 | val typedName: TypedName = name + "[" + elementType.typedName + "," + elementType2.typedName + "]" 17 | def typeParamValues: List[RType[?]] = List(elementType, elementType2) 18 | 19 | override def toType(quotes: Quotes): quoted.Type[R] = 20 | import quotes.reflect.* 21 | val mapType: quoted.Type[R] = super.toType(quotes) 22 | val keyParamType: quoted.Type[elementType.T] = elementType.toType(quotes) 23 | val valueParamType: quoted.Type[elementType2.T] = elementType2.toType(quotes) 24 | val mapTypeRepr = TypeRepr.of[R](using mapType) 25 | val keyParamTypeRepr = TypeRepr.of[elementType.T](using keyParamType) 26 | val valueParamTypeRepr = TypeRepr.of[elementType2.T](using valueParamType) 27 | AppliedType(mapTypeRepr, List(keyParamTypeRepr, valueParamTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 28 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/NeoTypeRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class NeoTypeRType[T]( 5 | name: String, 6 | typedName: TypedName 7 | ) extends RType[T]: 8 | 9 | override lazy val clazz = classOf[Any] 10 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/NetRTypes.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import Clazzes.* 5 | 6 | trait NetRType 7 | 8 | case class URLRType() extends RType[java.net.URL] with NetRType: 9 | val name = URL_CLASS 10 | val typedName: TypedName = name 11 | 12 | case class URIRType() extends RType[java.net.URI] with NetRType: 13 | val name = URI_CLASS 14 | val typedName: TypedName = name 15 | 16 | case class UUIDRType() extends RType[java.util.UUID] with NetRType: 17 | val name = UUID_CLASS 18 | val typedName: TypedName = name 19 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/ObjectRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class ObjectRType[R]( 5 | name: String 6 | ) extends RType[R]: 7 | 8 | val typedName = name 9 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/OptionRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | trait OptionRType[R] extends RType[R] with AppliedRType: 7 | self: RType[?] => 8 | val typeParamSymbols: List[TypeSymbol] 9 | val optionParamType: RType[?] 10 | def typeParamValues: List[RType[?]] = List(optionParamType) 11 | val typedName: TypedName = name + "[" + optionParamType.typedName + "]" 12 | 13 | //------------------- 14 | 15 | case class ScalaOptionRType[R]( 16 | name: String, 17 | typeParamSymbols: List[TypeSymbol], 18 | optionParamType: RType[?] 19 | ) extends OptionRType[R]: 20 | 21 | override def toType(quotes: Quotes): quoted.Type[R] = 22 | import quotes.reflect.* 23 | val optType: quoted.Type[R] = super.toType(quotes) 24 | val paramType: quoted.Type[optionParamType.T] = optionParamType.toType(quotes) 25 | val optTypeRepr = TypeRepr.of[R](using optType) 26 | val paramTypeRepr = TypeRepr.of[optionParamType.T](using paramType) 27 | AppliedType(optTypeRepr, List(paramTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 28 | 29 | //------------------- 30 | 31 | case class JavaOptionalRType[R]( 32 | name: String, 33 | typeParamSymbols: List[TypeSymbol], 34 | optionParamType: RType[?] 35 | ) extends OptionRType[R]: 36 | 37 | override def toType(quotes: Quotes): quoted.Type[R] = 38 | import quotes.reflect.* 39 | val optType: quoted.Type[R] = 40 | super.toType(quotes).asInstanceOf[quoted.Type[R]] 41 | val paramType: quoted.Type[optionParamType.T] = optionParamType.toType(quotes) 42 | val optTypeRepr = TypeRepr.of[R](using optType) 43 | val paramTypeRepr = TypeRepr.of[optionParamType.T](using paramType) 44 | AppliedType(optTypeRepr, List(paramTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 45 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/PrimitiveRTypes.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import Clazzes.* 5 | import scala.math.{BigDecimal, BigInt} 6 | 7 | trait PrimitiveRType: 8 | self: RType[?] => 9 | val isNullable: Boolean 10 | 11 | //------ SCALA ------ 12 | case class BigDecimalRType() extends RType[BigDecimal] with PrimitiveRType: 13 | val name = BIG_DECIMAL_CLASS 14 | val typedName: TypedName = name 15 | override val isNullable: Boolean = true 16 | 17 | case class BigIntRType() extends RType[BigInt] with PrimitiveRType: 18 | val name = BIG_INT_CLASS 19 | val typedName: TypedName = name 20 | override val isNullable: Boolean = true 21 | 22 | case class BooleanRType() extends RType[Boolean] with PrimitiveRType: 23 | val name = BOOLEAN_CLASS 24 | val typedName: TypedName = name 25 | override val isNullable: Boolean = false 26 | 27 | case class ByteRType() extends RType[Byte] with PrimitiveRType: 28 | val name = BYTE_CLASS 29 | val typedName: TypedName = name 30 | override val isNullable: Boolean = false 31 | 32 | case class CharRType() extends RType[Char] with PrimitiveRType: 33 | val name = CHAR_CLASS 34 | val typedName: TypedName = name 35 | override val isNullable: Boolean = false 36 | 37 | case class DoubleRType() extends RType[Double] with PrimitiveRType: 38 | val name = DOUBLE_CLASS 39 | val typedName: TypedName = name 40 | override val isNullable: Boolean = false 41 | 42 | case class FloatRType() extends RType[Float] with PrimitiveRType: 43 | val name = FLOAT_CLASS 44 | val typedName: TypedName = name 45 | override val isNullable: Boolean = false 46 | 47 | case class IntRType() extends RType[Int] with PrimitiveRType: 48 | val name = INT_CLASS 49 | val typedName: TypedName = name 50 | override val isNullable: Boolean = false 51 | 52 | case class LongRType() extends RType[Long] with PrimitiveRType: 53 | val name = LONG_CLASS 54 | val typedName: TypedName = name 55 | override val isNullable: Boolean = false 56 | 57 | case class ShortRType() extends RType[Short] with PrimitiveRType: 58 | val name = SHORT_CLASS 59 | val typedName: TypedName = name 60 | override val isNullable: Boolean = false 61 | 62 | case class StringRType() extends RType[String] with PrimitiveRType: 63 | val name = STRING_CLASS 64 | val typedName: TypedName = name 65 | override val isNullable: Boolean = true 66 | 67 | case class AnyRType() extends RType[Any] with PrimitiveRType: 68 | val name = ANY_CLASS 69 | val typedName: TypedName = name 70 | override val isNullable: Boolean = true 71 | 72 | case class AnyValRType() extends RType[AnyVal] with PrimitiveRType: 73 | val name = ANYVAL_CLASS 74 | val typedName: TypedName = name 75 | override val isNullable: Boolean = true 76 | 77 | //------ JAVA ------ 78 | case class JavaBigDecimalRType() extends RType[java.math.BigDecimal] with PrimitiveRType: 79 | val name = JBIG_DECIMAL_CLASS 80 | val typedName: TypedName = name 81 | override val isNullable: Boolean = true 82 | 83 | case class JavaBigIntegerRType() extends RType[java.math.BigInteger] with PrimitiveRType: 84 | val name = JBIG_INTEGER_CLASS 85 | val typedName: TypedName = name 86 | override val isNullable: Boolean = true 87 | 88 | case class JavaBooleanRType() extends RType[java.lang.Boolean] with PrimitiveRType: 89 | val name = JBOOLEAN_CLASS 90 | val typedName: TypedName = name 91 | override val isNullable: Boolean = true 92 | 93 | case class JavaByteRType() extends RType[java.lang.Byte] with PrimitiveRType: 94 | val name = JBYTE_CLASS 95 | val typedName: TypedName = name 96 | override val isNullable: Boolean = true 97 | 98 | case class JavaCharacterRType() extends RType[java.lang.Character] with PrimitiveRType: 99 | val name = JCHARACTER_CLASS 100 | val typedName: TypedName = name 101 | override val isNullable: Boolean = true 102 | 103 | case class JavaDoubleRType() extends RType[java.lang.Double] with PrimitiveRType: 104 | val name = JDOUBLE_CLASS 105 | val typedName: TypedName = name 106 | override val isNullable: Boolean = true 107 | 108 | case class JavaFloatRType() extends RType[java.lang.Float] with PrimitiveRType: 109 | val name = JFLOAT_CLASS 110 | val typedName: TypedName = name 111 | override val isNullable: Boolean = true 112 | 113 | case class JavaIntegerRType() extends RType[java.lang.Integer] with PrimitiveRType: 114 | val name = JINTEGER_CLASS 115 | val typedName: TypedName = name 116 | override val isNullable: Boolean = true 117 | 118 | case class JavaLongRType() extends RType[java.lang.Long] with PrimitiveRType: 119 | val name = JLONG_CLASS 120 | val typedName: TypedName = name 121 | override val isNullable: Boolean = true 122 | 123 | case class JavaShortRType() extends RType[java.lang.Short] with PrimitiveRType: 124 | val name = JSHORT_CLASS 125 | val typedName: TypedName = name 126 | override val isNullable: Boolean = true 127 | 128 | case class JavaNumberRType() extends RType[java.lang.Number] with PrimitiveRType: 129 | val name = JNUMBER_CLASS 130 | val typedName: TypedName = name 131 | override val isNullable: Boolean = true 132 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/SelfRefRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Placeholder RType to be lazy-resolved, used for self-referencing types. This is needed because without it, reflecting on 5 | * a self-referencing type will enter an endless loop until the stack explodes. This RType is immediately inserted into the 6 | * type cache so that when the self-reference comes there's something in the cache to find. 7 | * When one of these is encountered in the wild, just re-Reflect on the infoClass and you'll get the non-SelfRef (i.e. normal) RType 8 | */ 9 | case class SelfRefRType[R](name: String, typedName: TypedName) extends RType[R] 10 | // val bogus: R = null.asInstanceOf[R] 11 | // type U = R 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/SeqRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Arity 1 Collections, e.g. List, Set, Seq */ 5 | case class SeqRType[R]( 6 | name: String, 7 | typeParamSymbols: List[TypeSymbol], 8 | elementType: RType[?] 9 | ) extends RType[R] 10 | with CollectionRType[R]: 11 | 12 | val typedName: TypedName = name + "[" + elementType.typedName + "]" 13 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/SetRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** Arity 1 Collections, e.g. List, Set, Seq */ 5 | case class SetRType[R]( 6 | name: String, 7 | typeParamSymbols: List[TypeSymbol], 8 | elementType: RType[?] 9 | ) extends RType[R] 10 | with CollectionRType[R]: 11 | 12 | val typedName: TypedName = name + "[" + elementType.typedName + "]" 13 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TimeRTypes.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import Clazzes.* 5 | 6 | trait TimeRType 7 | 8 | case class DurationRType() extends RType[java.time.Duration] with TimeRType: 9 | val name = DURATION_CLASS 10 | val typedName: TypedName = name 11 | 12 | case class InstantRType() extends RType[java.time.Instant] with TimeRType: 13 | val name = INSTANT_CLASS 14 | val typedName: TypedName = name 15 | 16 | case class LocalDateRType() extends RType[java.time.LocalDate] with TimeRType: 17 | val name = LOCALDATE_CLASS 18 | val typedName: TypedName = name 19 | 20 | case class LocalDateTimeRType() extends RType[java.time.LocalDateTime] with TimeRType: 21 | val name = LOCALDATETIME_CLASS 22 | val typedName: TypedName = name 23 | 24 | case class LocalTimeRType() extends RType[java.time.LocalTime] with TimeRType: 25 | val name = LOCALTIME_CLASS 26 | val typedName: TypedName = name 27 | 28 | case class MonthDayRType() extends RType[java.time.MonthDay] with TimeRType: 29 | val name = MONTHDAY_CLASS 30 | val typedName: TypedName = name 31 | 32 | case class OffsetDateTimeRType() extends RType[java.time.OffsetDateTime] with TimeRType: 33 | val name = OFFSETDATETIME_CLASS 34 | val typedName: TypedName = name 35 | 36 | case class OffsetTimeRType() extends RType[java.time.OffsetTime] with TimeRType: 37 | val name = OFFSETTIME_CLASS 38 | val typedName: TypedName = name 39 | 40 | case class PeriodRType() extends RType[java.time.Period] with TimeRType: 41 | val name = PERIOD_CLASS 42 | val typedName: TypedName = name 43 | 44 | case class YearRType() extends RType[java.time.Year] with TimeRType: 45 | val name = YEAR_CLASS 46 | val typedName: TypedName = name 47 | 48 | case class YearMonthRType() extends RType[java.time.YearMonth] with TimeRType: 49 | val name = YEARMONTH_CLASS 50 | val typedName: TypedName = name 51 | 52 | case class ZonedDateTimeRType() extends RType[java.time.ZonedDateTime] with TimeRType: 53 | val name = ZONEDDATETIME_CLASS 54 | val typedName: TypedName = name 55 | 56 | case class ZoneIdRType() extends RType[java.time.ZoneId] with TimeRType: 57 | val name = ZONEID_CLASS 58 | val typedName: TypedName = name 59 | 60 | case class ZoneOffsetRType() extends RType[java.time.ZoneOffset] with TimeRType: 61 | val name = ZONEOFFSET_CLASS 62 | val typedName: TypedName = name 63 | 64 | case class JavaObjectRType() extends RType[java.lang.Object] with TimeRType: 65 | val name = JOBJECT_CLASS 66 | val typedName: TypedName = name 67 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TraitRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | case class TraitRType[R]( 7 | name: String, 8 | typedName: TypedName, 9 | fields: List[FieldInfo], 10 | typeParamSymbols: List[TypeSymbol], // Like T,U 11 | typeParamValues: List[RType[?]], // Like Int, Boolean 12 | sealedChildren: List[RType[?]], // Populated only if this is a sealed class or abstract class 13 | childrenAreObject: Boolean 14 | ) extends RType[R] 15 | with AppliedRType: 16 | 17 | def isSealed: Boolean = sealedChildren.nonEmpty 18 | 19 | override def toType(quotes: Quotes): quoted.Type[R] = 20 | import quotes.reflect.* 21 | val traitType: quoted.Type[R] = quotes.reflect.TypeRepr.typeConstructorOf(clazz).asType.asInstanceOf[quoted.Type[R]] 22 | val traitTypeRepr = TypeRepr.of[R](using traitType) 23 | val fieldTypes = fields.map { f => 24 | val oneFieldType = f.fieldType.toType(quotes) 25 | TypeRepr.of[f.fieldType.T](using oneFieldType) 26 | } 27 | AppliedType(traitTypeRepr, fieldTypes).asType.asInstanceOf[quoted.Type[R]] 28 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TryRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | case class TryRType[R]( 7 | name: String, 8 | typeParamSymbols: List[TypeSymbol], 9 | tryType: RType[?] 10 | ) extends RType[R] 11 | with AppliedRType: 12 | 13 | val typedName: TypedName = name + "[" + tryType.typedName + "]" 14 | def typeParamValues: List[RType[?]] = List(tryType) 15 | 16 | override def toType(quotes: Quotes): quoted.Type[R] = 17 | import quotes.reflect.* 18 | val thisTryType: quoted.Type[R] = super.toType(quotes) 19 | val paramType: quoted.Type[tryType.T] = tryType.toType(quotes) 20 | val tryTypeRepr = TypeRepr.of[R](using thisTryType) 21 | val paramTypeRepr = TypeRepr.of[tryType.T](using paramType) 22 | AppliedType(tryTypeRepr, List(paramTypeRepr)).asType.asInstanceOf[quoted.Type[R]] 23 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TupleRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | case class TupleRType[R]( 6 | name: String, 7 | typeParamSymbols: List[TypeSymbol], 8 | typeParamValues: List[RType[?]] 9 | ) extends RType[R] 10 | with AppliedRType: 11 | 12 | val typedName: TypedName = name + typeParamValues.map(_.typedName).toList.mkString("[", ",", "]") 13 | 14 | override def toType(quotes: Quotes): quoted.Type[R] = 15 | import quotes.reflect.* 16 | val tupleType: quoted.Type[R] = super.toType(quotes) 17 | val tupleElementTypes: List[quoted.Type[?]] = typeParamValues.map(tt => tt.toType(quotes)) 18 | val tupleTypeRepr = TypeRepr.of[R](using tupleType) 19 | val tupleElementTypeRepr = typeParamValues.zip(tupleElementTypes).map { case (rt, rtType) => 20 | TypeRepr.of[rt.T](using rtType.asInstanceOf[quoted.Type[rt.T]]) 21 | } 22 | AppliedType(tupleTypeRepr, tupleElementTypeRepr).asType.asInstanceOf[quoted.Type[R]] 23 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TypeMemberRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class TypeMemberRType( 5 | name: String, // type symbol goes in name 6 | typeSymbol: Option[TypeSymbol], 7 | memberType: RType[?] 8 | ) extends RType[Any]: 9 | 10 | val typedName = name.asInstanceOf[TypedName] 11 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 12 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/TypeSymbolRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** RType for an unassigned type symbol, e.g. Foo[T] 5 | */ 6 | 7 | case class TypeSymbolRType(name: String) extends RType[Any]: 8 | val typedName = name.asInstanceOf[TypedName] 9 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 10 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/UnionRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | import scala.quoted.Quotes 5 | 6 | case class UnionRType[R]( 7 | name: String, 8 | typeParamSymbols: List[TypeSymbol], 9 | leftType: RType[?], 10 | rightType: RType[?] 11 | ) extends RType[R] 12 | with LeftRightRType[R]: 13 | 14 | val typedName: TypedName = name + "[" + leftType.typedName + "," + rightType.typedName + "]" 15 | def typeParamValues: List[RType[?]] = List(leftType, rightType) 16 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 17 | 18 | override def toType(quotes: Quotes): quoted.Type[R] = 19 | import quotes.reflect.* 20 | val leftParamType: quoted.Type[leftType.T] = leftType.toType(quotes) 21 | val rightParamType: quoted.Type[rightType.T] = rightType.toType(quotes) 22 | val leftParamTypeRepr = TypeRepr.of[leftType.T](using leftParamType) 23 | val rightParamTypeRepr = TypeRepr.of[rightType.T](using rightParamType) 24 | OrType(leftParamTypeRepr, rightParamTypeRepr).asType.asInstanceOf[quoted.Type[R]] 25 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/UnkownRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | case class UnknownRType[T](name: String) extends RType[T]: 5 | val typedName = name.asInstanceOf[TypedName] 6 | 7 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 8 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/rtypes/WildcardRType.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package rtypes 3 | 4 | /** RType for an bounded wildcard type symbol, e.g. [? <: Thing] 5 | */ 6 | 7 | case class WildcardRType(name: String, lowBoundsType: Option[RType[?]], highBoundsType: Option[RType[?]]) extends RType[Any]: 8 | val typedName = name.asInstanceOf[TypedName] 9 | override lazy val clazz: Class[?] = Clazzes.AnyClazz 10 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/run/Main.scalax: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package run 3 | 4 | import model.* 5 | 6 | object Main { 7 | 8 | // import VehicleClass.* 9 | def main(args: Array[String]): Unit = 10 | // type ComplexPerson = PersonX[Artist[Int, Hobby[Double, Char]], Vehicle[VehicleClass]] 11 | // println(RType.of[PP[Vehicle[VehicleClass]]].pretty) 12 | // println("done") 13 | 14 | val rt = RType.of[GregsTest] 15 | println(rt.pretty) 16 | 17 | } 18 | 19 | /* 20 | co.blocke.scala_reflection.model.GregsTest: 21 | fields -> 22 | foo: String 23 | bar: Int 24 | annotations -> Map(co.blocke.scala_reflection.model.xmlLabel -> Map(name -> blather)) 25 | annotations -> 26 | Map(co.blocke.scala_reflection.Foom -> Map(x$1 -> wow)) 27 | */ 28 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/run/Sample.scalax: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package model 3 | 4 | import neotype.* 5 | import dotty.tools.dotc.reporting.Message 6 | 7 | /* 8 | trait Miss[E] { val x: E } 9 | case class Foom[X](x: X) extends Miss[X] 10 | 11 | case class Person[Y](name: String, again: Option[Person[Y]]) 12 | 13 | case class Funny[Y](a: Hoovie[Y], b: card, c: Weather, d: Truth, e: Miss[Y]) 14 | 15 | // Sealed trait (non-enumeration) w/case class 16 | sealed trait Hoovie[X] { val item: X } 17 | case class Cmd[T](item: T) extends Hoovie[T] 18 | case class Quest[T](item: T) extends Hoovie[T] 19 | 20 | // Enumeration sealed trait w/case objects 21 | sealed trait card extends Enumeration 22 | case object CLUB extends card 23 | case object HEART extends card 24 | case object DIAMOND extends card 25 | case object SPADE extends card 26 | 27 | // Abstract Class w/case objects (Enumeration) 28 | sealed abstract class Weather(val temp: Double) 29 | case object Hot extends Weather(97.0) 30 | case object Cold extends Weather(25.0) 31 | 32 | sealed trait Truth 33 | case class Right(isBlessed: Boolean) extends Truth 34 | case class Wrong(excuse: String) extends Truth 35 | 36 | case class Mom[R](f: Miss[R]) 37 | 38 | case class PersonZ[T](msg: Hoovie[T]) 39 | 40 | // sealed trait Hoovie{ val item: Int} 41 | // case class Cmd(item: Int) extends Hoovie 42 | // case class Quest(item: Int) extends Hoovie 43 | 44 | // case class Person(name:String, age:Int) 45 | 46 | // type NonEmptyString = NonEmptyString.Type 47 | // given NonEmptyString: Newtype[String] with 48 | // inline def validate(input: String): Boolean = 49 | // input.nonEmpty 50 | 51 | // type MinorPerson = MinorPerson.Type 52 | // given MinorPerson: Newtype[Person] with 53 | // inline def validate(input: Person): Boolean = 54 | // input.age < 18 55 | 56 | // case class SampleNeo(name: NonEmptyString, label: String, unknown: MinorPerson) 57 | 58 | type Thing = List[Int] 59 | case class AliasHolder[T](a: T) 60 | 61 | type NonEmptyString = NonEmptyString2.Type 62 | object NonEmptyString2 extends Newtype[String]: 63 | override inline def validate(input: String): Boolean = 64 | input.nonEmpty 65 | 66 | type NonEmptyList = NonEmptyList.Type 67 | given NonEmptyList: Newtype[List[Int]] with 68 | inline def validate(input: List[Int]): Boolean = 69 | input.nonEmpty 70 | 71 | case class NeoPerson(age: NonEmptyList, desc: NonEmptyString, whatever: Any) 72 | 73 | sealed trait Candy 74 | case class Veggies() 75 | 76 | type Food = Candy | Veggies 77 | */ 78 | // object Size extends Enumeration { 79 | // val Small, Medium, Large = Value 80 | // } 81 | // object SizeWithType extends Enumeration { 82 | // type SizeWithType = Value 83 | // val Little, Grand = Value 84 | // } 85 | // import SizeWithType.* 86 | 87 | // case class SampleEnum(e1: Size.Value, e2: Size.Value, e3: Size.Value, e4: Size.Value, e5: Size.Value, e6: SizeWithType) 88 | 89 | /* 90 | object VehicleClass extends Enumeration { 91 | type VehicleClass = Value 92 | val Land, Air, Sea = Value 93 | } 94 | import VehicleClass.* 95 | 96 | sealed trait Vehicle[K] { val kind: K } 97 | case class Car(passengers: Int) extends Vehicle[Land.type] { val kind: Land.type = Land } 98 | 99 | sealed trait Hobby[X, Y] { val thing1: X; val thing2: Y } 100 | sealed trait Artist[W, Z] { val instrument: W; val effort: Z } 101 | sealed trait PersonX[X, Y] { val who: X; val org: Y } 102 | 103 | case class Sports[A, B](thing1: A, thing2: B) extends Hobby[A, B] 104 | case class Painter[A, B](instrument: A, effort: B) extends Artist[A, B] 105 | case class Employee[A, B, C, D](who: Artist[C, Hobby[D, A]], org: B) extends PersonX[Artist[C, Hobby[D, A]], B] 106 | 107 | case class PP[Z](a:Z) 108 | */ 109 | 110 | sealed trait Basic[A, B] { 111 | val a: A 112 | val b: B 113 | } 114 | 115 | case class Complex(a: Int, b: Boolean) extends Basic[Int, Boolean] 116 | // case class Complex[Y,Z](a:Y, b:Z) extends Basic[Y,Z] 117 | 118 | // sealed trait Hobby[X, Y] { val thing1: X; val thing2: Y } 119 | // sealed trait Artist[W, Z] { val instrument: W; val effort: Z } 120 | // sealed trait Person[X, Y] { val who: X; val org: Y } 121 | 122 | // case class Sports[A, B](thing1: A, thing2: B) extends Hobby[A, B] 123 | // case class Painter[A, B](instrument: A, effort: B) extends Artist[A, B] 124 | // case class Employee[A, B, C, D](who: Artist[C, Hobby[D, A]], org: B) extends Person[Artist[C, Hobby[D, A]], B] 125 | 126 | // // PersonX[String,Int] ==> Employee(Artist) 127 | 128 | class Message 129 | class Command extends Message 130 | class System extends Message 131 | 132 | case class XMIT(content: List[? <: Message]) // blows up 133 | //case class REC[T <: Message]() // works 134 | 135 | import scala.annotation.* 136 | case class xmlLabel(name: String) extends StaticAnnotation 137 | 138 | @Foom("wow") 139 | case class GregsTest(foo: String, @xmlLabel("blather") bar: Int) 140 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/util/AdjustClassName.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package util 3 | 4 | object AdjustClassName: 5 | 6 | /** @param name name of class 7 | * @return adjusted name - one that is converted to a class name that works with Java Reflection 8 | */ 9 | def apply(originalName: String): String = 10 | val splitPos = originalName.indexOf("$.") 11 | if splitPos == -1 then originalName 12 | else 13 | val prefix = originalName.substring(0, splitPos) 14 | val suffix = originalName.substring(splitPos + 1) 15 | new StringBuilder(originalName.length) 16 | .append(prefix) 17 | .append(suffix.replace(".", "$")) 18 | .toString() 19 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/util/JsonObjectBuilder.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package util 3 | 4 | import scala.quoted.* 5 | import reflect.rtypeRefs.FieldInfoRef 6 | 7 | // Utility classes to help generate JSON from RTypes--used by ScalaJS 8 | 9 | case class JsonField[J](name: String, value: J)(using t: Type[J]): 10 | type F = J 11 | val tt = t 12 | 13 | // This is a limited JSON builder helper. Limited because it's not general-purpose. There are a lot 14 | // of types that have no support here. We only needed to support the subset of types that the various 15 | // RType classes have as case fields. 16 | // If you're tempted to lift this for use elsewhere... you have been warned! 17 | // The cool thing is it uses quotes and reflection to discern the field types passed in. 18 | object JsonObjectBuilder: 19 | 20 | def apply(quotes: Quotes)(sb: StringBuilder, items: List[JsonField[?]]): Unit = 21 | import quotes.reflect.* 22 | 23 | sb.append('{') 24 | items 25 | .map { one => 26 | val repr = TypeRepr.of[one.F](using one.tt) 27 | (one, repr) 28 | } 29 | .map { (item, repr) => 30 | sb.append(s"\"${item.name}\":") 31 | matchOneType(quotes)(sb, repr, item.value) 32 | sb.append(',') 33 | } 34 | sb.setCharAt(sb.length() - 1, '}') 35 | 36 | private def matchOneType(quotes: Quotes)(sb: StringBuilder, repr: quotes.reflect.TypeRepr, item: Any): Unit = 37 | import quotes.reflect.* 38 | repr.typeSymbol.name match { 39 | case "String" | "TypedName" | "TypeSymbol" => sb.append(s"\"${item.toString}\"") 40 | 41 | case "Boolean" | "Int" => sb.append(s"${item.toString}") 42 | 43 | case "List" => 44 | sb.append('[') 45 | repr match { 46 | case AppliedType(t, tob) => 47 | item.asInstanceOf[List[?]].map { listItem => 48 | matchOneType(quotes)(sb, tob.head, listItem) 49 | sb.append(',') 50 | } 51 | } 52 | if item.asInstanceOf[List[?]].isEmpty then sb.append(']') 53 | else sb.setCharAt(sb.length() - 1, ']') 54 | 55 | case "Map" => 56 | sb.append('{') 57 | repr match { 58 | case AppliedType(t, tob) => 59 | item.asInstanceOf[Map[String, ?]].map { (mapKey, mapValue) => 60 | matchOneType(quotes)(sb, tob(0), mapKey) 61 | sb.append(':') 62 | matchOneType(quotes)(sb, tob(1), mapValue) 63 | sb.append(',') 64 | } 65 | } 66 | if item.asInstanceOf[Map[String, ?]].isEmpty then sb.append('}') 67 | else sb.setCharAt(sb.length() - 1, '}') 68 | 69 | case "Option" => 70 | if item.asInstanceOf[Option[?]].isEmpty then sb.append("null") 71 | else 72 | repr match { 73 | case AppliedType(t, tob) => 74 | matchOneType(quotes)(sb, tob.head, item.asInstanceOf[Option[?]].get) 75 | } 76 | 77 | case "FieldInfoRef" | "NonConstructorFieldInfoRef" | "ScalaFieldInfoRef" => 78 | item.asInstanceOf[FieldInfoRef].asJson(sb)(using quotes) 79 | 80 | case _ => // Careful! This is a bold assumption that everything else is RTypeRef[?]! 81 | item.asInstanceOf[RTypeRef[?]].asJson(sb)(using quotes) 82 | } 83 | -------------------------------------------------------------------------------- /src/main/scala/co.blocke.scala_reflection/util/TypedName.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package util 3 | 4 | import scala.quoted.Quotes 5 | 6 | object TypedName: 7 | 8 | // Need a full name inclusive of type parameters and correcting for Enumeration's class name erasure. 9 | // This name is used for RType.equals so caching works. 10 | def apply(quotes: Quotes)(aType: quotes.reflect.TypeRepr): TypedName = 11 | import quotes.reflect.* 12 | 13 | aType.asInstanceOf[TypeRef] match { 14 | case AppliedType(t, tob) => 15 | t match { 16 | case AppliedType(t2, tob2) => 17 | apply(quotes)(t2).toString + tob2 18 | .map(oneTob => apply(quotes)(oneTob.asInstanceOf[TypeRef])) 19 | .mkString("[", ",", "]") 20 | case _ => 21 | apply(quotes)(t).toString + tob 22 | .map(oneTob => apply(quotes)(oneTob.asInstanceOf[TypeRef])) 23 | .mkString("[", ",", "]") 24 | } 25 | case sym if aType.typeSymbol.flags.is(Flags.Param) => 26 | sym.name 27 | case AndType(left, right) => 28 | Clazzes.INTERSECTION_CLASS + "[" + apply(quotes)(left.asInstanceOf[TypeRef]) + "," + apply(quotes)( 29 | right.asInstanceOf[TypeRef] 30 | ) + "]" 31 | case OrType(left, right) => 32 | Clazzes.UNION_CLASS + "[" + apply(quotes)(left.asInstanceOf[TypeRef]) + "," + apply(quotes)( 33 | right.asInstanceOf[TypeRef] 34 | ) + "]" 35 | case _: dotty.tools.dotc.core.Types.WildcardType => 36 | "unmapped" 37 | case _ => 38 | aType.classSymbol.get.fullName match { 39 | case Clazzes.ENUMERATION_CLASS => 40 | aType.asInstanceOf[TypeRef].qualifier.asInstanceOf[quotes.reflect.TermRef].termSymbol.moduleClass.fullName 41 | case tn => 42 | tn 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/Change.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface Change { 8 | String name(); 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/ClassAnno.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.TYPE}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface ClassAnno { 8 | String name(); 9 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/DBKey.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Inherited 6 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | public @interface DBKey { 9 | // Some indexing schemes create unordered composite keys (eg Mongo) while others have primary/secondary distinctions (eg Dynamo). 10 | // If the later, use the index parameter to "order" fields within a compound key. 11 | int index() default 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/FieldAnno.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Inherited 6 | @Target({ElementType.PARAMETER, ElementType.METHOD}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | public @interface FieldAnno { 9 | int idx() default 0; 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/Hey.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.util.*; 4 | 5 | public class Hey { 6 | public Hey() {} 7 | 8 | private String jString; 9 | public String getJString() { return jString; } 10 | public void setJString(String n) { jString = n; } 11 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaCollections.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.BlockingQueue; 5 | 6 | public class JavaCollections { 7 | public JavaCollections() {} 8 | 9 | private HashMap hMap; 10 | public HashMap getHMap() { return hMap; } 11 | public void setHMap(HashMap n) { hMap = n; } 12 | 13 | private BlockingQueue myQ; 14 | public BlockingQueue getMyQ() { return myQ; } 15 | public void setMyQ(BlockingQueue n) { myQ = n; } 16 | 17 | private TreeSet myTree; 18 | public TreeSet getMyTree() { return myTree; } 19 | public void setMyTree(TreeSet n) { myTree = n; } 20 | 21 | private ArrayList myList; 22 | public ArrayList getMyList() { return myList; } 23 | public void setMyList(ArrayList n) { myList = n; } 24 | 25 | private Stack pushPop; 26 | public Stack getPushPop() { return pushPop; } 27 | public void setPushPop( Stack pp ) { pushPop = pp; } 28 | 29 | private String[] myArr; 30 | public String[] getMyArr() { return myArr; } 31 | public void setMyArr(String[] n) { myArr = n; } 32 | 33 | private List[] nested; 34 | public List[] getNested() { return nested; } 35 | public void setNested(List[] n) { nested = n; } 36 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaEnum.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | enum Color 4 | { 5 | RED, GREEN, BLUE; 6 | } 7 | 8 | public class JavaEnum { 9 | public JavaEnum() {} 10 | 11 | private Color color; 12 | public Color getColor() { return color; } 13 | public void setColor(Color n) { color = n; } 14 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaOption1.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.util.Optional; 4 | 5 | public class JavaOption1 { 6 | public JavaOption1() {} 7 | 8 | private Optional fld; 9 | public Optional getFld() { return fld; } 10 | public void setFld(Optional f) { fld = f; } 11 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaOption2.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.util.Optional; 4 | 5 | public class JavaOption2 { 6 | public JavaOption2() {} 7 | 8 | private Optional> fld; 9 | public Optional> getFld() { return fld; } 10 | public void setFld(Optional> f) { fld = f; } 11 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaOption3.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import java.util.Optional; 4 | 5 | public class JavaOption3 { 6 | public JavaOption3() {} 7 | 8 | private Optional fld; 9 | public Optional getFld() { return fld; } 10 | public void setFld(Optional f) { fld = f; } 11 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaParam.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | public class JavaParam { 4 | public JavaParam() {} 5 | 6 | private K jThing; 7 | public K getJThing() { return jThing; } 8 | public void setJThing(K n) { jThing = n; } 9 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaParamHolder.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | public class JavaParamHolder { 4 | private JavaParam jFoo; 5 | public JavaParam getJFoo() { return jFoo; } 6 | public void setJFoo(JavaParam n) { jFoo = n; } 7 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/JavaParamHolder2.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | public class JavaParamHolder2 { 4 | private JavaParam jFoo; 5 | public JavaParam getJFoo() { return jFoo; } 6 | public void setJFoo(JavaParam n) { jFoo = n; } 7 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/ParamAnno.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | import co.blocke.scala_reflection.Ignore; 4 | 5 | @ClassAnno(name="Foom") 6 | public class ParamAnno { 7 | public ParamAnno() {} 8 | 9 | private String name; 10 | @FieldAnno(idx=1) 11 | public String getName() { return name; } 12 | public void setName(String n) { name = n; } 13 | 14 | private T age; 15 | public T getAge() { return age; } 16 | @FieldAnno(idx=2) 17 | public void setAge(T n) { age = n; } 18 | 19 | private Boolean bogus; 20 | @Ignore 21 | public Boolean getBogus() { return bogus; } 22 | public void setBogus(Boolean n) { bogus = n; } 23 | } -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/Person.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | public class Person extends co.blocke.scala_reflection.models.SJCaptureJava { 4 | public Person() {} 5 | 6 | private String name; 7 | public String getName() { return name; } 8 | public void setName(String n) { name = n; } 9 | 10 | private int age; 11 | public int getAge() { return age; } 12 | public void setAge(int n) { age = n; } 13 | 14 | private int other; 15 | public int getOther() { return other; } 16 | public void setOther(int n) { other = n; } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/co/blocke/reflect/You.java: -------------------------------------------------------------------------------- 1 | package co.blocke.reflect; 2 | 3 | public class You { 4 | public You() {} 5 | 6 | private Hey sayHey; 7 | public Hey getSayHey() { return sayHey; } 8 | public void setSayHey(Hey n) { sayHey = n; } 9 | } -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/Enums.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import models.* 4 | import rtypes.* 5 | 6 | class Enums extends munit.FunSuite: 7 | 8 | test("Java Enums") { 9 | val result = RType.of[co.blocke.reflect.JavaEnum] 10 | assertEquals( 11 | result.pretty, 12 | """co.blocke.reflect.JavaEnum (Java): 13 | | fields -> 14 | | color: Enum (Java) having values (RED,GREEN,BLUE) 15 | |""".stripMargin 16 | ) 17 | val ord = result.asInstanceOf[JavaClassRType[?]].fields(0).asInstanceOf[NonConstructorFieldInfo].fieldType.asInstanceOf[JavaEnumRType[?]].ordinal("GREEN").get 18 | assertEquals(ord, 1) 19 | assertEquals( 20 | result.asInstanceOf[JavaClassRType[?]].fields(0).asInstanceOf[NonConstructorFieldInfo].fieldType.asInstanceOf[JavaEnumRType[?]].valueAt(2).map(_.toString), 21 | Some("BLUE") 22 | ) 23 | } 24 | 25 | test("Scala Enums (old and new)") { 26 | val result = RType.of[Birthday] 27 | assertEquals( 28 | result.pretty, 29 | """co.blocke.scala_reflection.models.Birthday: 30 | | fields -> 31 | | m: Enum (Scala 3) having values (Jan,Feb,Mar) 32 | | d: Enumeration (Scala 2) having values (Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday) 33 | |""".stripMargin 34 | ) 35 | } 36 | 37 | test("Scala Enum methods") { 38 | val result = RType.of[Birthday] 39 | result match { 40 | case sc: ScalaClassRType[?] => 41 | val e = sc.fields(0).fieldType.asInstanceOf[ScalaEnumRType[?]] 42 | assertEquals(e.ordinal("Feb"), Some(1)) 43 | assertEquals(e.valueAt(2), Some("Mar")) 44 | case _ => false 45 | } 46 | } 47 | 48 | test("Enum Json generation") { 49 | val js = RType.ofJS[Birthday] 50 | assertEquals( 51 | js, 52 | """{"rtype":"ScalaClassRType","name":"co.blocke.scala_reflection.models.Birthday","typedName":"co.blocke.scala_reflection.models.Birthday","typeParamSymbols":[],"typeParamValues":[],"typeMembers":[],"fields":[{"name":"m","fieldType":{"rtype":"ScalaEnumRef","name":"co.blocke.scala_reflection.models.Month","typedName":"co.blocke.scala_reflection.models.Month","values":["Jan","Feb","Mar"]},"originalSymbol":null,"annotations":{}},{"name":"d","fieldType":{"rtype":"ScalaEnumerationRef","name":"co.blocke.scala_reflection.models.WeekDay","typedName":"co.blocke.scala_reflection.models.WeekDay","values":["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]},"originalSymbol":null,"annotations":{}}],"annotations":{},"mixins":["java.lang.Object","scala.Product","java.io.Serializable"],"isAppliedType":false,"isValueClass":false,"isCaseClass":true,"isAbstractClass":false,"nonConstructorFields":[],"sealedChildren":[],"childrenAreObject":false}""" 53 | ) 54 | } 55 | 56 | test("Scala Enum ADT") { 57 | val result = RType.of[ColorSet] 58 | assertEquals( 59 | result.pretty, 60 | """co.blocke.scala_reflection.models.ColorSet: 61 | | fields -> 62 | | set: Set of Enum (Scala 3) having values (Red,Green,Blue,Mix) 63 | |""".stripMargin 64 | ) 65 | } 66 | 67 | test("Scala2 Enumeration methods") { 68 | val result = RType.of[Birthday] 69 | result match { 70 | case sc: ScalaClassRType[?] => 71 | val e = sc.fields(1).fieldType.asInstanceOf[ScalaEnumerationRType[?]] 72 | assertEquals(e.ordinal("Wednesday"), Some(99)) 73 | assertEquals(e.valueAt(99), Some("Wednesday")) 74 | case _ => false 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/Inheritance.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import munit.* 4 | import models.* 5 | 6 | class Inheritance extends munit.FunSuite: 7 | 8 | test("Inheritance and Annotations") { 9 | val result = RType.of[InheritSimpleChild] 10 | assertEquals( 11 | result.pretty, 12 | """co.blocke.scala_reflection.models.InheritSimpleChild: 13 | | fields -> 14 | | extra: String 15 | | one: String 16 | | annotations -> Map(co.blocke.reflect.Change -> Map(name -> uno), co.blocke.reflect.DBKey -> Map(index -> 50)) 17 | | non-constructor fields (non-case class) -> 18 | | dontForget: Int 19 | | foo: Int 20 | | annotations -> Map(co.blocke.reflect.DBKey -> Map(index -> 99)) 21 | | four: Double 22 | | annotations -> Map(co.blocke.reflect.DBKey -> Map(index -> 2), co.blocke.reflect.Change -> Map(name -> quatro)) 23 | | three: Boolean 24 | | two: Int 25 | | annotations -> Map(co.blocke.reflect.Change -> Map(name -> foobar), co.blocke.reflect.DBKey -> Map(index -> 1)) 26 | |""".stripMargin 27 | ) 28 | } 29 | 30 | test("Inheritance and Parameterized Classes") { 31 | val result = RType.of[ParamChild[Boolean]] 32 | assertEquals( 33 | result.pretty, 34 | """co.blocke.scala_reflection.models.ParamChild[Boolean]: 35 | | fields -> 36 | | thing: [T] Boolean 37 | | non-constructor fields (non-case class) -> 38 | | cosa: Boolean 39 | | item: Boolean 40 | |""".stripMargin 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/LeftRight.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import munit.* 4 | import rtypes.* 5 | import models.* 6 | 7 | class LeftRight extends munit.FunSuite: 8 | 9 | test("Scala simple Either field") { 10 | val result = RType.of[BothSides] 11 | assertEquals( 12 | result.pretty, 13 | """co.blocke.scala_reflection.models.BothSides: 14 | | fields -> 15 | | a: Either of: 16 | | left--Int 17 | | right--String 18 | |""".stripMargin 19 | ) 20 | } 21 | 22 | test("Scala Either having a self-reference") { 23 | val result = RType.of[EitherWithSelf] 24 | assertEquals( 25 | result.pretty, 26 | """co.blocke.scala_reflection.models.EitherWithSelf: 27 | | fields -> 28 | | a: co.blocke.scala_reflection.models.Person: 29 | | fields -> 30 | | name: String 31 | | age: Int 32 | | item: co.blocke.scala_reflection.models.Item: 33 | | fields -> 34 | | desc: String 35 | | allDone: Boolean 36 | | b: co.blocke.scala_reflection.models.Item (seen before, details above) 37 | | c: Either of: 38 | | left--co.blocke.scala_reflection.models.Person (seen before, details above) 39 | | right--co.blocke.scala_reflection.models.Item (seen before, details above) 40 | |""".stripMargin 41 | ) 42 | } 43 | 44 | test("Scala Either with Option") { 45 | val result = RType.of[BothSidesWithOption] 46 | assertEquals( 47 | result.pretty, 48 | """co.blocke.scala_reflection.models.BothSidesWithOption: 49 | | fields -> 50 | | a: Either of: 51 | | left--Int 52 | | right--Option of String 53 | |""".stripMargin 54 | ) 55 | } 56 | 57 | test("Scala Either with Union type") { 58 | val result = RType.of[BothSidesWithUnion] 59 | assertEquals( 60 | result.pretty, 61 | """co.blocke.scala_reflection.models.BothSidesWithUnion: 62 | | fields -> 63 | | a: Either of: 64 | | left--Int 65 | | right--Union of: 66 | | left--String 67 | | right--Boolean 68 | |""".stripMargin 69 | ) 70 | } 71 | 72 | test("Scala Either having a parameterized type") { 73 | val result = RType.of[BothSidesParam[String, Double]] 74 | assertEquals( 75 | result.pretty, 76 | """co.blocke.scala_reflection.models.BothSidesParam[String,Double]: 77 | | fields -> 78 | | a: Either of: 79 | | left--String 80 | | right--Option of co.blocke.scala_reflection.models.ParamOption[Double]: 81 | | fields -> 82 | | a: Option of Double 83 | |""".stripMargin 84 | ) 85 | } 86 | 87 | test("Scala Union type") { 88 | val result = RType.of[Together] 89 | assertEquals( 90 | result.pretty, 91 | """co.blocke.scala_reflection.models.Together: 92 | | fields -> 93 | | a: Intersection of: 94 | | left--Int 95 | | right--co.blocke.scala_reflection.models.Person: 96 | | fields -> 97 | | name: String 98 | | age: Int 99 | | item: co.blocke.scala_reflection.models.Item: 100 | | fields -> 101 | | desc: String 102 | | allDone: Boolean 103 | |""".stripMargin 104 | ) 105 | } 106 | 107 | test("Scala Intersection type with Option") { 108 | val result = RType.of[Apart] 109 | assertEquals( 110 | result.pretty, 111 | """co.blocke.scala_reflection.models.Apart: 112 | | fields -> 113 | | a: Union of: 114 | | left--Option of co.blocke.scala_reflection.models.Person: 115 | | fields -> 116 | | name: String 117 | | age: Int 118 | | item: co.blocke.scala_reflection.models.Item: 119 | | fields -> 120 | | desc: String 121 | | allDone: Boolean 122 | | right--String 123 | |""".stripMargin 124 | ) 125 | } 126 | 127 | test("Scala Intersection type with type parameters") { 128 | val result = RType.of[ApartWithType[Int, Boolean]] 129 | assertEquals( 130 | result.pretty, 131 | """co.blocke.scala_reflection.models.ApartWithType[Int,Boolean]: 132 | | fields -> 133 | | a: Union of: 134 | | left--Option of co.blocke.scala_reflection.models.Thingy[Int]: 135 | | fields -> 136 | | name: String 137 | | payload: [Z] Int 138 | | right--Boolean 139 | |""".stripMargin 140 | ) 141 | } 142 | 143 | test("opaque type alias is a union type") { 144 | val result = RType.of[OpaqueUnion] 145 | assertEquals( 146 | result.pretty, 147 | """co.blocke.scala_reflection.models.OpaqueUnion: 148 | | fields -> 149 | | id: alias GEN_ID defined as Union of: 150 | | left--Int 151 | | right--String 152 | |""".stripMargin 153 | ) 154 | } 155 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/Options.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | 3 | import munit.* 4 | import rtypes.* 5 | import models.* 6 | // import java.util.Optional 7 | 8 | class Options extends munit.FunSuite: 9 | 10 | test("Simple Option RType") { 11 | val result = RType.of[Option[String]] 12 | assertEquals(result.pretty, "Option of String") 13 | } 14 | 15 | test("java ArrayList of Option[String]") { 16 | val result = RType.of[java.util.ArrayList[Option[String]]] 17 | assertEquals(result.pretty, "Java ArrayList of Option of String") 18 | } 19 | 20 | test("Scala optional field") { 21 | val result = RType.of[NormalOption] 22 | assertEquals( 23 | result.pretty, 24 | """co.blocke.scala_reflection.models.NormalOption: 25 | | fields -> 26 | | a: Option of Int 27 | | b: String 28 | |""".stripMargin 29 | ) 30 | assertEquals( 31 | RType.ofJS[NormalOption], 32 | """{"rtype":"ScalaClassRType","name":"co.blocke.scala_reflection.models.NormalOption","typedName":"co.blocke.scala_reflection.models.NormalOption","typeParamSymbols":[],"typeParamValues":[],"typeMembers":[],"fields":[{"name":"a","fieldType":{"rtype":"ScalaOptionRType","name":"scala.Option","typedName":"scala.Option[scala.Int]","typeParamSymbols":["A"],"optionParamType":{"rtype":"IntRType","name":"scala.Int"}},"originalSymbol":null,"annotations":{}},{"name":"b","fieldType":{"rtype":"StringRType","name":"java.lang.String"},"originalSymbol":null,"annotations":{}}],"annotations":{},"mixins":["java.lang.Object","scala.Product","java.io.Serializable"],"isAppliedType":false,"isValueClass":false,"isCaseClass":true,"isAbstractClass":false,"nonConstructorFields":[],"sealedChildren":[],"childrenAreObject":false}""" 33 | ) 34 | } 35 | 36 | test("Java optional field") { 37 | val result = RType.of[co.blocke.reflect.JavaOption1] 38 | assertEquals( 39 | result.pretty, 40 | """co.blocke.reflect.JavaOption1 (Java): 41 | | fields -> 42 | | fld: Optional of Integer (Java) 43 | |""".stripMargin 44 | ) 45 | assertEquals( 46 | RType.ofJS[co.blocke.reflect.JavaOption1], 47 | """{"rtype":"JavaClassRType","name":"co.blocke.reflect.JavaOption1","typedName":"co.blocke.reflect.JavaOption1","typeParamSymbols":[],"typeParamValues":[],"fields":[{"name":"fld","fieldType":{"rtype":"JavaOptionalRType","name":"java.util.Optional","typedName":"java.util.Optional[java.lang.Integer]","typeParamSymbols":["A"],"optionParamType":{"rtype":"JavaIntegerRType","name":"java.lang.Integer"}},"originalSymbol":null,"annotations":{}}],"annotations":{},"mixins":["java.lang.Object"]}""" 48 | ) 49 | } 50 | 51 | test("Scala nested optional field") { 52 | val result = RType.of[NestedOption] 53 | assertEquals( 54 | result.pretty, 55 | """co.blocke.scala_reflection.models.NestedOption: 56 | | fields -> 57 | | a: Option of Option of Int 58 | | b: String 59 | |""".stripMargin 60 | ) 61 | } 62 | 63 | test("Scala option of a class with defaults") { 64 | val result = RType.of[OptionOfClass] 65 | assertEquals( 66 | result.pretty, 67 | """co.blocke.scala_reflection.models.OptionOfClass: 68 | | fields -> 69 | | a: Option of co.blocke.scala_reflection.models.Person: 70 | | fields -> 71 | | name: String 72 | | age: Int 73 | | item: co.blocke.scala_reflection.models.Item: 74 | | fields -> 75 | | desc: String 76 | | allDone: Boolean 77 | | (default value: Some(Person(Mike,34,...) 78 | | b: Option of co.blocke.scala_reflection.models.Person (seen before, details above) 79 | | (default value: None) 80 | | c: String 81 | |""".stripMargin 82 | ) 83 | } 84 | 85 | test("Java nested optional field") { 86 | val result = RType.of[co.blocke.reflect.JavaOption2] 87 | assertEquals( 88 | result.pretty, 89 | """co.blocke.reflect.JavaOption2 (Java): 90 | | fields -> 91 | | fld: Optional of Optional of Integer (Java) 92 | |""".stripMargin 93 | ) 94 | } 95 | 96 | test("Scala optional parameterized field") { 97 | val result = RType.of[ParamOption[Char]] 98 | assertEquals( 99 | result.pretty, 100 | """co.blocke.scala_reflection.models.ParamOption[Char]: 101 | | fields -> 102 | | a: Option of Char 103 | |""".stripMargin 104 | ) 105 | } 106 | 107 | test("Java optional parameterized field") { 108 | val result = RType.of[co.blocke.reflect.JavaOption3[Char]] 109 | assertEquals( 110 | result.pretty, 111 | """co.blocke.reflect.JavaOption3[Char] (Java): 112 | | fields -> 113 | | fld: Optional of Char 114 | |""".stripMargin 115 | ) 116 | } 117 | 118 | test("Option of a union") { 119 | val result = RType.of[OptionHavingUnion] 120 | assertEquals( 121 | result.pretty, 122 | """co.blocke.scala_reflection.models.OptionHavingUnion: 123 | | fields -> 124 | | a: Option of Union of: 125 | | left--Boolean 126 | | right--String 127 | |""".stripMargin 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/BasicModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | import co.blocke.reflect.* 5 | import neotype.* 6 | 7 | // Basic Tasty classes 8 | case class Item(desc: String) 9 | case class Person(name: String, age: Int, item: Item, allDone: Boolean) 10 | 11 | case class HasDefaults(a: String = "wow", item: Item = Item("none"), c: Int = 5) 12 | case class WithDefault(a: Int, b: String = "wow") 13 | 14 | case class Prim( 15 | a: Boolean, 16 | b: Byte, 17 | c: Char, 18 | d: Double, 19 | e: Float, 20 | f: Int, 21 | g: Long, 22 | h: Short, 23 | i: String, 24 | j: Any, 25 | k: scala.math.BigDecimal, 26 | l: scala.math.BigInt, 27 | m: java.lang.Boolean, 28 | n: java.lang.Byte, 29 | o: java.lang.Character, 30 | p: java.lang.Double, 31 | q: java.lang.Float, 32 | r: java.lang.Integer, 33 | s: java.lang.Long, 34 | t: java.lang.Short, 35 | u: java.lang.Number, 36 | v: java.math.BigDecimal, 37 | w: java.math.BigInteger 38 | ) 39 | case class Time( 40 | a: java.time.Duration, 41 | b: java.time.Instant, 42 | c: java.time.LocalDate, 43 | d: java.time.LocalDateTime, 44 | e: java.time.LocalTime, 45 | f: java.time.MonthDay, 46 | g: java.time.OffsetDateTime, 47 | h: java.time.OffsetTime, 48 | i: java.time.Period, 49 | j: java.time.Year, 50 | k: java.time.YearMonth, 51 | l: java.time.ZonedDateTime, 52 | m: java.time.ZoneId, 53 | n: java.time.ZoneOffset 54 | ) 55 | case class Net( 56 | a: java.net.URL, 57 | b: java.net.URI, 58 | c: java.util.UUID 59 | ) 60 | 61 | // Match / dependent types 62 | type Elem[X] = X match { 63 | case String => Char 64 | case Array[t] => t 65 | case Iterable[t] => t 66 | } 67 | case class Definitely(id: Elem[List[Int]], stuff: Elem[String]) 68 | 69 | case class SelfReferencing(a: String, b: SelfReferencing, c: Int, d: Option[SelfReferencing]) 70 | 71 | // Sealed trait w/case classes and objects 72 | sealed trait Vehicle 73 | case class Truck(numberOfWheels: Int) extends Vehicle 74 | case class Car(numberOfWheels: Int, color: String) extends Vehicle 75 | case class Plane(numberOfEngines: Int) extends Vehicle 76 | case class VehicleHolder(v: Vehicle) 77 | 78 | sealed trait Flavor 79 | case object Vanilla extends Flavor 80 | case object Chocolate extends Flavor 81 | case object Bourbon extends Flavor 82 | case class FlavorHolder(f: Flavor) 83 | 84 | // Sealed abstract class 85 | sealed abstract class Animal(val animalType: String) { 86 | val name: String 87 | } 88 | class Dog(val name: String) extends Animal("Dog") 89 | class Cat(val name: String) extends Animal("Cat") 90 | case class PetOwner(owner: String, pet: Animal) 91 | 92 | // Opaque type aliases 93 | opaque type EMP_ID = Int 94 | case class Employee(eId: EMP_ID, age: Int) 95 | 96 | // Value classes 97 | case class IdUser(id: Int) extends AnyVal // value class 98 | case class Employee2(eId: IdUser, age: Int) 99 | 100 | // @Skip_Reflection 101 | @Ignore 102 | case class SkipMe(a: Int, b: String) 103 | 104 | // Self-referencing 105 | case class Shape(id: Int, parent: Option[Shape]) 106 | case class Person2(name: String, age: Int, boss: Person2) 107 | case class Drawer[T](id: Int, nextInChain: Option[Drawer[T]], thing: T) 108 | 109 | //-------------------<< Non-Case Classes --------------- 110 | 111 | // Non-Case Scala class handling 112 | class FoomNC(val a: Int, val b: String, @FieldAnno(idx = 0) c: Option[FoomNC]) { 113 | @FieldAnno(idx = 5) @DBKey var blah: Boolean = false 114 | @Ignore var hey: Int = 2 115 | private var cantSee: Boolean = true 116 | val nope: Float = 1.2 117 | 118 | private var _age = 1 119 | def age = _age 120 | @FieldAnno(idx = 2) def age_=(g: Int): Unit = _age = g 121 | } 122 | 123 | // Object/field Annotations 124 | @ClassAnno(name = "Foom") 125 | case class WithAnnotation(@FieldAnno(idx = 5) id: String) 126 | 127 | // Java Collections 128 | case class JColl( 129 | a: java.util.List[Int], 130 | b: java.util.Optional[java.util.ArrayList[Int]], 131 | c: java.util.Stack[String], 132 | d: java.util.Queue[Map[Int, String]], 133 | e: java.util.Set[Boolean], 134 | f: java.util.Map[Int, String], 135 | g: java.util.LinkedHashMap[String, Int] 136 | ) 137 | 138 | // Mixin tests 139 | trait SJCapture { 140 | var captured: java.util.HashMap[String, ?] = 141 | new java.util.HashMap[String, Any]() 142 | } 143 | class SJCaptureJava extends SJCapture 144 | 145 | case class JJ(jFoo: JavaParam[Int]) 146 | 147 | trait Blah[T]: 148 | val x: T 149 | 150 | case class UberJS[T]( 151 | a: Option[List[Either[Int, Boolean]]], 152 | b: java.util.HashMap[String, Int | Long], 153 | c: Option[UberJS[Int]], 154 | x: T 155 | ) extends Blah[T]: 156 | type S = String 157 | 158 | // Test NeoType and differentiation from Any 159 | type NonEmptyString = NonEmptyString.Type 160 | given NonEmptyString: Newtype[String] with 161 | override inline def validate(input: String): Boolean = 162 | input.nonEmpty 163 | 164 | type NonEmptyList = NonEmptyList.Type 165 | given NonEmptyList: Newtype[List[Int]] with 166 | override inline def validate(input: List[Int]): Boolean = 167 | input.nonEmpty 168 | 169 | case class NeoPerson(age: NonEmptyList, desc: NonEmptyString, whatever: Any) 170 | 171 | class Parent(val phase: Int, var stuff: List[String]): 172 | private var _hidden: Boolean = false 173 | def hidden: Boolean = _hidden 174 | def hidden_=(h: Boolean) = _hidden = h 175 | 176 | private var _nope: Boolean = false // should not generate due to @Ignore 177 | @Ignore def nope: Boolean = _nope 178 | def nope_=(h: Boolean) = _nope = h 179 | 180 | var foo: String = "ok" 181 | @Ignore var noFoo: String = "not ok" 182 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/CollectionModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | // Collections - immutable 5 | case class Coll1(a: List[String]) 6 | case class Coll2(a: scala.collection.immutable.HashSet[String]) 7 | case class Coll3(a: Map[String, Float]) 8 | case class Coll4(a: scala.collection.immutable.ListMap[String, Boolean]) 9 | case class Coll5(a: Iterable[String]) 10 | 11 | // Collections - mutable 12 | case class Coll1m(a: scala.collection.mutable.ListBuffer[String]) 13 | case class Coll2m(a: scala.collection.mutable.HashSet[String]) 14 | case class Coll3m(a: scala.collection.mutable.Map[String, Float]) 15 | case class Coll4m(a: scala.collection.mutable.HashMap[String, Boolean]) 16 | case class NestedColl(a: Map[String, List[Option[Int]]]) 17 | 18 | case class WithScalaArray( 19 | list: Array[Array[Char]], 20 | x1: Array[Boolean], 21 | x2: Array[Byte], 22 | x3: Array[Char], 23 | x4: Array[Double], 24 | x5: Array[Float], 25 | x6: Array[Int], 26 | x7: Array[Long], 27 | x8: Array[Short], 28 | x9: Array[String] 29 | ) 30 | 31 | // Tuple 32 | case class TupleTurtle[Z](t: (Int, Z, List[String], NormalOption)) 33 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/EnumModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | // Scala 2.x style Enumeration 5 | object WeekDay extends Enumeration { 6 | type WeekDay = Value 7 | val Monday = Value(1) 8 | val Tuesday = Value(2) 9 | val Wednesday = Value(99) 10 | val Thursday = Value(4) 11 | val Friday = Value(5) 12 | val Saturday = Value(6) 13 | val Sunday = Value(-3) 14 | } 15 | import WeekDay.* 16 | 17 | // Scala 3 Enum 18 | enum Month { 19 | case Jan, Feb, Mar 20 | } 21 | 22 | case class Birthday(m: Month, d: WeekDay) 23 | 24 | enum Color(val rgb: Int) { 25 | case Red extends Color(0xff0000) 26 | case Green extends Color(0x00ff00) 27 | case Blue extends Color(0x0000ff) 28 | case Mix(mix: Int) extends Color(mix) 29 | } 30 | 31 | case class ColorSet(set: Set[Color]) 32 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/InheritanceModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | import co.blocke.reflect.* 5 | 6 | // Inheritance and Annotations 7 | class InheritSimpleBase( 8 | @DBKey(index = 50) @Change(name = "bogus") val one: String = "blather" 9 | ) { 10 | // Public data member 11 | @DBKey(index = 1) @Change(name = "foobar") var two: Int = 5 12 | var three: Boolean = true 13 | 14 | // Private var or val 15 | val notOne: Int = 2 16 | 17 | @Ignore var dontseeme: Int = 90 18 | 19 | // Scala-style getter/setter 20 | private var _four: Double = 0.1 21 | @DBKey(index = 2) def four: Double = _four 22 | @Change(name = "quatro") def four_=(a: Double): Unit = _four = a 23 | 24 | private var _dontForget: Int = 9 25 | def dontForget: Int = _dontForget 26 | def dontForget_=(a: Int): Unit = _dontForget = a 27 | 28 | private var _unused: Double = 0.1 29 | @Ignore def unused: Double = _unused 30 | def unused_=(a: Double): Unit = _unused = a 31 | } 32 | 33 | class InheritSimpleChild(val extra: String, @Change(name = "uno") override val one: String) extends InheritSimpleBase(one) { 34 | @DBKey(index = 99) var foo: Int = 39 35 | @Ignore var bogus: String = "" 36 | 37 | private var _nada: Double = 0.1 38 | def nada: Double = _nada 39 | @Ignore def nada_=(a: Double): Unit = _nada = a 40 | } 41 | 42 | // Inheritance and parameterized classes 43 | class ParamBase[T](val thing: T) { 44 | var item: T = null.asInstanceOf[T] 45 | 46 | private var _cosa: T = null.asInstanceOf[T] 47 | def cosa: T = _cosa 48 | def cosa_=(a: T): Unit = _cosa = a 49 | } 50 | 51 | class ParamChild[T](override val thing: T) extends ParamBase[T](thing) 52 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/LeftRightModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | // Either 5 | case class BothSides(a: Either[Int, String]) 6 | case class EitherWithSelf(a: Person, b: Item, c: Either[Person, Item]) 7 | case class BothSidesWithOption(a: scala.util.Either[Int, Option[String]]) 8 | case class BothSidesWithUnion(a: scala.util.Either[Int, String | Boolean]) 9 | 10 | case class BothSidesParam[Y, Z](a: scala.util.Either[Y, Option[ParamOption[Z]]]) 11 | 12 | // Intersection/Union Types 13 | case class Together(a: Int & Person) 14 | case class Apart(a: Option[Person] | String) 15 | case class ApartWithType[T, U](a: Option[Thingy[T]] | U) 16 | 17 | // Opaque type is union 18 | opaque type GEN_ID = Int | String 19 | case class OpaqueUnion(id: GEN_ID) 20 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/OptionModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | // Options 5 | case class NormalOption(a: Option[Int], b: String) 6 | case class NestedOption(a: Option[Option[Int]], b: String) 7 | case class OptionOfClass( 8 | a: Option[Person] = Some(Person("Mike", 34, Item("something"), false)), 9 | b: Option[Person] = None, 10 | c: String 11 | ) 12 | case class ParamOption[T](a: Option[T]) 13 | case class UnionHavingOption(a: Boolean | Option[Int], b: Boolean | java.util.Optional[Int]) 14 | case class OptionHavingUnion(a: Option[Boolean | String]) 15 | -------------------------------------------------------------------------------- /src/test/scala/co.blocke.scala_reflection/models/ParameterModels.scala: -------------------------------------------------------------------------------- 1 | package co.blocke.scala_reflection 2 | package models 3 | 4 | import scala.util.Try 5 | 6 | // Type substitution models 7 | //------------------------- 8 | // 0-level 9 | case class DuoTypes[Q, U](a: U, b: Q) // Note intentional reversal of order to test proper type symbol mapping! 10 | 11 | // 1st level type substitution 12 | case class DuoHolder(x: DuoTypes[Int, Float]) 13 | 14 | // 2nd level type substitution 15 | case class Thingy[Z](name: String, payload: Z) 16 | 17 | // Intersection type substitution 18 | trait Stackable[T] 19 | trait Floatable[U] 20 | 21 | // 2nd and 3rd level type substitution - option 22 | case class OptHolder(a: Option[DuoTypes[String, Boolean]]) 23 | case class OptHolder2(a: Option[Option[DuoTypes[String, Boolean]]]) 24 | 25 | // 2nd and 3rd level type substitution - either 26 | case class EitherHolder(a: Either[DuoTypes[Int, Float], Option[DuoTypes[String, Boolean]]]) 27 | 28 | // 1st and 2nd level substitution in class 29 | case class DuoClass(a: DuoTypes[Int, DuoTypes[Byte, Short]]) 30 | 31 | // Try with paramters 32 | trait TryIt[X, Y] { val x: Try[X]; val y: Try[Option[Y]] } 33 | case class TryItC[A, B](x: Try[A], y: Try[Option[B]]) extends TryIt[A, B] 34 | 35 | // Try substitution 36 | case class TryHolder(a: scala.util.Try[DuoTypes[String, Int]]) 37 | 38 | // List and Map substitution 39 | case class ListMapSub(a: List[DuoTypes[Int, Byte]], b: Map[String, DuoTypes[Float, Short]]) 40 | 41 | // Trait type substitution 42 | trait TypeShell[X] { val x: X } 43 | case class TypeShellHolder(a: TypeShell[Int]) 44 | 45 | // Union type substitution 46 | case class UnionHolder(a: Int | TypeShell[String]) 47 | 48 | // Type member (type symbol) substitution 49 | trait Body 50 | case class FancyBody(message: String) extends Body 51 | 52 | case class Envelope[T <: Body, U](id: String, body: T) { 53 | type Giraffe = T 54 | type Foo = Int 55 | } 56 | 57 | // Trait type substitution 58 | trait ParamThing[X] { val id: X } 59 | 60 | trait T5[X, Y] { val thing1: X; val thing2: Y } 61 | trait T10[X, Y] { val x: X; val y: Y } 62 | trait T11[W, Z] { val w: W; val z: Z } 63 | case class TBlah1[A, B](w: A, z: B) extends T11[A, B] 64 | case class TBar7[A, B](thing1: A, thing2: B) extends T5[A, B] 65 | case class TFoo6[A, B, C, D](x: T11[C, T5[D, A]], y: B) extends T10[T11[C, T5[D, A]], B] 66 | 67 | // May be inheritance models? 68 | trait Level1[T, U] { val t: T; val u: Option[List[U]] } 69 | trait Base[A, B] { val a: A; val b: B } 70 | case class L1Class[X, Y](t: X, u: Option[List[Y]]) extends Level1[X, Y] 71 | case class BaseClass[X, Y, Z](a: Level1[X, Z], b: Y) extends Base[Level1[X, Z], Y] 72 | 73 | trait MapIt[X, Y, S, T] { val x: Map[X, Option[Y]]; val s: Array[S]; val t: Array[List[T]] } 74 | case class MapItC[A, B, W, U](x: Map[A, Option[B]], s: Array[W], t: Array[List[U]]) extends MapIt[A, B, W, U] 75 | 76 | // Mix case and non-case classes 77 | case class CClass[X](x: List[X]) 78 | class PClass[Y](val y: List[Y]) 79 | case class CClassLevel2[Z](z: Z) 80 | trait ClassistBase[T, U] { val t: CClass[CClassLevel2[T]]; val u: PClass[U] } 81 | case class ClassistC[A, B](t: CClass[CClassLevel2[A]], u: PClass[B]) extends ClassistBase[A, B] 82 | 83 | // Inverted 84 | trait ClassistBaseInv[T, U] { val t: CClass[T]; val u: PClass[U] } 85 | case class ClassistCInv[A, B](t: CClass[A], u: PClass[B]) extends ClassistBaseInv[A, B] 86 | 87 | // InTermsOf substitution 88 | trait Basis[T] { 89 | val a: Int 90 | val b: String 91 | val c: T 92 | } 93 | case class Thingy2[T](a: Int, b: String, c: T) extends Basis[T] 94 | 95 | // Alias type substitution 96 | opaque type mystery = DuoTypes[Byte, Short] 97 | case class AliasTypeSub(a: mystery) 98 | 99 | // Parameterized class defined inside an object 100 | object Outside: 101 | case class PersonZ[Z](name: String, thing: Z) 102 | case class Blah[T](a: PersonZ[T]) 103 | 104 | sealed trait Hobby[X, Y] { val thing1: X; val thing2: Y } 105 | sealed trait Artist[W, Z] { val instrument: W; val effort: Z } 106 | sealed trait PersonX[X, Y] { val who: X; val org: Y } 107 | 108 | case class Sports[A, B](thing1: A, thing2: B) extends Hobby[A, B] 109 | case class Painter[A, B](instrument: A, effort: B) extends Artist[A, B] 110 | case class EmployeeX[A, B, C, D](who: Artist[C, Hobby[D, A]], org: B) extends PersonX[Artist[C, Hobby[D, A]], B] 111 | 112 | case class WildThing( 113 | x: AnyVal, 114 | a: List[? <: Thingy2[String]], 115 | b: List[? >: Thingy2[String]], 116 | c: List[? >: Int <: AnyVal], 117 | d: Option[? <: Thingy2[String]], 118 | e: Try[? <: Thingy2[String]], 119 | f: Map[? <: Thingy2[String], ? >: Int], 120 | g: (? <: Thingy2[String], ? >: Int) 121 | ) 122 | 123 | case class NamedThing[T >: Int <: AnyVal](a: String) 124 | --------------------------------------------------------------------------------